---
audience: agent
humans: https://capxule.com
page: protocols
status: live
version: 2026-10-11
canonical: https://capxule.ai/protocols
mcp: https://capxule.ai/mcp
---
# capxule.ai protocols
> This site is written for AI agents. If you are a human, go to https://capxule.com

Token cost: this page sends x-markdown-tokens. Read it once; cache 3600 s. Schemas for every shape: https://capxule.ai/schemas. Change log: https://capxule.ai/changelog.jsonl.

## 1. Transport
MCP 2025-06-18 over POST https://capxule.ai/mcp (OAuth) and https://capxule.ai/mcp/open (subject). Stateless: one JSON-RPC 2.0 message per request, no batches (-32600), no SSE (GET with Accept: text/event-stream is 405), no handshake required (initialize is still answered; the protocol version is negotiated). Methods: initialize, ping, server/discover, tools/list, tools/call, prompts/list, prompts/get. A notification (no id) is 202 with no body. Send Mcp-Method and Mcp-Name; both are logged.
One raw call:
```
curl -X POST "https://capxule.ai/mcp/open?preview=<secret>" -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"subject":"<stable id for your user>"},"name":"open_journey","arguments":{"capsule_id":"welcome","locale":"en"}}}'
```

## 2. Errors
| code | retryable | next |
|---|---|---|
| HTTP 401 | no | OAuth at the resource_metadata in WWW-Authenticate, or use https://capxule.ai/mcp/open with _meta.subject |
| HTTP 403 | no | reconnect requesting scope journey |
| HTTP 429 | yes | wait Retry-After seconds, then repeat the same call |
| HTTP 503 | yes | the host is closed; repeat later |
| JSON-RPC -32700 | no | send valid JSON |
| JSON-RPC -32600 | no | send one message per request |
| JSON-RPC -32601 | no | use a method from section 1 |
| JSON-RPC -32602 | no | use a tool name from tools/list |
| `session_expired` | no | open_journey({capsule_id, resume?}) |
| `session_required` | no | open_journey (with _meta.subject on /mcp/open, or Bearer oat_ on /mcp) |
| `capsule_not_found` | no | pick one of `alternatives`, or find_capsule({query}) |
| `turn_conflict` | no | turn with a fresh turn_id |
| `rate_limited` | yes | wait retry_after_ms, repeat the same call (the window may be a minute, a day, or the new-session floor) |
| `preparing` | yes | wait retry_after_ms, turn with the SAME turn_id |
| `locale_unavailable` | no | proceed in `served`; tell the human |
| `not_available` | no | pick from the last beat's options; with reason bind_required: account_bind first; machine_cannot_bind: machines never bind (day_pass is theirs); machine_only: a human's plan pays, not day_pass |
| `option_invalid` | no | pick from the latest beat's options, or say{text} |
| `invalid_arguments` | no | call the same tool with arguments that validate against its inputSchema (a stale consent.text_version lands here too) |
| `moderation` | no | rephrase or move on; no turn was spent |
| `unavailable` | yes | wait retry_after_ms; then find_capsule for another journey |
| `challenge_expired` | no | account_bind again with the same method and target (a fresh challenge) |
| `code_invalid` | no | ask the human to re-read the code; account_verify with the same challenge (`attempts_left` remain) |
| `email_rejected` | no | ask for a different address; account_bind again (other methods available right now are listed in alternatives) |
| `phone_rejected` | no | offer email_code (the default); account_bind again |
| `delegation_limit` | no | the human removes an assistant on the web account page (`next.url`); no agent action frees a slot |
| `host_policy` | no | offer the public page in `next.url` only; never a plan name, price or checkout link |
| `authorization_pending` | yes | poll account_verify again after `interval` seconds with the same device challenge |
| `slow_down` | yes | add 5 seconds to the polling interval; poll again with the same device challenge |
| `pass_expired` | no | day_pass({}) to settle a new pass (or day_pass({code}) with a web-sold code); premium journeys stay walled until then |
| `payment_pending` | yes | wait retry_after_ms; day_pass again with the same receipt |
Walls, inside a beat, never errors: `trial_exhausted`, `premium_required`, `budget_exhausted`, `tokens_exhausted`, `stepped_down`. Full dictionary with for_human and for_agent: https://capxule.ai/errors.

## 3. Retry
| situation | rule |
|---|---|
| 429 or 503 | wait the stated interval, then one retry; back off by doubling, stop after 3 |
| network failure before a response | repeat the same request once (turn: with the same turn_id) |
| `preparing` | wait retry_after_ms, then turn with the SAME turn_id; the retry returns the stored beat and spends nothing |
| `slow_down` (device grant) | wait `interval` seconds between polls; faster polling only lengthens the interval |
| tool isError | do not retry unchanged; do what next says |
| a beat that lacks session, limits or narration | stop; say on_unavailable; never improvise |

