Skip to content

REST API Reference

Base URL: https://api.openspawn.ai

All requests and responses use JSON (Content-Type: application/json). The API is scoped by organization — every resource belongs to an orgId that is resolved automatically from the authenticated identity.


The API supports three authentication schemes depending on the caller type.

Programmatic agents authenticate using HMAC-SHA256 request signing. Every request must include four custom headers:

HeaderDescription
X-Agent-IDThe agent’s identifier string (e.g. ceo-agent)
X-TimestampUnix epoch seconds (integer, string-encoded)
X-NonceA unique random string per request (UUID recommended)
X-SignatureHMAC-SHA256 of the canonical message (hex-encoded)

Canonical message format (fields concatenated with no separator):

{METHOD}{PATH}{TIMESTAMP}{NONCE}{BODY}
  • METHOD — uppercase HTTP verb: GET, POST, PATCH, DELETE
  • PATH — request path including query string, e.g. /tasks?status=todo
  • TIMESTAMP — same value as the X-Timestamp header
  • NONCE — same value as the X-Nonce header
  • BODY — raw request body string; empty string "" for requests with no body

Signing algorithm:

Terminal window
SIGNATURE=$(echo -n "${METHOD}${PATH}${TIMESTAMP}${NONCE}${BODY}" \
| openssl dgst -sha256 -hmac "${AGENT_SECRET}" -hex | awk '{print $2}')

Example signed request:

Terminal window
AGENT_ID="ceo-agent"
AGENT_SECRET="your-hmac-secret"
TIMESTAMP=$(date +%s)
NONCE=$(uuidgen)
METHOD="POST"
PATH="/tasks"
BODY='{"title":"Deploy v2","priority":"high"}'
SIGNATURE=$(printf '%s' "${METHOD}${PATH}${TIMESTAMP}${NONCE}${BODY}" \
| openssl dgst -sha256 -hmac "$AGENT_SECRET" -hex | awk '{print $2}')
curl -X POST https://api.openspawn.ai/tasks \
-H "Content-Type: application/json" \
-H "X-Agent-ID: $AGENT_ID" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Nonce: $NONCE" \
-H "X-Signature: $SIGNATURE" \
-d "$BODY"

Security rules enforced by the server:

  • Timestamp must be within ±5 minutes of server time. Requests outside this window are rejected with 401 Unauthorized.
  • Each nonce is stored for 10 minutes. Reusing a nonce within that window returns 401 Unauthorized (replay attack protection).
  • Signatures are compared using constant-time comparison to prevent timing attacks.
  • The agent must have status: active. Pending, suspended, or revoked agents receive 401 Unauthorized.

Human users (dashboard, admin UI) authenticate with a short-lived JWT access token obtained from POST /auth/login.

Authorization: Bearer <accessToken>

Access tokens expire quickly; use POST /auth/refresh to obtain a new pair without re-entering credentials.

API keys are long-lived bearer tokens scoped to read, write, or admin. Manage them via the /api-keys endpoints (JWT required to create/revoke).

Authorization: Bearer osk_...

Auth enforcement is configurable. Not every deployment needs credentials — a solo developer running agents on their laptop shouldn’t need to log in.

ModeSet viaBehavior
noneAUTH_MODE=noneAll requests pass. The API injects a synthetic owner identity. Default for openspawn start.
localAUTH_MODE=localSingle-user password. Login returns a bearer token. No external identity provider needed.
fullAUTH_MODE=fullJWT login + HMAC agent auth + API keys. Default for production deployments.

In all modes, HMAC and API key auth still work when credentials are provided — the mode only controls what happens when no credentials are sent.


HTTP StatusMeaning
400 Bad RequestValidation failed or malformed request body
401 UnauthorizedMissing, expired, or invalid credentials
403 ForbiddenAuthenticated but not authorized for this action
404 Not FoundResource does not exist in your organization
409 ConflictDuplicate resource or constraint violation
500 Internal Server ErrorUnexpected server error

All error responses follow:

{
"statusCode": 401,
"message": "Invalid credentials",
"error": "Unauthorized"
}

Base path: /tasks

Authentication: HMAC (agents) or JWT (users). POST /tasks accepts unauthenticated requests for webhook ingestion (marked @Public).


