Docs menu and this page

On this page

Error types

Every error is application/problem+json (RFC 9457): branch on type, never on title. The types below are grouped by what to do next; each has its own page at /problems/<type>, for example /problems/rate-limited, and its Markdown for agents at the same path plus .md (/problems/rate-limited.md), and the retry rules and headers are in limits.

All 41 types, A to Z

Wait, then send the same request again

Nothing was done, or the work is still running. Wait for Retry-After and resend unchanged.

  • rate-limited 429 wait Retry-After seconds and send it again; after three in a row on the same call, stop and read GET /v1/limitsAlso in the answer: Retry-After, retryable: true
  • idempotency-in-progress 409 a create with this Idempotency-Key is still running: send the same request again after Retry-After; it then gets the first answerAlso in the answer: retryable: true, Retry-After: 1
  • temporarily-unavailable 503 nothing was done; send it again after a second. The other 503 temporarily-unavailable, with outcome: "unknown", carries Retry-After too: tell the two apart by outcome in the body, never by the headerAlso in the answer: retryable: true (Retry-After: 1), no outcome
  • key-unavailable 503 a reveal: the key that opens this secret is unavailable on our side. Nothing was spent (the secret, its reveals and its attempts are as they were; on a Strict riddle box whose commitment was already checked, that one attempt stays counted); try again after Retry-AfterAlso in the answer: retryable: true, Retry-After: 60
  • not-yet-open 425 a dated box before its opensAt: come back at resetsAt; nothing was spent

Check before you repeat a write

A write may have happened. Read the call in check before sending anything again.

  • temporarily-unavailable 503 a write may have happened: read the call in check first (a lost reveal is worked through in Limits). The other 503 temporarily-unavailable, without outcome, means nothing was done. check is none for a create (create again; the first, if it was made, expires unrevealed: nobody holds its token, so nobody can reveal it and the value is not delivered twice), GET /v1/secrets/{token}/public for a reveal (state says whether it was spent; if it was and the answer was lost, the value is gone: create again), a claim or a puzzle open, GET /v1/secrets/{token}/status for a revoke or a policy changeAlso in the answer: outcome: "unknown" (retryable: false)

Stay inside a limit

Shorten, split, wait for the reset, or use a key: resending as is fails again.

  • quota-exhausted 429 stop until resetsAt, or mint a keyAlso in the answer: no Retry-After, retryable: false
  • tier-budget-exhausted 503 the anonymous tier is paused: Retry-After is the seconds until it reopens at 00:00 UTC, not a hint to retry sooner; a key works nowAlso in the answer: retryable: false, Retry-After
  • ttl-too-long 400 the ttl is over the tier's maximum; shorten it or use a key
  • value-too-large 413 the value is over the tier's size; use a key (64 KB) or split it

Fix the request

The request itself is wrong. Change it, then send it once.

  • invalid-request 400 the request is malformed: bad JSON, a missing or empty value, a field of the wrong type or out of range (detail names it); fix it
  • unsupported-media-type 415 a write (POST, PATCH) must be Content-Type: application/json, or have no body and no type; send JSON. Parameters (; charset=utf-8) and letter case are ignored, and any application/…+json type is taken; anything else, application/json5 included, is 415. A create with no body at all is 400 invalid-request (no value)
  • audience-invalid 400 the audience has a type this version takes but names no single recipient: an OIDC audience whose claims would match more than one job. Name the job: sub, or on GitHub Actions repository (or repository_id) with ref or job_workflow_ref; under GitLab or CircleCI sub is required and repository or job_workflow_ref is refused, since their tokens never carry them. Nothing was made. (A malformed key id or public key is 400 invalid-request.)
  • audience-type-unknown 400 the audience's type is not one this version takes: it takes key, oidc and publicKey; httpsig is planned with the signed-agent tier, and an object or array with no type ({}, []) gets this answer too. Nothing was made. Use one of the three types, or leave audience out for a bearer link
  • version-unknown 400 the Ckrets-Version header names a date this server does not serve; supported (and detail) list the dates it does. Send one of them, or no header for the original contract
  • plaintext-refused 400 the key requires client encryption (clientEncryptionRequired, set at mint or with PATCH /v1/me/key) and this value would reach ckrets in the clear: encrypt it on your side and send clientEncrypted: true with its wrappedKey (client encryption); the helper and the SDKs do it. Through MCP, use the local helper (npx @ckrets/mcp), not the hosted server. Nothing was made or counted
  • idempotency-mismatch 409 that Idempotency-Key was used with another request; nothing was made, but it counts as one create; use a new key
  • idempotency-key-retired 409 that Idempotency-Key was used with three different requests and is retired: every request with it is refused, the right one included; a secret already made with it stands; use a new key
  • policy-widening 409 a policy change may only tighten a box's gate (fewer attempts, a later opensAt, a longer cooldown, revealOnce on); its window may be extended up to the tier's maximum but not shortened (revoke instead): detail names what was refused; nothing changed
  • hint-out-of-range 422 there is no hint at that index: hintCount in the body says how many the box has
  • wrong-host 403 the request reached the API's own address instead of https://ckrets.ai: send the same request to https://ckrets.ai (the door's WAF and limits apply only there)
  • not-found 404 no such route, or (status, /public) no such token. A method a /v1 path does not take (PUT /v1/secrets, HEAD on a reveal) is this 404 too: the API never answers 405

