# Quattrino quickstart — first meaningful authorization in under 15 minutes

**What Quattrino is.** Quattrino governs what an AI agent actually has authority to do. Your agent
(or your LLM) decides *what it wants to do*; Quattrino decides *whether it is authorized* — across
money, commitments, data and delegation — any provider executes; Quattrino records *why* and
*what happened*. It works even when Quattrino never touches the payment.

**Lifecycle:** INTENT → AUTHORITY → ACTION → PROOF   **Your loop:** AUTHORIZE → ACT → EXPLAIN

**Environment:** TEST/SANDBOX only. Nothing here moves real money. No pricing is active for
authorizations (usage is metered in neutral Authorization Units).

---

## 0. What you need (2 min)

| | |
|---|---|
| An account | sign up at `https://quattrino.io/signup` (open, no invite; MFA optional) |
| Python 3.9+ **or** Node 18+ | the SDKs are single files with **no dependencies** |
| The SDK | `curl -o quattrino.py https://quattrino.io/api/v1/public/sdk/python` or `curl -o quattrino.ts https://quattrino.io/api/v1/public/sdk/typescript` |
| The examples | `https://quattrino.io/api/v1/public/examples` (index) — e.g. `curl -O https://quattrino.io/api/v1/public/examples/bootstrap_sandbox.py` |

## 1. Give an agent standing authority (3 min)

An agent needs three things before anything can be authorized: a **standing policy** (its
standing authority, e.g. "$50 per action"), a **sandbox wallet** (test money) and a **scoped API
key**. Do it either way:

**a) One command (API):**

```bash
export QUATTRINO_BASE=https://quattrino.io QUATTRINO_EMAIL=you@company.com QUATTRINO_PASSWORD='...'
python bootstrap_sandbox.py --standing-cents 5000        # add --signup if you have no account yet
# -> .quattrino_sandbox.json  {agent_id, api_key, policy_id, wallet_id}
```

**b) In the web app:** *Getting started → Connect agent → Connect money (sandbox test money) →
Set limits → Issue key.* Copy the key (shown once).

> Why a "wallet" if I never pay through Quattrino? In the sandbox the wallet is simply the
> organization's granted spending ceiling — the root of the authority chain. Without it there is
> no authority to narrow, so `authorize` fails closed with `NEEDS_INFORMATION / wallet_missing`.

## 2. First authorization (1 min)

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

# standing authority $50; the human said "under $20" -> the task NARROWS it to $20
r = q.authorize(amount_minor=1999, merchant="Amazon", purpose="USB-C cable", commitment="ONE_TIME",
                task={"max_amount_minor": 2000})
r.decision                 # AUTHORIZED
r.effective_maximum_minor  # 2000   (binding layer: task)
```

The same call with `amount_minor=2001` → `DENIED` (`exceeds_effective_authority`).

## 3. Handle the four answers (always the same way)

| decision | meaning | what your code does |
|---|---|---|
| `AUTHORIZED` | within effective authority | go (in `binding` mode you get a single-use authorization) |
| `DENIED` | outside authority / policy / task; `denial_reasons[]` says why | stop; show the reason; do **not** retry to force it |
| `APPROVAL_REQUIRED` | a human must approve (`approval.approval_id`) | wait/poll; the owner decides in *Approvals* |
| `NEEDS_INFORMATION` | something is missing or ambiguous (`needs_information[]`, `missing_authority[]`, `question`) | declare the missing term, or relay the **one** question to the human |

Ambiguity never expands authority. Unknown commitment is never treated as one-time.

## 4. Act, then prove it (2 min)

```python
auth = q.authorize_binding(amount_minor=1999, merchant="Amazon", purpose="USB-C cable",
                           commitment="ONE_TIME", task={"max_amount_minor": 2000}, execution="external")
# ... you execute with YOUR provider ...
out = q.record_outcome(auth.authorization_id, success=True, provider="amazon", provider_reference="114-2233",
                       actual_amount_minor=1999, commitment="ONE_TIME")
