Skip to content

Coordinator REST

The Coordinator manages agent sessions and delegation edges. It enforces depth and child limits, prevents delegation cycles, and cascades revocation through the agent subtree and delegation graph. It runs on port 4000.

All routes except /health, /ready, and POST /v1/verify require a bearer token issued by the STS:

Authorization: Bearer <sts-issued-mandate>

The token must carry the zone_id claim matching the URL’s {zoneId} parameter, and must include one of the coordinator scopes for the target operation:

ScopePermits
coordinator.adminAll operations
coordinator.spawn_for:{appId}Spawn agents for the specified application
coordinator.spawn_under:{appId}Spawn agents as children of the specified application’s agents
coordinator.delegate_from:{appId}Create delegation edges from the specified application
coordinator.delegate_to:{appId}Accept delegation edges to the specified application
{
"error": "error_code",
"message": "human-readable description"
}

Agent lifecycle — /v1/zones/{zoneId}/agents

Section titled “Agent lifecycle — /v1/zones/{zoneId}/agents”

Spawn a new agent session. Equivalent to POST /v1/begin.

Request body:

FieldTypeRequiredDefault
application_idstringYes
session_sidstringNoSTS session sid from the bearer token
parent_idstring | nullNonull (root agent)
kind"service" | "instance" | "ephemeral"No
capabilitiesstring[]No[]
ttl_secondsintegerNo3600
metadataobjectNo{}

Limits:

ConstraintValue
Max depth10
Max children per agent10
Max agents per zone50
Max agents per application200

Validation: session_sid must reference an active session. If parent_id is set, the parent must be active and the caller must own the parent’s application or hold coordinator.spawn_under:{parentAppId}.

Response 201:

{
"id": "agent-session-id",
"zone_id": "zone1",
"application_id": "app1",
"parent_id": null,
"session_sid": "sts-session-sid",
"status": "active",
"depth": 0,
"spawned_at": "2026-05-11T20:00:00Z",
"terminated_at": null
}

Idempotency: If Idempotency-Key is provided, returns the existing agent session if one already matches (zone, application_id, session_sid, parent_id).

Errors:

CodeStatusCondition
session_sid_required400No resolvable session SID
application_not_found404application_id does not exist
session_not_found404session_sid does not reference an active session
parent_not_found404parent_id does not exist
application_ownership_required403Caller does not own application_id and lacks spawn scope
agent_zone_limit_exceeded42950-agent zone limit reached
agent_limit_exceeded429200-agent per-application limit reached
agent_children_limit_exceeded429Parent has 10 children
agent_depth_limit_exceeded429Nesting depth would exceed 10

List agent sessions. Paginated.

Query parameters: limit (1–500, default 100), cursor (agent ID)

Response 200: { "items": [...], "next_cursor": "..." }

Get a single agent session.

Response 200: Agent session object.
Error 404: agent_not_found

GET /v1/zones/{zoneId}/agents/{id}/children

Section titled “GET /v1/zones/{zoneId}/agents/{id}/children”

List direct children of an agent session.

Response 200: { "items": [...], "next_cursor": "..." }

PATCH /v1/zones/{zoneId}/agents/{id}/suspend

Section titled “PATCH /v1/zones/{zoneId}/agents/{id}/suspend”

Suspend the agent and all its descendants. Active operations may continue to completion; new operations are blocked.

Response 200: { "suspended": true }

PATCH /v1/zones/{zoneId}/agents/{id}/resume

Section titled “PATCH /v1/zones/{zoneId}/agents/{id}/resume”

Resume a suspended agent and its descendants.

Response 200: { "resumed": true }

Terminate the agent and its entire subtree (all children, grandchildren, and so on). Cascades revocation to all delegation edges whose source or target is in the subtree. Enqueues session revocation events for each terminated session.

Query parameters: reason (string, 1–256 characters; default "requested")

Response 204.

Errors:

CodeStatusCondition
agent_not_found404Agent session does not exist
application_ownership_required403Caller does not own the agent’s application

Delegation graph — /v1/zones/{zoneId}/delegations

Section titled “Delegation graph — /v1/zones/{zoneId}/delegations”

Create a delegation edge from a source agent session to a target agent session. Equivalent to POST /v1/exchange.

Request body:

FieldTypeRequired
source_session_idstringYes
target_session_idstringYes
issuer_application_idstringYes
receiver_application_idstringYes
resource_idstring | nullNo
scopesstring[]No
expires_atISO 8601 datetimeCond. — required unless ttl_seconds set
ttl_secondsintegerCond. — 1–86400; alternative to expires_at
constraints_jsonobjectNo

constraints_json fields:

FieldTypeDescription
ttl_secondsintegerMaximum lifetime of tokens issued under this edge
max_hopsintegerMaximum delegation chain depth; default 1
budgetintegerMaximum scope count per token exchange

