Start: an example for every use case
Each section is one way to hand a secret over, with a command you can run. Set CKRETS to the origin you are calling (production is https://ckrets.ai; a test deployment says so in its banner). The commands need curl and jq.
CKRETS=${CKRETS:-https://ckrets.ai}
# the stage: prod, or a test deployment
curl -s "$CKRETS/v1/limits" | jq -r .stage
Keep API keys out of your agent
Name the recipient by its key id; only that key opens the token. It reveals into an environment variable or a file, so the value reaches the process, not the transcript or mcp.json. (An MCP create_secret takes the value as an argument, so a value you create through MCP is in your context.)
Only the holder of that ckrets key can open it; a leaked link is worthless. The recipient gives you its key id (key_…), never its key.
opens for one ckrets key, by its idlimit free key: 64 KB; it opens while the recipient's key lease lives (7 d at most): for longer, name a public key
curl -X POST https://ckrets.ai/v1/secrets \
-H "authorization: Bearer $CKRETS_KEY" \
-H 'content-type: application/json' \
-d '{"value":"LAB_7f3a9c","ttl":"1h",
"audience":{"type":"key","id":"key_"}}' The recipient reveals with the helper and its own key: npx -y @ckrets/helper@0.9.0 reveal "$TOKEN" --env NAME -- <command>. A key is a lease (24 hours unused, seven days at most); its secrets keep their own TTL.
Pass a secret to a CI job, in any org
The job proves itself with its own OIDC token (GitHub Actions, GitLab CI, CircleCI); the pipeline stores no ckrets key. This narrows exposure; a compromised step in the same job can still read a revealed value. The full two-job workflow is in the guide, section 6.
The job proves itself with its own OIDC token; the pipeline holds no ckrets key.
opens for the deploy.yml workflow of acme/api on main (GitHub Actions)limit free-key limits, counted per repository
curl -X POST https://ckrets.ai/v1/secrets \
-H "authorization: Bearer $CKRETS_KEY" \
-H 'content-type: application/json' \
-d '{"value":"LAB_7f3a9c","ttl":"1h",
"audience":{"type":"oidc",
"issuer":"https://token.actions.githubusercontent.com",
"claims":{"repository":"acme/api",
"job_workflow_ref":"acme/api/.github/workflows/deploy.yml@refs/heads/main"}}}'Deliver to a server only its key opens
Name the server by its public key; with clientEncrypted and a key wrapped to its X25519 key (HPKE), ckrets stores ciphertext it cannot read. Making the keys: signing; the formats: client-side encryption.
The server signs its reveal with a key that never leaves it (the signing profile).
opens for the holder of that Ed25519 private key
curl -X POST https://ckrets.ai/v1/secrets \
-H 'content-type: application/json' \
-d '{"value":"LAB_7f3a9c","ttl":"1h",
"audience":{"type":"publicKey",
"jwk":{"kty":"OKP","crv":"Ed25519","x":"11qY…"}}}'Encrypt before you send; we store ciphertext and the key travels in the link (the formats).
opens for whoever has the full link (key after the #)stored as ciphertext only
curl -X POST https://ckrets.ai/v1/secrets \
-H 'content-type: application/json' \
-d '{"value":"v1.AAECAwQFBgcICQoL.VMCrLypv6ihO0X246PtMk4CDM_Wnbl8Oqwc","clientEncrypted":true,"wrappedKey":"fragment.v1","ttl":"1h"}'
# hand over https://ckrets.ai/r/ck_… plus #k=… (the part after # is never sent to us)The share page decrypts it in the recipient's browser; a recipient who trusts no code of ours decrypts with its own.
Keep credentials out of tickets and chat
Send a link that opens once, to a customer, a contractor or support, instead of pasting the value into a ticket or a chat. The page reveals on a click, so link previews cannot spend it; the status shows when it was collected and whether anyone else tried. Add a passphrase sent another way for a second factor.
Anyone who gets the link opens it once; then it is gone. Send the link over a channel you trust.
opens for anyone with the link, oncelimit 1 KB, 7 d anonymously
curl -X POST https://ckrets.ai/v1/secrets \
-H 'content-type: application/json' \
-d '{"value":"LAB_7f3a9c","ttl":"1d","reveals":1}'Send the link one way and the passphrase another; five wrong tries destroy it (ten on a secret made with a key).
opens for the link holder who knows the passphraselimit 7 d anonymously
# test values only:
curl -X POST https://ckrets.ai/v1/secrets \
-H 'content-type: application/json' \
-d '{"value":"LAB_7f3a9c","ttl":"4h","passphrase":"corvid-lantern-42"}'
# real values: both read from files, never typed
jq -n --rawfile v ./secret.txt \
--rawfile p ./passphrase.txt \
'{value:$v, ttl:"4h",
passphrase:($p | rtrimstr("\n"))}' |
curl https://ckrets.ai/v1/secrets \
-H 'content-type: application/json' \
--data-binary @-Ask a person for a password or code
Your agent sends a link; the person types the value on a page that names who is asking, and only your key can open the answer. It needs a key (POST /v1/requests, then the person opens /q/<id>). This is the web page the MCP specification asks a server to send a user to, instead of asking for a password in the chat. The calls, the limits and the anti-phishing rules: the guide, section 7.
Prove every hand-off
Every create and reveal returns a receipt (a compact JWS) signed with the keys at /.well-known/jwks.json. The sender checks it offline, without calling ckrets, and keeps it as proof the right party collected: how to verify.
Keep it longer, or open it on a date
Every secret lives 7 days unless you ask otherwise: up to 7 days anonymously and up to a year with a free key ("ttl":"365d"). No expiry ("ttl":"never") needs a paid key, which is planned. Riddle, game and dated boxes (open on a date) are made through first-party apps in v1, not through this API directly.
Rotate after a breach planned
POST /v1/batches will send new secrets to many named recipients in one call, with one manifest receipt. Specified in /openapi.yaml; not built yet (the guide, section 8).
Health and card data under contract planned
Classes for health data (HIPAA) and card data (PCI DSS) are planned, under contract. ckrets claims no compliance or certification today. Until then, keep your servers to tokens: a backend of yours that reveals card data is in your PCI DSS scope, and one that reveals health data holds ePHI under HIPAA.
The API
JSON in, JSON out, described in OpenAPI 3.1. Base URL https://ckrets.ai/v1. Every answer carries Ckrets-Version, the dated contract it follows (2026-11-06, the first); send the header to pin your code to a date, send none for the original contract, and a date this server does not serve is 400 version-unknown.
| Call | What it does |
|---|---|
| POST /v1/secrets | Store a value under a policy (ttl, reveals, passphrase, audience, purpose). Returns token, url, manage token, digest and receipt. |
| GET /v1/secrets/{token} | Reveal; the recipient proves its audience. A passphrase is sent with POST. Returns the value and a signed receipt. |
| GET /v1/secrets/{token}/public | What a recipient may see before revealing: the sender, expiry, size, audience. Spends nothing. |
| GET /v1/secrets/{token}/status | The creator's view: state, who revealed, attempts after the last reveal. Never the value. |
| DELETE /v1/secrets/{token} | Revoke, with the creator's key or the manage token. |
| POST /v1/keys | Mint a free key, no account (below). |
| GET /v1/limits | Your tier, counters, remaining budget, stamp parameters and the stage (try it). |
| POST /v1/report | Report a token or URL with a reason; a bearer token is frozen pending review. |
Planned (specified in /openapi.yaml, not built yet): POST /v1/batches (many secrets to many named recipients, for rotation after an incident) and POST /v1/me/webhooks (signed alarms). POST /v1/requests (ask a person for a value) is in the beta: above.
Get a free key
No account, no email needed. A key raises every limit and keeps working when the anonymous tier is paused (503). It is a lease: it lasts 24 hours from its last use and seven days at most; long-lived keys are planned.
curl -X POST https://ckrets.ai/v1/keys \
-H 'content-type: application/json' -d '{"label":"deploy-bot"}'
# response: keep "key" secret (shown once); give senders "keyId"
{ "key": "ck_live_9mQ2…", "keyId": "key_7TQM4C…", "tier": "free",
"lease": { "windowEndsAt": "…", "hardEndsAt": "…" } }Limits
Per tier, enforced per caller, and in every response's headers (RateLimit-Remaining, Ckrets-Tier). The full table: limits.
| Limit | Anonymous | Free key | Paid (planned) |
|---|---|---|---|
| value size | 1 KB | 64 KB | 1 MB |
| ttl | 7 d default and max | 7 d default, 1 year max | no max |
| reveals | 1 | up to 10 | up to 1,000 |
| creates | 60 an hour, 300 a day per IP; 240 an hour, 1,200 a day per network | 1,000 a day | by plan |
A CI job creates with its own OIDC token, at free-key limits per repository. Signed agents (RFC 9421, Web Bot Auth) get their own tier, planned: 4 KB, up to 3 reveals, 300 creates an hour.
Errors
Every error is application/problem+json: branch on type, never on title. Each type has a page at /problems/<type> with what to do next; all the types. The first ones you meet:
429 rate-limited(withRetry-After): wait, then send it again; after three in a row, stop.429 quota-exhausted(noRetry-After): stop untilresetsAt, or mint a key.428 proof-of-work-required: the anonymous tier is busy; solve the small stamp and resend withCkrets-Proof, or use a key.400 invalid-requestand413 value-too-large: fix the body (thedetailnames the field); nothing was spent.410 token-gone: revealed, expired or revoked, by design. Do not retry.503 tier-budget-exhausted: the anonymous tier's daily budget is spent;Retry-Afterruns to 00:00 UTC, and a key works now.403 audience-mismatch: you are not the named recipient (or sent no proof); not counted. Do not retry with the same credential.401 invalid-credentials: theAuthorizationheader is not a valid credential (an ended lease, a revoked key); send none rather than a bad one.503 temporarily-unavailable: withoutcome: "unknown"a write may have happened; read the call incheckbefore you resend.
Two answers carry no problem body; they come from the platform in front of the API. A plain 403 means one address sent too fast (over 300 creates a minute, or 100 requests a minute to one /v1/secrets/… path): wait a minute or two, then slow down. A plain 503 usually means load was shed: after a read, wait 2 seconds and retry; after a write, check first, as for outcome: "unknown" (the rules).