Authentication & limits¶
Authentication¶
All /v1 routes require a Bearer API key:
/health/* and the interactive /docs are public. A missing, malformed, unknown, or
revoked key yields 401 with the standard error envelope and code
unauthorized.
Try it from /docs
The OpenAPI document declares the key as a bearer security scheme, so
/docs has an Authorize button — paste a rok_… key there and the
Try it out requests carry it. Clients generated from
/openapi.json pick the header up the same way.
Keys are minted and revoked self-serve from your account dashboard (/account
— live usage metrics, and as many active keys as your plan allows), or by the operator CLI
(python -m app.auth.cli) on any plan. Either way the plaintext is shown once at creation
and only a SHA-256 hash is stored. Revocation takes effect immediately.
Limits apply to your account, not to each key
Your rate limit and monthly quota are counted across all the keys on your account. Extra keys are for separating environments and rotating secrets — they do not add capacity. Usage is still reported per key, so you can see which one is spending.
Calling from a browser¶
CORS is enabled for all origins on GET/HEAD — dashboards, Observable notebooks, and
other browser-based consumers can call the /v1 data API directly with the
Authorization header. The Bearer token is the only credential the data API accepts:
cookies play no part in /v1 requests (the account dashboard's session cookie is scoped
to the account pages only), so the wildcard origin is safe. Rate-limit and caching headers
(X-RateLimit-*, ETag, X-Cache, Retry-After) are exposed to scripts. HEAD is
supported on every GET route for monitors and link-checkers.
Plans¶
| Plan | Price / month | Requests / minute | Requests / month | Active keys |
|---|---|---|---|---|
free |
€0 | 60 | 10,000 | 5 |
starter |
€19 | 120 | 100,000 | 10 |
pro |
€59 | 300 | 1,000,000 | 25 |
business |
€199 | 1,200 | 10,000,000 | 50 |
enterprise |
custom | 6,000 | custom | unlimited |
Two gates are enforced in order on every request, both counted per account:
- a per-minute rate limit (burst control), then
- a monthly quota (volume control).
A rejected request never consumes quota — the rate gate rejects before the usage counter increments, and a quota rejection is rolled back.
Cache hits still count
Responses served from the API's Redis cache (X-Cache: HIT) and bodyless 304 replies
still pass through auth, rate limiting, and metering. Caching saves latency and
database work — not quota.
Rate-limit headers¶
Successful responses report your current rate-limit state:
X-RateLimit-Reset is the number of seconds until the current window resets. When a limit
is exceeded, the API returns 429 (code rate_limited) with a Retry-After header —
sleep that many seconds and retry:
Good citizenship¶
- Reuse a single HTTP client/connection pool instead of reconnecting per request.
- Use cursor pagination or a bulk export instead of hammering deep offset pages.
- Honor
Retry-After— retrying immediately just re-hits the same window. - Send
If-None-Matchon repeat reads;304s are much faster (see Caching).