Skip to content

API guide

Everything Yagra does is driven through one REST API. This page covers how to authenticate against it and the conventions every endpoint follows; the per-endpoint details live in the generated API reference, and AI assistants get their own surface — the MCP server.

The API is served by core on the API port (8080 in the bundled compose files), with every path under the /api/v1 prefix — about 206 endpoints, plus the two unversioned health probes GET /healthz (liveness) and GET /readyz (readiness; in a high-availability pair only the leader answers 200, so a load balancer can route to it).

Everything the WebUI does goes through this same API. There is no private backchannel: every inventory screen, dashboard, alert action, and settings page is a sequence of /api/v1 calls, so anything you can see or do in the browser you can automate. When in doubt about how to do something programmatically, doing it once in the WebUI with the browser’s network tab open shows the exact requests.

Two credentials authenticate this API, both sent as Authorization: Bearer <token>:

  • a session token, exchanged for a username and password — short-lived, and what the WebUI uses;
  • an API token (yat_…, minted under Settings ▸ API tokens) — long-lived, for unattended clients. Use this one for scripts, CI jobs and integrations.

Exchange a username and password for a bearer session token at POST /api/v1/auth/login:

Terminal window
curl -s http://<yagra-host>:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "your-password"}'
{ "token": "<session-token>", "role": "admin" }

Send that token on every subsequent request:

Terminal window
curl -s http://<yagra-host>:8080/api/v1/nodes \
-H "Authorization: Bearer <session-token>"

The response also carries the account’s role, so a client knows up front what it will be allowed to do. Login is rate-limited per account and globally — too many attempts answer 429 with a Retry-After header — and POST /api/v1/auth/logout revokes the token.

For anything unattended, mint an API token instead of scripting a login. An admin creates one under Settings ▸ API tokens; the raw value is shown once and never again. Send it exactly like a session token:

Terminal window
curl -s http://<yagra-host>:8080/api/v1/nodes \
-H "Authorization: Bearer yat_…"

Four things about a token are worth knowing before you issue one:

  • It names the surfaces it may be used at. A token reaches this REST API only if it was created with the rest surface. Tokens created before that field existed carry mcp alone and answer 401 here — an upgrade never widens what an existing credential can do.
  • It acts as an account. Every token has an owner. Its effective role is the lower of the token’s role and the owner’s current role, so demoting the account narrows the token immediately, and disabling or deleting the account revokes it. Prefer a service account as the owner (see Users & SSO) so an integration does not depend on the person who set it up.
  • It cannot administer users. POST /api/v1/users, the API-token endpoints themselves, and anything else behind the user-management permission answer 403 token_not_permitted to a token, whatever its role — a credential that could mint its own successor would survive revoking the original.
  • It has no interactive identity. Endpoints that mean “the signed-in account” — GET /api/v1/auth/me and the personal dashboard — answer 403 session_required.

An optional expiry (expires_at) is offered when creating one, and omitting it still means no expiry. Revoke a token from the same page at any time; revocation takes effect immediately.

A few endpoints are deliberately unauthenticated, because a client needs them before it can log in and none of them reveal inventory, configuration, or state:

Endpoint Returns
GET /api/v1/version The running core version
GET /api/v1/config Client bootstrap flags (whether auth, SSO, and AI analysis are available) — no secrets
GET /api/v1/openapi.json The generated OpenAPI document (below)
GET /healthz, GET /readyz Liveness and readiness probes

If the deployment runs with the public-dashboard option, read endpoints open up without a token; every write stays authenticated.

The whole surface follows one set of rules, so what you learn on one endpoint carries to the rest:

  • JSON everywhere. Request bodies and responses are JSON; set Content-Type: application/json when sending a body.

  • Creation returns 201 with the new resource’s id: {"id": "<uuid>"}.

  • Deletion returns 204 No Content — no body, and deletes are idempotent where that is safe.

  • Errors are typed. Every failure renders the same envelope:

    { "error": { "code": "invalid_filter", "message": "" } }

    The code is stable and machine-readable — branch on it, not on the message. The message is operator-safe prose and never carries internal error text. Status codes follow the usual meanings: 400 malformed input, 401 unauthenticated, 403 authenticated but not permitted, 404 no such resource, 409 conflict, 429 throttled (with a Retry-After header), 502 an upstream store or provider failed (distinct from 500, a fault in Yagra itself), and 503 an optional subsystem this deployment has not configured.

  • Authentication is checked before availability. An endpoint whose subsystem is unconfigured answers 503 only to a caller who is already authenticated and permitted; an anonymous request gets 401 regardless. This is deliberate — the deployment does not disclose which subsystems it runs to a caller holding no credential. (Before v0.1.19 the two checks ran in the other order on most endpoints.)

  • Large lists are enveloped and capped. A listing that could grow unbounded returns {"items": [...], "total": <n>, "truncated": <bool>} rather than a bare array — for example GET /api/v1/thresholds returns at most 500 rules per request, with total carrying the unfiltered count. A ?limit= parameter can narrow a cap, never widen it.

  • Timestamps are RFC 3339 strings (2026-08-01T09:30:00Z) in responses and in query parameters that take a time.

Every request is authorized per endpoint by the role the token carries:

  • Viewer — read-only: inventory, metrics, alerts, events, flows.
  • Operator — Viewer plus incident response: acknowledge and mute alerts, open and close maintenance windows, run Troubleshoot analyses and AI root-cause explanations.
  • Admin — full control: nodes, profiles, thresholds, credentials, users, and the audit log.

A request with a valid token but an insufficient role answers 403 — distinguishable from the 401 an unauthenticated request gets. State-changing requests are recorded in the audit log automatically. The full role and permission model, including single sign-on, is described in Users & SSO.

The API publishes its own contract at GET /api/v1/openapi.json — an OpenAPI 3.1 document covering every path, query parameter, request body, response shape, and error code. It is generated from the handlers themselves, so it describes what the server actually does and cannot drift from the code; the WebUI’s own types and API client are generated from this same document. It contains no inventory, configuration, or state, so it is served unauthenticated and is identical on every deployment.

Point any OpenAPI client generator at it to get a typed client in your language:

Terminal window
curl -s http://<yagra-host>:8080/api/v1/openapi.json -o yagra-openapi.json

The same document is served on this site at /openapi.json, so you can generate a client without a running deployment.

The per-endpoint reference — every route with its parameters, bodies, and responses, grouped by domain — is generated from that same document and lives at /docs/api/reference/, right below this page in the sidebar. Because it is generated, it is always in step with the release it documents.

Behavior changes an API client could notice — response shapes, status codes, defaults, the meaning of a query parameter, removed endpoints — are always listed in the release notes, each under the version that shipped it. Recent examples of the kind of thing to look for: a bare-array response gaining an envelope and a cap, delete and create endpoints aligning on 204/201, endpoints nothing called being removed outright, and a list field being replaced by a more precise one rather than deprecated in place — nodes.source became nodes.kind in v0.2.1, and the old two-valued field is gone in the same release. Skim the notes for the versions you are crossing before upgrading a deployment your automation talks to; see the changelog for where they live. Regenerating your client from the new deployment’s /api/v1/openapi.json picks up the shape changes mechanically.

For AI assistants there is a second, purpose-built surface: an opt-in MCP server at /mcp that exposes monitoring state as tools — mostly read-only queries plus a few audited actions — so a client like Claude Code or Claude Desktop can answer “which nodes are down?” directly. It takes the same yat_… API tokens, and a token can name both surfaces or just one; keeping an assistant’s credential to mcp alone is the reason that field exists. It has its own page: MCP server.