# Quattrino Developer Golden Path

**One canonical path. Everything else is optional.**

```
DISCOVER  ->  SIGN UP  ->  CONNECT (agent + standing authority + key)  ->  AUTHORIZE  ->  VERIFY
```

Quattrino authorizes what an AI agent may do before it does it. Your agent decides *what it wants to
do*; Quattrino decides *whether it is authorized* (across money, commitment, data, counterparty, time and
delegation); any provider executes; Quattrino records *why* and *what happened*. It works even when
Quattrino never touches the payment.

Target: a developer with no prior knowledge reaches a **verified `AUTHORIZED` decision** and sees all
**four decisions** in about ten minutes, using only public surfaces.

---

## Step 0 - Discover (1 minute, no credential)

| You are | start here |
|---|---|
| a human developer | `https://quattrino.io/developers` then `https://quattrino.io/docs` |
| an AI agent / LLM | `GET https://quattrino.io/llms.txt` (mirror: `/api/v1/public/llms.txt`) |
| an MCP client | `GET https://quattrino.io/.well-known/mcp.json` -> server at `https://quattrino.io/api/mcp/` |
| a code generator | `GET https://quattrino.io/api/openapi.json` (tag `authorize` is the contract that matters) |
| a machine reading capability truth | `GET https://quattrino.io/api/v1/public/discovery` -> `capability_status` |

What you learn: one endpoint (`POST /api/v1/authorize`), two modes (`dry_run`, `binding`), four decisions
(`AUTHORIZED`, `APPROVAL_REQUIRED`, `DENIED`, `NEEDS_INFORMATION`), an agent credential of the form
`qtrn_sk_key_<id>_<secret>` sent as `X-API-Key: <key>` **or** `Authorization: Bearer <key>` (both work;
the SDKs use Bearer).

## Step 1 - Sign up (1 minute)

Open signup, no invite: `https://quattrino.io/signup` - or via the API (sandbox):

```bash
curl -X POST https://quattrino.io/api/v1/auth/signup -H "Content-Type: application/json" -H "X-Environment: sandbox" \
  -d '{"email":"you@company.com","password":"<strong password>","name":"Your Name","organization_name":"Your Co",
       "accepted_documents":["TERMS_OF_SERVICE","PRIVACY_POLICY"]}'
# -> { "access_token": "...", "user": {...}, "organization": {"organization_id": "org_..."} }
# all five fields are required; a missing one returns 422 validation_error with details.fields[]
```

The owner session token (`access_token`) is for **owner** operations (create agents, policies, approve).
It is **not** an agent credential.

## Step 2 - Connect: give an agent standing authority (2 minutes)

An agent needs three things before anything can be authorized:

| object | meaning | Golden Path value |
|---|---|---|
| **standing policy** (ACTIVE) | the agent's *standing authority*: per-action ceiling + ask-first line | `$50.00` per action, ask the owner first above `$20.00` |
| **sandbox wallet** | the organization's granted spending ceiling (root of the authority chain; simulated money) | `$1,000.00` sandbox |
| **agent API key** | scoped, revocable credential your code or MCP client uses (shown once) | `qtrn_sk_key_...` |

**One command** (Python 3.9+, standard library only):

```bash
curl -O https://quattrino.io/api/v1/public/examples/bootstrap_sandbox.py
export QUATTRINO_BASE=https://quattrino.io QUATTRINO_EMAIL=you@company.com QUATTRINO_PASSWORD='...'
python bootstrap_sandbox.py --signup            # defaults: --standing-cents 5000 --approval-line-cents 2000
# -> .quattrino_sandbox.json  {agent_id, api_key, policy_id, wallet_id, standing_cents, approval_line_cents}
```

**Or in the web app:** *Getting started -> Connect agent -> Connect money (sandbox test money) -> Set limits
(per-action $50, ask first above $20) -> Issue key.*

**Or with the owner API** (what the script does):

