GraphQL
An Apollo Server (Federation subgraph) is mounted at/api/graphql through a Next.js route handler. Outside production — or when APOLLO_SANDBOX=true — introspection and the Apollo sandbox are enabled, so opening /api/graphql in a browser lets you explore the schema.
The SDL lives in schemas/**/*.graphql, one file per domain, and is bundled into the app by scripts/prebuild-schema.ts. Resolvers are dependency-injected factories under src/graphql/resolvers/.
Subscriptions are served over a separate WebSocket listener rather than the HTTP route:
Browser clients authenticate with a Better Auth cookie. Native clients and enrolled agents send their bearer credential in the
authorization connection parameter. API clients send X-API-Key as a header or connection parameter. The server resolves exactly one typed principal — user, API key, or agent — before execution. The app also rewrites /graphql on the HTTP origin to the WebSocket listener, so a browser and a reverse proxy can reach subscriptions on a single origin.
Every query, mutation, and type is documented in the GraphQL API tab, generated from that schema.
A placeholder health query verifies database connectivity. It returns "ok" when the database is reachable, and "degraded" otherwise:
REST endpoints
Codebases
Read-only access to the codebase checkouts registered across every enrolled agent. These endpoints require a Better Auth session. API keys and agent credentials are not accepted.id, path, observedOrigin, branch, headSha, upstream, ahead, behind, syncState, availability, statusError, the lastCheckedAt / lastFetchedAt timestamps, an optional branch listing, plus nested repository, agent, and activeJob objects.
Errors use a consistent envelope — { "error": { "code", "message" } }:
Telemetry ingestion
Applications under test post their own logs and analytics events here. These endpoints are unauthenticated by design — they are meant to be reachable from a device or simulator.
Both ingestion endpoints accept either a single record or an atomic
{ "items": [...] } batch of at most 500 records, with the request body capped at 2 MiB.
A
202 is not a failure. It means the server understood the payload and
deliberately dropped it because collection is turned off, so a client can keep
posting without special-casing the disabled state.PAYLOAD_TOO_LARGE beyond that.
APNs device registration
The body carries
clientRegistrationId, token, tokenEncoding (HEX or BASE64), topic, environment (SANDBOX or PRODUCTION), supportedPushTypes, and displayName. A new registration returns 201, refreshing an existing one returns 200. The body is capped at 32 KiB and each source IP is limited to 120 requests per minute, after which it gets 429. See Push notifications.
Notification device registration
The body carries
clientRegistrationId, token, tokenEncoding, topic, environment, and displayName, plus the optional deviceModel, osVersion, appVersion, appBuild, and locale. Unknown fields are rejected rather than ignored. The response returns the device id, whether it was created, its status, and lastRegisteredAt — 201 for a new registration, 200 for a refresh.
The same 32 KiB body cap and 120-requests-per-minute-per-IP limit apply, counted separately from the push console’s budget. Registered devices are managed on Notifications.
This endpoint is deliberately narrower than
/api/ios/apns-devices. It serves
one first-party app, so it has no push-type catalog and no certificate
authentication.OpenAPI contract
The document is assembled at request time from the same Zod schemas the handlers validate against, so it cannot drift from the implementation. It describes the cookie and bearer security used by protected operations while leaving integration endpoints explicitly public. The document itself is served with
cache-control: public, max-age=300 and is the right thing to point a client generator at.
Browser and agent routes
The dashboard and the control agents use a handful of further routes. They are part of the application’s own plumbing rather than an integration surface, but they are worth knowing when reading logs:Public endpoints
Everything under/api/public/* is intentionally unauthenticated: iOS enrollment callbacks, short-lived build artifact and over-the-air manifest downloads, and the two signature-verified webhooks. Hosting and networking lists the full path set and explains how to expose exactly that namespace and nothing else.
MCP
/api/mcp is a stateless Streamable HTTP MCP endpoint. Point Claude Code, Cursor, or any other MCP client at it and the server’s built-in tools become callable directly.
The catalog spans the whole product: agents, builds and build data, codebases and worktrees, commands and workflows, runs, GitHub and Jira (including their caches), skills, notifications and push notifications, signing assets, iOS devices, disk space, usage and costs, and debugging. Browse it, run tools by hand, and audit every call from the Tools page.
Scopes
The same endpoint serves three scopes, selected by query parameter:
Supplying both
preset and run, or either one twice, is rejected with 400 INVALID_MCP_SCOPE.
Authentication
Unscoped and preset MCP calls require a signed-in user’s session or a Better Auth API key. Scripts should use anaide_ key in X-API-Key. The raw key is shown only once when you create it on API keys.
Correlation and auditing
Send anx-request-id header — up to 128 characters — and it is echoed back on the response and recorded as the call’s correlation ID. Without one, the server generates a UUID. Every call is written to the audit log on the Tools page with its caller, source, duration, and a SHA-256 hash of its arguments. Arguments themselves are never stored.
Callers are recorded by their Better Auth user or API-key identity. Run-scoped calls are recorded as agent:<agentId>@<address>.
Tool HTTP API
The Tools page drives two plain HTTP routes, which are also usable directly when you want a single tool call without speaking MCP:
Both Tools routes require a signed-in user session. They do not accept API keys or agent credentials.
POST /api/tools/call returns { "result", "requestId" } on success. Failures use INVALID_TOOL_CALL (400), CODEBASE_NOT_FOUND (404), TOOL_CALL_FAILED (502), or a 409 for an ambiguous codebase lookup.
External MCP servers
The Tools page can also manage and test external Streamable HTTP or legacy SSE MCP servers, whose tools then appear in the same catalog and are callable everywhere built-in tools are. Saved custom header values stay server-side and are never returned to the browser.External tools are reachable from the Tools page and from workflows and runs,
but they are not re-exported over this server’s own
/api/mcp endpoint. That
endpoint serves built-in tools only.Related pages
GraphQL API
The generated schema reference, subscriptions, and Apollo Studio.
Tools
Browse the tool catalog, build presets, and read the audit log.
Hosting and networking
Which of these paths may be exposed publicly, and which must not be.
Local development
Regenerating the schema and resolver types.