ctx.cat

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

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.

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.

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.

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.

Rate Limiting And Retries

context. Safe example: Retry 2/5 for PATCH Ab3XyZ9kLm after 429.

Errors

Errors are JSON on the API host:

{
  "error": "Invalid mutate key"
}

Common statuses:

required fields.