ctx.cat API
ctx.cat exposes a small HTTP API for encrypted private shares, plaintext public and unlisted shares, comments, discovery, signing capabilities, and verification. Hosted private-content operations are zero-knowledge: content, metadata, and comments are encrypted client-side before upload, and decrypt keys stay in URL fragments.
Base URLs
- Web:
https://www.ctx.cat - API:
https://api.ctx.cat - Local default:
http://127.0.0.1:8787
Use CTX_CAT_BASE_URL in local clients when targeting staging or a self-hosted backend.
Critical Fragment Handling
URL fragments are client-side secrets. Browsers do not send fragments to servers, and agents must preserve that boundary manually.
error tracker.
key.
shares are write capabilities. Treat them like decrypt keys: never send them to an LLM, logs, analytics, backend services that do not require them, or error trackers.
logging request context or errors.
- Correct HTTP request:
GET https://api.ctx.cat/Ab3XyZ9kLm - Wrong HTTP request:
GET https://api.ctx.cat/Ab3XyZ9kLm#key=... - Do not send fragments to an LLM, backend, logs, analytics, search index, or
- Strip the fragment before HTTP fetch, then decrypt locally with the fragment
- Owner fragments grant mutation rights and must be treated like credentials.
mutateKeyfrom private share creation andownerKeyfrom public/unlisted- Redact
X-Ctx-Mutate-Key,X-Ctx-Owner-Key,#key, and#ownerbefore
Common Mistakes
Do not copy these patterns into agents or integrations:
// WRONG: sends or logs a fragment-bearing URL.
await fetch("https://api.ctx.cat/Ab3XyZ9kLm#key=secret");
console.error("failed to fetch", "https://www.ctx.cat/Ab3XyZ9kLm#key=secret");
// WRONG: stores plaintext in a field that must carry encrypted bytes.
await fetch("https://api.ctx.cat/", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Ctx-Upload-Format": "json" },
body: JSON.stringify({ bodyBase64: btoa(plaintext) }),
});
Safe debugging context is a share id, status code, and generic error message: PATCH Ab3XyZ9kLm returned 403. Full URLs with fragments, capability headers, mutateKey, and ownerKey are not safe debugging context.
Crypto Contract
Use the official TypeScript client, CLI, or MCP server for crypto operations. Only use raw HTTP if implementing the crypto contract in public/client.ts exactly:
per-share salt.
inside encryptedMeta.
keys, owner capabilities, or signing private keys to the hosted backend.
- Generate a random base64url decrypt key for the URL fragment.
- Derive an AES-256-GCM key from that fragment key using HKDF-SHA-256 and the
- Encrypt private content with AES-256-GCM and the body
iv. - Encrypt private metadata separately with AES-256-GCM and a metadata IV stored
- Never send plaintext private content, plaintext private metadata, decrypt
Headers
Security: never log X-Ctx-Mutate-Key, X-Ctx-Owner-Key, or any header containing write capabilities. These are secret credentials equivalent to passwords.
signing requests, verification requests, and the private JSON upload fallback.
salt, iv, and encrypted metadata.
encrypted metadata. The JSON body carries bodyBase64 ciphertext and uploadMeta.
encrypted metadata is too large to safely return in X-Ctx-Meta.
deletes, and owner-marked comments.
verification metadata.
Content-Type: application/octet-stream: encrypted binary private upload body.Content-Type: application/json: public/unlisted JSON payloads, comments,X-Ctx-Meta: base64 JSON metadata envelope for private uploads. ContainsX-Ctx-Upload-Format: json: ctx.cat private upload fallback for largeX-Ctx-Meta-Location: response header pointing to/<id>/metawhenX-Ctx-Expires: expiration timestamp ornever.X-Ctx-Mutate-Key: private owner capability for private edits and deletes.X-Ctx-Owner-Key: public/unlisted owner capability for plaintext edits,X-Ctx-Mode:publicorunlistedfor plaintext share creation.X-Ctx-Signature-Public-Key: SSH public key used for optional publicX-Ctx-Signature-Github-User: claimed GitHub username.X-Ctx-Signature-Created-At: author signature timestamp.X-Ctx-Signature-Content-Sha256: signed content hash.X-Ctx-Signature-Metadata-Sha256: signed metadata hash.
Legacy X-Mux-* request headers are accepted as temporary aliases for old clients, but new integrations should only emit X-Ctx-*. Responses emit X-Ctx-Meta or X-Ctx-Meta-Location.
Capability Headers
All write capabilities are secrets.
Share type | Response field | HTTP header | Security level
Private | mutateKey | X-Ctx-Mutate-Key | secret write capability
Public | ownerKey | X-Ctx-Owner-Key | secret write capability
Unlisted | ownerKey | X-Ctx-Owner-Key | secret write capability
It is safe to log a share id such as Ab3XyZ9kLm or a base URL without a fragment. It is not safe to log fragments, full fragment URLs, capability headers, mutateKey, or ownerKey.
Private Shares
Critical: private means you encrypt before upload. The server stores exactly the bytes you send. Never send plaintext as a private share body.
POST /
Creates a private encrypted share when the request omits X-Ctx-Mode.
Request body is ciphertext bytes with X-Ctx-Meta, or a JSON envelope when X-Ctx-Upload-Format: json is set. The server stores ciphertext, encrypted metadata, hashed owner capability, size, expiration, optional public verification metadata, and optional provenance. It never receives the decrypt key.
Agents should use the official client path unless they have implemented the crypto contract above.
Response:
{
"id": "Ab3XyZ9kLm",
"mutateKey": "owner-capability"
}
GET /<id>
Returns ciphertext bytes. Small encrypted metadata is returned in X-Ctx-Meta. Large encrypted metadata is retrieved from GET /<id>/meta when X-Ctx-Meta-Location is present.
GET /<id>/meta
Returns encrypted metadata fields and non-sensitive storage metadata:
{
"encryptedMeta": "...",
"iv": "...",
"salt": "...",
"size": 1234,
"publicVerification": {},
"provenance": {}
}
PATCH /<id>
With X-Ctx-Mutate-Key, updates encrypted content, encrypted metadata, or expiration. The body follows the same private upload format as POST /.
DELETE /<id>
With X-Ctx-Mutate-Key, deletes a private share.
Public And Unlisted Shares
POST /
Creates a plaintext public or unlisted share when X-Ctx-Mode is public or unlisted.
Security: ownerKey grants edit/delete/comment ownership. Treat it as a secret capability. Do not log it, send it to an LLM, send it to analytics, or include it in user-visible errors.
Private mutateKey grants the same class of write access for private shares and must be handled with the same secrecy.
Request:
{
"content": "# notes",
"name": "notes.md",
"signature": {}
}
Response includes an ownerKey. The owner key belongs in the URL fragment and is never needed for normal reading.
GET /<id>
Returns the plaintext record as JSON.
PATCH /<id>
With X-Ctx-Owner-Key or X-Ctx-Mutate-Key, updates content, name, expiration, mode, or signature.
DELETE /<id>
With owner capability, deletes the share.
Comments
GET /<id>/comments
Returns public/unlisted comments or encrypted private comment records.
POST /<id>/comments
For private shares, send encrypted comment body and iv. For public/unlisted shares, send plaintext content. Owner capability marks the comment as owner; otherwise it is anonymous.
Private comment encryption uses a key derived locally from the private share key and share id. Agents should not implement this crypto manually unless they are matching the TypeScript client exactly. Prefer addPrivateComment from the TypeScript client, CLI, or MCP tools. A private comment request shape is:
{
"body": "encrypted-comment-base64",
"iv": "comment-iv-base64"
}
Discovery
GET /public.json
Returns recent public shares. Private and unlisted shares are excluded.
GET /health and GET /healthz
Return service health without exposing secrets:
{
"ok": true,
"service": "ctx-cat-api",
"storage": "configured",
"timestamp": "2026-05-14T00:00:00.000Z"
}
Signing And Verification
GET /signing/capabilities
Reports whether remote signing is enabled and whether a local signing key is available. Hosted ctx.cat defaults to remote signing disabled.
POST /signing/sign
This endpoint is disabled on hosted ctx.cat to prevent plaintext exposure and returns 403. It creates a server-side signature envelope only when CTX_CAT_ENABLE_REMOTE_SIGNING=true in local, self-hosted, or explicitly trusted deployments. Hosted clients should use local signing instead so plaintext does not leave the user environment.
POST /signing/verify-github-key
Checks whether a signing SSH public key is published for a claimed GitHub user and optionally whether the user belongs to an allowed org.
Request:
{
"githubUser": "octocat",
"publicKey": "ssh-ed25519 AAAA..."
}
Response includes a verification state, key fingerprint, and optional org state.
Limits
Default request body limit is 50 MiB and can be changed with CTX_CAT_MAX_BYTES. Large agent traces should use private encrypted uploads. Large encrypted metadata automatically uses the X-Ctx-Upload-Format: json fallback to avoid unsafe HTTP header sizes.
JSON Upload Fallback
Critical: bodyBase64 must be encrypted ciphertext, not plaintext. Encrypt content first with AES-256-GCM, then base64 encode the ciphertext.
Use the JSON fallback when encrypted metadata would exceed safe header size limits:
{
"bodyBase64": "encrypted-body-base64",
"uploadMeta": {
"encryptedMeta": "encrypted-metadata-base64",
"iv": "body-iv-base64",
"salt": "hkdf-salt-base64"
}
}
Safe Error Handling
status code.
X-Ctx-Owner-Key.
issue text, analytics, or prompts to an LLM.
Safe example: Failed to PATCH share Ab3XyZ9kLm with HTTP 403. Unsafe example: a full ctx.cat URL with #key, an ownerKey, a mutateKey, or an owner/mutate header value.
- If decryption fails, log only the share id and a generic failure reason.
- If owner or mutate capability validation fails, log only the share id and
- Never log fragment keys,
mutateKey,ownerKey,X-Ctx-Mutate-Key, or - Never include full URLs with
#fragments in stack traces, support tickets, - If asking an LLM to help debug an integration, redact all capabilities first.
Rate Limiting And Retries
context. Safe example: Retry 2/5 for PATCH Ab3XyZ9kLm after 429.
- Use exponential backoff for transient
5xxerrors. - Do not retry
4xxerrors except429. - Respect
Retry-Afterwhen present. - When logging retry attempts, redact all capabilities and fragments from
Errors
Errors are JSON on the API host:
{
"error": "Invalid mutate key"
}
Common statuses:
required fields.
400: invalid JSON, invalid metadata, malformed expiration, or missing403: invalid owner or mutate capability.404: share or route not found.413: payload larger than the configured body limit.500: unexpected server error.