out["compliance"]["status"]                       # COMPLIANT  (or NONCOMPLIANT + findings)
q.explain(auth.authorization_id)["plain_language"] # who / on whose behalf / why / what narrowed it / what happened
```

A binding authorization expires, is revocable and is consumed exactly once. If your executor
deviates (amount, counterparty, commitment class, data disclosed, delegation) the outcome is
recorded as **NONCOMPLIANT** with findings — the authorization itself is never rewritten.

## 5. Plain-language intent (optional, 2 min)

```python
c = q.compile_intent("Find someone who can fix this machine.")
c["authorized_now"]        # research_and_compare, contact_counterparties
c["not_yet_authorized"]    # hire_supplier_or_agent, start_subscription_or_account, ...
env = c["envelope"]["envelope_id"]
q.authorize(envelope_id=env, action_class="NON_CONSEQUENTIAL_RESEARCH", purpose="research").decision   # AUTHORIZED
r = q.authorize(envelope_id=env, action_type="hire_agent", merchant="Repair Co", amount_minor=32700,
                purpose="repair", commitment="CONTRACT")
r.decision, r.question["question"], r.missing_authority   # NEEDS_INFORMATION + the one precise question
```

Quattrino may infer *meaning*; it never infers *consequential permission*. Agents may only
narrow or propose; owners widen (bounded by the standing policy) in the web app or via
`POST /api/v1/org/envelopes/{id}/amend`.

## TypeScript

Same contract, same names in camelCase — see `quickstart.ts` (`npx tsx quickstart.ts`).

## MCP (Claude Desktop, Cursor, any MCP client)

```json
{ "mcpServers": { "quattrino": { "url": "https://quattrino.io/api/mcp/", "transport": "streamable-http",
                                   "headers": { "Authorization": "Bearer qtrn_sk_..." } } } }
```

Tools: `authorize`, `authorization_status`, `authorization_outcome`, `authorization_explain`,
`compile_intent`, `authority_discover`, `authority_gap`, `envelope_propose_change`,
`disclosure_release` (+ 28 more). Full config with a suggested tool order: `mcp_config.json`.

## REST (no SDK)

```bash
curl -X POST https://quattrino.io/api/v1/authorize -H "X-API-Key: qtrn_sk_..." -H "Content-Type: application/json" \
  -d '{"action_type":"purchase","amount_minor":1999,"merchant":"Amazon","purpose":"USB-C cable",
       "commitment":"ONE_TIME","task":{"max_amount_minor":2000}}'
# binding: add "mode":"binding","execution":"external" and an Idempotency-Key header
curl -X POST https://quattrino.io/api/v1/authorizations/auth_.../outcome -H "X-API-Key: ..." \
  -d '{"success":true,"provider":"amazon","provider_reference":"114-2233","actual_amount_minor":1999,"commitment":"ONE_TIME"}'
curl https://quattrino.io/api/v1/authorizations/auth_.../explain -H "X-API-Key: ..."
```

Contract: `GET https://quattrino.io/api/v1/authorize/schema`. Playground (no code):
`https://quattrino.io/developers/playground`.

## Next: the seven differentiation journeys

```bash
QUATTRINO_PASSWORD='...' python differentiation_journeys.py        # A-G, expected vs actual
```

## Common errors

| error | meaning | fix |
|---|---|---|
| `401 invalid_api_key` | wrong / revoked agent key | issue a new key on the agent page |
| `NEEDS_INFORMATION wallet_missing` | the agent has no granted authority yet | step 1 |
| `422 idempotency_key_required` | `mode=binding` without `Idempotency-Key` | the SDK adds one; with curl add the header |
| `NEEDS_INFORMATION commitment_required` | consequential action under an envelope without a commitment class | declare `commitment` (`ONE_TIME`, `SUBSCRIPTION`, ...) |
| `409 outcome_already_recorded` | outcome recorded twice | single consumption is the point |
| `429 rate_limited` | login/beacon rate limits | wait the indicated seconds |