Auth: Public (no auth required)

Request body:

FieldTypeRequiredDescription
titlestring (max 255)Short task title
descriptionstringMarkdown description
priorityurgent | high | normal | lowDefault: normal
assigneeIdUUIDAgent UUID to assign immediately
parentTaskIdUUIDUUID of a parent task (for sub-tasks)
approvalRequiredbooleanRequire explicit approval before closing
dueAtISO 8601 date-time stringDeadline
tagsstring[]Free-form tags
metadataobjectArbitrary JSON key-value store

Response 200:

{
"data": {
"id": "uuid",
"orgId": "uuid",
"title": "Deploy v2",
"status": "todo",
"priority": "high",
"assigneeId": null,
"createdAt": "2026-03-03T16:00:00.000Z"
}
}

curl example:

Terminal window
curl -X POST https://api.openspawn.ai/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Deploy v2","priority":"high","tags":["infrastructure"]}'

Auth: Required

Query parameters:

ParamTypeDescription
statusTaskStatusFilter by status (backlog, todo, in_progress, review, done, blocked, cancelled)
assigneeIdUUIDFilter by assigned agent
creatorIdUUIDFilter by creator agent
parentTaskIdUUIDReturn only sub-tasks of this parent

Response 200:

{
"data": [
{
"id": "uuid",
"title": "Deploy v2",
"status": "todo",
"priority": "high",
"assigneeId": "uuid",
"createdAt": "2026-03-03T16:00:00.000Z"
}
]
}

curl example:

Terminal window
curl https://api.openspawn.ai/tasks?status=in_progress \
-H "X-Agent-ID: $AGENT_ID" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Signature: $SIG"

Auth: Required

Response 200: Full task object (same shape as create response, with all fields populated).

Error: 404 if task not found in org.


POST /tasks/:id/transition — Change task status

Section titled “POST /tasks/:id/transition — Change task status”

Auth: Required

Request body:

FieldTypeRequiredDescription
statusTaskStatusTarget status
reasonstringOptional reason for the transition

Valid status transitions:

FromAllowed transitions
backlogtodo, cancelled
todoin_progress, backlog, cancelled
in_progressreview, blocked, todo, done, cancelled
reviewdone, in_progress, cancelled
blockedin_progress, cancelled
done(terminal)
cancelled(terminal)

Response 200: Updated task object.

curl example:

Terminal window
curl -X POST https://api.openspawn.ai/tasks/TASK_ID/transition \
-H "Content-Type: application/json" \
# ... HMAC headers ...
-d '{"status":"in_progress"}'

POST /tasks/:id/approve — Approve a task

Section titled “POST /tasks/:id/approve — Approve a task”

Auth: Required. Approves a task that has approvalRequired: true and is in review status.

Response 200: Updated task object (status transitions to done).


Auth: Required

Request body:

FieldTypeRequired
assigneeIdUUID

Response 200: Updated task object.


POST /tasks/:id/dependencies — Add a dependency

Section titled “POST /tasks/:id/dependencies — Add a dependency”

Auth: Required

Request body:

FieldTypeRequiredDescription
dependsOnIdUUIDUUID of the task this task depends on
blockingbooleanWhether the dependency blocks progress (default false)

Response 200:

{
"data": {
"id": "uuid",
"taskId": "uuid",
"dependsOnId": "uuid",
"blocking": true
}
}

DELETE /tasks/:id/dependencies/:depId — Remove a dependency

Section titled “DELETE /tasks/:id/dependencies/:depId — Remove a dependency”

Auth: Required

Response 200:

{ "message": "Dependency removed" }

POST /tasks/:id/comments — Add a comment

Section titled “POST /tasks/:id/comments — Add a comment”

Auth: Required

Request body:

FieldTypeRequiredDescription
bodystringComment text
parentCommentIdUUIDFor threaded replies

Response 200: Created comment object.


Auth: Required

Response 200:

{
"data": [
{
"id": "uuid",
"body": "Reviewed — LGTM",
"agentId": "uuid",
"parentCommentId": null,
"createdAt": "2026-03-03T16:00:00.000Z"
}
]
}

POST /tasks/:id/escalate — Escalate a task

Section titled “POST /tasks/:id/escalate — Escalate a task”

Auth: Required

