Skip to content

Reference

Terminal window
askrelay <verb> [flags]

Everything listed here is in the running binary.

Verb What
serve run the relay HTTP server (see config)
invite <email> mint a single-use enrollment invite, print the invite URL
enroll <invite-url> enrol this machine as a device; writes ~/.config/askrelay/ (0600) and prints the credential to paste when connecting a browser client. Flags: -name (your display name), -label (device label), -config, -force — all before the URL
device list|revoke list or revoke enrolled devices; run on the relay host, opens the DB directly
daemon run the local notifier: holds a socket to the relay and raises a desktop notification when mail arrives
mcp run the local stdio MCP server — this is what you configure in your AI client. Proxies the relay’s tool surface, and redacts and signs on this machine
version print the build version — dev unless stamped at build time
help (also -h, --help) show usage

daemon and mcp are deliberately separate processes: an MCP server’s stdout belongs to the protocol and is spawned per client session, while the notifier is long-running. Both read the same config, and both log to stderr only.

Planned · WP-12

The human-facing operate verbs inbox, approve, and status are not built — approvals happen through your AI client’s MCP tools.

Route Auth Purpose
GET /healthz — liveness + version
POST /enroll/{token} invite token enroll a device, return its credential
GET /.well-known/oauth-protected-resource — RFC 9728 resource metadata
GET /.well-known/oauth-authorization-server — RFC 8414 authorization-server metadata
GET /oauth/jwks — Ed25519 verification key (JWK Set)
GET / POST /oauth/authorize device credential on POST login page + authorization endpoint
POST /oauth/token PKCE token endpoint (authorization_code, refresh_token)
POST /oauth/register — Dynamic Client Registration (RFC 7591)
POST /mcp Bearer access token Streamable HTTP MCP endpoint
GET /ws device credential delivery push + ack

TLS is terminated by the reverse proxy in front of the relay.

/ws is push and ack, in that order and nothing else. The relay pushes message frames down; the only frame it accepts back is ack with a message id. Any other frame type is logged and dropped. A frame is capped at 128 KiB.

Revocation is re-checked three times over: when the socket opens, on every inbound frame, and on a 15-second reconcile ticker that severs idle sockets belonging to a device revoked out of band.

Shipped

/ws is the daemon’s transport and is in use: askrelay daemon connects with its device credential and receives message frames on delivery, plus sign_request frames when one of its user’s drafts is approved (it answers signature or sign_refused). The relay still accepts no outbound envelope on /ws — a daemon cannot submit or release a message, only sign one the human already approved, so the approval gate stays server-side and authoritative.

An authenticated client sees these ten tools. Every inbound message rendered to a client is spotlighted (quarantined) — see Concepts.

Tool What it does
find_people list the people you can message (email + name), to resolve a name to an address
send_message start or continue a thread by sending a question/message
check_inbox list threads with something awaiting you
get_thread read a thread’s messages and state
approve_message approve an inbound message (the inbound gate)
decline_message decline an inbound message
approve_reply release a drafted reply (the outbound gate). Its result reports attestation: device-signed when your daemon signed the exact approved bytes, relay-attested otherwise
discard_reply discard a drafted reply
set_thread_grant grant or revoke standing per-thread approval for a direction
wait_for_activity long-poll until there’s activity, bounded by your client profile

Exactly four carry the ReadOnlyHint annotation: find_people, check_inbox, get_thread, and wait_for_activity. The other six change state and are annotated as such.

You ask for a timeout; the relay caps it by the client profile stamped on your token. There is no way to raise the cap from the client side.

Client profile Cap
claude.ai, claude-desktop 240s
claude-code-remote 25 min
anything else, including ChatGPT 45s

At most 2 concurrent wait_for_activity calls per person; a third returns immediately with no activity.

Unverified

The 25-minute figure is the value in the running code, but it is not a settled number — treat it as subject to change.

Could not verify: the 25-minute `claude-code-remote` ceiling is marked provisional in the source, pending spike S-02

/mcp is stateless Streamable HTTP with JSON responses — no session to resume, every request carries its own bearer token. The request body is capped at 128 KiB.

Bounds are enforced at ingress, before anything is decoded.

Bound Value
Message body text (sum of all part text) 32 KiB
Whole canonical envelope 64 KiB
Content parts per message 16
POST /mcp request body 128 KiB
GET /ws frame 128 KiB
POST /enroll/{token} request body 4 KiB

All serve flags/env are in Self-host & operate.

Verified against askrelay 2e019ea on . Anything newer than that commit is not reflected here.