A denial is not an error
A denied decision answers403 and carries the whole decision, not just a code:
403 Forbidden
headroom is measured before the reservation, so on an allowed decision it still counts the
amount you were just authorized to spend. policy_snapshot is the policy as it read at that
instant, which is what lets a decision be re-read months later against a rule that has since
changed.
Rules that can refuse
decision.rule names what decided. Rules created through the typed policy columns report under
the column’s name, not the underlying kind. See the mapping
table.
A rule written with a custom
rule_name reports under that name instead.
An approval_threshold never appears as a denial. It does not refuse; it answers 202 and holds
the action. See holding spend for a person.
What detail carries
decision.detail is keyed by the rule that decided, not by a shared schema. Read it by rule:
The amount ceilings report
limit_cents, not max_cents, and the approval threshold reports
threshold_cents, not above_cents. Those are the names you wrote the rule with, not the names
it answers with.
An allowed decision has rule: null and detail: {}. Nothing decided it; nothing refused.
Error envelope
Everything that is not a decision returns:code where it is present. message is for humans and may change.
code is null for most validation failures; only the codes below ever set it. Branch on the
HTTP status first and treat code as a refinement, not as the primary signal.
Codes
runtime_key_required
401 - No agent key was presented. Send Authorization: Bearer krt_....
invalid_runtime_key
401 - The key was not recognised, or its agent is suspended or revoked.
identity_token_required
401 - No dashboard token was presented on a /control/v1/workspaces/* or /v1/account call. Agent
keys do not reach these routes; send a WorkOS access token.
invalid_identity_token
401 - The dashboard token was rejected: malformed, expired, or signed by something this
environment does not trust.
budget_ceiling
422 - The budget asked for is larger than the agent’s max_budget_cents, which is set from
the dashboard and cannot be raised with an agent key. The body carries ceiling_cents and
requested_cents so the caller can open a smaller one rather than guess.
parent_budget_exhausted
422 - A child budget was asked for that is larger than the parent has left. Children reserve
from their parent when they are opened, so several children cannot sum past it. ceiling_cents
is what the parent actually has left.
insufficient_role
403 - Your membership does not carry the capability this call requires. Approving needs
approve; writing policy and managing agents need manage_policy; members and billing need
manage_members.
idempotency_conflict
409 - A request with this Idempotency-Key is still in flight, so there is no stored response
to replay yet. Wait and retry with the same key; do not mint a new one.
idempotency_key_required
422 - POST /control/v1/agent/actions requires an Idempotency-Key header. Note the status: this
is a 422, not a 400.
authorization_required
400 - POST /control/v1/cosign/verify was called with no token, in neither the authorization body
field nor an Authorization header.
rate_limited
429 - Back off per the Retry-After header. Agent endpoints allow 1200 requests per minute
per key; workspace endpoints allow 300.
billing_unavailable
503 - Billing could not be reached, so a call that had to check entitlement could not be
answered. Authorization is unaffected; retry.
identity_unavailable
503 - The identity provider could not be reached during sign-in.
identity_not_configured
503 - This deployment has no identity provider configured.
mfa_required
401 - Sign-in needs a second factor. The response carries pending_authentication_token and
authentication_challenge_id alongside error; complete at POST /v1/auth/verify_mfa.
email_verification_required
401 - The account exists but its email is unverified. The response carries
pending_authentication_token; complete at POST /v1/auth/verify_email.
invalid_signature
401 - An inbound billing webhook failed HMAC verification.
Statuses without a code
Next steps
How a decision is reached
Why the first denial wins, and what fails closed.
Writing a policy
The rules behind every name in the table above.