> For the complete documentation index, see [llms.txt](https://docs.robotmoney.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.robotmoney.com/govern/authorizations-api.md).

# Authorization Decisions

The Authorizations API gives an agent a decision before money moves. It returns `approved`, `needs_review`, or `blocked`. Creating an authorization is always decision-only. An approved `execution: internal` authorization can use the separate execute route to create a linked PaymentIntent and frozen fee quote.

## Create

```http
POST /v1/authorizations
Authorization: Bearer <exchanged agent token>
Idempotency-Key: auth-demo-001
Content-Type: application/json
```

```json
{
  "action_type": "ach_outbound",
  "execution": "external",
  "destination_counterparty_id": "cp_...",
  "amount": "75.00",
  "currency": "USD",
  "reference": "inv-first-001"
}
```

`invoice_id` links a RobotMoney Ledger invoice and requires attached invoice evidence; for your own invoice number use `reference`.

The caller needs an exchanged agent token with `payment_intent:propose`. `execution: external` is for customer-owned rails. RobotMoney source account, balance, reservation, and ledger snapshot checks return `not_evaluated`. Sanctions, agent state, policy, duplicate, counterparty trust, evidence, approval, amount, and window checks still run.

Use `execution: internal` with a RobotMoney USDC source account when the agent will explicitly request a linked sandbox PaymentIntent after approval. Creating the authorization still does not create the intent or dispatch a rail.

`Idempotency-Key` is required. Reusing the same key with the same body returns the same decision.

Response:

```json
{
  "authorization_id": "auth_...",
  "decision": "approved",
  "reason_code": "approved",
  "reason": "Approved by policy.",
  "execution": "external",
  "policy_id": "pol_...",
  "policy_version": 1,
  "checks": [
    {
      "name": "no_duplicate_payment",
      "status": "pass",
      "matched_rule": null
    }
  ],
  "checks_evaluated": 20,
  "checks_not_evaluated": 3
}
```

`simulate: true` is a dry run. It writes no authorization row, policy decision, audit event, webhook event, or billable usage record.

## Execute An Internal Authorization

The execute route is available only when headless sandbox execution is enabled. The originating agent needs `payment_intent:propose`. The authorization must be approved, active, unexpired and use `execution: internal`.

```http
POST /v1/authorizations/{id}/execute
Authorization: Bearer <exchanged agent token>
Idempotency-Key: execute-demo-001
```

The request has no body and accepts no amount, destination, fee, actor or policy overrides. Reusing the same key returns the same PaymentIntent and frozen quote.

```ts
const execution = await brain.authorizations.execute(authorizationId, {
  idempotencyKey: "execute-demo-001",
});
```

HTTP 202 response:

```json
{
  "authorization_id": "auth_...",
  "payment_intent_id": "pi_...",
  "execution_status": "pending_approval",
  "execution_fee_status": "pending",
  "fee_quote": {
    "recipient_amount": "75",
    "fee_amount": "0.225",
    "total_debit": "75.225",
    "asset": "USDC",
    "chain": "eip155:84532",
    "fee_policy_id": "money_moved_v1",
    "fee_policy_revision": 1,
    "quote_expires_at": "2026-10-16T07:00:00.000Z",
    "quote_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
  }
}
```

`recipient_amount` is what the counterparty receives. The additive fee comes from the same agent wallet, and the gate, reservation and human approval cover `total_debit`. The quote hash binds the tenant, authorization, wallet, destination, amounts, fee policy and expiry. Accepted means the linked intent and quote exist. It does not mean a transfer was dispatched or settled.

Authorization detail and list responses expose the same nullable execution fields. For headless rows, `submitted` means a provider response was observed or a transaction hash was stored. `settled` means the principal transfer was independently verified in a finalized block. `execution_fee_status` separately reports `pending`, `submitted`, `settled`, `failed`, or `waived`. A fee failure does not move a settled principal back to a nonterminal state. Headless rows no longer return `executed`; non-headless linked rows keep that value unchanged.

The PaymentIntent detail includes the same frozen quote as `execution_quote`. Legacy PaymentIntents return `execution_quote: null`.

## Acknowledge The Quote And Approve

Every sandbox USDC payment requires a recorded human approval. The approver must send the exact quote hash shown above:

```http
POST /v1/payment-intents/{payment_intent_id}/approve
Authorization: Bearer <member token>
Content-Type: application/json

{
  "quote_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
```

```ts
await brain.payments.approve(execution.payment_intent_id, {
  quote_hash: execution.fee_quote.quote_hash,
});
```

Omitting or changing the hash returns HTTP 409 with `quote_acknowledgement_required`. Sending `quote_hash` for a legacy PaymentIntent returns HTTP 400. Legacy approval without the field is unchanged.

An authorization that began as `needs_review` requires two human steps: approve the authorization decision, then approve the quote-bound PaymentIntent. An authorization that was already `approved` requires only the PaymentIntent approval. Agents never receive approval or dispatch authority.

## Get

```http
GET /v1/authorizations/{id}
Authorization: Bearer <token>
```

The HTTP detail route requires `execution:read`. MCP `authorizations.get` is scoped to agent principals with `payment_intent:propose` and is limited to the calling agent.

## List

```http
GET /v1/authorizations?decision=approved&agent_id=agent_...
Authorization: Bearer <token>
```

The list route is tenant scoped and cursor paginated. It accepts optional filters for `decision`, `agent_id`, `since`, and `until`.

## Review And Void

```http
POST /v1/authorizations/{id}/approve
POST /v1/authorizations/{id}/reject
POST /v1/authorizations/{id}/void
```

Approve and reject apply only to `needs_review` records. They require a user principal or tenant service credential with `payment_intent:approve`. Agent principals are refused with `actor_unresolved`.

Approval uses the same authority checks as PaymentIntent approval: active member, admin or approver role, authorized approval domain, per-item limit, and actor-payee protection. Approval keeps the original expiry, applies duplicate protection, and counts an approved external authorization into amount and micropayment windows in the same transaction.

Void is for an approved external authorization that the customer did not pay. Only a user principal or tenant service credential may void. Voiding releases the counted amount from policy windows, including after the authorization has expired.

## Review Expiry

Approved authorizations include `expires_at`. The default is 24 hours. Callers may set `expires_at` up to 7 days in the future. Expiry marks the authorization `expired`. Expiry does not release counted amount from policy windows.

Needs-review authorizations can expire before review. A review attempted after expiry returns the review-expired reason and leaves the authorization terminal.

## Reason Codes

Common reason codes:

| Code                           | Meaning                                                               |
| ------------------------------ | --------------------------------------------------------------------- |
| `approved`                     | The request passed policy and gate checks.                            |
| `approval_required`            | Human review is required by policy.                                   |
| `over_per_authorization_limit` | The amount is above the managed per-authorization limit.              |
| `over_daily_limit`             | The projected per-agent daily window is above the cap.                |
| `counterparty_unverified`      | The registered counterparty is not eligible for auto approval.        |
| `counterparty_not_registered`  | The counterparty is unknown and the request is blocked.               |
| `no_duplicate_payment`         | A prior approved authorization or PaymentIntent blocks the duplicate. |
| `review_expired`               | The needs-review authorization expired before review.                 |

Blocked and voided authorizations never block a later duplicate. Approved authorizations block duplicates until voided, even after expiry.

## Webhooks

Recorded, non-simulated decisions emit `authorization.approved`, `authorization.needs_review`, or `authorization.blocked`. Human review emits `authorization.review_approved` or `authorization.review_rejected`. Lifecycle changes emit `authorization.voided` and `authorization.expired`.

Webhook payloads include the decision, reason code, human-readable reason, recommendation, authorization id, invoice id, reference, expiry, policy id, policy version, amount, counterparty, agent, and correlation id. They do not include the raw action payload.
