Skip to content

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/defiant

Or if you are using the local Supabase stack:

http://localhost:54321/functions/v1/defiant

Authentication

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

TierRequests per minute
Authenticated user120
Service role600

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:

CodeHTTP StatusDescription
UNAUTHORIZED401Invalid or expired JWT
FORBIDDEN403Authenticated but lacks permission
NOT_FOUND404Resource does not exist
VALIDATION_ERROR422Request body failed validation
CONDUCTOR_OFFLINE503The local Conductor is not running
BUDGET_EXHAUSTED429Daily token budget exhausted
MANDATE_VIOLATION409Request blocked by an active mandate

Projects

GET /projects

List all projects for the authenticated user.

GET /projects
Authorization: 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 /projects
Authorization: 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=0

Query parameters:

ParameterTypeDescription
projectIdstringFilter by project
statestringFilter by state: active, complete, failed, blocked
limitintegerResults per page (default: 20, max: 100)
offsetintegerPagination offset

POST /sprints

Create a sprint.

POST /sprints
Authorization: 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/retry
Content-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=50

Query parameters:

ParameterTypeDescription
sprintIdstringFilter by sprint
projectIdstringFilter by project
typestringFilter by event type
agentIdstringFilter by agent
sinceISO 8601Events after this timestamp
beforeISO 8601Events before this timestamp
limitintegerMax results (default: 50, max: 500)

Event types:

TypeDescription
sprint.createdSprint was created
state.transitionSprint state changed
agent.dispatchedAgent was dispatched
agent.completedAgent issued completion certificate
mandate.checkedMandate check ran
mandate.violatedMandate violation found
pr.openedPull request opened
pr.mergedPull request merged
inbox.createdInbox item created
deploy.triggeredDeploy pipeline triggered
deploy.succeededDeploy confirmed healthy

GET /events/stream

Server-Sent Events stream for real-time event updates.

GET /events/stream?sprintId=spr_01hw...
Accept: text/event-stream
Authorization: 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=critical

GET /inbox/:id

Get a single inbox item.

POST /inbox/:id/resolve

Resolve an inbox item.

POST /inbox/:id/resolve
Content-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=security

GET /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/check
Content-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 /webhooks
Content-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
);