Titentiten.devꦠꦶꦠꦺꦤ꧀
v0.1.1 · npmGitHub
DocsReferenceHTTP API

HTTP API

Every route the service declares, with the scope its credential must hold, the two response envelopes, and the rules that apply to all of them.

58 routesBearer titen_sk_snake_case1 MiB body capMCP at /mcp

The route table below is the router’s own contents. Anything absent from it does not exist yet, and scripts/verify-live.ts fails if the router declares a route the verification script never called — which is the mechanism that keeps this page honest rather than a promise to keep it updated.

Authentication

Two routes are unauthenticated: GET /healthz and GET /readyz. Every other route declares a required scope, and a route with a scope always authenticates first.

Send Authorization: Bearer titen_sk_…. A token that does not start with titen_sk_ is rejected without a database lookup.

Verification is one indexed lookup on the SHA-256 of the key. Nothing about the principal is cached, so a revoked key stops working on the next request.

Organization comes from the credential. A request body never grants tenant authority, and no route accepts an org_id field.

A key may hold * in place of a scope list. A key can never mint another key with scopes or a trust ceiling it does not itself hold.

Scope failures are distinguishable from identity failures, but resource ownership is not: see Errors for why a foreign id returns 404 and never 403. The full scope list and trust ceilings are on Keys & scopes.

Envelopes

Success is data plus meta. Failure is error plus meta. Both always carry meta.request_id, and the same value is repeated in an x-request-id response header so you can correlate a log line without parsing the body. Envelope responses are content-type: application/json; charset=utf-8, and every response is cache-control: no-store.

{ "data": { "": "" },
  "meta": { "request_id": "req_930dd110186f42ad959858d8745fc0b6" } }
{ "error": { "code": "VALIDATION_ERROR", "message": "Field \"kind\" must be one of: …" },
  "meta": { "request_id": "req_71b6d2e2eba54b0298b41de8544dedc6" } }

Request ids are req_ + 32 lowercase hex. Record ids follow the same shape with their own prefix: org_, key_, obs_, claim_, ctx_, fb_.

Three responses sit outside the JSON envelope on purpose. GET /v1/export and GET /v1/audit/export stream application/x-ndjson, and POST /mcp returns a bare JSON-RPC body because an MCP client expects the protocol’s own shape.

Request limits

REQUEST RULES

MAX_BODY_BYTES 1,048,576. Checked against the declared content-length first, then against the body that actually arrived, so a lying header gets no free pass. Over the limit is 413 PAYLOAD_TOO_LARGE.

content-type JSON requests are parsed as JSON; an empty body or unparseable body is a 400 VALIDATION_ERROR, not a 500.

Idempotency-Key Optional, trimmed to 200 characters. Honoured on three endpoints — see below.

Idempotency and meta.replayed

POST /v1/observations, POST /v1/consolidations and POST /v1/context/:id/feedback accept an Idempotency-Key header. The replay record is inserted inside the same atomic batch as the canonical rows, so a duplicate key makes the whole write fail and roll back instead of producing a second copy.

First call: the normal 201, with meta.replayed: false.

Same key, same body: the stored response, with meta.replayed: true and status 200 rather than 201 — nothing was created this time.

Same key, different body: 409 CONFLICT, "Idempotency-Key was reused with a different request body." A key identifies one request, not one endpoint.

POST /v1/context/:id/feedback additionally accepts a client_mutation_id field in the body, deduplicated per organization by a unique index. It exists because a feedback call is the one write an agent is most likely to retry from a queue it does not control.

MUTATIONS WITHOUT A KEY

Every other mutating route is a plain write. Retrying POST /v1/leases or POST /v1/channel-releases without a key does what the underlying rule says — a second lease acquire by the same holder replaces its own lease, a second release creates a new version — rather than replaying a stored response.

Health

Method Path Scope What it does
GET /healthz Liveness, plus the runtime label and build revision. No sensitive detail.
GET /readyz Storage check, applied-vs-expected migration counts, capability flags. Returns 503 NOT_READY with the same body when not ready.

The memory loop

Method Path Scope What it does
POST /v1/projects/resolve projects:resolve Normalize a project reference to an opaque project id. create: true additionally requires projects:create.
POST /v1/observations observations:write Append content-hashed evidence. 201. trust may not exceed the credential’s ceiling.
POST /v1/consolidations claims:write Derive claims from observations. Deterministic — model_used: false, meta.model: "disabled".
POST /v1/context/compile context:compile Rank authorized claims into a token budget and record the selection as a ctx_ run.
POST /v1/context/:id/feedback feedback:write Record used, useful, irrelevant, incorrect or harmful against a run or one of its items.
GET /v1/claims/:id/evidence evidence:read Resolve a claim to its supporting, contradicting and qualifying observations, plus hidden_source_count.