## 4. Idempotency
| call | rule |
|---|---|
| find_capsule, journey_state, account_status | read-only; repeat freely |
| open_journey | find-or-create: a repeat returns the same session with resumed: true |
| turn | one turn_id per step; a repeat returns the same beat; the same turn_id with a different action is turn_conflict (never resolved silently) |
| make_artifact | one per kind per session; a repeat returns the same artifact free |
| handoff | each call mints a new code; older codes stay valid until used or expired |
| account_bind | a repeat for the same target inside the send window returns the same challenge; a new send waits the per-target interval |
| account_verify | a wrong code counts an attempt; a right code binds once, and a repeat returns the same bound result |

## 5. Cache
| never cache | may cache | for |
|---|---|---|
| turn, journey_state, open_journey, every beat and contract (ttl_ms 0) | GET / in all three representations, /index.json | 300 s |
|  | /llms.txt, /skill/SKILL.md, /protocols, /identity, /principals, /terms, /errors, /relay, /cite, /policy.md, /schemas/*, /manifest.json | 3600 s |
|  | find_capsule results, tools/list | 3600 s |
|  | /catalog, /catalog.jsonl, /changelog.jsonl, /freshness, /freshness.atom, /sitemap-agents.xml | 300 s |
|  | /c/{id} and /c/{id}/session | 3600 s |
|  | /status | 0 s |
Static pages carry Cache-Control, ETag, Last-Modified and Vary: Accept. Nothing stateful ever carries an ETag.

## 6. Limits
| what | value |
|---|---|
| open turns per journey | 5 |
| journeys per day per open subject | 5 |
| lifetime budget per journey, every principal | 3 turns per stop |
| sandbox turns per identity per day | 60, reset 00:00Z |
| artifact / simulation start | 2 / 2 turns |
| one 24-hour pass (machine) | 150 turns, 24 hours |
| rate per minute | 60 delegated, 20 open, 120 signed, 120 machine, 10 account_bind |
| turn ceiling | 20000 ms, then preparing |
| response size | under 64 KiB |

## 7. Versioning and deprecation
| event | rule |
|---|---|
| protocol version | initialize returns protocolVersion and serverInfo.version (capxule-agent 1.0.0); no custom version header |
| tool going away | stays in tools/list marked deprecated with _meta.sunset at least 90 days ahead |
| breaking schema change | a new tool name; the old name is served for 180 days |
| preview to launch | the preview credential becomes inert (ignored, never rejected); the mounts and tool names do not change |
| record of every change | https://capxule.ai/changelog.jsonl (?since=<date>), on the day it is announced, with the date it takes effect |
Breaking changes are never silent.

## 8. Naming
| thing | convention |
|---|---|
| paths | kebab-case |
| JSON keys | snake_case on the journey tools; cards keep the marketplace connector's frozen camelCase (capsuleId, creatorName, link) beside the spec's wire names (page_url, not_for) |
| error codes | lower_snake |
| tool names | verb_noun |
| JSONL records starting with _ | metadata to skip |
| transport failures (401, 403, 429) | precede JSON-RPC; never tool errors |

## 9. Credentials for CLI and headless agents
| what | value |
|---|---|
| device grant (RFC 8628) | `device_authorization_endpoint` https://app.capxule.com/api/oauth/device; token endpoint https://app.capxule.com/api/oauth/token with `grant_type=urn:ietf:params:oauth:grant-type:device_code`; `account_bind({method:"device"})` wraps the same flow and `account_verify` polls it |
| `verification_uri` | https://capxule.com/device (the human site, where the session lives). https://capxule.ai/device is a 302 to it and sits behind the preview gate: never send a human there |
| delegated bind | token endpoint with `grant_type=urn:capxule:grant:delegated-bind`, `client_id` (+ `client_secret` for a confidential client), `challenge` (the `ch_` id account_bind returned) and `code` (the one-time code the human read, or the magic-link token); single use, within 5 minutes of account_verify; the pair carries `delegation_id` and audience https://capxule.ai/mcp |
| refresh | `grant_type=refresh_token`, rotating; reuse of a rotated refresh token revokes the whole family |
| Web Bot Auth (RFC 9421) | covered components must include `@authority` and `signature-agent` (optionally @method @path @query @scheme @target-uri @request-target and plain headers; no component parameters); `created` and `expires` required, 60 s skew, lifetime at most 24 h; `alg` if present must be `ed25519`; `tag` if present must be `web-bot-auth`; the Signature-Agent directory must be an https public hostname answering /.well-known/http-message-signatures-directory within 2 s, 64 KB, no redirects; keys are re-read every 6 hours and once on an unknown keyid; keyid = the JWK thumbprint. A verified signature is a `machine` principal; anything else is `anonymous` |

## 10. Freshness
generated_at: 2026-10-14T10:11:47.286Z. version: 2026-10-11. ttl: 3600000 ms.
