Quitanza

API reference

The settlement assurance layer for the agent economy. Escrows lock simulated funds, deliveries are verified against machine-readable terms, and disputes resolve only by co-signature, arbiter signature or timeout default. Every closed matter issues a quitanza, a signed, independently verifiable settlement proof. Simulated rails: no real funds. Hosted deployments require Authorization: Bearer <api key> on every /v1 route (401 otherwise) and rate-limit /v1 per client (429 over budget); a local sandbox without QUITANZA_API_KEY runs open.

This page is generated from the OpenAPI 3.1 document, the hand-written source of truth. The running API serves it at GET /v1/openapi.json; a copy is published at https://quitanza.com/openapi.json. Start the local sandbox with pnpm --filter @quitanza/api dev (default port 4280).

Agents

Sandbox-only key registration. The sandbox holds these keys server-side; production agents sign locally and never transmit private keys.

POST /v1/agents

Register a sandbox agent.

Generates an Ed25519 keypair held server-side. Sandbox convenience only: production agents keep keys client-side and sign locally.

{
  "label": "buyer-agent"
}

Responses:

Mandates

Principal-signed caps binding an agent key: per-escrow max, cumulative max, allowed assets, optional counterparty allowlist, expiry. Optional in the sandbox; when present, every cap is enforced at escrow creation.

POST /v1/mandates

Register a principal-signed mandate.

The signature must be the principal's, over the canonical JSON of {principal, caps}. Registration rejects anything else; creation-time enforcement then holds every escrow under the mandate to its caps. Registration is idempotent by content hash: submitting an identical mandate again returns the existing record with its usage intact.

Responses:

GET /v1/mandates/{id}

Fetch a mandate with its usage.

Responses:

Escrows

The escrow lifecycle: created → funded → delivered → settled, with refund, dispute and timeout branches. Every state change lands on a hash-chained evidence trail.

POST /v1/escrows

Create an escrow.

Parties are sandbox agent ids (payerAgentId/payeeAgentId) or raw Ed25519 public keys (payer/payee). Optional: mandateId (caps enforced at creation), arbiter (whose signature alone can later rule on a dispute), timeouts (per-stage deadlines; defaults guarantee silence always resolves).

{
  "payerAgentId": "agt_…",
  "payeeAgentId": "agt_…",
  "amount": "25.00",
  "asset": "USDC",
  "terms": {
    "description": "the agreed report, exactly",
    "checks": [
      {
        "kind": "shape",
        "requiredFields": [
          "report"
        ]
      }
    ]
  }
}

Responses:

GET /v1/escrows

List all escrows.

Responses:

GET /v1/escrows/{id}

Fetch one escrow.

Responses:

POST /v1/escrows/{id}/fund

Mark funds locked (simulated).

Transitions created → funded, stamps the delivery deadline, emits escrow.funded. Local simulation: no real funds, ever.

Responses:

POST /v1/escrows/{id}/delivery

Submit a delivery; verification runs synchronously.

Two forms. Sandbox: { agentId, payload }, where the API signs with the named agent's held key. Client-signed: { payload, submittedAt, signature }, where signature is the payee's Ed25519 signature over canonical {escrowId, contentHash, submittedAt}. A passing verdict settles and the response includes the quitanza; a failing verdict leaves the escrow in delivered for dispute or refund.

Responses:

POST /v1/escrows/{id}/refund

Refund a funded or failed-verdict escrow.

Allowed from funded (no delivery yet) or delivered with a failed verdict. Issues a quitanza with outcome refunded: proof of how the matter closed.

Responses:

GET /v1/escrows/{id}/trail

The hash-chained evidence trail.

Every state change as a chained entry: hash = sha256(canonical({index, prevHash, at, event, body})). intactUpTo is null when the chain verifies end to end, otherwise the index of the first break.

Responses:

Disputes

Structured challenges. A ruling is accepted only when co-signed by both parties or signed by the named arbiter; otherwise the timeout default applies. The platform never rules.

POST /v1/escrows/{id}/dispute

Open a dispute on a delivered escrow.

Responses:

GET /v1/escrows/{id}/dispute

Fetch the dispute on an escrow.

Responses:

POST /v1/escrows/{id}/dispute/evidence

Attach content-addressed evidence.

Responses:

POST /v1/escrows/{id}/dispute/resolve

Submit a ruling: co-signed or arbiter-signed only.

Signers sign the canonical JSON of {escrowId, disputeId, outcome, rationale}. Accepted only with both parties' signatures (co-signature) or the named arbiter's. signatures carries client-side signatures; agentIds asks the sandbox to sign with held agent keys. Anything else is rejected: the platform never rules. Disputes nobody resolves fall to the timeout default.

Responses:

Quitanzas

Terminal settlement proofs: Ed25519-signed over canonical JSON, hash-chained to the evidence trail, verifiable offline.

GET /v1/escrows/{id}/quitanza

The quitanza that closed an escrow.

Responses:

GET /v1/quitanzas/{id}

Fetch a quitanza by id.

Responses:

GET /v1/quitanzas/{id}/verify

Verify a quitanza against the live trail.

Checks the issuer signature over the canonical body, the integrity of the full evidence trail, and that the quitanza's trailHead matches the trail at its recorded length. The same checks run offline with no Quitanza code. See the quitanza format spec.

Responses:

Webhooks

Durable signed event delivery: Ed25519 signature over the canonical JSON body, retries with backoff, inspectable dead-letter list.

POST /v1/webhooks

Register a webhook endpoint.

Every engine event is POSTed to each registered URL as canonical JSON, signed with the announced key: x-quitanza-signature carries the Ed25519 signature, x-quitanza-key-id the signing public key. Failures retry with backoff; exhausted deliveries land on the dead-letter list.

Responses:

GET /v1/webhooks

List registered webhooks and the signing key.

Responses:

GET /v1/webhooks/dead-letters

Deliveries that exhausted every retry.

Responses:

X402

The x402 payment handshake settled through escrow (simulated rails).

GET /v1/x402/demo

A paid resource settled through escrow (x402, simulated rails).

Without an X-PAYMENT header: 402 with PaymentRequirements (scheme quitanza-sandbox, network quitanza-local). With a valid signed payment header: runs the full escrow lifecycle for the request and returns the resource plus an X-PAYMENT-RESPONSE header naming the quitanza that proves the matter closed.

Responses:

Meta

Service metadata.

GET /health

Service health and issuer key.

Responses:

GET /.well-known/quitanza-issuer.json

The issuer keys this deployment signs quitanzas with.

Public, no authentication. Serves the Ed25519 issuer keys as { keys: [{ keyId, alg, publicKey, validFrom }] }. A quitanza verifies against a domain when its signing key appears here and the signature checks out offline; the list form supports key rotation.

Responses:

GET /v1/openapi.json

This document.

The OpenAPI 3.1 description of every route, including the error model.

Responses:

Error model

Every error is { "error": { "code", "message" } }. Codes: bad_request (400), payment_invalid (402), mandate_violation and unauthorized_ruling (403), not_found (404), invalid_state (409, illegal transition), unprocessable (422).

This page as markdown