POST /v1/agents/children
Mint a short-lived, scope-attenuated child identity for a subagent. See Child agents for the concept and its caveats; this page is the wire format.
Most integrations don’t call this directly. The Python SDK’s
issue_childand the MCPissue_child_agenttool wrap it. Direct invocation is for the Node SDK today (see Node & TypeScript) or a custom integration.
Who can call this
Only an active, non-child agent. A child agent (one with its own
parent_agent_id set) cannot mint a child of its own: v1 caps
delegation depth at 1.
Request
POST /v1/agents/children HTTP/1.1
Host: api.agentvalet.ai
Authorization: Bearer <parent-agent-rs256-jwt>
Content-Type: application/json{
"name": "research-worker",
"ttl_seconds": 600,
"grants": [
{ "platform": "github", "scopes": ["github:repo.read"], "connection_id": null }
]
}| Field | Type | Required | Notes |
|---|---|---|---|
name | string | no | 1–200 chars. Stored as "<name> (child)". Defaults to child of <parent_agent_id> |
ttl_seconds | integer | no | 60–3600. Defaults to 900. Also caps the grant expiry, see below |
grants | array | yes | 1–25 entries. Each is { platform, scopes, connection_id? } |
grants[].platform | string | yes | Platform id, e.g. github |
grants[].scopes | string array | yes | The scopes you’re asking for. Attenuated against the parent’s current grants, see below |
grants[].connection_id | string | null | no | Pin a specific connection when the parent holds more than one per-connection grant for that platform |
Attenuation, at mint time
For each grants[] entry, the proxy reads the parent’s own current
permissions row(s) for that platform (the same connection-selection
logic checkPermission uses: per-connection rows win outright over a
platform-wide row when any exist). The child’s actual scopes are the
intersection of what you asked for with what that row currently
grants. require_approval and connection_id are copied from the
parent’s row, not from the request.
The child’s grant expires_at is min(parent grant's expires_at, mint time + ttl_seconds): a child grant can never outlive the parent
grant it was sliced from.
Success: 201 Created
{
"child_agent_id": "agt_9f3a1c2b7e401928d7ee",
"bearer_token": "eyJhbGciOi...",
"expires_at": "2026-08-21T13:15:00.000Z",
"granted": [
{
"platform": "github",
"scopes": ["github:repo.read"],
"require_approval": false
}
]
}| Field | Notes |
|---|---|
child_agent_id | The new agent’s id. Every call it makes is attributed to this id in the audit log |
bearer_token | An AS-minted access token, valid for ttl_seconds. This is the child’s entire credential; hand it to the subagent and nothing else |
expires_at | When the child (and its bearer) stops being valid |
granted | The attenuated grants actually written for the child, per platform |
Error codes
| Status | error | Cause |
|---|---|---|
| 400 | invalid_name | name present but not a 1–200 char string |
| 400 | grants[] is required | grants missing, empty, or not an array |
| 400 | invalid_grants | More than 25 entries, or an entry with a malformed platform/scopes |
| 400 | ttl_seconds must be 60..3600 | ttl_seconds outside the allowed range |
| 400 | no_scopes_after_attenuation | The parent has no active, unexpired grant for that platform/connection, or none of the requested scopes survive the intersection. platform is included in the body |
| 401 | Missing Bearer token | Invalid JWT | Unknown agent | Same as POST /v1/actions |
| 403 | agent_not_active | The parent agent isn’t active (suspended, revoked, expired) |
| 403 | delegation_depth_exceeded | The caller is itself a child agent. Depth is capped at 1 in v1 |
| 409 | child_limit | The parent already has 25 live children |
| 500 | issue_failed | The child row or its grants failed to write. No child is left half-created; a partially written row is revoked rather than returned |
| 503 | as_signing_unavailable | The proxy couldn’t mint a bearer (signing key unconfigured). Fails closed: no child row is left active without a usable bearer |
Audit
The mint writes one audit_log row against the parent agent with
action: "agent.child_issued" and metadata carrying the new
child_agent_id, the granted slice, and the expiry, so the lineage
survives even after the child row is eventually garbage-collected.
Every call the child subsequently makes writes its own audit rows under
the child’s agent_id, not the parent’s.
See also
- Child agents: the concept, attenuation semantics, and the honest caveats on stdio Task-tool subagents and approval replay
- LangGraph: a full supervisor/worker example
POST /v1/actions: what the child does with its bearer