API reference
Transport: HTTPS REST, bearer token, tenant-scoped. All client input is untrusted — the server re-parses raw stdout, computes fingerprints itself, and echoes prompt_hash for integrity.
Endpoints
Enroll
POST /v1/enroll
{ enrollment_code }
→ { client_token, client_id, org_id }
Enrollment codes are minted in the web UI or via bun run src/mint-enrollment-code.ts — single-use, expiring.
Identity & catalog
GET /v1/me → { org, projects[], models[], entitlements }
GET /v1/models → [{ code, display_name, provider }] # NO command_template — ever
The model catalog is identity-only; command_template is machine-specific and lives in the client's config.toml.
Campaigns
POST /v1/projects/{id}/campaigns
{ type, git:{branch,commit,remote_url,dirty}, base_commit?, changed_paths?, config_version? }
→ { campaign_id, config_version_id, status, reused }
changed_paths for incremental campaigns. The server intersects them with pass-scope globs + risk triggers. Paths travel, contents do not. Dedup identity is keyed by (project, branch, commit, type) so re-creating the same campaign reuses it.Claim, heartbeat, results
POST /v1/campaigns/{id}/claim
{ client_id, models:[...], max_slices, lease_seconds }
→ { leases:[ { lease_id, slice_id, model_code, pass_code, bucket_code,
prompt:"<full rendered prompt.md>", prompt_hash, marker,
timeout_seconds, lease_expires_at } ] }
POST /v1/leases/{lease_id}/heartbeat → { lease_expires_at } # best-effort, online clients only
POST /v1/leases/{lease_id}/results
{ exit_code, duration_ms, model_code, client_version, prompt_hash,
stdout:"<raw output.txt>", # inline if KB-scale, else presigned upload
stderr_tail }
→ { accepted, parsed_report_count, warnings[] } # idempotent on (lease_id, prompt_hash)
Gate
GET /v1/campaigns/{id}/gate → { status: pass|fail|pending, new_findings:[...] }
Cheap CI poll — no model creds or long runtimes in CI.
Lease policy
The client requests lease_seconds sized to its batch (server caps it, e.g. 24h). Heartbeats extend a lease when the client is online but are not required — claim and submit are the only calls that need connectivity. On expiry the slice requeues.
- A late result is still accepted if no other lease has completed the slice.
- If one has, first-accepted-wins and the late output is stored as a provenance artifact.
- Never double-billed — billing happens once at claim, idempotent per
slice_id.
Error semantics
| Status | Meaning | Client action |
|---|---|---|
200 | accepted | — |
202 | accepted-as-artifact / superseded | — |
409 | prompt_hash mismatch | re-claim |
413 | payload too large | use presigned upload / enable --redact |
429 | rate limited | retry with backoff |
5xx | server error | retry with backoff |
Network errors, 429, and 5xx are retried with exponential backoff + jitter by the agent.
Webhooks (optional)
POST /v1/webhooks/github — enabled by GITHUB_WEBHOOK_SECRET. On pull_request opened/synchronized it maps the PR's repo_remote_url to a project, fetches changed paths from the forge API (paths only), and creates an incremental campaign.
Next steps
- The agent — the client that drives this API.
- Environment reference — server configuration.