/orchestrate/*. The base URL is https://api.visiqlabs.com.
These endpoints govern agent-to-agent handoffs: a parent agent creates a scoped grant, the named child accepts it and receives a signed token, and the child evaluates each delegated action against that grant. The decision vocabulary here is permit and deny — a delegated action is either within the grant’s intersected authority or it is not.
Authentication
All endpoints require a Bearer credential:Authorization: Bearer <key>. Requests without a valid credential receive 401 Unauthorized.
The whole /orchestrate/* surface is management-audience. A harness key (vq_prod_... / vq_test_... minted under Settings → Harness Keys, or an allow_... agent key) is confined to the SDK operational routes and receives 403 {"error": "harness_key_not_permitted"} here — delegation is driven by a management key or a dashboard session. Management keys are launching soon: they are visible in the dashboard under Settings → API Keys, but creating one is not yet enabled. Until then, drive these endpoints from the dashboard, which authenticates with your session.
Scoped keys are additionally checked against their granted scopes. Every endpoint on this page requires allow:write (the read-only GET /orchestrate/grants/:id also accepts allow:read); full_access satisfies everything. A key without the required scope receives 403 {"error": "insufficient_scope"} listing the required and granted scopes.
Rate limiting
Every API-key request passes a per-key sliding-window rate limit (default 600 requests per 60 seconds). Responses carryX-RateLimit-Limit and X-RateLimit-Remaining headers; exceeding the window returns 429 with a Retry-After header and body {"error": "rate_limited", "detail": "API key rate limit exceeded.", "retryAfter": <seconds>}.
Grant lifecycle
POST /orchestrate/grants
Create a delegation from a parent agent to a child agent. Both agents must be registered for your organization. The platform runs a policy check on the delegation itself, intersects the requested scopes against the parent’s authority, writes a synchronous record proof, and returns apending grant awaiting acceptance.
Scope: allow:write
Request body:
When
parent_grant_token is supplied, the delegating parent_agent_id must equal the child_agent_id of that token’s grant — you can only sub-delegate authority you were actually granted. The new grant’s depth is the parent grant’s depth plus one, and its max_depth is inherited from the parent grant.
Decision & failure semantics:
- If no rule permits
orchestrate.delegatefor the parent and the parent is in enforce mode, the delegation is denied fail-closed:403 {"error": "Delegation denied by policy", "reason": "..."}. - If the synchronous record proof cannot be written, the just-created grant is revoked and the call returns
500 {"error": "Failed to write ORCHESTRATE proof record"}.
200 OK, 400 Bad Request (invalid body), 401 Unauthorized (missing credential, or invalid parent_grant_token), 403 Forbidden (insufficient scope, delegation denied by policy, or the parent grant does not belong to the delegating agent), 404 Not Found (parent or child agent not registered), 422 Unprocessable Entity (delegation depth ceiling reached), 500 Internal Server Error
Example:
GET /orchestrate/grants/:id
Fetch a grant’s current status — the parent polls this to learn whether the child has accepted, and to read the effective scopes and lifecycle timestamps. Scope:allow:write or allow:read
Path parameter: :id — the grant UUID (from POST /orchestrate/grants)
Response (200):
status is one of pending, active, revoked, or expired. The grant token itself is never returned here — it is issued once, at acceptance.
Status codes: 200 OK, 401 Unauthorized, 403 Forbidden (insufficient scope), 404 Not Found, 500 Internal Server Error
POST /orchestrate/grants/:id/accept
The child agent accepts a pending grant and receives its signed grant token. Bilateral identity: the child declares itself in theX-Agent-ID header, which must match the grant’s child_agent_id.
Scope: allow:write
Request headers:
Path parameter:
:id — the grant UUID
This endpoint takes no request body. Acceptance is guarded against a concurrent double-accept: only the caller that flips the grant from pending to active wins; a lost race returns 409.
Response (200):
Status codes:
200 OK, 400 Bad Request (missing X-Agent-ID), 401 Unauthorized, 403 Forbidden (insufficient scope, X-Agent-ID does not match the grant’s child, or the claimed agent is not registered), 404 Not Found (unknown grant), 409 Conflict (grant not pending, accept window expired, or already accepted), 500 Internal Server Error
POST /orchestrate/grants/:id/revoke
Revoke a grant. Revocation cascades eagerly to every descendant grant in one atomic step, and is checked at enforcement time, so there is no revocation lag. Scope:allow:write
Path parameter: :id — the grant UUID
Request body:
Response (200):
revoked_count is the number of grants invalidated — the target plus every descendant in its subtree.
Status codes: 200 OK, 400 Bad Request, 401 Unauthorized, 403 Forbidden (insufficient scope), 404 Not Found, 422 Unprocessable Entity (grant already revoked), 500 Internal Server Error
Per-action enforcement
POST /orchestrate/evaluate
The child agent’s per-action check. For each action it takes under a grant, the child presents its grant token; the platform verifies the token, walks the ancestor chain, checks the action against the grant’s intersected tool and context scopes, records a handoff event, and returns the decision. Scope:allow:write
Request body:
Response (200):
Decision & failure semantics:
- A token whose signature is invalid, whose vendor does not match the caller, that references an unknown grant, or that no longer matches the grant’s stored token returns
401 {"error": "Invalid grant token"}. - An expired token or a grant that is not
activereturns403(Grant expired/Grant is <status>). - A revoked or invalid ancestor grant yields
decision: "deny"with the reason naming the broken link — a200response with a deny, not an error. - An action outside the grant’s tool scope, or a resource type outside its context scope, yields
decision: "deny". - If the handoff event cannot be recorded, the call returns
500 {"error": "Failed to record handoff event"}— never a silent permit.
200 OK, 400 Bad Request, 401 Unauthorized (invalid or mismatched grant token), 403 Forbidden (insufficient scope, expired token, or inactive grant), 500 Internal Server Error
Errors & conventions
Every endpoint on this page follows the platform-wide REST conventions — the validation-error body shape, the/v1/ vs unversioned split, and the API stability policy. See REST API conventions.