A walkthrough with real payloads is in the Quickstart; the ranking is explained on Compile context.

Claim lifecycle

Method Path Scope What it does
POST /v1/claims/:id/supersede claims:write Replace a claim with an active one named in superseded_by. Only active or disputed claims can be superseded.
POST /v1/claims/:id/revoke claims:write Withdraw a claim. Idempotent, reports already_revoked. A superseded claim cannot be revoked.
POST /v1/claims/:id/expire claims:write End validity now by setting valid_to. Idempotent, reports already_expired.

All three stop the claim appearing in compiled context and none of them delete an observation. Details on Claim lifecycle.

Checkpoints

Method Path Scope What it does
POST /v1/checkpoints checkpoints:write Upsert task state on organization + subject + agent + kind. A second write returns 200 with updated: true and the same checkpoint_id.
GET /v1/checkpoints checkpoints:read Read the current checkpoint. Requires subject_id and kind query parameters; returns 404 once expired.
DELETE /v1/checkpoints/:id checkpoints:write Delete a checkpoint row outright. Checkpoints are task state, not evidence.

kind is one of task_state, conversation, workflow, cursor. ttl_seconds is between 60 and 2,592,000, and the serialized state is capped at 64,000 characters.

Keys

Method Path Scope What it does
POST /v1/keys keys:manage Mint a key. The raw titen_sk_… value is in the response once and never again; storage holds only its hash.
GET /v1/keys keys:manage List key metadata with status. No hashes, no secrets.
DELETE /v1/keys/:id keys:manage Revoke a key. Effective on the next request.

Portability

Method Path Scope What it does
GET /v1/export export:read Stream NDJSON: one header line, then one record per line. type is projects, observations or claims; limit 1–2000, default 500.
POST /v1/import import:write Load NDJSON, at most 2000 lines per request, preserving record ids so evidence links survive the move.

Order matters across requests, because an observation references a project and a claim source references an observation. Follow import_order from the export header: projects, then observations, then claims. A reference to a record that does not exist yet is 400 UNRESOLVED_REFERENCE, not a conflict. See Backup & export.

Collaboration

Method Path Scope What it does
POST /v1/workspaces workspaces:write Create a workspace. A duplicate name in one organization is 409.
GET /v1/workspaces workspaces:read List workspaces.
POST /v1/memberships memberships:write Attach a principal to the organization or a workspace with role owner, admin, member or reader.
GET /v1/memberships memberships:read List current memberships, optionally filtered by workspace_id.
DELETE /v1/memberships/:id memberships:write Soft removal: the member stops being listed, prior provenance and audit entries remain.
POST /v1/leases leases:write Acquire an exclusive lease on resource_type + resource_id. A live lease held by another principal is 409; an expired one or the same holder re-acquires.
DELETE /v1/leases/:id leases:write Release a lease before its TTL.
POST /v1/handoffs handoffs:write Offer work to another principal, optionally naming a context_id and checkpoint_id.
POST /v1/handoffs/:id/resolve handoffs:write The recipient sets status to accepted or rejected. Nobody else can resolve it.
GET /v1/handoffs handoffs:read Pending handoffs addressed to the caller — the pull path for agents that cannot receive webhooks.

Lease ttl_seconds is between 10 and 86,400. More on Leases & handoffs.

MCP

Method Path Scope What it does
POST /mcp mcp:call JSON-RPC 2.0 over Streamable HTTP: initialize, notifications/initialized, initialized, ping, tools/list, tools/call. Seven tools.

Protocol versions 2025-06-18, 2025-03-26 and 2024-11-05 are supported. A notification returns 202 with an empty body; a batch returns an array. Key management, membership, retention, webhook, federation, release and Atlas operations are deliberately absent from the agent tool profile — see MCP server.

Events

Method Path Scope What it does
GET /v1/events events:read Authorized, metadata-only domain events after an opaque cursor. after, limit 1–200 default 50, kind.
GET /v1/events/:id events:read One event by id.

Event rows are written in the same atomic batch as the canonical change they describe, so an event can never exist for a write that rolled back or be missing for one that committed. This is not a transcript or a raw memory feed.

Memory Atlas

Method Path Scope What it does
POST /v1/memory-views/compile views:compile Compile a bounded authorized graph view. lens is evidence_trace, neighborhood, conflict_freshness or scope_preview; limit 1–200, default 50.

Read-only despite the POST, because the request needs a bounded body. evidence_trace requires focus_id, neighborhood requires subject_id. Policy runs before traversal: both endpoints of every edge must be authorized, and hidden candidates contribute no edges, labels or counts. An unauthorized focus is a non-disclosing 404.

Governance and channel releases

