HTTP API reference
Every endpoint lives under /api/v1/. Authentication is via x-api-key: qsk_<JWT> for proxy calls; control-plane endpoints are open in dev_mode and require admin auth in production deployments.
Auth
| Method | Path | Body | Returns |
|---|---|---|---|
POST | /auth/dev/mint-token | {principal_id, class_slug, tenant?} | {api_key, token, expires_in, principal_id, class_slug, tenant} |
/auth/dev/mint-token only works when QUAYSIDE_DEV_MODE=true. Returns 403 otherwise.
Registry
Classes
| Method | Path | Body | Returns |
|---|---|---|---|
GET | /registry/classes?lifecycle_status=… | — | AgentClass[] |
POST | /registry/classes | ClassRegisterRequest | AgentClass (201) |
GET | /registry/classes/{class_id} | — | AgentClass |
GET | /registry/classes/by-slug/{slug} | — | AgentClass |
PATCH | /registry/classes/{class_id} | ClassUpdateRequest | AgentClass |
POST | /registry/classes/{class_id}/lifecycle | {lifecycle_status} | AgentClass |
Shadow
| Method | Path | Returns |
|---|---|---|
GET | /registry/shadow | ShadowEntry[] |
DELETE | /registry/shadow/{entry_id} | 204 |
Shapes
type LifecycleStatus = "draft" | "active" | "deprecated" | "sunset" | "external";
interface AgentClass { id: UUID; slug: string; name: string; purpose: string; owner_principal_id: string; lifecycle_status: LifecycleStatus; supersedes: UUID | null;}
interface ClassRegisterRequest { slug: string; // [a-z0-9][a-z0-9._/-]* name: string; purpose: string; owner_principal_id: string; lifecycle_status?: "draft" | "active"; // refuses external / sunset supersedes?: UUID;}
interface ClassUpdateRequest { name?: string; purpose?: string; owner_principal_id?: string; supersedes?: UUID;}
interface ShadowEntry { id: UUID; slug: string; principal_id: string; tenant: string; first_seen_at: ISO8601; last_seen_at: ISO8601; attempt_count: integer;}Policy
| Method | Path | Body | Returns |
|---|---|---|---|
GET | /policy/schema.json | — | JSON Schema of PolicyBody |
GET | /policy/class/{class_id}/active | — | Policy |
GET | /policy/class/{class_id}/versions | — | Policy[] (newest first) |
POST | /policy/class/{class_id}/drafts | PolicyBody | Policy (201) |
POST | /policy/class/{class_id}/rollback/{target_version} | — | Policy |
GET | /policy/{policy_id} | — | Policy |
POST | /policy/{policy_id}/publish | — | Policy |
interface Policy { id: UUID; class_id: UUID; version: integer; body: PolicyBody; published_at: ISO8601 | null;}See Policy schema reference for PolicyBody.
Proxy
| Method | Path | Headers | Returns |
|---|---|---|---|
POST | /proxy/anthropic/v1/messages | x-api-key: qsk_<JWT> | SSE stream (Anthropic shape) |
Body shape is Anthropic’s — model, max_tokens, messages, etc. The proxy adds a stream: true field on the upstream call regardless of what the caller sent.
Error responses:
| Status | When |
|---|---|
| 400 | Missing headers / malformed body |
| 401 | JWT invalid / expired / missing qsk_ prefix |
| 403 | Blocked by detector cascade or budget cap |
| 404 | Class slug unknown — also writes a shadow row |
Mid-stream block emits an SSE event: error frame:
event: errordata: {"type":"error","error":{"type":"quayside_policy_block","message":"…"}}Audit
| Method | Path | Returns |
|---|---|---|
GET | /audit/runs?class_id=&final_effect=&limit=1..500 | RunSummary[] |
GET | /audit/runs/{run_id} | RunDetail (summary + ordered steps) |
interface RunSummary { id: UUID; started_at: ISO8601; finished_at: ISO8601 | null; final_effect: "Allow" | "Flag" | "Block" | null; class_id: UUID; class_slug: string; instance_id: UUID; principal_id: string; step_count: integer;}
interface StepDetail { seq: integer; direction: "request" | "response"; detector: string | null; effect: "Allow" | "Flag" | "Modify" | "Approve" | "Block" | null; score: number | null; reason: string | null; created_at: ISO8601;}
interface RunDetail extends RunSummary { steps: StepDetail[];}Health
| Method | Path | Returns |
|---|---|---|
GET | /healthz | {status: "ok"} |
GET | /api/v1/auth/healthz | {module: "auth", status: "ok"} |
GET | /api/v1/registry/healthz | {module: "registry", status: "ok"} |
GET | /api/v1/policy/healthz | {module: "policy", status: "ok"} |
GET | /api/v1/audit/healthz | {module: "audit", status: "ok"} |
GET | /api/v1/proxy/healthz | {module: "proxy", status: "ok"} |
GET | /api/v1/tokens/healthz | {module: "tokens", status: "ok"} |
Error format
Quayside follows FastAPI’s standard error shape:
{ "detail": "human readable message" }…for application errors (404, 403, 409), and:
{ "detail": [{"loc": ["body","fail_mode"], "msg": "Input should be 'open' or 'closed'", "type": "..."}] }…for validation errors (422).