Use the API from a client
Typed TypeScript clients
Typed clients infer their shape from the router rather than redeclaring request and response types:
import { createORPCClient } from '@orpc/client';
import { RPCLink } from '@orpc/client/fetch';
import type { RouterClient } from '@orpc/server';
import type { RpcRouter } from '@agent-zero/api';
const client: RouterClient<RpcRouter> = createORPCClient(
new RPCLink({
url: 'http://localhost:3000/rpc',
headers: { authorization: `Bearer ${process.env.CONTROL_PLANE_TOKEN}` },
}),
);
const { tasks } = await client.tasks.list();
await client.approvals.decide({ taskId: tasks[0]!.id, decision: 'approved' });
When a procedure changes, every client type-checks against the new shape at compile time — there is no generated client to regenerate.
Plain HTTP
Non-TypeScript or external callers can use plain HTTP against /api/v1/** instead, with the same bearer-token rules:
curl http://localhost:3000/api/v1/tasks
curl -X POST http://localhost:3000/api/v1/tasks \
-H "Authorization: Bearer $CONTROL_PLANE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"repository": "/path/to/repo", "feedback": "…"}'
The interactive reference at /api/v1/docs documents every route from the generated OpenAPI spec at /api/v1/openapi.json.
Authorization is identical on both transports
/rpc/** and /api/v1/** serve the exact same rpcRouter, so a token that may create tasks over RPC may create the same tasks over REST and vice versa. Reads are open; mutations fail closed — see Protect endpoints.
Define endpoints
Procedures are defined once in packages/api/src/orpc/router.ts and served over both transports automatically. Before changing routes, read the orpc-server Agent Skill.
Protect endpoints
The control plane fails closed. Reads (tasks.list, tasks.get, health) stay open for the dashboard; every mutation requires an operator-issued bearer credential, and without configuration every mutation is rejected.