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
attempts-exhaustedaudience-invalidaudience-mismatchaudience-type-unknownbatch-too-largedomain-unverifiedfeature-sunsethint-out-of-rangeidempotency-in-progressidempotency-key-retiredidempotency-mismatchinternalinvalid-credentialsinvalid-requestkey-unavailablenot-creatornot-foundnot-in-v1not-yet-openpassphrase-requiredplaintext-refusedpolicy-wideningproof-of-work-requiredproof-requiredquota-exhaustedrate-limitedscope-missingsession-invalidtemporarily-unavailableticket-invalidtier-budget-exhaustedtoken-frozentoken-gonettl-too-longunsupported-media-typevalue-too-largeverdict-invalidversion-unknownvoid-not-allowedwebhook-unverifiedwrong-host
Wait, then send the same request again
Nothing was done, or the work is still running. Wait for Retry-After and resend unchanged.
rate-limited429 waitRetry-Afterseconds and send it again; after three in a row on the same call, stop and readGET /v1/limitsAlso in the answer:Retry-After,retryable: trueidempotency-in-progress409 a create with thisIdempotency-Keyis still running: send the same request again afterRetry-After; it then gets the first answerAlso in the answer:retryable: true,Retry-After: 1not-yet-open425 a dated box before itsopensAt: come back atresetsAt; nothing was spent
Check before you repeat a write
A write may have happened. Read the call in check before sending anything again.
temporarily-unavailable503 a write may have happened: read the call incheckfirst (a lost reveal is worked through in Limits). The other503 temporarily-unavailable, withoutoutcome, means nothing was done.checkisnonefor 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}/publicfor a reveal (statesays 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}/statusfor 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-exhausted429 stop untilresetsAt, or mint a keyAlso in the answer: noRetry-After,retryable: falsetier-budget-exhausted503 the anonymous tier is paused:Retry-Afteris 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-Afterttl-too-long400 thettlis over the tier's maximum; shorten it or use a keyvalue-too-large413 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-request400 the request is malformed: bad JSON, a missing or emptyvalue, a field of the wrong type or out of range (detailnames it); fix itunsupported-media-type415 a write (POST,PATCH) must beContent-Type: application/json, or have no body and no type; send JSON. Parameters (; charset=utf-8) and letter case are ignored, and anyapplication/…+jsontype is taken; anything else,application/json5included, is415. A create with no body at all is400 invalid-request(novalue)audience-invalid400 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 Actionsrepository(orrepository_id) withreforjob_workflow_ref; under GitLab or CircleCIsubis required andrepositoryorjob_workflow_refis refused, since their tokens never carry them. Nothing was made. (A malformed key id or public key is400 invalid-request.)audience-type-unknown400 the audience'stypeis not one this version takes: it takeskey,oidcandpublicKey;httpsigis planned with the signed-agent tier, and an object or array with notype({},[]) gets this answer too. Nothing was made. Use one of the three types, or leaveaudienceout for a bearer linkversion-unknown400 theCkrets-Versionheader names a date this server does not serve;supported(anddetail) list the dates it does. Send one of them, or no header for the original contractplaintext-refused400 the key requires client encryption (clientEncryptionRequired, set at mint or withPATCH /v1/me/key) and this value would reach ckrets in the clear: encrypt it on your side and sendclientEncrypted: truewith itswrappedKey(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 countedidempotency-mismatch409 thatIdempotency-Keywas used with another request; nothing was made, but it counts as one create; use a new keyidempotency-key-retired409 thatIdempotency-Keywas 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 keypolicy-widening409 a policy change may only tighten a box's gate (fewer attempts, a lateropensAt, a longer cooldown,revealOnceon); its window may be extended up to the tier's maximum but not shortened (revoke instead):detailnames what was refused; nothing changedhint-out-of-range422 there is no hint at that index:hintCountin the body says how many the box haswrong-host403 the request reached the API's own address instead ofhttps://ckrets.ai: send the same request tohttps://ckrets.ai(the door's WAF and limits apply only there)not-found404 no such route, or (status,/public) no such token. A method a/v1path does not take (PUT /v1/secrets,HEADon a reveal) is this404too: the API never answers405
Prove who you are
Send the right credential, proof or ticket; a wrong one may count as an attempt.
invalid-credentials401 theAuthorizationheader is not a valid credential (unknown, revoked, ended or malformed, or notBearer <credential>at all:Basic,Token, a bareBearer; the wordBeareritself 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/limitsanswers the anonymous tier,200;/v1/me/key, which needs a key, answers401). A manage token that is not its secret's is403 not-creatoron status and revoke. Ackm_of any other form is401 invalid-credentialsaudience-mismatch403 you are not the named recipient, or sent no proof; not counted; do not retry with the same credentialnot-creator403 status and revoke need the creator's credential or the manage tokenscope-missing403 the key lacks the scope this call needs; use the manage token for status and revoke, or a key with the scopepassphrase-required401 the token needs its passphrase:POSTit as{"passphrase": "…"}(a wrong one is counted)proof-required401 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); a403with this type is a failed Turnstile check: solve it again and send the fresh tokenproof-of-work-required428 solve thestampin the body and resend withCkrets-Proof(anonymous creates only)session-invalid401 an anonymous puzzle create needs a session ticket:POST /v1/sessionswith a Turnstile token, then sendCkrets-Proof: session=<ticket>ticket-invalid401 the attempt ticket is malformed, expired, for another box, or already used or voided: claim a new attempt for a fresh ticketverdict-invalid401 the judge's verdict does not match this ticket, box or judge: ask the judge again with the ticket you holdvoid-not-allowed403 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-gone410 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-frozen410 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-exhausted410 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.
internal500 our fault; nothing to fix on your side: report therequestId, 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-v1501 the path is planned (specified, not built yet); do not retryfeature-sunset410 the feature you called was deprecated (announced in/.well-known/ckrets.jsondeprecationsand/llms.txtat least six months before, withDeprecationandSunsetheaders) and has now ended; use the replacement the deprecation names, and do not retrybatch-too-large413 (planned) the batch is over its item or size limit: split itdomain-unverified422 (planned) the verification file was not found at the path or held the wrong token: serve it exactly and call verify again within 24 hourswebhook-unverified422 (planned) the webhook's host is not verified for this key: verify the domain first