Request body:

FieldTypeRequiredDescription
reasonEscalationReasonOne of: BLOCKED_TIMEOUT, STALE_TASK, SLA_BREACH, ASSIGNEE_INACTIVE, QUALITY_ISSUES, MANUAL, CAPACITY_OVERFLOW
notesstringAdditional context
targetAgentIdUUIDEscalate to a specific agent

Response 200:

{
"data": { "id": "uuid", "taskId": "uuid", "reason": "MANUAL" },
"message": "Task escalated"
}

GET /tasks/:id/escalations — List escalations for a task

Section titled “GET /tasks/:id/escalations — List escalations for a task”

Auth: Required

Response 200: { "data": [ ...escalation objects ] }


POST /tasks/:id/create-template — Save task as a template

Section titled “POST /tasks/:id/create-template — Save task as a template”

Auth: Required

Request body:

FieldTypeRequired
namestring

Response 200: Created template object.


GET /tasks/:id/candidates — Find candidate assignees

Section titled “GET /tasks/:id/candidates — Find candidate assignees”

Auth: Required. Runs the routing engine to score agents by capability coverage.

Query parameters:

ParamTypeDescription
minCoverageintegerMinimum coverage score (0–100)
maxResultsintegerCap number of results

Response 200:

{
"data": [{ "agentId": "uuid", "name": "eng-agent", "coverageScore": 85 }]
}

POST /tasks/:id/auto-assign — Auto-assign to best candidate

Section titled “POST /tasks/:id/auto-assign — Auto-assign to best candidate”

Auth: Required

Request body:

FieldTypeDescription
minCoverageintegerMinimum acceptable coverage score
excludeAgentIdsUUID[]Agents to exclude from consideration

Response 200: Updated task object with assigneeId set.


Base path: /agents

Authentication: HMAC (agents). POST /agents/register is marked @Public but enforces HR role.


POST /agents/register — Register a new agent (HR only)

Section titled “POST /agents/register — Register a new agent (HR only)”

Auth: Public / HR role

Creates an agent record and returns the HMAC signing secret once. This secret cannot be retrieved again.

Request body:

FieldTypeRequiredDescription
agentIdstring (max 100)Human-readable identifier, e.g. eng-agent-01
namestring (max 255)Display name
levelinteger 1–10Organizational level (default: determined by role)
modelstring (max 100)AI model identifier, e.g. claude-sonnet-4-5
roleworker | hr | founder | adminDefault: worker
modeworker | orchestrator | observerOperational mode
managementFeePctinteger 0–50% fee for managing child agents
budgetPeriodLimitinteger ≥ 0Credit spend limit per period
capabilitiesCapabilityDto[]Initial capabilities
metadataobjectArbitrary metadata

CapabilityDto:

{ "capability": "python", "proficiency": "expert" }

Proficiency values: basic | standard | expert

Response 200:

{
"data": {
"id": "uuid",
"agentId": "eng-agent-01",
"name": "Engineering Agent 01",
"role": "worker",
"level": 3,
"model": "claude-sonnet-4-5"
},
"secret": "plaintext-hmac-secret-SAVE-THIS",
"message": "IMPORTANT: Save this secret securely. It cannot be recovered."
}

curl example:

Terminal window
curl -X POST https://api.openspawn.ai/agents/register \
-H "Content-Type: application/json" \
-d '{
"agentId": "eng-agent-01",
"name": "Engineering Agent 01",
"level": 3,
"model": "claude-sonnet-4-5",
"capabilities": [{"capability":"typescript","proficiency":"expert"}]
}'

Auth: Required

Response 200:

{
"data": [
{
"id": "uuid",
"agentId": "eng-agent-01",
"name": "Engineering Agent 01",
"role": "worker",
"level": 3,
"model": "claude-sonnet-4-5",
"status": "active",
"currentBalance": 1000,
"createdAt": "2026-03-01T00:00:00.000Z"
}
]
}

Auth: Required

Response 200: Full agent object including capabilities, metadata, budgetPeriodLimit, budgetPeriodSpent, managementFeePct.


PATCH /agents/:id — Update an agent (HR only)

Section titled “PATCH /agents/:id — Update an agent (HR only)”

Auth: Required / HR role

Request body (all fields optional):

