Skip to content

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

MethodPathBodyReturns
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

MethodPathBodyReturns
GET/registry/classes?lifecycle_status=…AgentClass[]
POST/registry/classesClassRegisterRequestAgentClass (201)
GET/registry/classes/{class_id}AgentClass
GET/registry/classes/by-slug/{slug}AgentClass
PATCH/registry/classes/{class_id}ClassUpdateRequestAgentClass
POST/registry/classes/{class_id}/lifecycle{lifecycle_status}AgentClass

Shadow

MethodPathReturns
GET/registry/shadowShadowEntry[]
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

MethodPathBodyReturns
GET/policy/schema.jsonJSON Schema of PolicyBody
GET/policy/class/{class_id}/activePolicy
GET/policy/class/{class_id}/versionsPolicy[] (newest first)
POST/policy/class/{class_id}/draftsPolicyBodyPolicy (201)
POST/policy/class/{class_id}/rollback/{target_version}Policy
GET/policy/{policy_id}Policy
POST/policy/{policy_id}/publishPolicy
interface Policy {
id: UUID;
class_id: UUID;
version: integer;
body: PolicyBody;
published_at: ISO8601 | null;
}

See Policy schema reference for PolicyBody.

Proxy

MethodPathHeadersReturns
POST/proxy/anthropic/v1/messagesx-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:

StatusWhen
400Missing headers / malformed body
401JWT invalid / expired / missing qsk_ prefix
403Blocked by detector cascade or budget cap
404Class slug unknown — also writes a shadow row

Mid-stream block emits an SSE event: error frame:

event: error
data: {"type":"error","error":{"type":"quayside_policy_block","message":"…"}}

Audit

MethodPathReturns
GET/audit/runs?class_id=&final_effect=&limit=1..500RunSummary[]
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

MethodPathReturns
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).