API keys and credentials
Scoped project keys minted once, stored hashed — plus the other bearer tokens Camplax hands out and where each belongs.
Camplax hands out several kinds of credential, and mixing them up is the fastest way to a 403. The rule of thumb: cx_live_/cx_test_ keys act on one project, cx_cli_ acts as you, cak_live_ only writes telemetry, cxm_ is for AI chat clients.
Which token where
| Prefix | What it is | Where it goes |
|---|---|---|
| (none — cookie) | Console session after sign-in | the browser only — reaches session-only routes like team admin, billing and key management |
cx_cli_… | A CLI token for your user — every project you can reach | CAMPLAX_TOKEN, Authorization: Bearer on CLI/API and MCP calls |
cx_live_… / cx_test_… | Scoped project API keys | Authorization: Bearer on one project's data routes — database, storage, users, logs |
cak_live_… | The ingest key behind CAMPLAX_ANALYTICS_KEY | errors, analytics and flags ingestion — write-only telemetry |
cxm_… / cxr_… | OAuth access/refresh pair minted for an MCP client | Authorization: Bearer on https://camplax.dev/mcp |
If a bearer works at https://camplax.dev but not on your app's own domain, that is expected: cx_cli_/cxm_ identify a user to the platform, while cx_live_/cak_live_ identify a project.
Minting a cx_cli_ token without the CLI
camplax login creates one through the browser device flow. For a machine that cannot open a browser — a CI runner, a hosted agent — mint the same credential in the console under Account → Access tokens → New token, or call POST /v1/cli/tokens with a console session:
curl -X POST https://camplax.dev/v1/cli/tokens \
-H "Cookie: <your console session>" \
-H "Content-Type: application/json" \
-d '{"name": "ci-runner", "expiresInDays": 90}'
The secret is shown once, hashed at rest like a project key. GET /v1/cli/tokens lists each token's name, prefix, created and last-used times; DELETE /v1/cli/tokens/:id revokes. All three routes are session-only on purpose: a leaked cx_cli_ can act as you on the API, but it cannot mint or kill other tokens.
Project API keys
Create them under Project → API keys or through POST /v1/projects/:slug/keys — any owner or member seat, console session only. Each key carries a subset of 16 scopes:
| Scope | Unlocks | Scope | Unlocks |
|---|---|---|---|
db:read / db:write | Postgres data API | payments:read / payments:write | store checkout APIs |
storage:read / storage:write | bucket files | ai:invoke | the AI gateway |
users:read | list app users | deploy | trigger deployments |
users:admin | create/disable/reset users | env:read / env:write | secrets vault |
webhooks:send | outbound event webhooks | logs:read | the log stream |
widgets:read / widgets:write | hosted UI widgets | manage | console-parity routes — mailbox inboxes and sends, analytics, cron, MCP domain tools |
The console shows an "everything" quick toggle plus a Build preset (db:read, db:write, storage:read, storage:write, users:read, users:admin).
What is actually stored
A key is a cx_live_/cx_test_ prefix plus 256 bits of randomness, minted once. The platform stores its SHA-256 hash and an 8-char display prefix — never the secret itself. Two consequences:
- The full key is shown exactly once, in the create/roll response. Copy it then; it cannot be recovered.
- A leaked database row cannot be replayed: lookup hashes the presented key and compares digests.
Expiry and lifecycle
| Preset | Behaviour |
|---|---|
0 | No expiry |
30 / 90 / 180 / 365 | Days until the key dies |
Skip expiresInDays (or pass something that is not a number) and the API defaults to 90 days.
- Roll — mints a new key with the same name, scopes, mode and expiry, and revokes the old one in the same call. The new secret is shown once.
- Revoke — flips a flag; the key immediately fails. To restore access you roll a replacement — revoke is not undone.
- Delete — removes the record entirely.
- An expired key authenticates as nothing; requests fail closed.
lastUsedAton each key tells you which ones are dead weight.
Using a key
# db:read scope
curl https://camplax.dev/v1/projects/my-app/database/schema \
-H "Authorization: Bearer cx_live_…"
# any POST needs db:write — even a read-only query — and all SQL passes a safety guard
curl -X POST https://camplax.dev/v1/projects/my-app/database/run \
-H "Authorization: Bearer cx_live_…" \
-H "Content-Type: application/json" \
-d '{"sql":"select count(*) from orders","allowWrites":false}'
A missing scope comes back 403 { "error": "forbidden", "message": "Missing required scope" }; a revoked, expired or unknown key is a flat 401 — the response never says which, on purpose. Keys only authenticate Authorization: Bearer against project routes — they cannot mint other keys, manage the team, or touch deploy hooks.
The other two project secrets
CAMPLAX_ANALYTICS_KEY(cak_live_…) — the injectable SDK key forcx.js, error and analytics ingest. Write-only by design: it can record telemetry, never read your data, so it is safe to expose in client-side config.- Deploy-hook and public tokens — deploy-hook secrets and share tokens are generated per feature; see logs for share URLs and the CLI/API docs for hooks.
Related docs
- REST API — where each credential is accepted.
- Agents and MCP —
cx_cli_bearer versus the OAuthcxm_flow. - Errors — what
cak_live_ingestion looks like.