Skip to main content
Approvals are real end to end on this surface, because the approvals table is already keyed on (company, tenant_user) — which is exactly the address these routes use.

List pending approvals

next_cursor is always null here. This list takes a limit, not a cursor. If you have more pending approvals than your limit, raise the limit (up to 200) — paging past that is not available on this address yet, and the response says so rather than minting a cursor the next call could not honour.

Decide

Body

One write path, two addresses

This route calls the same service the existing /v1/users/{user_id}/approvals/{id}/approve and /deny routes call. It is a second address for one write path, not a second write path — so these two rules hold identically here:
  • A human must resolve. An agent-authored resolution is rejected. Approving with an API key that authenticates an agent does not work.
  • No self-approval. The actor that requested an approval cannot resolve it.
Approving replays the frozen payload captured when the approval was created. It does not re-evaluate the request against current policy.
One rule that does NOT hold identically, measured. The legacy approval addresses re-check that the calling agent profile is still active before a mutating request; this address does not. A credential that has been revoked can still reach POST …/decide here after it has stopped working at /v1/users/{user_id}/approvals/….This is the same class of gap that GET …/plan reports as revocation_on_reads.enforced: false, except that here it extends to a write. If you revoke an operator for a security reason, rotate the key — do not rely on revocation alone to close this surface.

Why grant is null

A grant in the durable-runtime design is argument-bound, single-use and pinned to a policy snapshot. Nothing here mints one: approving replays a stored payload. The field is null with the reason beside it rather than absent, so a client written against the eventual shape does not have to guess whether the key is missing or the value is. decision_id and trace_id are null for the same reason — there is no decision ledger and no trace column in this build.