# Authorize-only: you execute, Quattrino authorizes and proves

Most integrations never let Quattrino touch money. The agent asks, Quattrino answers, **your** code executes
with **your** provider (Stripe, a bank API, a merchant checkout, an internal tool, another agent), and Quattrino
records what happened and whether it matched. This is the primary product path and it needs **no** payment rail,
no Credits and no marketplace.

```
agent -> POST /authorize (binding, execution: external) -> AUTHORIZED + authorization_id + portable_proof
      -> [optional] counterparty verifies the proof without a credential
      -> YOUR executor performs the action
      -> POST /authorizations/{id}/outcome  (consumes the authorization exactly once)
      -> GET  /authorizations/{id}/explain
```

## Endpoints (agent credential unless noted)

| | endpoint | notes |
|---|---|---|
| authorize | `POST /api/v1/authorize` | `mode: binding`, `execution: external`, header `Idempotency-Key` |
| status | `GET /api/v1/authorizations/{id}` | `status`, `expires_at`, `approval`, `portable_proof`, `consumption` |
| list | `GET /api/v1/authorizations?status=AUTHORIZED&limit=50` | |
| proof | `GET /api/v1/authorizations/{id}/proof` | the signed token again |
| verify (public) | `POST /api/v1/authorizations/verify` | **no credential** - for executors / counterparties |
| outcome | `POST /api/v1/authorizations/{id}/outcome` | what your executor did |
| revoke | `POST /api/v1/authorizations/{id}/revoke` | `{"reason": "..."}` -> `status: REVOKED` |
| explain | `GET /api/v1/authorizations/{id}/explain` | machine + plain language |

SDK: `authorize_binding(..., execution="external")`, `get_authorization`, `get_proof`, `verify_proof`,
`record_outcome`, `revoke`, `explain`. MCP: `authorize`, `authorization_status`, `authorization_proof`,
`verify_authorization_proof`, `authorization_outcome`, `authorization_explain`.

## `execution` modes

| value | meaning |
|---|---|
| `external` | **you execute.** `execution_plan.execution_mode: external`, `quattrino_moves_money: false`; a `portable_proof` is issued when `AUTHORIZED`. |
| `auto` (default) | Quattrino picks: if a certified sandbox adapter matches (`service_id` -> Credits marketplace; a registered SPT test merchant) the plan is `quattrino`, otherwise `external`. For an arbitrary merchant name the plan is `external`. |
| `quattrino` | require a Quattrino adapter; fails closed if none applies. Only for sandbox execution through Credits / SPT test merchants - see `/developers#adapters`. |

Authorize-only integrations should pass `execution: "external"` explicitly so the plan never surprises them.

## What a binding external authorization reserves

A binding `AUTHORIZED` authorization with `execution: external` **reserves** `amount_minor` against the same
standing authority the transaction engine uses (wallet ceiling, daily / monthly period, velocity). The outcome
**commits** the actual amount (never more than the hold) and releases the rest; failure, revocation and expiry
**release**. External execution therefore counts exactly once toward budgets. (`aggregate_accounting` in the response.)

## Lifecycle

```
DRY_RUN (mode dry_run - nothing issued)
AUTHORIZED ──(outcome)──> CONSUMED
    │ ──(revoke)───────> REVOKED
    │ ──(expires_at)───> EXPIRED
APPROVAL_PENDING ──(owner approves)──> AUTHORIZED   ──(owner rejects)──> APPROVAL_REJECTED   ──(TTL)──> EXPIRED
DENIED / NEEDS_INFORMATION (terminal; ask again with more authority / information)
```

Default TTL is 15 minutes (`expires_at`); `ttl_seconds` in the request sets it between 30 seconds and 24 hours - the effective expiry is still the **earliest** of the TTL, the task / envelope window and the delegation expiry (time intersects like every other dimension).
After `CONSUMED`, `REVOKED` or `EXPIRED` an outcome is refused: `409 authorization_not_executable`
("Outcome cannot be recorded in status REVOKED"); a second outcome is `409 outcome_already_recorded`.

## Minimal integration (Python)

```python
from quattrino import Quattrino, verify_proof
q = Quattrino(api_key="qtrn_sk_key_...", base_url="https://quattrino.io")

auth = q.authorize_binding(amount_minor=1999, merchant="Amazon", purpose="USB-C cable", commitment="ONE_TIME",
                           task={"max_amount_minor": 2000}, execution="external", proof_audience="amazon.example",
                           idempotency_key="order-1001")
if auth.decision != "AUTHORIZED":
    ...  # handle APPROVAL_REQUIRED / DENIED / NEEDS_INFORMATION (DECISIONS.md)

# hand auth["portable_proof"]["token"] to the executor; it may verify without a credential:
assert verify_proof("https://quattrino.io", auth["portable_proof"]["token"], audience="amazon.example")["valid"]

receipt = my_provider.charge(cents=1999)          # YOUR code, YOUR provider

out = q.record_outcome(auth.authorization_id, success=True, provider="amazon", provider_reference=receipt.id,
                       actual_amount_minor=1999, commitment="ONE_TIME",
                       proof_id=auth["portable_proof"]["proof_id"], proof_verified=True)
out["status"], out["compliance"]["status"]          # CONSUMED, COMPLIANT
```

## When Quattrino executes instead (optional, sandbox)

If `execution_plan.execution_mode == "quattrino"`, consume the authorization through the named adapter
(`q.execute(auth)` / `POST /agent/services/{service_id}/purchase` or `/agent/merchant-payments/purchase` with
`authorization_id`). The outcome is then recorded by Quattrino; do **not** call `/outcome`. Details: `/developers#adapters`.
