Changelog
One entry per Swaps-Version date. Nothing lands here before its contract test passes (API-CANON §12) — mirrors docs/api/CHANGELOG.md in the repository, the source of record.
Unreleased — align public integration guidance with the enabled resource API
The public MCP page, its Markdown companion, localized installation guidance and generated
LLM manifests now describe /v1 as enabled, subject to scopes, account capabilities and
resource availability, and point to the live documentation host. Test mode remains limited to
the operations listed in Test mode; no complete provider
sandbox or completed rollout acceptance is claimed. Published npm route tools remain read-only;
hosted account tools preserve per-transaction human confirmation and signing requirements.
This corrects documentation only: no API, SDK, tool schema or Swaps-Version change.
Unreleased (2026-10-05) — one login, one account: POST /v1/accounts answers 409
POST /v1/accounts no longer opens a second account under the same login. A login that already owns a live account gets 409 conflict, code account_already_exists, and nothing is created. Every signed-in login is given its account on its first request without Swaps-Account, and only the owner of the live account a request acts on gets past the earlier refusals, so today a request that gets past them always gets this 409 and never a new account. The wallet, verification, payment links, bank details and transactions belong to the login rather than to an account, so a second account would promise a separation that does not exist. The reference marks accounts.create as not open today. Every earlier refusal still comes first and is unchanged: 403 for a business key or a member, 503 for a test-mode session, 400 for a malformed body or an unrecognised country. GET /v1/accounts and the Swaps-Account header are unchanged. Separate accounts under one login, each with its own verification, are planned for a later release. No Swaps-Version change.
Unreleased — explicit acceptance of legal revisions
Adds a disabled-by-default registry and server receipt for a published legal revision, with
separate publication, acceptance, effective and per-action requirement dates. No legal release,
mailing or enforcement date is activated by this change. Selected new live operations can return
409 terms_acceptance_required (capability_unavailable, details.revision/details.action)
or 503 legal_acceptance_unavailable (temporarily_unavailable, Retry-After). People accept
through an authenticated Swaps screen, with owner authority required for account terms; personal
wallet intents require their own receipt. Keys and agents cannot mint receipts. Existing
completed idempotent responses, reads, cancellations and recovery retain their existing paths.
No Swaps-Version change. See Errors.
Unreleased — save a destination the recipient submitted as their default (payroll)
PayrollRecipient gains pending_destination: a destination the recipient saved through their payout link that is not their default yet (masked; null when nothing waits). New operation POST /v1/payroll_recipients/{id}/adopt_pending_destination (payroll.write, Idempotency-Key) saves it as the default, bound to the submitted_at you showed (expected_submitted_at), and returns the recipient plus updated_run_items / skipped_run_items for their waiting rows in unapproved runs. New error codes: pending_destination_changed, no_pending_destination (409), and destination_rail_keyless (422) — the last one also replaces the generic invalid_request code payroll approve, execute and the recipient's own destination write answered for the same refusal. On 409 pending_destination_changed the recipient's default is unchanged; in a rare race, rows the call already filled with the destination you confirmed keep it. Additive; no Swaps-Version change.
Unreleased — fund a payout from the Swaps wallet balance
POST /v1/payouts/{id}/funding_instructions takes an optional body: source_chain (chosen at funding, fixed afterwards) and funding_source: swaps_wallet, which prepares one unsigned send from the payer's own wallet to exactly the deposit address — the payer signs it with their passkey. New read: GET /v1/payouts/{id}/wallet_funding_quote. GET /v1/capabilities?product=payouts lists funding_sources. Additive; an absent body behaves exactly as before. Off until enabled per account.
Unreleased — naming freeze
- Operation ids follow one rule,
<resource>.<verb>, and something under a parent is part of the resource name. For example,payouts.list_eventsis nowpayouts.events.list, andscreenings.create_reportis nowscreenings.reports.create. Paths are unchanged. See Conventions. - The screening scopes are now
screenings.read/screenings.write. A key created with the oldscreening.*names keeps working until the nextSwaps-Versiondate. - The
mealias on/customers/{id}is deprecated. Use yourcus_…id instead. - Payouts and capabilities now return UK Faster Payments as
faster_paymentsinstead offps.
Unreleased — hosted MCP and the OpenAPI contract withdraw the advertised OAuth authorization server
Swaps operates no public OAuth authorization server. /.well-known/oauth-authorization-server
and /.well-known/oauth-protected-resource answer 404; the OpenAPI contract's agentOAuth
scheme and every MCP discovery document that named OAuth 2.1 + PKCE are corrected to advertise a
business key (sk_live_…/sk_test_…) as the sole agent/MCP credential. See
Authentication. No credential class that worked before this entry stops
working — this corrects what was advertised, not what was enforced.
Unreleased — truthful quote projection
rate(the winner, everyoffers[]entry, everyproviders[].offerecho, androute_facts.best_price.rate) is now derived server-side from each offer's ownfinal_in÷final_outdecimal values, computed before either is rounded into the publishedMoneyfields — alwaysfrom_assetperto_asset, identically for every provider on bothbuyandsell— never a provider's own field, whose basis previously differed by provider and by side. Because the derivation runs pre-rounding,rate× the publishedfinal_outamount can differ from the publishedfinal_inamount in the last digit(s) when an asset's published decimals are coarser than the server's internal precision — treat that as approximate, not an exact identity. Treatrateas informational only, never for ranking: compare byfinal_outfor afrom_amountrequest, or byfinal_infor ato_amountrequest — but only among offers whosefinal_outmatches the amount you requested; not every provider honors an exact-output target, so a differingfinal_outmeans that offer bought a different amount and itsfinal_inis not comparable this way. A non-winning offer whose facts cannot produce a usable rate is omitted rather than failing the whole response.- Transak quote execution requires an explicit crypto network. When no usable offers remain for this reason,
409 capability_unavailablepreservesQUOTE_NETWORK_REQUIREDand names the missing directional network field. - Winner and per-provider offers add optional
payment_method, preserving the actual method with itsoffer_id. Unknown methods remain absent; inconsistent methods for the same winning offer are rejected. This additive field does not changeSwaps-Versionor grant execution eligibility. POST /v1/quotesexposes the winningoffer_idand safe terminal provider outcomes, preserving success across payment methods. Route facts are projected from existing evidence.- Readiness and status come from validated provider expiry; malformed upstream facts return an explicit unavailable response. Public cache hits retain their original fetch time as indicative prices.
- Authentication remains required on
/v1/quotes. Test callers are refused before provider dispatch until sandbox routing is implemented. Order creation remains proposed. GET /v1/capabilities?product=buy_sellnow publishes fresh-snapshot defaults (primary_method, optionalresolved_limits) and a bounded coverage grid carryingfiatanddirection; it never creates a quote or starts provider work. A stale or cold route snapshot returns503 temporarily_unavailable.
2026-09-05 — K1: the /v1 gateway skeleton
- New edge function
supabase/functions/api-v1behind a global kill switch (feature_flags.api_v1, off by default) — this release changes no live behaviour until an operator enables it. - First two live operations:
GET /v1/account,PATCH /v1/account, plus the newGET /v1/accountsandPOST /v1/accounts(one login, many accounts).account.get/account.updateflip fromproposedtoavailable;accounts.list/accounts.createare new andavailablefrom the start. - New tables
accounts,account_members(backfilled 1:1 from every existing account) andapi_idempotency_keys(the(key, account, route)idempotency store, 24-hour TTL sweep). - Three route-resolution gates land: fail-closed global and per-resource kill switches, business-key and bearer auth classes with fail-closed scopes, and per-class rate limiting on the distributed limiter.
Account.idnow carries theacct_prefix (see Conventions).
2026-09-04 — Contract v1 (gate [A1])
Corrected 2026-09-16
The "OAuth 2.1 + PKCE with CIMD" auth class this entry announced was never built and is now withdrawn (decision C4-D32) — see Authentication & keys and the entry above. Two live credential classes remain: business key and dashboard session, plus the public capability token.
- Resource-shaped
/v1onapi.swaps.app: payment links, clients, products, payments, subscriptions, payouts, payroll, wallet, quotes, orders, capabilities, eligibility, customers, account, activity, events, webhook endpoints, address book, screenings, traces, credits, API keys. Three auth classes (business key, OAuth 2.1 + PKCE with CIMD, public capability token); the dashboard is a first-party OAuth client.Superseded above — see the 2026-09-16 correction.Moneyobject,Idempotency-Key, cursor pagination, one error envelope,Swaps-Versiondate header,livemodeon every object, test mode by key prefix.- The status of every operation is stated in the reference (
available·dark-flag·proposed); non-available operations are documented with their reason and are not callable. - Business-key scopes fail closed: a key with
scopesnull or empty now denies every action instead of granting all of them. - Usage-log KPI columns and distributed rate limiting land on the usage-tracking table, feeding the platform KPIs.
Next: Get started to build against the current contract · Providers & coverage for what's live right now, rail by rail.