ADVANCED
DocsThe API
Kilo is API-first: the dashboard you sign in to is a client of the same HTTP API you can call yourself. Anything you can do by clicking - create a link, read its stats, add a routing rule - is one request with a key. This page covers the base URL, how to authenticate, the two scopes, the error shape, and rate limits.
Base URL
The API is served by the same Cloudflare Worker that resolves your links, on the Kilo QR origin. Today that means requests go to qr2r.com under the /api prefix:
https://qr2r.com/apiEvery path in these guides is relative to that base - /links means https://qr2r.com/api/links. A quick way to confirm a key works is the API root, which echoes your identity:
{ "api": "qr2r", "version": 0, "user": { "uid": 42, "email": "", "admin": false } }A stable hostname is coming
The API shares the origin your links resolve on because that Worker is the platform. A dedicated api.qr2r.com hostname is being wired up for the launch window; until it goes live, point clients at the same origin above. The paths and payloads do not change when the hostname does.
Authenticate with an API key
Send your key as a bearer token in the Authorization header. The key is checked before the session cookie, so an API client stays on one auth mode with no stale browser cookie leaking in.
Authorization: Bearer qr2r_live_1a2b3c…Keys begin with qr2r_live_ (or qr2r_test_ for a dev-mode key). That qr2r_ prefix is an infrastructure identifier that was deliberately not rebranded - the same reason the domain is still qr2r.com. You mint a key in the dashboard under API keys; the full token is shown once at creation, and Kilo stores only its SHA-256 hash, so put it straight into a secret manager. A request with no valid key (and no session) is refused:
{ "error": "unauthorized" }Keys are minted interactively
Creating, listing, and revoking keys is a session-only action - a bearer key cannot mint or revoke another key, so a leaked token can’t quietly provision a fleet of its own. Manage keys from the dashboard. An account may hold up to 10 active keys at once.
API access is a paid-plan feature. A key on a plan without it is refused with 402 and { "error": "plan_required", "feature": "api_access" }.
Scopes: read and write
A key carries one or both of two scopes. A read key may call safe methods only - GET, HEAD, OPTIONS. A write key is additionally allowed POST, PUT, PATCH, and DELETE. A write attempt with a read-only key is refused:
{ "error": "insufficient_scope", "required": "write" }Scopes are the whole permission story for a key: a key can manage links, rules, and read analytics, but it can never manage members, billing, or other keys - those actions stay bound to an interactive session. Give an integration a read key when all it does is pull numbers.
Errors are always JSON
Every error response is a JSON object with an error string, plus extra fields where they help. Validation failures carry the full list of problems under issues:
{ "error": "invalid_body", "issues": [ /* zod issues */ ] }The codes you’ll meet most:
400 invalid_body- the request body failed validation.401 unauthorized- missing or invalid key.402 plan_required- the feature needs a higher plan (carriesfeature).403 insufficient_scope/forbidden- the key can’t perform this method or action.404 not_found- no such link, rule, or domain in your workspace.409- a conflict, such asname_takenorcap_reached.422 destination_unsafe- the destination failed the safety check.429 rate_limited- too many requests (see below).
Rate limits
Authenticated requests are rate limited. The general bucket allows 60 requests per minute per user. On top of that, an API key gets its own per-key, per-minute ceiling taken from your plan’s api_rpm limit. Cross either and the response is:
{ "error": "rate_limited", "retry_after": 42 }The retry_after value (also sent as a Retry-After header) is the number of seconds until the window resets. Back off for that long and retry.
Honest about the current limiter
The limiter runs at each Cloudflare edge location independently today, so the per-minute numbers are a floor rather than a hard global contract; a durable, cross-location limiter lands alongside the paid tier. Build your client to honour 429 and Retry-After and it will keep working unchanged when that switch flips.
With a key in hand, the next guide walks through the link endpoints one call at a time.
Manage your API keys