- Part 1, the concept. Read this first. v3 is a remodel, not a rename: if you map old endpoints one-to-one you will fight the API. Ten minutes here saves days later.
- Part 2, the API. Endpoint-by-endpoint mapping, request examples, state machines, and a migration checklist.
api.deprecation webhook event for sunset notices.
Contents. Part 1: 1.1 why v3 exists, 1.2 object model, 1.3 per-capability readiness, 1.4 task loop, 1.5 money movement, 1.6 state machines, 1.7 conventions, 1.8 golden path. Part 2: 2.1 customers, 2.2 capabilities, 2.3 tasks and submissions, 2.4 accounts, 2.5 recipients and destinations, 2.6 quotes and transfers, 2.7 webhooks, 2.8 sandbox, 2.9 legacy endpoints, 2.10 migration order, 2.11 gotcha checklist.
Part 1, the concept
1.1 Why v3 exists
v1 and v2 grew four overlapping ways to make a customer payment-ready:/rails, /banks, /accounts/applications, and the business rail-applications surface, each with its own status vocabulary. Document collection (/documents, KYC imports, verification SDK tokens) was disconnected from the thing it actually unblocked. v3 collapses all of it into six resources, the customer plus five things it owns:
1.2 The object model
Two structural rules to internalize:- Capabilities gate everything. Accounts are provisioned under a
readycapability; quotes are priced against a capability. Onboarding equals getting the capabilities you need toready. - Tasks attach anywhere. A capability, an account, or an in-flight transfer can carry
openTaskIds. Wherever you see them, the loop is the same: read task, submit answers, wait for review, re-read the parent.
1.3 Readiness is per capability, not per customer
v1 tangled/rails readiness with a customer-wide KYC gate. In v3 there is no customer status: a customer can be fully usable on stablecoin_transfers while their sepa capability still has open tasks. Pooled-account capabilities generally go ready faster than named ones, so start transacting on what is ready instead of waiting for everything.
If your v1 or v2 code drives UI badges off customer verification status, rewrite it:
- “Can they transact on X?” becomes capability X
status == "ready". - “Do they need to do something?” becomes any task with status
action_required(the capability typically showsrestrictedwithstatusReason.resolution: "complete_tasks"). - “Are we waiting on Swipelux?” becomes tasks
in_review, capabilitypending.
1.4 The task loop
Everything the old document and KYC surface did is now this one loop: Key properties:- A task carries
requirements[], the individual asks. Each has a per-taskrequirementId, a stablekeynaming the ask (for example proof of address, deduplicate your UI by it), and a typedrequestdescribing exactly what input is wanted (text, date, select, document, attestation, and so on). - Submitting is review-gated: it never mutates capability or account state directly, acceptance does. One exception:
profileanswers write through to the customer profile on submit (2.3). After submitting, poll the task or the parent resource. taskRevision(echo of the task’srevision) is a concurrency guard: if the task changed since you read it, re-read and rebuild your answers.absenceis a first-class answer (“I don’t have this because …”), use it instead of leaving requirements dangling.
1.5 Money movement
One flow for payins, payouts, and stablecoin moves. There is no direction input, you never declare payin vs payout. The in and out currency shapes derive a read-onlydirection on the quote and transfer: fiat_to_stablecoin (payin), stablecoin_to_fiat (payout), or stablecoin_move.
1.6 One state machine per resource
Every status-bearing resource has its own enum, and every non-happy status carries a structured reason. Accounts, applications, and transfers share the shape{ code, message, actor, retryable }: accounts and applications expose it as statusReason, transfers as stateDetail. actor says who must act (customer, developer, provider, network, swipelux), retryable says whether retrying can help. Capabilities use { code, resolution, message }, where resolution (complete_tasks, wait, contact_support, none) says what moves the capability forward. code values are an open, append-only catalog: branch on resolution (or actor plus retryable), and tolerate codes you have never seen.
States this guide does not walk through (
rejected, suspended, disabled, failed, canceled) are terminal or support-driven; per-resource definitions are in the spec.
Transfer, drawn out:
1.7 Conventions
Idempotency rules worth internalizing before writing code:
- Reusing a key with a different body is a
409 idempotency_conflictfor as long as the key is retained (at least 7 days), so never plan to reuse a key. Generate a fresh UUID per logical operation and persist it with your job. - Replay covers errors too: if the original request ended in a terminal 4xx, the same key plus body returns that same problem response again.
- Two concurrent requests with the same key: one wins, the other gets
409. Retry the loser after the winner settles; the replay returns the original response.
1.8 The golden path
Part 2, the API
2.1 Customers
Create, discriminated on
type (illustrative values, field names per spec):
- Creation is progressive:
{ "type": "individual" }alone is a valid create. Missing facts never invalidate the customer, they surface later as intake tasks on the capabilities that need them. - Businesses carry
businessplus registration data. v1’s shareholder CRUD maps onto related parties, widened to cover directors, officers, and owners: create them inline at customer creation (each gets a stablerp_id) or manage them through the dedicated related-parties endpoints. - No customer
statusfield, see 1.3. - Existing customers carry over: customers created on v1 or v2 are addressable by the same id on v3 endpoints. The v3 read is a sanitized view, legacy values that fail v3 validation come back absent. After your first v3 write that view becomes permanent: absent values do not come back on their own. So enrich early, budget a one-time pass that
PATCHes the full profile in from your own records before relying on v3 reads. v1metadatais a separate namespace and is not carried over, re-set it on v3. externalIdis first-class and unique across your customers on v3, per environment (409 duplicate_external_id). Archiving a customer does not release itsexternalId, clear it by PATCH before DELETE if you intend to reuse it.DELETEis an archive cascade (no restore; ids never reused). It is blocked with409 customer_has_active_resourcesplusblockingResources[]while any non-archived account or in-flight transfer exists.- PATCH merge rules: explicit
nullclears a nullable field, arrays replace wholesale (except inline related parties, which upsert by id),metadatakeys merge. Full schemas and list filters are in the OpenAPI spec.
2.2 /rails, /banks, applications become Capabilities
- A capability equals
method(ach,wire,rtp,pix,sepa,swift,spei,pse,transfers_3_0,faster_payments,sepa_instant,uaefts,card,stablecoin_transfers, and so on) plusaccountType(pooledornamed,nullfor non-bank methods) plusdirections(payinorpayout). The publiccapabilityIdis the qualified pair (sepa_pooled,ach_named) or the bare method forcardandstablecoin_transfers. - Each capability request spawns an application, the per-attempt record under
.../capabilities/{capabilityId}/applications(plus/{applicationId}/history), with its own statuses (1.6) andstatusReason. It is the audit trail of a request; day-to-day, poll the capability itself. capabilities/supportedreturns availability (available,beta, ordisabled), eligibility, and institutions on offer. Bank selection happens at request time via the optionalinstitutionsarray, there is no separate/banksresource. Omitting it (or sending[]) selects every default institution;isDefault: trueis a customer-and-capability-specific flag, not a global one. A non-empty list overrides the defaults, and a bank-backed capability with no applicable default returns422 capability_institutions_required. Institution ids are opaque, tolerate new ones.stablecoin_transfersis auto-granted at customer creation and bornready(so it is never requested and not cancelable).cardis individual-only.openTaskIdson the capability is your “what do I do next” pointer. Open equalsaction_requiredorin_review, and the rollup includes shared customer-level tasks reached through active dependencies.cancelworks only frompendingorrestrictedand with no blocking resources, otherwise409 capability_not_cancelable, whose problem body listsblockingResources. Re-requesting after cancel is a fresh create with a new idempotency key.- Poll the GET. Capability state refreshes when you read it; poll
GET .../capabilities/{capabilityId}or subscribe tocapability.status_changed, do not cache. - Requesting one method can make related methods available at once, treat capabilities as a set you re-read, not a single row you track.
- Use
tasks-previewto show onboarding asks before committing to a request. - Verification is not one-time: new tasks can appear on an already-
readycapability (periodic or event-driven re-verification). Keep the task loop wired for the whole customer lifetime, not just onboarding.
2.3 Documents and KYC become Tasks and Submissions
Naming note. These endpoints briefly shipped as
requirements and fulfillments. Since 2026-08-02 the public names are tasks and submissions. The rename covered the resources and endpoint paths only, the requirements[] array inside a task and its requirementId keep those names.GET /v3/customers/{customerId}/tasks, answer with POST .../tasks/{taskId}/submissions.
Around that loop:
- Raw file storage:
POST/GET/DELETE /v3/customers/{customerId}/documents(plus/{documentId}), upload once with your API key, then reference document ids in submission answers. This replaces every upload-token and direct-upload intake. - Net-new reads:
GET /v3/tasks(merchant-wide inbox),GET /v3/transfers/{transferId}/tasks,GET .../tasks/{taskId}/history,GET .../tasks/{taskId}/submissions(plus/{submissionId}).
- Answer types:
profile,text,date,single_select,multi_select,boolean,attestation,document,resource_reference,absence. Each requirement’srequestobject tells you which type it expects. - A submission must answer every actionable requirement in the current round, with the exact
taskRevisionyou read. Partial submissions are rejected. profileanswers write through: they update the customer profile via the normal validation path and immediately re-evaluate every capability referencing the same intake work. Sibling intake tasks whose requirements are all satisfied close automatically.- Requirements can form alternative groups (
alternativeKey): submit exactly one of the group. changes_requestedbumpsremediationRoundand carriesreviewFeedback. Re-read the task, submit again with a fresh idempotency key.- Hosted verification URLs appear only on customer-scoped task detail (
GET /v3/customers/{customerId}/tasks/{taskId}) and only while the session is actionable; lists andGET /v3/tasks/{taskId}are deliberately URL-free. - Terms of service is a task too:
openTaskIdscan include acategory: "terms_of_service"task whose hosted acceptance page is linked the same way (customer-scoped detail only). Generic submissions cannot accept terms, and KYC approval never implies terms acceptance. - Tasks are scoped per capability, so the “same” ask (for example proof of address) can appear once per capability. Deduplicate in your UI by requirement
key. - No translation layer: posting to
/v1/documentswill not unblock v3 capabilities. Once a customer is on v3, drive all asks through tasks.
2.4 Accounts and wallets
Create, discriminated on
origin plus type. Issued bank accounts take a single method; external bank accounts take a methods array instead (sending method there is rejected):
countryon issued bank accounts is optional (defaulted per method); on external bank accounts provide it explicitly. Wallet accounts carry no country at all.settlement.accountIdis required on issued bank accounts: it names the issued wallet account that receives settled funds from deposits into the bank account.- Issued accounts expose
details(IBAN or routing plus account or address), versionedrouting(deposit coordinates can rotate, always render the latest read),fees,balances. - Networks:
polygon,ethereum,base,arbitrum,optimism,bsc,avalanche. - The capability gate applies to issued accounts only: creating one against a non-ready capability fails with a capability-coded error, request the capability first (2.2). External accounts need no capability (and no customer approval); they get request-schema and bank-detail validation only.
- Issued bank accounts are born
provisioningwithdetails: null. Poll the account or watchaccount.status_changeduntilready. DELETEarchives, never hard-deletes. Accounts referenced by in-flight transfers return409 account_has_active_transfers. Retry after those transfers reach a terminal state.- Net-new in v3: Rules, standing instructions on an issued wallet account (
POST/GET /v3/customers/{customerId}/rules,GET/PATCH/DELETE .../rules/{ruleId}) that auto-sweep incoming funds to another account or a wallet destination. No v1 or v2 equivalent.
2.5 Recipients and destinations
v2 had no recipient concept. If you are on v2 and pay out to third parties, this is new surface, not a rename.
- Recipient equals who:
individual(first and last name) orbusiness(company name), with requiredrelationship(employee,contractor,vendor,subsidiary,merchant,customer,landlord,family,other). Recipients and destinations are for third parties only. A first-party payout does not use a recipient at all: target one of the customer’s ownacc_accounts as the quotedestinationId(2.6). - Destination equals where: typed per method,
sepa(iban, bic optional),achorwire(routing plus account),swift(full coordinates plus optional intermediary),spei(clabe),pse,transfers_3_0(cbu), and so on, plus wallet destinations. Each destination has its own status. Watchdestination.status_changed. - Fiat destinations require the recipient’s complete
address(street, city, postal code, country) before creation. Missing pieces fail with422 recipient_address_required. Wallet destinations skip the address but require top-levelownership(self_custodied, orcustodialwith a custodian name). - Beneficiary-name accuracy matters: receiving banks match the account’s legal name. Send exact legal first plus last name or company name, not a display nickname.
- Per-method destination field schemas are in the OpenAPI spec.
2.6 Quotes and transfers
destinationIdtakes anacc_(customer-owned account) ordst_(recipient destination) id. Fiat-funded quotes (payins) must target anacc_account, adst_target always means a payout (422 quote_direction_invalidotherwise).externalIdon quotes and transfers is a non-unique correlation reference (echoed on reads, filterable on lists). The per-environment uniqueness rule (2.1) applies to customerexternalIdonly.- Execute a quote exactly once, before
expiresAt. An expired quote fails with409 quote_expired, a second execution with409 quote_already_executed(the problem carries the existingtransferId). - Transfer cancellation is not yet supported:
POST .../cancelreturns409 transfer_not_cancelablein every state. Today’scanceledtransfers come from the funding window expiring on an unfunded payin, not from this endpoint. - Payins start
awaiting_funds: renderGET .../instructionsto the payer, bank coordinates plus reference or memo code for fiat, deposit address for crypto. The reference code is how the deposit is matched. Always display it. stateplusstateDetailfor machine-readable substates;action_requiredmeans a compliance task is attached (openTaskIds,GET .../tasks), answer via submissions.- Inbound deposits detected on issued accounts appear as transfers with
origin: "inbound_deposit"(vs"quoted"). - Payment-network references consolidated under
references:transactionHash,traceNumber,imad,uetr,explorerUrl,returnedTransferId.
Two migration warnings:
- Transfers do not cross versions. Transfers created on v1 or v2 are not readable from v3. The list omits them and
GET /v3/transfers/{transferId}404s. Cut over creation first, keep the v1 read path until those transfers reach terminal states, then drop it. - No token swaps.
stablecoin_moverequires the same currency in and out: USDC to USDT fails with422 recipient_destination_invalidcarrying acurrency_mismatchfield error. Same network on both sides, no bridging, and wallet-to-wallet moves currently support zero-fee delivery only: a quote whose platform or developer fee is non-zero fails with422 amount_not_deliverable.
2.7 Webhooks
Event catalog:
customer.created, customer.updated, customer.archived, capability.created, capability.status_changed, application.status_changed, recipient.status_changed, destination.status_changed, account.created, account.status_changed, account.details_changed, transfer.created, transfer.state_changed, api.deprecation.
GET /v3/webhooks/portalreturns a hosted management-portal URL for delivery logs, retries, and manual replay.transfer.createdis currently delivered with the legacy v1 payload shape (the v3 envelope activates when v1 webhooks sunset). Treat it purely as a hint and GET the transfer; do not build against its body.- Events are hints: on receipt, GET the resource and act on the read. Never build state off event payloads or ordering. Delivery is at-least-once and may be delayed or reordered. Deduplicate by event id, and recover missed events with each list’s inclusive
updatedAfterfilter. - Subscribe to
api.deprecation, the machine channel for version sunsets. - No
task.*event today: after submitting, poll the task or its parent.
2.8 Sandbox
Same base URL; the sandbox API key selects the environment.
The v3 sandbox simulates the review loop end to end: create a task, submit against it,
review it to accepted or rejected, watch the capability unblock. Rehearse your remediation UX before production. Both sandbox-created tasks and the regular intake tasks that appear on requested capabilities are reviewable this way; as in production, no task webhooks fire, poll (2.7).
2.9 Legacy endpoints with no v3 replacement
These have no v3 replacement. Most stay on v1 unchanged (keep your existing calls); two are retired outright (see Disposition):
Every other public v1 or v2 endpoint appears in a mapping table above.
2.10 Suggested migration order
Each step ships independently; v1 or v2 and v3 run side by side against the same customer base. Rehearse every step against your sandbox key (2.8) before repeating it in production.1
Plumbing
Idempotency-Key on all effectful requests (POST, PATCH, PUT, DELETE; sandbox endpoints exempt); money as strings; cursor pagination helpers.2
Webhooks
Register v3 endpoints per event, including
api.deprecation. The v1 single-endpoint config is a separate surface, leave it in place; the two run side by side until the drain in step 9.3
Profile enrichment
PATCH /v3/customers/{id} with the full profile you hold (v1 collected less than v3 exposes) and re-set metadata. Make this deliberately the first v3 write per customer: it fills the sanitized view before that view becomes permanent (2.1).4
Reads
Point customer, capability, and account reads at v3; rewrite customer-status logic per 1.3. Only after step 3, unenriched reads come back with legacy-invalid fields absent.
5
Onboarding writes
Create via
POST /v3/customers; request capabilities instead of /rails, /banks, or applications; build the task loop (largest net-new UI work, tasks-preview helps show asks upfront). From this point, stop posting /v1/documents for v3-driven customers, they do not unblock capabilities (2.3).6
Accounts
Issue via v3; move imports to
origin: external.7
Payouts
Recipients plus destinations, then quote and transfer.
8
Payins
Quote, transfer, instructions; keep rendering the reference code.
9
Drain
Transfers do not cross versions (2.6). Keep the v1 or v2 read path and the v1 webhook endpoint for transfers created there, dual-read until they reach terminal states, then drop the old client and the v1 webhook config.
2.11 Gotcha checklist
- Fresh UUID per logical operation, persisted with your job and reused on retry; never reuse a key with a changed body (
409 idempotency_conflict). Sandbox endpoints are exempt from the header. - Enrich existing customers (
PATCHthe full profile, re-setmetadata, it does not carry over) before any other v3 write, the first v3 write makes the sanitized view permanent. -
externalIdis unique per environment and not released by archive, clear it byPATCHbeforeDELETEif you plan to reuse it. - No customer
statusfield exists, derive readiness per capability. -
action_requiredandin_reviewboth mean an open task. - Submissions are review-gated (submitting is not the same as unblocked) and must answer every actionable requirement with the exact
taskRevision. On mismatch, re-read and rebuild. - No
task.*webhook exists, poll the task (or its parent) after every submit. -
changes_requestedretry equals re-read the task, fresh answers, fresh idempotency key. - Posting to
/v1/documentsnever unblocks a v3 capability, once a customer is on tasks, drive every ask through tasks. - New tasks can appear on an already-
readycapability, keep the task loop wired after onboarding, not just during it. - Capability
cancelworks only frompendingorrestrictedwith no blocking resources (409 capability_not_cancelable); re-requesting after cancel is a fresh create with a fresh idempotency key. - Capability must be
readybefore issuing accounts under it or quoting against it. - Quote has amount on exactly one side; no direction field; execute exactly once before
expiresAt(409 quote_expiredor409 quote_already_executed). - Stablecoin moves are same-currency, same-network only (USDC to USDT fails
422); wallet-to-wallet supports zero-fee delivery only. -
DELETEarchives, never hard-deletes. Account delete is blocked by in-flight transfers (409 account_has_active_transfers); customer delete additionally by any non-archived account (409 customer_has_active_resources,blockingResources[]names them). - v1 or v2 transfers are invisible to v3 reads (list omits, GET 404s), dual-read until drained, then drop the old paths.
- Deposit
routingand instructions can rotate, always render the latest GET, and always show the reference code. - Webhooks are hints; GET is truth, deduplicate by event id, recover missed events with
updatedAfter.