---
title: "HTTP routes"
summary: "Application, operational and protocol routes and the SPA catch-all, with the authorization requirement for each."
description: "Every HTTP route the server registers and what guards each one. Mostly the browser client's backend rather than a public API."
status: preview
generated: true
read_when:
  - "You are auditing what an instance exposes and what guards each route."
  - "A request came back 401 or 403 and you want to know which capability it wanted."
---

Every route below is served on `FANOUT_ADDR` (`127.0.0.1:7520` by default;
the image sets `:7520`).
OTLP shares this listener but uses its own handlers and ingest credential; see
[send your first telemetry](/docs/get-started/send-telemetry).

## Conditional routes

The table includes every route the binary can register. These routes depend on
configuration:

- `/debug/pprof/*`: only when `FANOUT_PPROF_ENABLED` is true, which is
  **not** the default. Enabling it also turns on mutex and block sampling.
- `/mcp`, `/api/mcp`, `/api/auth/oauth/*`, `/oauth/*` and `/.well-known/*`: only when
  `FANOUT_MCP_ENABLED` is true, which **is** the default.
- `/api/agent/*`: only when `FANOUT_AI_PROVIDER` selects `anthropic` or
  `openai`, with that provider's required key. An empty provider disables chat.
- `/api/auth/oidc/start` and `/api/auth/oidc/callback`: only in
  `FANOUT_AUTH_MODE=oidc`.

Everything else is always registered. A path shown with `:name` or `*` is the
pattern Echo matches on, not a literal URL.

## Interface stability

The `/api/*` routes serve the browser client and can change without migration
paths. [MCP](/docs/guides/mcp) is the agent interface. OTLP ingest and
[operational endpoints](/docs/reference/endpoints) have their own contracts.

Manage alert rules through the [HTTP API](/docs/guides/alerts). Administrators
rotate ingest tokens on **Connect telemetry** or with
`POST /api/settings/ingest/token/rotate`.

## Errors

Application API errors, including unknown routes and wrong methods, return objects shaped like
`{"code":"bad_request","message":"Safe explanation"}`. Codes are stable
snake_case identifiers; clients branch on `code` rather than message text.
Invalid dashboard or panel specs also include `problems` with field paths,
messages and optional hints. Every error response uses `Cache-Control: no-store`;
HEAD errors have no body. Server failures return a short, safe message; the
underlying error is logged and never returned.

Default codes by HTTP status:

| Status | Code |
|---|---|
| 400 | `bad_request` |
| 401 | `unauthorized` |
| 403 | `forbidden` |
| 404 | `not_found` |
| 405 | `method_not_allowed` |
| 409 | `conflict` |
| 413 | `payload_too_large` |
| 415 | `unsupported_media_type` |
| 422 | `unprocessable` |
| 429 | `rate_limited` |
| 500 | `internal` |
| 502 | `bad_gateway` |
| 503 | `unavailable` |
| 504 | `timeout` |

Other 4xx statuses use `bad_request`; other 5xx statuses use `internal`.
Specific conditions use `already_current`, `dashboard_version_not_found`,
`dashboard_version_conflict`, `invalid_spec` and `last_active_administrator`.
Tool failures carry `error: {code, message}` inside the tool result.
Panel execution results keep their own status and error fields.
OAuth, OTLP/gRPC, MCP JSON-RPC and AG-UI run errors keep their protocol spelling.
Operational responses at `/healthz`, `/readyz` and `/metrics` keep their bodies.

[Roles](/docs/reference/roles) maps capabilities to roles.