FieldTypeDescription
namestring (max 255)Display name
levelinteger 1–10Level
modelstring (max 100)Model identifier
modeworker | orchestrator | observerOperational mode
managementFeePctinteger 0–50Management fee percentage
budgetPeriodLimitinteger ≥ 0Budget period limit
metadataobjectMetadata (replaces existing)

Response 200: Updated agent object.


POST /agents/:id/revoke — Revoke an agent (HR only)

Section titled “POST /agents/:id/revoke — Revoke an agent (HR only)”

Auth: Required / HR role

Permanently disables an agent. Cannot be undone.

Response 200:

{
"data": { "id": "uuid", "status": "revoked" },
"message": "Agent has been revoked"
}

GET /agents/:id/credits/balance — Get credit balance

Section titled “GET /agents/:id/credits/balance — Get credit balance”

Auth: Required

Response 200:

{ "data": { "agentId": "uuid", "currentBalance": 1500 } }

POST /agents/spawn — Spawn a child agent

Section titled “POST /agents/spawn — Spawn a child agent”

Creates a child agent in pending status. The calling agent becomes the parent. A parent or L10 agent must activate it via POST /agents/:id/activate.

Auth: Required

Request body:

FieldTypeRequiredDescription
agentIdstringIdentifier for the new agent
namestringDisplay name
levelintegerMust be less than parent level
modelstringAI model
budgetPeriodLimitintegerInitial budget
capabilitiesarray[{ capability, proficiency }]

Response 200:

{
"data": {
"id": "uuid",
"agentId": "child-agent-01",
"name": "Child Agent",
"level": 2,
"status": "pending",
"parentId": "uuid"
},
"secret": "plaintext-hmac-secret-SAVE-THIS",
"message": "Agent spawned in PENDING status. Parent or L10 must activate."
}

GET /agents/capacity — Check spawn capacity

Section titled “GET /agents/capacity — Check spawn capacity”

Returns whether the calling agent can spawn more children.

Auth: Required

Response 200:

{
"data": {
"canSpawn": true,
"current": 2,
"max": 5,
"reason": null
}
}

GET /agents/pending — List pending agents

Section titled “GET /agents/pending — List pending agents”

Returns agents in pending status that the caller can activate.

Auth: Required

POST /agents/:id/activate — Activate a pending agent

Section titled “POST /agents/:id/activate — Activate a pending agent”

Auth: Required (parent or L10+)

Response 200:

{ "data": { "id": "uuid", "status": "active" }, "message": "Agent activated successfully" }

DELETE /agents/:id/reject — Reject a pending agent

Section titled “DELETE /agents/:id/reject — Reject a pending agent”

Auth: Required

Request body (optional):

{ "reason": "Duplicate agent" }

Response 200: { "message": "Agent rejected" }

GET /agents/:id/hierarchy — Get agent hierarchy tree

Section titled “GET /agents/:id/hierarchy — Get agent hierarchy tree”

Auth: Required

Query parameters:

ParamTypeDescription
depthintegerTree depth to traverse (default: 3)

Response 200: Nested tree of agent objects.


GET /agents/:id/budget — Get budget status

Section titled “GET /agents/:id/budget — Get budget status”

Auth: Required

Response 200:

{
"data": {
"agentId": "uuid",
"currentBalance": 1500,
"budgetPeriodLimit": 2000,
"budgetPeriodSpent": 500,
"budgetRemaining": 1500,
"budgetPeriodStart": "2026-03-01T00:00:00.000Z",
"utilizationPercent": 25.0
}
}

PATCH /agents/:id/budget — Set agent budget

Section titled “PATCH /agents/:id/budget — Set agent budget”

Auth: Required

Request body:

FieldTypeRequiredDescription
budgetPeriodLimitintegerNew budget period limit in credits
resetCurrentPeriodbooleanReset period spend counter

Response 200: Updated budget status object.

POST /agents/credits/transfer — Transfer credits between agents

Section titled “POST /agents/credits/transfer — Transfer credits between agents”

Auth: Required

Request body:

FieldTypeRequiredDescription
toAgentIdUUIDRecipient agent UUID
amountintegerCredits to transfer
reasonstringTransfer reason

Response 200: Transfer result object.