```
POST /api/v1/agents                               {"name": "Golden path agent", "environment": "sandbox"}          -> agent.agent_id
POST /api/v1/policies                             {"name": "...", "currency": "USD", "transaction_limit": 5000,
                                                   "transaction_auto_limit": 2000, "daily_limit": 100000, "monthly_limit": 1000000}
                                                                                                              -> policy.policy_id (status DRAFT)
POST /api/v1/policies/{policy_id}/activate        {"confirmed": true}                                          -> status ACTIVE   (a DRAFT is NOT enforced)
POST /api/v1/wallets                              {"wallet_type": "agent", "agent_id": "...", "display_name": "...", "currency": "USD",
                                                   "authority_minor": 100000, "default_policy_id": "...", "environment": "sandbox"}
PATCH /api/v1/agents/{agent_id}                   {"default_policy_id": "..."}
POST /api/v1/agents/{agent_id}/credentials        {"name": "golden path"}                                       -> credential.api_key (ONCE)
```

> Why a "wallet" if I never pay through Quattrino? In the sandbox the wallet is simply the organization's
> granted ceiling - the root of the authority chain. Without it `authorize` fails closed with
> `NEEDS_INFORMATION / wallet_missing`. No real money is involved.

## Step 3 - Authorize: see all four decisions (2 minutes)

With the Golden Path authority (standing $50, ask-first above $20) these four calls are deterministic.
All are `mode: dry_run` (nothing is issued or reserved):

| call | decision | why (real `binding_layer` / reason code) |
|---|---|---|
| `$19.99`, task `max_amount_minor: 2000` | **AUTHORIZED** | the task narrowed $50 to $20; `effective_maximum_minor: 2000`, `binding_layer: task` |
| `$25.00`, no task | **APPROVAL_REQUIRED** | above the $20 ask-first line, inside $50: `approval_reasons[0].code = above_autonomous_limit` |
| `$75.00` | **DENIED** | above standing authority: `denial_reasons[0].code = exceeds_effective_authority`, `binding_layer: agent_standing` |
| no amount | **NEEDS_INFORMATION** | `needs_information[0].code = amount_required` - Quattrino never infers an amount |

```bash
KEY=qtrn_sk_key_...
curl -s -X POST https://quattrino.io/api/v1/authorize -H "X-API-Key: $KEY" -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}}'
```

Real response (core fields; the full payload carries ~60 evidence fields you can ignore at first):

```json
{
  "decision": "AUTHORIZED",
  "mode": "dry_run",
  "status": "DRY_RUN",
  "authorization_id": "auth_4z97eznt7ktx5zeb7sob",
  "amount_minor": 1999, "currency": "USD", "merchant": "Amazon", "purpose": "USB-C cable", "commitment": "ONE_TIME",
  "effective_maximum_minor": 2000,
  "autonomous_maximum_minor": 2000,
  "effective_authority": { "binding_layer": "task", "effective_maximum_minor": 2000, "layers": ["...organization, agent_standing, task..."] },
  "denial_reasons": [], "needs_information": [], "approval_reasons": [],
  "approval": { "required": false, "state": "not_required", "approval_id": null },
  "question": null, "question_required": false, "missing_authority": [],
  "execution_plan": { "execution_mode": "external", "quattrino_moves_money": false, "how": "Execute with your own provider ... then POST .../outcome" },
  "expires_at": "2026-09-26T05:22:15Z"
}
```

Handle the four decisions the same way every time - see [DECISIONS.md](DECISIONS.md).

## Step 4 - Verify: bind, act, report, explain (3 minutes)

