Keys
The two key families, production and draft keys, and which key to give each site, script and browser.
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
| Family | Looks like | Works on | Carries scopes |
|---|---|---|---|
| Scoped | cap_live_…, cap_test_… | /api/ only | Yes |
| Legacy | pk_…, 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.
| Environment | Prefix | Sees | Cached at the CDN |
|---|---|---|---|
| Production | cap_live_, pk_ | Published entries only | Yes |
| Draft, or any other environment | cap_test_, sk_ | Every entry, drafts included | Never |
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
- Open Developers > Keys and choose New key.
- Give it a Name after the system that will hold it, such as
WebsiteorShopify sync. - Pick an Environment: Production for a live site, Draft for a preview or staging build.
- Under Grants, pick a starting point. Read only "reads published content, models and media" and suits almost every site.
- Optionally set an Expiry.
- 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
| Job | Key |
|---|---|
| A public website | cap_live_, Read only |
| A preview or staging build that shows drafts | cap_test_, Read only, server-side only |
| A browser app | cap_live_, Read only, bound to your origins |
| A build script or static site generator | cap_live_, Read only |
An existing site on /v2/api | Keep its legacy key |
| An AI assistant through the MCP server | cap_live_, Read only. See AI and MCP |
One key per site or integration makes rotation painless and makes the Last used column meaningful.
Related
- Authentication for scopes, presets, rotation and allowed origins in detail.
- The Developers section for the Keys screen.
- Versions for how a key's version pin works.