| Method | Path | Requires |
|---|---|---|
| `GET` | `/` | none |
| `GET` | `/*` | none |
| `GET` | `/.well-known/oauth-authorization-server` | protocol handshake |
| `GET` | `/.well-known/oauth-protected-resource` | protocol handshake |
| `GET` | `/.well-known/oauth-protected-resource/mcp` | protocol handshake |
| `POST` | `/api/agent/runs` | `agent:run` |
| `GET` | `/api/agent/threads` | `agent:run` |
| `DELETE` | `/api/agent/threads/:id` | `agent:run` |
| `GET` | `/api/agent/threads/:id` | `agent:run` |
| `PATCH` | `/api/agent/threads/:id` | `agent:run` |
| `GET` | `/api/alerting/alerts` | `telemetry:read` |
| `GET` | `/api/alerting/rules` | `telemetry:read` |
| `POST` | `/api/alerting/rules` | `alerts:manage` |
| `DELETE` | `/api/alerting/rules/:id` | `alerts:manage` |
| `PUT` | `/api/alerting/rules/:id` | `alerts:manage` |
| `POST` | `/api/alerting/rules/:id/test` | `alerts:manage` |
| `GET` | `/api/alerting/summary` | `telemetry:read` |
| `POST` | `/api/annotations` | `telemetry:read` |
| `POST` | `/api/auth/code/send` | none |
| `POST` | `/api/auth/code/verify` | none |
| `POST` | `/api/auth/link/verify` | none |
| `POST` | `/api/auth/logout` | any signed-in user |
| `GET` | `/api/auth/me` | any signed-in user |
| `GET` | `/api/auth/oauth/authorize` | any signed-in user |
| `POST` | `/api/auth/oauth/authorize` | any signed-in user |
| `GET` | `/api/auth/oauth/connections` | any signed-in user |
| `POST` | `/api/auth/oauth/connections/:id/revoke` | any signed-in user |
| `GET` | `/api/auth/oidc/callback` | none |
| `GET` | `/api/auth/oidc/start` | none |
| `POST` | `/api/auth/setup` | none |
| `GET` | `/api/auth/status` | none |
| `GET` | `/api/dashboards` | `dashboards:manage-own` |
| `POST` | `/api/dashboards` | `dashboards:manage-own` |
| `DELETE` | `/api/dashboards/:id` | `dashboards:manage-own` |
| `GET` | `/api/dashboards/:id` | `dashboards:manage-own` |
| `PATCH` | `/api/dashboards/:id` | `dashboards:manage-own` |
| `PUT` | `/api/dashboards/:id` | `dashboards:manage-own` |
| `GET` | `/api/dashboards/:id/versions` | `dashboards:manage-own` |
| `GET` | `/api/dashboards/:id/versions/:version` | `dashboards:manage-own` |
| `POST` | `/api/dashboards/:id/versions/:version/restore` | `dashboards:manage-own` |
| `POST` | `/api/fanql/complete` | `telemetry:read` |
| `POST` | `/api/fanql/format` | `telemetry:read` |
| `POST` | `/api/fanql/parse` | `telemetry:read` |
| `GET` | `/api/fanql/reference` | `telemetry:read` |
| `GET` | `/api/intelligence` | `telemetry:read` |
| `Any` | `/api/mcp` | `telemetry:read` |
| `POST` | `/api/queries/exemplars` | `telemetry:read` |
| `POST` | `/api/queries/run` | `telemetry:read` |
| `POST` | `/api/queries/variables/resolve` | `telemetry:read` |
| `GET` | `/api/settings/ingest` | `ingest:read-metadata` |
| `POST` | `/api/settings/ingest/token/rotate` | `ingest:manage` |
| `GET` | `/api/telemetry/schema` | `telemetry:read` |
| `GET` | `/api/traces/:id` | `telemetry:read` |
| `GET` | `/api/users` | `users:manage` |
| `POST` | `/api/users` | `users:manage` |
| `DELETE` | `/api/users/:id` | `users:manage` |
| `PATCH` | `/api/users/:id` | `users:manage` |
| `POST` | `/api/users/:id/access/revoke` | `users:manage` |
| `PATCH` | `/api/users/:id/role` | `users:manage` |
| `PATCH` | `/api/users/:id/status` | `users:manage` |
| `GET` | `/debug/pprof/` | `operations:read` |
| `GET` | `/debug/pprof/:name` | `operations:read` |
| `GET` | `/debug/pprof/cmdline` | `operations:read` |
| `GET` | `/debug/pprof/profile` | `operations:read` |
| `GET` | `/debug/pprof/symbol` | `operations:read` |
| `GET` | `/debug/pprof/trace` | `operations:read` |
| `GET` | `/healthz` | none |
| `Any` | `/mcp` | protocol handshake |
| `GET` | `/metrics` | `operations:read`, or a service credential |
| `POST` | `/oauth/register` | protocol handshake |
| `POST` | `/oauth/token` | protocol handshake |
| `GET` | `/readyz` | none |

## What the requirements mean

- **none**: reachable without a credential.
- **any signed-in user**: a session, but no particular capability.
- **protocol handshake**: the OAuth and MCP endpoints, which authenticate
  as part of their own protocol rather than through the middleware.
- **a named capability**: the caller's role must carry it.
- **or a service credential**: additionally reachable without a browser
  session, which is how a scraper reaches the metrics endpoint.

`Any` registers every HTTP method with the same authorization requirement.
`GET /*` serves the single-page application for browser paths. Unknown `/api/*`
and OTLP paths return errors.