Skip to main content
The Conversions API enables automated fiat ↔ crypto flows using regulated banking rails. It is designed for partners building wallet-native or payments products who want to offer their users a seamless on- and off-ramp experience — without relying on card widgets or assembling multiple providers for KYC, rails, conversion, and settlement. The core primitive is a conversion rule: a persistent per-account configuration that provisions a dedicated inbound endpoint (a named virtual bank account for fiat deposits, or a deposit address for crypto deposits) and automatically converts and settles any matching deposit to the destination you have defined. Each time a matching deposit lands, the rule fires and produces a conversion transaction.
Not what you’re looking for? Conversions handle asset exchange with automatic fund movement across rails. If you need to move funds between accounts in the same asset without conversion, see Transfers. To execute an institutional FX-style trade against a locked quote, see Swap. For fiat-to-crypto purchases via a hosted checkout UI, see Ramp.

How it works

A conversion rule is a persistent fromto configuration scoped to a PWS account (identified by its IIBAN). When you create a rule, PWS provisions a dedicated inbound endpoint — a virtual IBAN for fiat, or a deposit address for crypto — and returns it on the rule’s from side. You share those payment instructions with your end customer. Every matching credit to that endpoint automatically triggers a conversion and settles the converted asset to the rule’s to destination. Rules remain active and reusable as long as the account’s KYC profile is valid. There is no per-transaction “one-shot” endpoint — all conversions flow through a standing rule.

Creating a rule — fiat on-ramp (EUR → USDC on Polygon)

An end customer sends EUR via SEPA and automatically receives USDC on Polygon at the destination wallet you specify. On the from (source) side you supply only symbol, type, and the rail (bank_account.via / wallet.via). The inbound routing is provisioned by PWS and returned on the response — any routing fields you send on from are ignored.
The response is the created rule. PWS populates from.bank_account with the provisioned virtual IBAN and BIC:
Share from.bank_account.iban and from.bank_account.bic with the end customer as their payment instructions. Every SEPA credit to that IBAN fires the rule and produces a new conversion transaction.
Provisioning: For some fiat rails the inbound account is provisioned asynchronously. The rule is returned with status: provisioning and the from routing fields are populated once provisioning completes (the rule then moves to active). Fetch the rule with GET /v1/accounts/{account_id}/conversions/{conversion_id} to read the final routing.

Creating a rule — crypto off-ramp (USDC on Ethereum → EUR via SEPA)

An end customer sends USDC on Ethereum and automatically receives EUR via SEPA to the bank account you specify.
On the response, from.wallet.address carries the provisioned Ethereum deposit address. Share it with the end customer — any USDC deposit to that address triggers the rule and initiates the SEPA payout.
Fiat destinations: The beneficiary on a fiat destination is the account’s KYC identity — third-party bank accounts are not permitted. The beneficiary is derived from KYC, so there is no beneficiary field on the request.

Supported rails and networks

Bank destinations specify a bank_account.via and the routing fields that rail requires; crypto destinations specify a wallet.via (network) and address: The optional bank field on any bank rail is the name of the beneficiary’s financial institution; it is passed through to the underlying provider. The Conversions API currently supports sepa and fps bank destinations. An unsupported via returns 400.
Only SEPA and FPS bank destinations are currently supported. Unsupported bank rails, including ACH and Fedwire, are rejected with 400.

Crypto destination tags and memos

Networks that need a destination tag or memo to route a deposit accept them as optional fields on the wallet destination — the XRP destination tag, or the memo used by chains such as XLM, EOS, and Cosmos:
Omit tag / memo for networks that don’t use them.

Multi-network support

A rule matches a single asset on a single network. Accepting the same asset on multiple networks requires one rule per network. Each rule gets its own provisioned inbound address and can be paused or deleted independently — keeping reconciliation and per-network reporting straightforward. To accept USDC on Ethereum and Polygon, create two rules identical except for from.wallet.via:

Rule lifecycle

Deleting a rule is a soft delete: after DELETE /v1/accounts/{account_id}/conversions/{conversion_id}, the rule no longer fires and a subsequent GET returns 404 Not Found. Deleted rules are never returned by listConversions.

Managing a rule

Update a rule’s destination, label, or status with PUT /v1/accounts/{account_id}/conversions/{conversion_id}. This is a full-state replacement of to, name, and status — the source side (from) is immutable, so re-create the rule to change input symbol, rail, or source type. status must be active or paused.

Monitoring conversions

Each firing of a rule is a conversion transaction. List the transactions for a rule with GET /v1/accounts/{account_id}/conversions/{conversion_id}/transactions:
A transaction moves through the following states:
Webhooks. Subscribe to conversion webhook events for real-time updates instead of polling — register your endpoint with the Register Webhook API.
You can also list an account’s rules and filter by lifecycle state:
listConversions supports only status, page_size, and page_token (cursor) parameters. Use the per-rule transactions endpoint above for transaction history.

Whitelisting external wallets

Register a reusable external wallet address on an account with POST /v1/accounts/{account_id}/wallets. Set verification_method to self-hosted when the end user controls the wallet, or hosted when the wallet is held by another custodial service. This makes the address eligible for Travel Rule checks used by conversions.
If the address requires a verification flow that this endpoint does not support, such as Satoshi test or digital signature verification, the API returns 409 Conflict with wallet_additional_verification_required.

Error handling

Missing or invalid off-ramp bank details

When you create or update an off-ramp rule (crypto → fiat), the to.bank_account fields are validated against the chosen rail before the rule goes live. If a field the rail requires is missing or malformed, the request is rejected with 400 Bad Request and code: conversion_invalid_withdrawal_address. Each causes[] entry names the offending field with a to.bank_account.* path so you know exactly which value to supply.
The exact set of required fields depends on the rail and the provider routing the payout. Fields listed as optional in Supported rails and networks may still be required for a specific route — for example, some SEPA payout providers require bic. Populate every field the rejection names and resubmit.

API reference

Not yet available. The following are planned but not exposed by the API today: a fee breakdown / executed rate / swap-quote on conversions, a structured failure_reason, idempotency keys, and an expires_at on inbound deposit addresses.