REST API Reference
The Defiant REST API is hosted by your Supabase deployment. It provides HTTP access to projects, sprints, events, inbox items, and mandates. The CLI and TypeScript SDK both use this API internally.
Base URL
https://<your-supabase-project>.supabase.co/functions/v1/defiantOr if you are using the local Supabase stack:
http://localhost:54321/functions/v1/defiantAuthentication
All endpoints require a valid Supabase JWT in the Authorization header:
Authorization: Bearer <supabase-jwt>Obtain a JWT by signing in with the Supabase client:
const { data } = await supabase.auth.signInWithPassword({ email, password });const jwt = data.session?.access_token;For server-to-server calls, use the Supabase service role key:
Authorization: Bearer <supabase-service-role-key>Rate limiting
| Tier | Requests per minute |
|---|---|
| Authenticated user | 120 |
| Service role | 600 |
Exceeded limits return 429 Too Many Requests with a Retry-After header.
Error format
All errors follow this format:
{ "error": { "code": "SPRINT_NOT_FOUND", "message": "Sprint spr_01hw... not found", "details": null }}Common error codes:
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Invalid or expired JWT |
FORBIDDEN | 403 | Authenticated but lacks permission |
NOT_FOUND | 404 | Resource does not exist |
VALIDATION_ERROR | 422 | Request body failed validation |
CONDUCTOR_OFFLINE | 503 | The local Conductor is not running |
BUDGET_EXHAUSTED | 429 | Daily token budget exhausted |
MANDATE_VIOLATION | 409 | Request blocked by an active mandate |
Projects
GET /projects
List all projects for the authenticated user.
GET /projectsAuthorization: Bearer <jwt>{ "data": [ { "id": "proj_01hwabcdefg0000000000000000", "name": "my-app", "repo": "yourorg/my-app", "vertical": "b2b-saas", "branch": "main", "deployUrl": "https://myapp.com", "createdAt": "2026-05-01T10:00:00Z", "updatedAt": "2026-05-05T14:00:00Z" } ], "meta": { "total": 1 }}POST /projects
Create a new project.
POST /projectsAuthorization: Bearer <jwt>Content-Type: application/json
{ "name": "my-app", "repo": "yourorg/my-app", "vertical": "b2b-saas", "branch": "main", "deployUrl": "https://myapp.com"}GET /projects/:id
Get a single project.
PATCH /projects/:id
Update a project. Any field except id and createdAt can be updated.
DELETE /projects/:id
Delete a project. Does not delete the event log. Active sprints are cancelled first.
Sprints
GET /sprints
List sprints. Supports filtering and pagination.
GET /sprints?projectId=proj_01hw...&state=active&limit=20&offset=0Query parameters:
| Parameter | Type | Description |
|---|---|---|
projectId | string | Filter by project |
state | string | Filter by state: active, complete, failed, blocked |
limit | integer | Results per page (default: 20, max: 100) |
offset | integer | Pagination offset |
POST /sprints
Create a sprint.
POST /sprintsAuthorization: Bearer <jwt>Content-Type: application/json
{ "projectId": "proj_01hwabcdefg0000000000000000", "goal": "Add user profile page with avatar and activity feed", "priority": "normal", "tokenBudget": 200000}Response:
{ "data": { "id": "spr_01hwxyz0000000000000000", "projectId": "proj_01hwabcdefg0000000000000000", "goal": "Add user profile page with avatar and activity feed", "state": "INTAKE", "priority": "normal", "tokenBudget": 200000, "tokensUsed": 0, "agents": [], "prs": [], "createdAt": "2026-05-05T14:00:00Z", "updatedAt": "2026-05-05T14:00:00Z" }}GET /sprints/:id
Get a single sprint with full detail.
{ "data": { "id": "spr_01hwxyz0000000000000000", "state": "BUILD", "agents": ["captain", "architect", "builder"], "prs": [ { "number": 42, "url": "https://github.com/yourorg/my-app/pull/42", "state": "open" } ], "technicalPlan": { ... }, "tokensUsed": 47823, "elapsed": "00:12:34" }}DELETE /sprints/:id
Cancel a running sprint.
POST /sprints/:id/retry
Retry a failed or blocked sprint.
POST /sprints/:id/retryContent-Type: application/json
{ "fromState": "PLAN" // optional — defaults to last checkpoint}Events
GET /events
Query the event log.
GET /events?sprintId=spr_01hw...&type=state_transition&limit=50Query parameters:
| Parameter | Type | Description |
|---|---|---|
sprintId | string | Filter by sprint |
projectId | string | Filter by project |
type | string | Filter by event type |
agentId | string | Filter by agent |
since | ISO 8601 | Events after this timestamp |
before | ISO 8601 | Events before this timestamp |
limit | integer | Max results (default: 50, max: 500) |
Event types:
| Type | Description |
|---|---|
sprint.created | Sprint was created |
state.transition | Sprint state changed |
agent.dispatched | Agent was dispatched |
agent.completed | Agent issued completion certificate |
mandate.checked | Mandate check ran |
mandate.violated | Mandate violation found |
pr.opened | Pull request opened |
pr.merged | Pull request merged |
inbox.created | Inbox item created |
deploy.triggered | Deploy pipeline triggered |
deploy.succeeded | Deploy confirmed healthy |
GET /events/stream
Server-Sent Events stream for real-time event updates.
GET /events/stream?sprintId=spr_01hw...Accept: text/event-streamAuthorization: Bearer <jwt>data: {"type":"state.transition","sprintId":"spr_01hw...","from":"BUILD","to":"SHIP"}
data: {"type":"agent.dispatched","sprintId":"spr_01hw...","agent":"reviewer"}Inbox
GET /inbox
List inbox items.
GET /inbox?resolved=false&priority=criticalGET /inbox/:id
Get a single inbox item.
POST /inbox/:id/resolve
Resolve an inbox item.
POST /inbox/:id/resolveContent-Type: application/json
{ "response": "Use the existing users table for profile data"}Mandates
GET /mandates
List all mandates, optionally filtered by project (includes vertical pack mandates).
GET /mandates?projectId=proj_01hw...&category=securityGET /mandates/:id
Get a single mandate by ID (e.g., mandate_7).
POST /mandates/check
Run a mandate check against a code snapshot.
POST /mandates/checkContent-Type: application/json
{ "projectId": "proj_01hw...", "mandateId": "mandate_7", // optional — checks all if omitted "files": { "src/server.ts": "<file contents>" }}Response:
{ "data": { "passed": false, "violations": [ { "mandateId": "mandate_7", "ruleId": "no-raw-sql", "severity": "blocking", "file": "src/server.ts", "line": 47, "message": "Raw SQL detected. Use parameterized queries." } ] }}Webhooks
Defiant can POST events to a URL you configure. Register a webhook:
POST /webhooksContent-Type: application/json
{ "url": "https://myapp.com/defiant-webhook", "events": ["sprint.complete", "sprint.failed", "inbox.created"], "secret": "whsec_..." // used to sign payloads — verify with HMAC-SHA256}Payload format:
{ "id": "evt_01hw...", "type": "sprint.complete", "createdAt": "2026-05-05T14:38:22Z", "data": { "sprintId": "spr_01hw...", "projectId": "proj_01hw...", "state": "COMPLETE", "tokensUsed": 182304, "elapsed": "00:38:22" }}Signature verification (Node.js):
import { createHmac } from 'crypto';
function verifyWebhook(payload: string, signature: string, secret: string): boolean { const expected = createHmac('sha256', secret) .update(payload) .digest('hex'); return `sha256=${expected}` === signature;}
// In your webhook handler:const isValid = verifyWebhook( req.rawBody, req.headers['x-defiant-signature'], process.env.WEBHOOK_SECRET);