API¶
The HTTP API is an OpenAPI 3.1 contract at
api/openapi/openapi.yaml,
served under /api/v1. The Go server is generated from it with ogen; the web UI's client
types are generated from the same file. Errors are RFC 9457 problem details
(application/problem+json).
Authentication¶
- Browser sessions:
POST /api/v1/auth/loginsets thesnatcharr_sessioncookie. Unsafe methods from a browser must be same-origin: a request whoseSec-Fetch-SiteorOriginheader names another site gets403problem+json. Thecsrf_tokenthatGET /api/v1/auth/sessionreturns is kept for clients that sendX-CSRF-Token; the API does not check it. - API keys:
Authorization: Bearer sk_...on any request; the origin check does not apply.
The Server-Sent Events streams GET /api/v1/events/stream and GET /api/v1/logs/stream are
served outside the generated router and take the same session cookie or API key; without either
they answer 401, and the log stream answers 403 to a non-admin.
The first start has no users; GET /api/v1/auth/setup/status says so and
POST /api/v1/auth/setup creates the admin.
Resources¶
| Path | What |
|---|---|
/instances, /instances/{id}/test, /instances/test |
*arr instances and connectivity probes |
/instances/{id}/policy |
Snatch policy |
/instances/{id}/runs |
Quickie (manual snatch) |
/runs, /runs/{id}/cancel |
Snatch runs |
/events, /events/stream |
History and Server-Sent Events |
/logs, /logs/stream |
Recent redacted log records and their live tail, admin only (Logs) |
/hourly-caps |
Stamina per instance |
/state/reset |
End every afterglow |
/schedules |
Pause and cap-override windows |
/download-clients, /download-clients/status, /instances/{id}/download-clients/discover |
Download clients |
/system/status |
Version and build info; worker_last_contact_at, the newest LeaseRun or Heartbeat of any worker (absent before the first) |
Worker contract¶
Workers talk gRPC (proto/snatcharr/v1/worker.proto): LeaseRun, Heartbeat,
FilterCandidates, AcquireBudget, ReportEvents, CompleteRun. The API is the only
source of truth for stamina and afterglow; a worker never decides on its own.
LeaseRun is a long-poll: with no run queued, the API checks the queue once a second until the
wait the worker asked for (at most 30 s) has passed, then answers empty. A run queued meanwhile
reaches a waiting worker within about a second, and each run reaches exactly one worker.
api/internal/workergrpc/latency_integration_test.go holds both against 64 concurrent
long-polls on one Postgres: an empty poll answers within 2 s after its wait, and a queued run
reaches a worker within 3 s.
A worker retries a CompleteRun the API refused with UNAVAILABLE, DEADLINE_EXCEEDED,
RESOURCE_EXHAUSTED or ABORTED: three calls at most, the n-th wait a random time
between half and all of 1 s x 2^(n-1). Any other refusal ends the attempt; the run then
fails once its lease expires MaxLeases times. CompleteRun carries events_unconfirmed,
the events the worker emitted for the run that ReportEvents did not confirm as accepted.
A non-zero value records an events_unconfirmed warning on the run: its history may be
incomplete.