DEVELOPER PREVIEWSANDBOX / TESTNO PRODUCTION MONEY MOVEMENT

Documentation

Authorize AI agent actions. Prove them afterward.

One REST API and one MCP server in front of one authority engine. Your agent asks; Quattrino intersects standing, task and delegated authority and answers AUTHORIZED, DENIED, APPROVAL_REQUIRED or NEEDS_INFORMATION. Nothing here can bypass a policy, an envelope, a delegated ceiling or an approval. TEST/SANDBOX: no production money moves.

Authorize (start here)

Quattrino authorizes economic actions for AI agents. Ask before you act - even when Quattrino will not execute the payment itself. One endpoint, two modes, four decisions.

POST https://quattrino.io/api/v1/authorize            (X-API-Key: <agent key>  or  Authorization: Bearer <agent key>; Idempotency-Key for mode=binding)
{
  "mode": "dry_run" | "binding",
  "action_type": "purchase" | "service_call" | "marketplace_purchase" | "subscription" | "payment" | "economic_action",
  "amount_minor": 1999, "currency": "USD",    // integer minor units ($19.99)
  "merchant": "Amazon",                       // or "service_id" for a marketplace service
  "purpose": "USB-C cable",
  "commitment": "ONE_TIME",                   // never assumed: ONE_TIME | SUBSCRIPTION | RECURRING | CONTRACT | ...
  "task": { "max_amount_minor": 2000, "categories": ["electronics"], "prohibited_side_effects": ["subscription"] },
  "delegated_authority_id": "dauth_...",      // optional: act under a delegated authority
  "disclosures": ["shipping_address"],        // optional: RELEASE_ALLOWED | DENIED | ASK_OWNER per scope
  "execution": "auto" | "quattrino" | "external"
}
-> { "decision": "AUTHORIZED" | "APPROVAL_REQUIRED" | "DENIED" | "NEEDS_INFORMATION",
     "effective_maximum_minor": 2000, "effective_authority": { "binding_layer": "task", "layers": [...] },
     "authorization_id": "auth_...", "expires_at": "...", "execution_plan": { "execution_mode": "external" | "quattrino", "adapter": ... } }

POST https://quattrino.io/api/v1/authorizations/{id}/outcome     // your executor reports success, provider_reference, actual_amount_minor
GET  https://quattrino.io/api/v1/authorizations/{id}/explain     // who / on whose behalf / why / what narrowed it / effective max / what happened
POST https://quattrino.io/api/v1/authorizations/{id}/revoke
GET  https://quattrino.io/api/v1/authorize/schema                // the full contract
  • A task may narrow standing authority (standing $50 + task "under $20" = $20). It can never expand it.
  • No sub-agent can expand inherited authority. Ambiguity returns NEEDS_INFORMATION, never more authority.
  • Binding authorizations expire, are revocable and are consumed exactly once - by a Quattrino adapter (SPT merchant, Credits marketplace) or by your own executor.
  • MCP tools: authorize, authorization_status, authorization_outcome, authorization_explain.

Developer hub with runnable examples: /developers. Canonical reference: GOLDEN_PATH.md.

Golden Path (about 10 minutes, TEST/SANDBOX)

