arizuko

arizukoreference › Route tokens

route tokens

Developer reference for the route_tokens primitive: schema, mint surface (MCP + REST), URL handlers at /chat/<token>/ and /hook/<token> — either URL accepts any valid token; the JID kind is metadata, 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,
  context       TEXT
);
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.
context
Optional. Free-text instructions from the issuer on how to process this link's inbound. Snapshotted onto each arriving message and rendered to the agent as <link-context>. NULL means no instructions — the pre-context behavior.

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?, context?)POST /v1/route_tokens/chat
Issue webhookissue_webhook(source_label, jid_suffix?, context?)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
  "context": "bug reports; triage, don't chat" }     // optional

POST /v1/route_tokens/hook
{ "source_label": "github", "jid_suffix": "main",    // jid_suffix optional
  "context": "push events; summarize daily" }        // 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 issuance verb's canonical URL — /chat/<token>/ for issue_chat_link, /hook/<token> for issue_webhook. Either URL accepts any valid token (below).

The optional context minted onto a token is the issuer's instructions on how to process the data received through that link — per link, not per folder or per sender. webd snapshots it onto every message arriving through the token (messages.link_context), and routd renders it into the handling agent's prompt as a sibling of <topic>:

<link-context>
Bug reports from the acme website. Triage and file; don't chat back.
</link-context>

/chat/<token>/ and /hook/<token> — any valid token at either URL

The two URLs share handler internals — same lookup, same append path. Lookup does not filter on the token's JID kind: a hook: token works at /chat/<token>/ and a web: token works at /hook/<token>. Kind is metadata — it drives sender attribution and default output style — not a URL access gate. Each issuance verb still returns its canonical URL: issue_chat_link/chat/<token>/, issue_webhook/hook/<token>.

MethodPathPurpose
GET/chat/{token}/Chat widget (HTML).
POST/chat/{token}/Append body as one inbound at the row's JID. Returns turn_id (browsers open SSE).
GET/chat/{token}/{turn_id}/sseLive SSE for the round.
GET, POST/chat/{token}/mcpPer-token MCP surface: send_message, get_round, get_round_status.
POST/hook/{token}Append body verbatim as one inbound at the row's JID. Fire-and-forget; returns 204.

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>",
  "content": "<raw POST body, verbatim>"
}

sender is the <source> segment of the JID (the value the agent passed as source_label at mint time). Request headers are dropped at ingest — only the body is forwarded, so upstream HMAC headers (e.g. X-Hub-Signature-256) can't be verified downstream; the URL is the only secret. 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 <instance> issue chat <folder>                    # mints web:<folder>
arizuko token <instance> issue chat <folder> support            # mints web:<folder>/support
arizuko token <instance> issue webhook <folder> github          # mints hook:<folder>/github
arizuko token <instance> issue webhook <folder> linear issues   # mints hook:<folder>/linear/issues
arizuko token <instance> issue chat <folder> --context "bug reports; triage, don't chat"
arizuko token <instance> list <folder>
arizuko token <instance> revoke <jid> [<owner_folder>]

The CLI is the FS-mounted host-admin path: it writes the same route_tokens row directly to routd.db (cmd/arizuko/token.go) and prints the raw token once. --context (-c) attaches per-link instructions, same as the context mint param.