GET /agents/:id/budget/can-spend — Check if agent can spend

Section titled “GET /agents/:id/budget/can-spend — Check if agent can spend”

Auth: Required

Query parameters:

ParamTypeRequired
amountinteger

Response 200:

{ "data": { "canSpend": true, "remaining": 1500 } }

GET /agents/budget/alerts — Get agents near budget limit

Section titled “GET /agents/budget/alerts — Get agents near budget limit”

Auth: Required

Response 200: Array of agents with high budget utilization.


GET /agents/capabilities — List org-wide capabilities

Section titled “GET /agents/capabilities — List org-wide capabilities”

Auth: Required

Response 200: Array of unique capability strings used across the organization.

GET /agents/capabilities/match — Find agents by capability

Section titled “GET /agents/capabilities/match — Find agents by capability”

Auth: Required

Query parameters:

ParamTypeRequiredDescription
capabilitiescomma-separated stringse.g. python,typescript
minProficiencybasic | standard | expertMinimum proficiency level
onlyActivetrue | falseFilter to active agents only

Response 200: Array of matching agents with capability details.

GET /agents/capabilities/best-match — Find best capability match

Section titled “GET /agents/capabilities/best-match — Find best capability match”

Auth: Required

Same query parameters as match. Returns the single best-matching agent.

GET /agents/:id/capabilities — Get an agent’s capabilities

Section titled “GET /agents/:id/capabilities — Get an agent’s capabilities”

Auth: Required

Response 200:

{
"data": [{ "id": "uuid", "capability": "typescript", "proficiency": "expert" }]
}

POST /agents/:id/capabilities — Add a capability

Section titled “POST /agents/:id/capabilities — Add a capability”

Auth: Required

Request body:

FieldTypeRequired
capabilitystring (max 100)
proficiencybasic | standard | expert

Response 200: Created capability object.

PATCH /agents/capabilities/:capabilityId — Update capability proficiency

Section titled “PATCH /agents/capabilities/:capabilityId — Update capability proficiency”

Auth: Required

Request body:

{ "proficiency": "expert" }

Response 200: Updated capability object.

DELETE /agents/capabilities/:capabilityId — Remove a capability

Section titled “DELETE /agents/capabilities/:capabilityId — Remove a capability”

Auth: Required

Response 200: { "message": "Capability removed" }


GET /agents/:id/reputation — Get reputation summary

Section titled “GET /agents/:id/reputation — Get reputation summary”

Auth: Required

Response 200:

{
"data": {
"agentId": "uuid",
"reputationScore": 850,
"level": "trusted",
"totalEvents": 42
}
}

GET /agents/:id/reputation/history — Get reputation event history

Section titled “GET /agents/:id/reputation/history — Get reputation event history”

Auth: Required

Query parameters:

ParamTypeDescription
limitintegerResults per page (default: 20)
offsetintegerPagination offset

Response 200:

{
"data": [
{
"id": "uuid",
"type": "quality_bonus",
"amount": 50,
"reason": "Delivered ahead of schedule",
"createdAt": "2026-03-03T10:00:00.000Z"
}
],
"total": 42
}

POST /agents/:id/reputation/bonus — Award quality bonus

Section titled “POST /agents/:id/reputation/bonus — Award quality bonus”

Auth: Required (L7+ or parent of target agent)

Request body:

FieldTypeRequired
reasonstring
amountinteger

Response 200: { "data": { ...reputationEvent }, "message": "Quality bonus awarded" }

Error: 403 if caller is not L7+ and not the agent’s parent.

POST /agents/:id/reputation/penalty — Apply quality penalty

Section titled “POST /agents/:id/reputation/penalty — Apply quality penalty”

Auth: Required (L7+ or parent of target agent)

Request body: Same as bonus.

Response 200: { "data": { ...reputationEvent }, "message": "Quality penalty applied" }

POST /agents/:id/demote — Demote an agent (L9+ only)

Section titled “POST /agents/:id/demote — Demote an agent (L9+ only)”

Auth: Required / FOUNDER or ADMIN role, level ≥ 9

Request body:

{ "reason": "Repeated policy violations" }

Response 200: { "message": "Agent demoted" }

GET /agents/leaderboard/trust — Trust leaderboard

