route tokens

arizukoreference › tokens

Developer reference for the route_tokens primitive: schema, mint surface (MCP + REST), URL handlers at /chat/<token>/ (for web: JIDs) and /hook/<token> (for hook: JIDs) — each URL bound to its JID prefix kind, mechanics shared. SSE events, rate limits. Spec: specs/5/W-webhook-routes.md.

Schema

CREATE TABLE route_tokens (
  token_hash    BLOB PRIMARY KEY,
  jid           TEXT NOT NULL,
  owner_folder  TEXT NOT NULL,
  created_at    TEXT NOT NULL
);
CREATE INDEX route_tokens_jid ON route_tokens(jid);
token_hash
sha256 of the raw token. The raw token is returned exactly once at issuance.
jid
Destination. web:<folder>[/<suffix>] for chat tokens, hook:<folder>/<source>[/<suffix>] for webhooks. <folder> is the destination folder, distinct from owner_folder (the issuer).
owner_folder
Folder of the issuing principal. Bounds revocation: revoke_route_token requires admin on this folder, not on the JID folder.
created_at
RFC3339 timestamp.

Token format

Mint surface

One internal writer (insertRouteToken) reached through two faces, per specs/5/5-uniform-mcp-rest.md.

ActionMCP toolREST endpoint
Issue chat linkissue_chat_link(jid_suffix?)POST /v1/route_tokens/chat
Issue webhookissue_webhook(source_label, jid_suffix?)POST /v1/route_tokens/hook
Listlist_route_tokens()GET /v1/route_tokens
Revokerevoke_route_token(jid)DELETE /v1/route_tokens/{jid}

REST request body

Each POST body carries the same params as the matching MCP tool:

POST /v1/route_tokens/chat
{ "jid_suffix": "support" }                          // optional

POST /v1/route_tokens/hook
{ "source_label": "github", "jid_suffix": "main" }   // jid_suffix optional

owner_folder is bound from session context (the agent's folder or the caller's grants) and is never a parameter. Tier scope (lower = wider reach):

TierMint scope
0Any folder.
1Self and descendants. Issued on behalf of a descendant: owner_folder stays at the issuer.
2Self only.
3+No mint.

Mint response

{
  "token": "Yp3v...Q2",
  "url":   "https://<host>/hook/Yp3v...Q2",
  "jid":   "hook:acme/eng/github"
}

Token returned once. The <folder> segment of the JID is the destination folder (here acme/eng); for cross-folder mints the row's owner_folder may differ. url is built from WEB_HOST plus the URL bound to the JID prefix kind — /chat/<token>/ for web: JIDs, /hook/<token> for hook: JIDs. Each URL accepts only its bound prefix; the issuance verb picks the URL.

/chat/<token>/ and /hook/<token> — each URL bound to its JID prefix kind

The two URLs share handler internals — same widget, same POST path, same SSE — but the binding is enforced at token lookup. A hook: token at /chat/<token>/ returns 404; a web: token at /hook/<token> returns 404.

MethodPathToken JIDPurpose
GET/chat/{token}/web:Chat widget (HTML).
POST/chat/{token}/web:Append body as one inbound at the row's JID. Returns turn_id (browsers open SSE).
GET/chat/{token}/{turn_id}/sseweb:Live SSE for the round.
GET, POST/chat/{token}/mcpweb:Per-token MCP surface: send_message, get_round, get_round_status.
GET/hook/{token}hook:Same widget HTML as the /chat/ surface.
POST/hook/{token}hook:Append body verbatim as one inbound at the row's JID. Returns turn_id.
GET/hook/{token}/{turn_id}/ssehook:Live SSE for the round.

The token row's JID prefix drives sender attribution and default output style. Body shape, snapshot/status URLs, SSE events, and reconnect via Last-Event-Id are the same round-handle protocol used by every web reply — see components/webd.

POST body:

{"content": "...", "topic": "..."}    # JSON
content=...&topic=...                  # form
<any opaque bytes>                      # webhook payloads: body verbatim

Default JSON response (browser path):

{
  "user":    { "id": "msg_abc", "content": "...", "created_at": "..." },
  "turn_id": "msg_abc",
  "status":  "pending"
}

For hook: tokens, the inbound message webd writes:

{
  "jid":     "hook:<folder>/<source>[/<suffix>]",
  "sender":  "<source>",
  "body":    "<raw POST body, verbatim>",
  "headers": { "x-github-event": "push", ... }
}

sender is the <source> segment of the JID (the value the agent passed as source_label at mint time). headers carries the full request header set as a lowercase-keyed map for skill-side validation (e.g. X-Hub-Signature verification). Body is opaque to webd — whatever the source POSTed, verbatim, up to 1 MiB.

Rate limits

JID prefixBucket scopeDefault ceiling
web:per token, in-memoryhuman-typing rate
hook:per token, in-memorymachine-burst rate

Excess returns 429. Body cap is 1 MiB, env-configurable.

CLI

arizuko token issue <folder> chat                       # mints web:<folder>
arizuko token issue <folder> chat --suffix support       # mints web:<folder>/support
arizuko token issue <folder> hook github                 # mints hook:<folder>/github (destination folder)
arizuko token issue <folder> hook linear --suffix issues # mints hook:<folder>/linear/issues
arizuko token list
arizuko token revoke <jid>

All four commands hit the same REST endpoints listed under Mint surface; the CLI does no extra work beyond shaping the JSON and printing the URL.