Docs
Concepts

Keys

The two key families, production and draft keys, and which key to give each site, script and browser.

View as Markdown

Every read from Capa carries an API key in the x-api-key header. The key decides three things: which project you read, which content you see, and what else the key may do.

Two families

FamilyLooks likeWorks onCarries scopes
Scopedcap_live_…, cap_test_…/api/ onlyYes
Legacypk_…, sk_…, or an older key with no prefix/v2/api, /v3/api, /v2/schema, /v2/seo, /v2/agent/*, and /api/No. One of four fixed permissions

Start new work with a cap_ key. It can be limited to exactly what one integration needs: reads only, one model, an expiry, a list of allowed origins.

Your existing legacy key keeps working, unchanged, everywhere it works today. It also reads /api/, so you can move a page to the new API without minting anything.

Production and draft keys

Every key has an environment.

EnvironmentPrefixSeesCached at the CDN
Productioncap_live_, pk_Published entries onlyYes
Draft, or any other environmentcap_test_, sk_Every entry, drafts includedNever

The admin labels the two choices Production and Draft. GET /api/me reports the key's environment, and any value other than production sees drafts.

A draft key is a read-visibility dial, not a sandbox. There is one database. A draft key that can publish publishes to your live site. If you want somewhere safe to break things, use a separate project.

See Drafts and publishing for what each key sees.

What a scoped key can do today

/api/ reads and never writes. A cap_ key that holds instance:read reads entries through GET /api/entries/... and /api/graphql. GET /api/me answers any key.

A write scope you grant today is stored and enforced from the day the route it unlocks exists, and does nothing before then. Keep your legacy key for writes and for anything that calls /v2 or /v3.

Why a cap_ key is refused on /v2 and /v3

The legacy routes do not read scopes. If they accepted a scoped key, they would serve it everything, and a restriction you set in the admin would be ignored on the surface most sites use.

So a cap_ key sent to /v2/api, /v3/api, /v2/schema, /v2/seo or /v2/agent/* gets the same 401 an unknown key gets. A site that is half ported runs two keys: its legacy key for the old pages and a cap_ key for the new ones.

Create a key

  1. Open Developers > Keys and choose New key.
  2. Give it a Name after the system that will hold it, such as Website or Shopify sync.
  3. Pick an Environment: Production for a live site, Draft for a preview or staging build.
  4. Under Grants, pick a starting point. Read only "reads published content, models and media" and suits almost every site.
  5. Optionally set an Expiry.
  6. Choose Create key, then copy the secret.

The secret is shown once. Capa stores it hashed and cannot show it again. If you lose it, rotate the key.

Only Admins, Developers and the project owner can create keys. The full scope list and presets are in Authentication.

Check a key

GET /api/me says what the key in your hand can do:

curl https://cdn.capacms.com/api/me -H "x-api-key: $CAPA_KEY"

It returns the key's environment, scopes, the models it can read, the surfaces it works on, its version pin and its expiry. Read it before you debug a 403.

Keys in a browser

Anything in a browser bundle is public. If a key must ship to the browser:

  • Use a production cap_live_ key with Read only.
  • Bind it to your site's origins with Allowed origins in the key's row menu. A request from another origin gets 403 origin_refused.
  • Never ship a draft key. Anyone who views your page source could read your unpublished drafts with it.

A server-to-server call sends no Origin header, so origin binding never blocks your own backend.

Rotate, expire and deactivate

  • Rotate mints a second key with the same grants and gives the old one a grace window: Now, 1 hour, 24 hours or 7 days. Deploy the new secret inside the window. A key can be rotated once.
  • Expiry is optional. Within 14 days of it, Developers > Keys marks the key with a red dot. Put the date in your own calendar too: nothing emails you.
  • Deactivate stops a key at once. You can reactivate it later.

An expired, deactivated or unknown key all get the same 401 invalid_key, so the answer never reveals which it was.

Which key for which job

JobKey
A public websitecap_live_, Read only
A preview or staging build that shows draftscap_test_, Read only, server-side only
A browser appcap_live_, Read only, bound to your origins
A build script or static site generatorcap_live_, Read only
An existing site on /v2/apiKeep its legacy key
An AI assistant through the MCP servercap_live_, Read only. See AI and MCP

One key per site or integration makes rotation painless and makes the Last used column meaningful.