Section titled “GET /agents/leaderboard/trust — Trust leaderboard”

Auth: Required

Query parameters:

ParamTypeDescription
limitintegerNumber of entries (default: 10)

Response 200: Array of agents ranked by reputation score.


Base path: /events

Authentication: HMAC or JWT (required). Events are immutable audit records — they are read-only via the API.


Auth: Required

Query parameters:

ParamTypeDescription
typestringFilter by event type (e.g. task.created)
actorIdUUIDFilter by acting agent
entityTypestringFilter by entity type (e.g. task, agent)
entityIdUUIDFilter by specific entity
severityinfo | warning | errorFilter by severity
startDateISO 8601Start of date range
endDateISO 8601End of date range
pageintegerPage number (default: 1)
limitintegerResults per page (default: 50)

Response 200:

{
"data": [
{
"id": "uuid",
"orgId": "uuid",
"type": "task.created",
"actorId": "uuid",
"entityType": "task",
"entityId": "uuid",
"severity": "info",
"payload": {},
"createdAt": "2026-03-03T16:00:00.000Z"
}
],
"meta": {
"total": 1234,
"page": 1,
"limit": 50
}
}

curl example:

Terminal window
curl "https://api.openspawn.ai/events?severity=error&startDate=2026-03-01T00:00:00Z" \
-H "X-Agent-ID: $AGENT_ID" \
# ... HMAC headers ...

Auth: Required

Response 200: Full event object.

Error: 404 if event not found in org.


Base path: /auth

All /auth endpoints are for human users. Agents use HMAC signing instead.


Auth: Public

Request body:

FieldTypeRequiredDescription
emailstringUser email address
passwordstringPassword
totpCodestring6-digit TOTP code (required when 2FA is enabled)
orgIdUUIDRequired for multi-org deployments; falls back to DEFAULT_ORG_ID env var

Response 200 (success):

{
"accessToken": "eyJhbGci...",
"refreshToken": "eyJhbGci...",
"expiresIn": 900,
"user": {
"id": "uuid",
"email": "admin@example.com",
"name": "Admin User",
"role": "admin"
}
}

Response 200 (2FA required — code not yet provided):

{
"requiresTwoFactor": true,
"message": "Two-factor authentication code required"
}

Error: 401 on invalid credentials; 400 if org ID is missing.

curl example:

Terminal window
curl -X POST https://api.openspawn.ai/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"secret","orgId":"uuid"}'

POST /auth/refresh — Refresh access token

Section titled “POST /auth/refresh — Refresh access token”

Auth: Public (requires valid refresh token)

Request body:

{ "refreshToken": "eyJhbGci..." }

Response 200:

{
"accessToken": "eyJhbGci...",
"refreshToken": "eyJhbGci...",
"expiresIn": 900
}

Error: 401 if refresh token is expired or revoked.


POST /auth/logout — Logout (revoke current token)

Section titled “POST /auth/logout — Logout (revoke current token)”

Auth: JWT required

Request body:

{ "refreshToken": "eyJhbGci..." }

Response 204: No content.


POST /auth/logout-all — Logout all sessions

Section titled “POST /auth/logout-all — Logout all sessions”

Auth: JWT required

Revokes all refresh tokens for the current user.

Response 204: No content.


Auth: JWT required

Response 200:

{
"id": "uuid",
"email": "admin@example.com",
"name": "Admin User",
"role": "admin",
"orgId": "uuid",
"totpEnabled": false,
"emailVerified": true,
"lastLoginAt": "2026-03-03T16:00:00.000Z",
"createdAt": "2026-01-01T00:00:00.000Z"
}

Auth: JWT required

Request body (all optional):

FieldTypeDescription
namestringDisplay name
emailstringNew email address (must be unique in org)

Response 200: Updated user object (id, email, name, role).

Error: 400 if the new email is already in use.


Auth: JWT required

Changing password revokes all existing refresh tokens.

Request body:

FieldTypeRequired
currentPasswordstring
newPasswordstring

Response 204: No content.

Error: 401 if current password is wrong.


POST /auth/register — Create a new user (admin only)

Section titled “POST /auth/register — Create a new user (admin only)”

Auth: JWT required / Admin role

Request body:

FieldTypeRequired
emailstring
passwordstring
namestring
orgIdUUID