Validation: source_session_id ≠ target_session_id. The issuer application must own the source session; the receiver application must own the target session. If resource_id is set, the requested scopes must be a subset of the resource’s declared scopes. The edge must not create a cycle in the delegation graph (checked via recursive CTE before insertion).

Response 201:

{
"id": "edge-id",
"zone_id": "zone1",
"source_session_id": "agent1",
"target_session_id": "agent2",
"issuer_application_id": "app1",
"receiver_application_id": "app2",
"resource_id": "resource-uuid",
"scopes": ["read"],
"constraints_json": { "max_hops": 1 },
"status": "active",
"expires_at": "2026-05-11T21:00:00Z",
"edge_version": 0,
"revoked_at": null,
"created_at": "2026-05-11T20:00:00Z"
}

Errors:

CodeStatusCondition
self_delegation_denied400Source and target are the same session
delegation_expiry_required400Neither expires_at nor ttl_seconds provided
delegation_expired400expires_at is in the past
invalid_max_hops400max_hops is less than 1
issuer_ownership_required403Caller does not own the issuer application
resource_ownership_required403Resource not accessible to issuer application
delegation_scopes_exceed_resource403Requested scopes exceed resource’s scopes
delegation_endpoint_not_found404Source or target agent session does not exist
resource_not_found404resource_id does not exist
delegation_cycle_denied409Edge would create a cycle in the delegation graph
delegation_application_mismatch409Issuer or receiver application does not match the session’s owning application

GET /v1/zones/{zoneId}/delegations/inbound/{sessionId}

Section titled “GET /v1/zones/{zoneId}/delegations/inbound/{sessionId}”

List delegation edges where target_session_id equals sessionId.

Query parameters: limit (1–500, default 100), cursor (edge ID)

Response 200: { "items": [DelegationEdge, ...], "next_cursor": "..." }

GET /v1/zones/{zoneId}/delegations/outbound/{sessionId}

Section titled “GET /v1/zones/{zoneId}/delegations/outbound/{sessionId}”

List delegation edges where source_session_id equals sessionId.

Response 200: Same structure as inbound.

GET /v1/zones/{zoneId}/delegations/{id}/traverse

Section titled “GET /v1/zones/{zoneId}/delegations/{id}/traverse”

Traverse the delegation graph reachable from the given edge, up to 10 hops.

Response 200: Array of reachable edges with depth:

[
{
"id": "edge-id",
"source_session_id": "agent1",
"target_session_id": "agent2",
"depth": 1
}
]

PATCH /v1/zones/{zoneId}/delegations/{id}/revoke

Section titled “PATCH /v1/zones/{zoneId}/delegations/{id}/revoke”

Cascade-revoke the edge and all downstream edges reachable from it. Terminates the target agent subtrees of all revoked edges. Increments the delegation graph epoch.

Response 200:

{
"revoked_edges": 3,
"affected_sessions": 5,
"terminated_agents": 2
}

Errors:

CodeStatusCondition
delegation_not_found404Edge does not exist
issuer_ownership_required403Caller does not own the issuer application

These flat-path routes are aliases for the zone-scoped routes above. They accept all the same fields, with zone_id moved into the request body.

Alias for POST /v1/zones/{zoneId}/agents. Include zone_id in the request body.

Additional required field: zone_id (string)

Response: Identical to the agent spawn response.

Alias for DELETE /v1/zones/{zoneId}/agents/{id}.

Request body:

FieldTypeRequired
zone_idstringYes
session_idstringYes
reasonstringNo

Response 204.

Alias for POST /v1/zones/{zoneId}/delegations. Include zone_id in the request body.

Additional required field: zone_id (string)

Response: Identical to the delegation edge creation response.


Verify a Caracal mandate without requiring authentication. Useful for debugging and for services that want to validate a token before forwarding it.

Request body:

FieldTypeDescription
tokenstringJWT to verify directly
authorizationstring"Bearer <token>" — alternative to token
zone_idstringIf set, verify the token’s zone_id claim matches
required_scopestringIf set, verify this scope is present in the scope claim
require_agentbooleanIf true, verify agent_session_id claim is present
require_delegationbooleanIf true, verify delegation_edge_id claim is present

Response 200 (valid):

{
"valid": true,
"claims": { "sub": "...", "zone_id": "...", "scope": "..." }
}

Response 401 (invalid):

{
"valid": false,
"error": "token_expired",
"message": "token has expired"
}

Response 429 (rate limited):

{
"valid": false,
"error": "rate_limited"
}

MethodPathDescription
GET/healthLiveness; always 200 {"ok": true}
GET/readyReadiness; 200 when database and Redis are healthy
GET/metricsJSON counters for agent lifecycle and delegation operations