Method Path Scope What it does
POST /v1/policies policies:write Record a retention, approval_required, visibility_default or trust_ceiling policy with a JSON config.
GET /v1/policies policies:read List the organization’s policies.
POST /v1/channel-releases releases:write Draft a release for one channel and audience. Only active claims with verified or policy_approved trust are included; up to 200 claim_ids.
POST /v1/channel-releases/:id/publish releases:write draftactive. Publishing from any other state is 409.
POST /v1/channel-releases/:id/revoke releases:write activerevoked, keeping the release record and its audit trail.
GET /v1/channel-releases/:id releases:read Read a release with its released items.
POST /v1/channel-context channel:read Serve only the content of active releases matching a channel and audience, packed under max_tokens (128–32,000).

verified trust describes evidence authority; it does not by itself permit disclosure. A public-facing chatbot holds a credential pinned to its channel and reads /v1/channel-context, never canonical memory. Reasoning in ADR-0002 and on Channel releases.

Audit

Method Path Scope What it does
GET /v1/audit audit:read Metadata-only audit entries with cursor pagination. after, action, limit 1–500 default 50.
GET /v1/audit/export audit:export Stream the log as NDJSON behind a titen.audit.header line. after, limit 1–5000 default 1000.

Federation

Method Path Scope What it does
POST /v1/federation/peers federation:write Register a peer with endpoint, shared_secret (16+ characters) and direction push, pull or bidirectional. One endpoint per organization.
GET /v1/federation/peers federation:read List peers with status and sync cursor.
POST /v1/federation/peers/:id/suspend federation:write Suspend a peer. Its pull and push then return 403.
POST /v1/federation/peers/:id/filters federation:write Add a filter on resource_type with optional include_kinds, exclude_subjects, min_trust. Filters bound both directions.
GET /v1/federation/peers/:id/filters federation:read List a peer’s filters.
POST /v1/federation/pull federation:write Pull up to 200 filtered events after the peer’s cursor and advance it.
POST /v1/federation/push federation:write Accept events from a peer. Requires x-titen-peer-signature: sha256=HEX over the raw body; each event reports success, conflict or rejected.
GET /v1/federation/log federation:read The exchange log for one peer_id.

Webhooks and delivery

Method Path Scope What it does
POST /v1/webhooks webhooks:write Register a destination with secret (16+ characters) and a non-empty events array (up to 50; * matches every kind). The response states the signing contract and never echoes the secret.
GET /v1/webhooks webhooks:read List webhooks with status, event filter and failure count.
DELETE /v1/webhooks/:id webhooks:write Stop future delivery.
POST /v1/webhooks/:id/pause webhooks:write Suspend delivery without dropping events.
POST /v1/webhooks/:id/resume webhooks:write Resume delivery and clear the failure counter.
GET /v1/webhooks/:id/deliveries webhooks:read Metadata-only delivery state: status, attempts, last_attempt_at, next_retry_at, response_status.
POST /v1/webhooks/deliver webhooks:write Drain the delivery outbox. after, limit 1–200 default 50.

The destination must be https://, or http://localhost / http://127.0.0.1 for development; private and link-local ranges are rejected. Deliveries are signed X-Titen-Signature: sha256=HEX, an HMAC-SHA256 of the raw body, alongside X-Titen-Event and X-Titen-Delivery. Backoff is 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours, capped at 5 attempts, and a webhook is disabled after 10 consecutive failures. A unique index on (webhook, event) makes delivery exactly once per event per webhook even if a drain is replayed. More on Webhooks & events.

Indexing

Method Path Scope What it does
POST /v1/index/drain index:write Embed queued claims into the vector store. limit 1–100, default 50. Returns drained, indexed, skipped, remaining, model, dimensions, at.

Observation and claim writes queue an outbox row, and until something consumes them the vector index stays empty however well it is configured. This route is that consumer. It is pull-driven because an embedding call crosses the network and a canonical write must not wait on one. With no vector capability configured it returns 400; if the model or index is unreachable it returns 503 UNAVAILABLE with meta.pending and meta.retryable and leaves the queue untouched. GET /readyz reports background_repair so an operator can tell whether anything is calling it. See Semantic vectors.

Not implemented

Named here because earlier drafts documented them as if they existed: batch observation append, channel CRUD at /v1/channels, a separate approve/activate release workflow, /v1/webhook-subscriptions, /v1/webhook-deliveries as a top-level collection, /v1/audit/events, and /v1/channels/:id/context/compile. The tables above are authoritative.

Compatibility

The v1 export format is versioned independently from the HTTP API.

Breaking request or response changes require a new API version or a migration path.

External field names are snake_case. Internal names are not part of the contract.

Next

↑↓ navigate↵ open