Skip to content

REST API Reference

The Defiant public REST API provides HTTP access to projects, sprints, mandates, and inbox items. The TypeScript SDK and MCP server call this API internally.

Base URL

https://app.defiant.build/api/v1

For self-hosted deployments using Supabase Edge Functions directly:

https://<your-project>.supabase.co/functions/v1/api-v1

Authentication

All endpoints require a Defiant API key in the Authorization header:

Authorization: Bearer dft_your_api_key_here

Generate API keys at Settings → API Keys in the Defiant web app. Keys are SHA-256 hashed before storage; the raw key is shown once at creation.

For local development:

Terminal window
export DEFIANT_API_KEY=dft_your_key
# The SDK picks this up automatically

Legacy: server-to-server calls previously used Supabase service keys:

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
);