Authorize API
POST /v1/authorize
Asks the PDP: can this caller call this target, with this data class, at this hop? Presents the current hop token as the bearer.
Request body (AuthorizeRequest):
{
"session_id": "...",
"caller": "user:alice",
"target": "mcp://kb.internal/search",
"data_class": "kb.public",
"taint": "trusted_internal",
"enforced": true
}
Note what's absent: no hop_count field. Hop depth is always derived from the presented
token's verified act chain, never accepted as a caller-supplied number — see
Why authority narrows at every hop.
taint is recorded on the decision but does not (yet) affect the outcome — see
The taint and provenance model for the
design that will enforce it. enforced is reported by the caller (the enforcement point):
whether it will act on a deny or is running in observe mode; it never affects the decision
itself.
Response — 200 (AuthorizeDecision), always, whether allow or deny:
{
"outcome": "allow",
"hop": 1,
"policy_version": 1,
"session_id": "...",
"action_id": "...",
"acting_agent": "user:alice",
"recorded": true
}
On deny, reason is one of:
| Reason | Recoverable | Category |
|---|---|---|
SCOPE_NOT_CONSENTED | Yes | Policy |
OUT_OF_SCOPE | No | Policy |
HOP_BUDGET_EXCEEDED | No | Policy |
EDGE_NOT_IN_DAG | No | Policy |
PDP_UNAVAILABLE | No | Infrastructure |
TOKEN_SERVICE_UNAVAILABLE | No | Infrastructure |
AUDIT_UNAVAILABLE | No | Infrastructure (returned, never itself stored) |
recoverable is a property of the reason, not a per-call judgment — the PDP, the sidecar, and
the SDK never disagree about it. Only SCOPE_NOT_CONSENTED is a step-up-consent candidate
(designed, Phase 5).