```bash
# 4a. BINDING authorization (single use, expires, revocable). Requires an Idempotency-Key.
curl -s -X POST https://quattrino.io/api/v1/authorize -H "X-API-Key: $KEY" -H "Idempotency-Key: order-1001" -H "Content-Type: application/json" \
  -d '{"mode":"binding","execution":"external","action_type":"purchase","amount_minor":1999,"merchant":"Amazon",
       "purpose":"USB-C cable","commitment":"ONE_TIME","task":{"max_amount_minor":2000},"proof_audience":"amazon.example"}'
# -> decision AUTHORIZED, status AUTHORIZED, authorization_id auth_..., expires_at (15 min), portable_proof.token (EdDSA JWS)

# 4b. (optional) anyone holding the proof verifies it WITHOUT a credential
curl -s -X POST https://quattrino.io/api/v1/authorizations/verify -H "Content-Type: application/json" \
  -d '{"proof":"<portable_proof.token>","audience":"amazon.example"}'
# -> {"valid": true, "status": "VALID", "checks": {"signature": true, "audience": true, "live": true, ...}}

# 4c. YOU execute with your own provider, then report the outcome (consumes the authorization exactly once)
curl -s -X POST https://quattrino.io/api/v1/authorizations/auth_.../outcome -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"success":true,"provider":"amazon","provider_reference":"114-2233","actual_amount_minor":1999,"commitment":"ONE_TIME"}'
# -> status CONSUMED, compliance.status COMPLIANT   (a $24.99 actual -> NONCOMPLIANT, findings ["AMOUNT_EXCEEDED"])

# 4d. Explain - who / on whose behalf / why / what narrowed it / what happened / did it match
curl -s https://quattrino.io/api/v1/authorizations/auth_.../explain -H "X-API-Key: $KEY"
```

Real `plain_language` from the sandbox:

> Golden path agent asked to purchase at Amazon for $19.99 because: USB-C cable. It acted on behalf of
> p6 (organization org_...). Its standing policy allows up to $50.00 per action, autonomously up to
> $20.00. The task instruction narrowed it further to $20.00. Commitment class: one time (declared). The
> effective maximum is $20.00, set by the task instruction. ... Decision: AUTHORIZED (status CONSUMED).
> Execution route: external. What happened: external_executor reported success for $19.99 (provider ref
> 114-2233). The outcome matched the authorized terms. Compliance: COMPLIANT.

**First success = an `AUTHORIZED` binding authorization whose outcome you reported and whose `/explain`
you read.** You are done with the Golden Path.

---

## After the Golden Path (optional, in this order)

1. **Plain-language intent** - `POST /api/v1/intents/compile {"statement": "Find someone who can fix this machine."}`
   -> envelope; research is authorized now, hiring is `not_yet_authorized`. Authorizing a hire **inside standing
   authority** (e.g. $32.70) returns `NEEDS_INFORMATION` with exactly one question; a hire **above** standing
   authority ($327.00 against a $50 policy) is `DENIED` first - the money ceiling is checked before the purpose gap.
   See [NEEDS_INFORMATION.md](NEEDS_INFORMATION.md).
2. **Approvals without polling** - [WEBHOOKS.md](WEBHOOKS.md).
3. **MCP** for assistants - [MCP_SETUP.md](MCP_SETUP.md).
4. **Execution adapters** (only if you want Quattrino to execute in the sandbox: Credits marketplace, Stripe SPT test
   merchant) - `/developers#adapters`. Authorize-only integrations never need them.
5. **Seven differentiation journeys A-G** - `python differentiation_journeys.py`.

## Rules that never change

- A task, envelope or delegation may **narrow** standing authority; nothing may widen it.
- Ambiguity returns `NEEDS_INFORMATION`, never more authority. An undeclared commitment is never treated as `ONE_TIME`.
- `DENIED` means denied. Do not retry with altered wording.
- Binding authorizations expire, are revocable and are consumed **exactly once** - by your executor (`/outcome`) or by a
  Quattrino adapter. The authorization is immutable; deviations become compliance findings.
- The agent never receives card numbers, passwords or backend secrets - only its scoped key.

## Versions

Contract: `POST /api/v1/authorize` (API `v1`). SDKs: `quattrino` (Python) and `@quattrino/sdk` (TypeScript) **0.31.0**,
single-file downloads, not yet on PyPI/npm. Reason-code registry `1.0.0`. Portable proof version `1`.
