API Authentication
jekcms's REST API uses Bearer tokens — a single long-lived secret passed in the Authorization header. No OAuth dance, no session cookies, no CSRF tokens. It's the simplest thing that works for server-to-server integration, and it's what n8n, Make, Zapier, and most other automation platforms expect.
This page covers how to generate tokens, the scope system, rate limits, and IP restrictions.
The Bearer header
Every authenticated request must carry:
Authorization: Bearer YOUR_TOKEN
A minimal example:
curl -H "Authorization: Bearer YOUR_TOKEN" https://yoursite.com/api/v1/posts
Missing or invalid header returns 401 Unauthorized:
{ "error": "unauthorized", "message": "missing or invalid bearer token" }
Do not put the token in a query string. Query strings end up in server access logs, proxy logs, and browser history — headers don't.
Generating a token
Admin → Settings → API Tokens → Create new token.
Fill in:
- Name — a label only you see. Use the integration's name (
n8n,zapier,mobile-app-staging) so revoking later is obvious - Scopes — what this token can do (see next section)
- Expiry —
never,30 days,90 days,1 year, or custom date - IP restriction — optional allowlist (see below)
Click Generate. The full token appears once. Copy it into your secret store (1Password, AWS Secrets Manager, n8n credentials, env var — anywhere except "a sticky note on my monitor"). The admin UI stores only a truncated prefix so you can identify the token later, never the full value.
If you lose the token, you can't recover it. Generate a new one and revoke the old.
Scopes
Tokens are least-privilege. A token with posts:read can't write posts, upload media, or manage users. Available scopes:
posts:read— list and read posts (including drafts, with this scope)posts:write— create, update, delete postsmedia:read— list and read media itemsmedia:write— upload and delete mediacategories:read/categories:writetags:read/tags:writecomments:read/comments:moderateusers:read/users:write— requires adminqueue:read/queue:write— content queuesettings:read/settings:write— requires admin*— full access (use sparingly; treat as a root-equivalent secret)
For a typical n8n integration that publishes AI drafts with images, grant: posts:write, media:write, categories:read, tags:read. That's it — four scopes, no ambient authority.
Expiry options
- Never — token is valid until you revoke it. Acceptable for low-risk, server-to-server integrations
- 30 / 90 / 365 days — token stops working after expiry. Good for external contractors, time-bound integrations, CI pipelines
- Custom — pick a date
Expired tokens return 401 with "error": "token_expired". Replace and rotate — don't extend.
Expiry is a good hygiene habit. Even "forever" tokens should be rotated at least annually.
Revoking a token
Settings → API Tokens → find the row → Revoke. Takes effect within 60 seconds (tokens are cached briefly in memory — don't worry, there's no cache longer than one minute).
Always revoke immediately when:
- An employee or contractor with access leaves
- You suspect a secret leaked (committed to Git, posted in Slack, etc.)
- You rotate credentials per your security calendar
The revoke action is irreversible. Revoked rows stay visible in the UI for audit; their tokens never validate again.
Rate limits
Each token gets a rate limit bucket, separate from other tokens and from anonymous traffic:
- Default — 60 requests/minute, 1000 requests/hour
- PRO tier — 300 req/min, 10,000 req/hour
- Custom — set per-token under advanced settings
Exceed the bucket and you get 429 Too Many Requests with a Retry-After header (seconds until the bucket refills):
HTTP/1.1 429 Too Many Requests
Retry-After: 37
Content-Type: application/json
{ "error": "rate_limited", "retry_after": 37 }
Well-behaved clients respect Retry-After. n8n's HTTP Request node does this automatically when you enable Retry on fail.
The /api/v1/health endpoint is exempt from rate limits so monitoring agents can ping without eating quota.
IP restriction
For high-privilege tokens (anything with * or settings:write), restrict to specific source IPs. Add a comma-separated list of IPv4/IPv6 addresses or CIDR ranges under IP restriction when you create the token.
Request from a non-allowlisted IP → 403 Forbidden:
{ "error": "ip_restricted", "message": "source IP not in allowlist" }
Combine with expiry: a 90-day token scoped to your n8n VPS's IP address is dramatically safer than a forever-token with no IP restriction.
Best practices summary
- Least-privilege scopes always
- Name tokens after their integration
- Set expiry; rotate on a schedule
- Put high-privilege tokens behind IP restriction
- Store secrets in a real secret manager, never in Git
- Revoke the moment someone leaves or a secret leaks