Authentication

One header, on every request - plus scopes and expiry, so a leaked key is bounded.

On this page
HTTP
X-API-Key: pk_live_...

That is the whole scheme. There is no OAuth dance, no token exchange and no refresh: a key is a long-lived credential for a server, and a server does not need a login screen.

Authorization: Bearer <your key> and a bare Authorization: <your key> are also accepted, because half of every HTTP client defaults to one of them. X-API-Key is what these docs promise and what error messages name.

What a key can do #

  • It acts on the organization that minted it, and only that organization.
  • It can never sign in to the console. A key is for calling the API; a session is for a browser.
  • It carries no admin rights, whoever minted it.
  • It carries scopes, which decide whether it may spend. See below.

Scopes #

A key carries read, write, or both. Choose when you mint it, in the console under Keys -> New API key.

ScopeCanCannot
readGET /batches, /batches/{id}, /batches/{id}/results, /account, /usage.Submit anything, cancel a batch, or change the account.
writePOST any operation and its /sync twin, cancel a batch, PATCH /account.Read results - pair it with read unless you genuinely want a write-only key.

The point of read is that it cannot spend. A leaked read-only key is a disclosure problem; a leaked key that can submit is also a billing one, because it can burn the whole monthly allowance before anyone notices. Give your dashboards, alerting and anything that only consumes results a read-only key.

A key without the scope a route needs is 403 insufficient_scope, naming the scope it wanted. That is deliberately not 401: the credential is valid and is the wrong one, so the fix is to mint a different key rather than to re-check the one you have. Do not retry it - it will never succeed.

JSON
{
  "error": {
    "type": "insufficient_scope",
    "message": "this key does not carry the write scope; mint one that does",
    "requestId": "req_8f2ka91x"
  }
}

Every key that existed before scopes carries both, and nothing about it changed. Narrowing is something you opt into when you mint a new one - a security feature whose rollout is an outage is a security feature people turn off.

GET /v1/account reports the scopes of the key that asked, so a client can check what it is holding without provoking a 403 to read the message.

Expiry #

A key can be given a lifetime when you mint it - 30 days, 90, a year, or never. It stops working the moment it passes, with the same 401 invalid_api_key a revoked key gives. Never is the default and is the right choice for a long-running server; an expiry is what you want for a contractor, a trial, or anything you would otherwise have to remember to clean up.

Expiry is not a substitute for rotation. It bounds how long a leak stays useful; rotation is how you replace a key you still need.

Rotation #

Keys are created, revoked and listed in the console. The plaintext is shown exactly once, at creation - we store a hash, so there is no second chance to copy it and no way for us to read it back to you.

To rotate without downtime: create the new key, deploy it, confirm traffic has moved by watching last-used on the old one, then revoke the old one. Revocation takes effect immediately.

When it fails #

A missing, malformed, revoked or expired key is 401 invalid_api_key. It is never metered - a rejected request does not reach the rate limiter, does not create a batch and does not write a usage row.

JSON
{
  "error": {
    "type": "invalid_api_key",
    "message": "invalid, revoked or expired API key",
    "requestId": "req_8f2ka91x"
  }
}

Two ways to get one. Buy credits in the console and mint a key straight away, or agree an allowance and rate limits with us first - the rate you pay either way is on the pricing page, and it falls with the size of the order.