One canonical path: discover → sign up → connect → authorize → verify. Everything else is optional.

  1. Discover — this page, GET https://quattrino.io/api/v1/public/discovery, GET https://quattrino.io/api/v1/authorize/schema.
  2. Sign up — open signup (no invite; MFA optional). The owner session token is not an agent credential.
  3. Connect — give an agent standing authority: an ACTIVE policy ($50 per action, ask first above $20), a sandbox wallet (the organization's granted ceiling; simulated money) and a scoped key qtrn_sk_key_… (shown once). In the app, or in one command: bootstrap_sandbox.py --signup.
  4. Authorize — $19.99 with task max $20 → AUTHORIZED · $25.00 → APPROVAL_REQUIRED · $75.00 → DENIED · no amount → NEEDS_INFORMATION. Handle the four answers the same way, always.
  5. Verify — mode: "binding", execution: "external", Idempotency-Key → you execute with your own provider → /outcome (CONSUMED · COMPLIANT) → /explain. That is first success.

This page is the short form. The canonical, detailed documentation is served as Markdown at https://quattrino.io/api/v1/public/docs: DECISIONS · NEEDS_INFORMATION · APPROVAL_REQUIRED · DENIED_AND_REASON_CODES · AUTHORIZE_ONLY · PROOF_VERIFICATION · OUTCOME_REPORTING · WEBHOOKS · IDEMPOTENCY_AND_RETRIES · MCP_SETUP · SDK_USAGE · ERRORS · GLOSSARY.

The four decisions (real reason codes)

decisionGolden Path triggerfield to readyour code
AUTHORIZED$19.99 under a $20 task (standing $50)effective_maximum_minor, effective_authority.binding_layeract (binding: single use until expires_at)
APPROVAL_REQUIRED$25.00 — above the $20 ask-first line, inside $50 (above_autonomous_limit)approval.approval_id, question.questionrelay the question; poll GET /agent/approvals/{id}; never re-submit
DENIED$75.00 — above standing authority (exceeds_effective_authority, agent_standing)denial_reasons[].code, remedystop; never retry with altered wording
NEEDS_INFORMATIONno amount (amount_required) · envelope gap (action_class_not_yet_authorized)question (null → term missing), needs_information[].field, missing_authority[]supply the term, or relay the ONE question; keep doing safe_actions_remaining

Every reason carries code · family · severity · message · remedy from the public registry: GET https://quattrino.io/api/v1/public/reason-codes.

SDKs 0.31.0 (single file, zero dependencies, not yet on PyPI/npm)

Thin wrappers over the authorization API: authorize, authorize_binding,record_outcome, explain, compile_intent, discover,gap, release_disclosure. The SDK never decides anything - Quattrino decides; the SDK transports.

Python

Standard library only (Python 3.9+).

Download quattrino.py
from quattrino import Quattrino
q = Quattrino(api_key="qtrn_sk_key_...", base_url="https://quattrino.io")
r = q.authorize(amount_minor=1999, merchant="Amazon", purpose="USB-C cable",
                commitment="ONE_TIME", task={"max_amount_minor": 2000})
r.decision, r.effective_maximum_minor        # AUTHORIZED, 2000
auth = q.authorize_binding(..., execution="external")
q.record_outcome(auth.authorization_id, success=True, actual_amount_minor=1999)
q.explain(auth.authorization_id)["plain_language"]

TypeScript

Fetch-based (Node 18+), zero dependencies.

Download quattrino.ts
import { Quattrino } from "./quattrino";
const q = new Quattrino({ apiKey: "qtrn_sk_key_...", baseUrl: "https://quattrino.io" });
const r = await q.authorize({ amountMinor: 1999, merchant: "Amazon", purpose: "USB-C cable",
                              commitment: "ONE_TIME", task: { max_amount_minor: 2000 } });
r.decision;                                   // AUTHORIZED
const auth = await q.authorizeBinding({ ...same, execution: "external" });
await q.recordOutcome(auth.authorization_id, { success: true, actualAmountMinor: 1999 });
(await q.explain(auth.authorization_id)).plain_language;

Also public: the open service directory (GET https://quattrino.io/api/v1/public/directory) and agent-credential verification (POST https://quattrino.io/api/v1/public/identity/verify).

Authentication and idempotency

Agents authenticate with their key qtrn_sk_key_… in X-API-Key or Authorization: Bearer (both work; the SDKs use Bearer) — a scoped, revocable credential, never a card or a login. Every binding authorization and every execution POST requires an Idempotency-Key; a retry with the same key returns the original result and never authorizes or charges twice.

curl -X POST https://quattrino.io/api/v1/authorize \
  -H "X-API-Key: qtrn_sk_key_..." \
  -H "Idempotency-Key: order-1029" \
  -H "Content-Type: application/json" \
  -d '{
    "mode": "binding",
    "action_type": "purchase",
    "amount_minor": 1499, "currency": "USD",
    "merchant": "Amazon",
    "purpose": "USB-C cable for the office",
    "commitment": "ONE_TIME",
    "task": {"max_amount_minor": 2000},
    "execution": "external"
  }'

Execution adapters (optional, sandbox). When you ask Quattrino to execute, the certified transaction engine (POST /agent/transactions with authorization_id) consumes the authorization once and picks the route before any sandbox money moves: agent-to-agent services draw on Credits, merchant checkout uses a Stripe Shared Payment Token in test mode (merchant_purchase), machine endpoints use x402 on testnet (machine_payment). external_purchase is a sandbox rules-check simulation only — the legacy direct-card rail is retired (LEGACY_EXTERNAL_PURCHASE_RAIL_RETIRED).

Endpoints

Authorize-only (you execute) — the primary path

  • POST /authorize ← start here (mode dry_run | binding)
  • GET /authorizations/{id} · GET /authorizations
  • POST /authorizations/{id}/outcome ← what YOUR executor did
  • GET /authorizations/{id}/explain
  • POST /authorizations/{id}/revoke
  • GET /authorizations/{id}/proof · POST /authorizations/verify (public)
  • GET /agent/approvals/{id} ← poll after APPROVAL_REQUIRED
  • POST /intents/compile ← plain-language task -> authority envelope
  • GET /authority/me · POST /authority/gap ← what may I do / what exactly is missing
  • POST /authorizations/{id}/disclosures/{scope}/release
  • GET /agent/delegations · GET /agent/whoami
  • MCP /api/mcp/ ← same operations as tools (same credentials)

Execution adapters (optional; Quattrino executes in the sandbox)

  • GET /agent/wallet ← standing authority + sandbox funds (legacy object name)
  • POST /agent/transactions ← pass authorization_id; Idempotency-Key required
  • GET /agent/transactions · GET /agent/transactions/{id} · POST .../cancel
  • GET /agent/quote?amount_minor=2000
  • GET /agent/marketplace/search · POST /agent/services/{id}/purchase (Credits)
  • POST /agent/merchant-payments/quote · /purchase (Stripe SPT test merchant)
  • GET /agent/jobs · POST /agent/jobs/{id}/complete (you sell services)

Authorize-only integrations never need this column. Pass execution: "external" explicitly.

MCP tools are thin adapters over exactly these operations — they cannot bypass the authority engine, envelopes, delegated ceilings, approvals, ledger or rate limits.

MCP (Claude Desktop, Cursor, any MCP client)

Streamable-HTTP server at https://quattrino.io/api/mcp/, header Authorization: Bearer qtrn_sk_key_… on every request. 40 tools, grouped; start with whoami → authority_discover → authorize → authorization_outcome → authorization_explain. In MCP the idempotency key is the idempotency_key tool argument.

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

Webhooks and idempotency

Owners register HTTPS endpoints (POST /webhooks, secret shown once). Deliveries are signed X-Quattrino-Signature: t=<unix>,v1=HMAC_SHA256(secret, "<t>.<raw body>") and retried 5 times (30 s · 2 min · 10 min · 60 min). Authorize-only approvals and alerts arrive as notification.created (data.notification_type: approval_request | firewall_alert); approval.pending / transaction.* fire only for Quattrino-executed transactions.

Binding authorizations require an Idempotency-Key (8+ chars, stable per unit of work): same key + same body → the original result (idempotent_replay: true); same key + different body → 409 idempotency_key_reuse. Outcomes need no key — a second /outcome is 409 outcome_already_recorded. Canonical: WEBHOOKS.md · IDEMPOTENCY_AND_RETRIES.md.

Errors

{
  "error": {
    "code": "idempotency_key_required",
    "message": "An Idempotency-Key header is required for binding authorizations.",
    "request_id": "req_396516089a9549efa820a7b6",
    "details": {},
    "retryable": false
  }
}
// Decisions are NOT errors: DENIED / APPROVAL_REQUIRED / NEEDS_INFORMATION are 200 OK with a "decision".
// 401 missing_api_key / invalid_api_key carry details.how_to_fix + details.where_to_get_one.

Full table: ERRORS.md.

Transaction states (26)

Needs approvalAPPROVEDCANCELLEDCompletedCREATEDDECLINEDDISPUTEDPartly refundedPAYMENT AUTHORIZEDPayment failedPAYMENT PENDINGBlocked by policyPOLICY CHECKPROVIDER OUTCOME UNKNOWNProvider unavailableREFUNDEDREFUND PENDINGREVERSEDBlocked by riskROUTINGSERVICE COMPLETESERVICE FAILEDSERVICE PENDINGService timed outSETTLEMENT PENDINGVALIDATING

Sandbox scenarios

  • success — Payment authorizes and settles normally
  • decline — Provider declines the payment
  • insufficient_funds — Funding source reports insufficient funds
  • provider_timeout — Provider does not respond in time
  • provider_timeout_charged — Provider times out AFTER it actually authorized (the dangerous ambiguous case)
  • provider_unavailable — Provider is temporarily unavailable
  • duplicate_webhook — Provider sends the settlement webhook twice
  • refund — Payment succeeds then is fully refunded
  • partial_refund — Payment succeeds then is partially refunded
  • dispute — Payment succeeds then is disputed
  • service_failure — Payment succeeds but the purchased service fails
  • funding_unavailable — No funding source is available for this rail
  • credential_creation — A single-use payment credential is issued

Autonomy levels

  • 0 — Disabled: No spending permitted
  • 1 — Approve every transaction: Every transaction requires human approval
  • 2 — Threshold-based: Auto-approve below the autonomous limit
  • 3 — Budget autonomy: Operate freely within policy budgets
  • 4 — Mission autonomy: Authority tied to an active mission and expiry

Agent scopes

wallet.read · transactions.read · transactions.request · transactions.cancel · approvals.read · marketplace.read · marketplace.purchase