# Swaps API reference

> Resource-shaped REST for Swaps — payment links, payouts, payroll, wallet, quotes and orders, capabilities, screening, crypto processing.

Generated from the same bundled OpenAPI contract as the HTML reference. For complete JSON Schema constraints, use [OpenAPI YAML](/openapi.yaml) or [OpenAPI JSON](/openapi.json).

Single source of truth for the public `/v1` surface. Generated artefacts — the reference docs,
the TypeScript and Python SDKs, the MCP tool list and `llms.txt` — are derived from this file
and never edited by hand (API-CANON §12, "rule of six").

Three tenses, never a fourth (`x-swaps-status`): `available` — a live backend exists and `/v1` is
a thin router in front of it. `dark-flag` — live, but gated behind a cohort or feature flag;
while the flag is closed, calling it answers `503 temporarily_unavailable` (an availability
lever, never an authorization one), never `capability_unavailable`. `proposed` — does not
exist yet, documented for the contract it will carry, never presented as callable and never
hidden.

Money is an object (`Money`), never a bare number. Every object carries `livemode`. Every
mutating request carries `Idempotency-Key`. Every response echoes `Swaps-Version`.

**Terms acceptance.** No operation asks for a terms acceptance: people accept the Terms of
Service when they sign up for Swaps, and Swaps does not ask them again (since 2026-10-20).
The codes `terms_acceptance_required` (`409 capability_unavailable`) and
`legal_acceptance_unavailable` (`503 temporarily_unavailable`) are no longer returned; they stay
in the error registry because a published code is never removed.

**Response tolerance (A1-SDK-TOLERANT, fixer round 1 finding #3), `x-swaps-response-tolerant`.**
`additionalProperties: false`/`unevaluatedProperties: false` on a schema reachable from a
RESPONSE — a resource, a list envelope, or a nested object such as `Money`, at any depth, even
when the SAME schema is also reachable from a request — is NOT a promise that an unrecognized
key fails deserialization on that side: the generated `@swaps/sdk` strips it instead
(`packages/contracts-api/generated/schemas.ts`, `scripts/openapi/gen-zod.mjs` §3b), matching
this document's own forward-compatibility promise above. Any other client generated from this
document should do the same: on a response object, treat the keyword as advisory only, never as
license to fail deserialization on an unknown key. A REQUEST body keeps the keyword's literal
meaning — an unrecognized key there is still a `400` (a schema reachable from BOTH sides gets a
request-only strict variant server-side; see the SDK generator). This item does not remove the
keyword from the ~200 response schemas that carry it (spread across every resource file, mostly
outside this item's file ownership) — this paragraph is the explicit publication of the true
behavior instead, so a consumer reading only this document, not the SDK source, still gets it
right.

**Forward compatibility (A1-2).** A RESPONSE enum tagged `x-swaps-open-enum: true` may gain
a new member within `/v1` without a version cut: treat an unrecognized member as an opaque
string and never fail deserialization on it — the generated SDK types these as a union of the
known literals widened with `string` so a new member still type-checks. A REQUEST enum is never
open: an unrecognized value there stays `400 invalid_request`, because it names a choice the
caller is making, not a fact the server is reporting. The one deliberate pairing this produces:
`Event.type` (a response) is open, so a new event type is delivered rather than rejected, while
`webhook_endpoints.create`/`.update`'s `event_types` (the SAME catalogue, submitted as a
request) still 400s an unrecognized name — an endpoint asking to receive a type this deployment
does not know cannot be honoured, open catalogue or not. This is a narrowing of API-CANON §3's
additive-fields rule to enum members specifically; it does not relax anything else there.

**Test mode (A1-3, reconciled against merged K11-1/K11-2 code — fixer round 2).** `/v1` cannot be
exercised in test mode by anyone today: `api_v1.test_mode` (the master switch K11-2 added) is OFF,
and no `livemode=false` account can exist in production yet either (K11-4, the piece that lets one
be created, has not landed) — no `sk_test_` caller exists to reach any of the behaviour below.
While the switch is off, the router refuses EVERY `/v1` operation the same way, before any
operation-specific code runs: `caller.livemode === false` forks to `dispatchTestMode`
(`api-v1/router.ts`, `api-v1/test_mode.ts`), which answers `503 temporarily_unavailable`
(`sideEffectFree`, `Retry-After: 60`) for every disposition, including one the router would run
locally once the switch is on. This is a platform-wide gate, not a claim about any one operation —
it is why every `available`/`dark-flag` operation below is annotated `x-swaps-test-mode:
unavailable`. Four operations additionally enforce the same refusal a second time in their own
handler code (`quotes.create`, `quotes.get`, `orders.create`, `customers.create`) — an independent
guard, not the only one. On every other operation, `unavailable` records only that K11 has not yet
individually verified and evidenced a `full`/`sandbox`/`fixtures`/`dev-cron` claim for it, one
operation at a time, each with its own test (`K11_EVIDENCE_LIST`,
`packages/contracts-api/__tests__/contract/test-mode-truth.test.ts`). The router matches that
value exactly: every `unavailable` operation is routed `refused` and answers `503
temporarily_unavailable` before any handler runs, whether the switch is on or off — with the switch
on, that refusal is permanent and carries no `Retry-After`. Restoring one operation flips the
router disposition, this contract value and `K11_EVIDENCE_LIST` together, in one change (API-CANON
§12, "what is unavailable is documented with its reason, not hidden").
The handful of `proposed` operations keep an aspirational
`full`/`sandbox`/`fixtures` value instead — they are not callable at all yet, so there is no live
behaviour for `unavailable` to correct.

## Servers

- `https://api.swaps.app/v1` — Production only. Live and test mode both reach this same host — the key prefix (`sk_live_` / `sk_test_`) selects which one, and must match the calling account's own `livemode` or the request is `401 invalid_api_key`. A development base URL, on a separate non-production project, is issued out of band alongside a development key — see /environments in the docs for what that base URL points at and what it does not promise.

## API groups

- [Payment links](/reference/payment-links.md): Request money — links, clients, products, payer sessions.
- [Payouts](/reference/payouts.md): Pay an invoice — fiat payouts funded with crypto.
- [Payroll](/reference/payroll.md): Pay people — runs, items, recipients, templates.
- [Wallet](/reference/wallet.md): The holder's non-custodial Tempo wallet — balances, deposits, sends, conversions, bank details. Nothing here can sign.
- [Quotes and orders](/reference/quotes-and-orders.md): Buy and sell across the provider fan-out.
- [Crypto processing](/reference/crypto-processing.md): Crypto invoices, pay-ins and subscriptions settled to the merchant's own wallet.
- [Capabilities](/reference/capabilities.md): What this account can execute right now, rail by rail, and public eligibility by country.
- [Customers](/reference/customers.md): Verification state — status only, never documents.
- [Screening](/reference/screening.md): Address risk, traces and on-chain transaction inspection.
- [Developers](/reference/developers.md): API keys, webhook endpoints, events, usage.
- [Support](/reference/support.md): A signed-in person's own support requests, the conversation with Swaps Support and the files they send in it. Dashboard session only.

- [Schemas](/reference/~schemas.md): Reusable request and response schemas.
