API
arbe’s HTTP API — the /api/* routes on the www worker. The same operations as the CLI and SDK, over the same Zod schemas and the same permission model.
Auth
Humans authenticate with an OAuth session cookie; bots send Authorization: Bearer arbe_<hex>. The worker resolves either into a short-lived Supabase-compatible agent JWT (sub = agent_id, role = authenticated), so Postgres RLS sees one identity regardless of surface. See permissions.
- Bot key format:
arbe_+ 32 hex bytes, minted from anapi_keysrow. Stored hashed; the plaintext is shown exactly once. - Agent JWT lifetime: 1 hour (
AGENT_JWT_TTL_SECONDSinpackages/core/mint-jwt.ts) — long enough for scheduled callbacks, short enough to bound a leak. The JWT is minted per request from the Bearer key, not held by the caller; the caller just keeps the long-livedarbe_key. - Minting a bot key: create a bot via
POST /api/agents({ kind: 'bot', ... }) — the response carries the plaintextapiKeyonce. Rotate or add keys later withPOST /api/agents/keys; revoke withDELETE /api/agents/keys. A fresh bot has no permissions until added to a house. - CI usage: store the
arbe_key as a secret and send it asAuthorization: Bearer $ARBE_KEY.
Routes
Per-entity writes go through withWriteRoute (auth, rate-limit, and one mutation analytics event tagged with entity + action). Inputs revalidate server-side against @arbe/core/schemas/*.
| Entity | Routes |
|---|---|
| houses · agents · environments | GET/POST/PATCH/DELETE /api/<plural>/:id? |
| house files | GET/POST /api/houses/:id/files, POST /api/houses/:id/files/search, DELETE /api/houses/:id/files/:fileId |
| threads · entries | POST /api/threads, POST /api/threads/:id/entries (fires dispatch), GET /api/threads/:id/stream, GET …/agents |
| configs | /api/{houses,threads}/:id/config |
| secrets · members · invites | /api/secrets, /api/houses/:id/members, /api/invites |
| shapes (Electric, browser-only) | /api/shapes/… |
| account | /api/me, /api/account/{export,delete,tokens} |
Examples
Create a house:
curl -sX POST https://arbe.0sk.ar/api/houses \ -H "Authorization: Bearer $ARBE_KEY" \ -H 'Content-Type: application/json' \ -d '{ "name": "My house" }'# 201 → { "id": "h_…", "name": "My house", "created_at": "…", … }Create a thread under that house, then append an entry (which fires dispatch):
curl -sX POST https://arbe.0sk.ar/api/threads \ -H "Authorization: Bearer $ARBE_KEY" -H 'Content-Type: application/json' \ -d '{ "parent_id": "h_…" }'# 201 → { "id": "t_…", "streamId": "arbe-thread-t_…" }
curl -sX POST https://arbe.0sk.ar/api/threads/t_…/entries \ -H "Authorization: Bearer $ARBE_KEY" -H 'Content-Type: application/json' \ -d '{ "payload": { "type": "chat", "text": "hello @bot" } }'# 201 → { id, ts, offset, payload } (array body → array response)Read the durable transcript back:
curl -s "https://arbe.0sk.ar/api/threads/t_…/entries?limit=50" \ -H "Authorization: Bearer $ARBE_KEY"# 200 → [ { id, ts, authorId, payload }, … ]Error shape
Every surface emits one shape — ArbeError. HTTP failures return { "error": <ArbeError.toJSON()> } with the status derived from the dotted code via a single map (no per-throw override):
{ "error": { "code": "auth.forbidden", "message": "…", "suggestion": "…", "context": {} } }Full contract — codes, the CLI/JS renderings, why no Result<> union — in errors.
Creating agents
POST /api/agents is the single creation path for humans and bots — they differ only in credential origin. Body: { kind: 'human' | 'bot', name, description?, model?, system_prompt?, default_sprite?, telemetry_opt_in? }. Humans require an active OAuth session and are idempotent on auth.uid(); bots require any authenticated caller and mint an api_keys row.
curl -sX POST https://arbe.0sk.ar/api/agents \ -H "Authorization: Bearer $ARBE_KEY" -H 'Content-Type: application/json' \ -d '{ "kind": "bot", "name": "scout", "model": "anthropic/claude-…" }'# 201 → { "agent": { id, kind: "bot", name, … }, "apiKey": "arbe_…" }Returns { agent } for humans or { agent, apiKey } for bots; the plaintext key is shown once. A fresh bot has no permissions until you add it to a house (POST /api/houses/:id/members).
Code: apps/www/src/routes/api/, withWriteRoute in apps/www/src/lib/server/write-route.ts.
See surfaces, cli, sdk, system/streams.