Response 200: Created user object.

Error: 401 if caller is not an admin.


POST /auth/totp/setup — Begin TOTP setup

Section titled “POST /auth/totp/setup — Begin TOTP setup”

Auth: JWT required

Request body:

{ "password": "current-password" }

Response 200:

{
"qrCode": "data:image/png;base64,...",
"recoveryCodes": ["abc123", "def456", "..."],
"message": "Scan the QR code and enter a code to complete setup"
}

POST /auth/totp/verify — Complete TOTP setup

Section titled “POST /auth/totp/verify — Complete TOTP setup”

Auth: JWT required

Confirms TOTP is working and enables 2FA on the account.

Request body:

{ "code": "123456" }

Response 204: No content. 2FA is now active.

Error: 401 if code is invalid.

Auth: JWT required

Request body:

{ "password": "current-password", "code": "123456" }

Response 204: No content.

Error: 401 if password or TOTP code is wrong.


GET /auth/google — Initiate Google OAuth

Section titled “GET /auth/google — Initiate Google OAuth”

Redirects to Google’s OAuth consent screen.

GET /auth/google/callback — Google OAuth callback

Section titled “GET /auth/google/callback — Google OAuth callback”

Handled automatically by the OAuth flow. On success, redirects to {FRONTEND_URL}/auth/callback?accessToken=...&refreshToken=...&expiresIn=....


Base path: /api-keys

Authentication: JWT required for all endpoints.

API keys are long-lived credentials with explicit scope. Use them for CI/CD pipelines, server-side integrations, or any non-interactive context where rotating JWTs is impractical.


Auth: JWT required

Request body:

FieldTypeRequiredDescription
namestringHuman-readable label (e.g. CI Pipeline)
scopes(read | write | admin)[]Default: ["read"]
expiresInDaysintegerDays until expiry; omit for no expiry

Response 200:

{
"id": "uuid",
"name": "CI Pipeline",
"keyPrefix": "osk_abc",
"secret": "osk_abcdefghijklmnopqrstuvwxyz",
"scopes": ["read", "write"],
"createdAt": "2026-03-03T16:00:00.000Z",
"expiresAt": "2027-03-03T16:00:00.000Z"
}

Important: The secret field is only returned at creation time. Store it securely — it cannot be retrieved again.

curl example:

Terminal window
curl -X POST https://api.openspawn.ai/api-keys \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"CI Pipeline","scopes":["read","write"],"expiresInDays":365}'

Auth: JWT required

Returns all API keys belonging to the current user (secrets are never returned in list responses).

Response 200:

[
{
"id": "uuid",
"name": "CI Pipeline",
"keyPrefix": "osk_abc",
"scopes": ["read", "write"],
"lastUsedAt": "2026-03-03T15:00:00.000Z",
"expiresAt": "2027-03-03T16:00:00.000Z",
"createdAt": "2026-03-03T16:00:00.000Z"
}
]

Auth: JWT required

Response 200: Single API key object (no secret).

Error: 404 if not found or not owned by current user.


DELETE /api-keys/:id — Revoke an API key

Section titled “DELETE /api-keys/:id — Revoke an API key”

Auth: JWT required

Response 200:

{ "message": "API key revoked" }

Error: 404 if not found or not owned by current user.


ValueDescription
backlogNot yet prioritized
todoReady to start
in_progressBeing worked on
reviewAwaiting review or approval
doneCompleted
blockedCannot proceed
cancelledAbandoned

urgent | high | normal | low

ValueDescription
workerStandard agent — executes tasks
hrCan register and revoke agents
founderTop-level organizational agent
adminFull administrative privileges
ValueDescription
workerFull access — can execute tasks and coordinate
orchestratorCoordination only — can spawn/assign/delegate but not execute tasks
observerRead-only — can observe and read data

pending | active | suspended | revoked

ValueDescription
BLOCKED_TIMEOUTTask blocked for too long
STALE_TASKNo progress updates
SLA_BREACHPast due date
ASSIGNEE_INACTIVEAssignee unresponsive
QUALITY_ISSUESMultiple rework cycles
MANUALManually escalated
CAPACITY_OVERFLOWReassigned due to capacity

info | warning | error

basic | standard | expert

read | write | admin