Prove who you are

Send the right credential, proof or ticket; a wrong one may count as an attempt.

  • invalid-credentials 401 the Authorization header is not a valid credential (unknown, revoked, ended or malformed, or not Bearer <credential> at all: Basic, Token, a bare Bearer; the word Bearer itself may be in any case). It is refused on every route, even one that needs no credential (a bearer reveal, GET /v1/limits): send no header rather than a bad one. A well-formed manage token (ckm_ and 26 uppercase Crockford base32 characters) is not an invalid credential, even one we never issued: everywhere but its own secret's status, revoke and policy it counts as no credential, so the caller is anonymous (a create with it makes an anonymous secret, 201; GET /v1/limits answers the anonymous tier, 200; /v1/me/key, which needs a key, answers 401). A manage token that is not its secret's is 403 not-creator on status and revoke. A ckm_ of any other form is 401 invalid-credentials
  • audience-mismatch 403 you are not the named recipient, or sent no proof; not counted; do not retry with the same credential
  • not-creator 403 status and revoke need the creator's credential or the manage token
  • scope-missing 403 the key lacks the scope this call needs; use the manage token for status and revoke, or a key with the scope
  • passphrase-required 401 the token needs its passphrase: POST it as {"passphrase": "…"} (a wrong one is counted)
  • proof-required 401 a box needs a proof you did not send or that was wrong (result: "wrong" counts an attempt; the body says how many are left and the cooldown); a 403 with this type is a failed Turnstile check: solve it again and send the fresh token
  • proof-of-work-required 428 solve the stamp in the body and resend with Ckrets-Proof (anonymous creates only)
  • session-invalid 401 an anonymous puzzle create needs a session ticket: POST /v1/sessions with a Turnstile token, then send Ckrets-Proof: session=<ticket>
  • ticket-invalid 401 the attempt ticket is malformed, expired, for another box, or already used or voided: claim a new attempt for a fresh ticket
  • verdict-invalid 401 the judge's verdict does not match this ticket, box or judge: ask the judge again with the ticket you hold
  • void-not-allowed 403 voiding or judging a ticket is for the app's own layer, through its private API only

It will not reveal: ask the sender for a new one

Its reveals or its attempts are used up, it is gone, or it is held for review. Never retry it.

  • token-gone 410 the token will not reveal again: it was revealed for the last time, expired, revoked (or its creator's key was), or never existed. Never retry; ask the sender for a new one. (A request page that is no longer open answers the same)
  • token-frozen 410 the token was reported and is held for review; never retry or report it again: ask its sender for a new token (a review may lift the freeze, but v1 promises no time for it)
  • attempts-exhausted 410 the token's passphrase attempts are used up (5 anonymous, 10 with a key): it will never reveal, even with the right passphrase. Never retry; ask the sender to create it again

Our side

Nothing to fix on yours: report the requestId and do not loop.

  • internal 500 our fault; nothing to fix on your side: report the requestId, and do not loop on it

Planned or retired features

Not something to retry: the feature is not built yet, or was turned off.

  • not-in-v1 501 the path is planned (specified, not built yet); do not retry
  • feature-sunset 410 the feature you called was deprecated (announced in /.well-known/ckrets.json deprecations and /llms.txt at least six months before, with Deprecation and Sunset headers) and has now ended; use the replacement the deprecation names, and do not retry
  • batch-too-large 413 (planned) the batch is over its item or size limit: split it
  • domain-unverified 422 (planned) the verification file was not found at the path or held the wrong token: serve it exactly and call verify again within 24 hours
  • webhook-unverified 422 (planned) the webhook's host is not verified for this key: verify the domain first