Not what you’re looking for? Conversion rules 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 persistentfrom → to 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 thefrom (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.
from.bank_account with the provisioned virtual IBAN and BIC:
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_rule_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.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 abank_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 conversion rules 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 destinationtag, or the memo used by chains such as XLM, EOS, and Cosmos:
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 forfrom.wallet.via:
Rule lifecycle
Deleting a rule is a soft delete: after
DELETE /v1/accounts/{account_id}/conversions/{conversion_rule_id}, the rule no longer fires and a subsequent GET returns 404 Not Found. Deleted rules are never returned by listConversionRules.
Managing a rule
Update a rule’s destination, label, or status withPUT /v1/accounts/{account_id}/conversions/{conversion_rule_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 withGET /v1/accounts/{account_id}/conversions/{conversion_rule_id}/transactions:
Webhooks. Subscribe to conversion webhook events for real-time updates instead of polling — register your endpoint
with the Register Webhook API. See Conversion
webhooks.
listConversionRules supports only status, page_size, and page_token (cursor) parameters. Use the per-rule transactions endpoint above for transaction history.
Conversion webhooks
Conversion lifecycle webhook payloads include optional enrichment fields. They are omitted when the source event does not carry the corresponding fact.error_code, when present, is one of: amount_too_small, quote_expired, destination_rejected, wallet_not_whitelisted, wallet_verification_method_not_supported, travel_rule_data_missing, or unknown.
destination_address is the crypto destination only. It is never a fiat destination, tag, or memo.
chain_references.blockchain_transaction_id is the on-chain transaction id or hash for a completed crypto withdrawal. It is distinct from withdraw_txid.
Resolving a Travel Rule block
conversion.travel_rule_blocked means a pre-trade Travel Rule check stopped the conversion before any quote was executed, because the destination wallet needs address-ownership verification (or the check could not be evaluated). The error_code tells you how to resolve it:
wallet_not_whitelisted— the destination address is not yet ownership-verified. Whitelist it withPOST /v1/users/{user_id}/travel-rule/verifications(hosted declaration or self-attestation); once the address is verified, subsequent conversions to it will proceed.wallet_verification_method_not_supported— the address requires Satoshi-test or digital-signature verification, which is not available via the API yet. Contact Support to resolve.travel_rule_data_missing— eligibility could not be evaluated because required account information (for example, the user’s country) is missing. Complete the user’s profile and retry; contact Support if the block persists.
Whitelisting external wallets
Before a conversion can pay out to an external crypto address, that address must pass a Travel Rule address-ownership check. Whitelist it withPOST /v1/users/{user_id}/travel-rule/verifications: use method: hosted_declaration when the wallet is held by another custodial service, or method: self_attestation (supplying the end user’s ownership evidence) when the end user controls the wallet.
Error handling
Missing or invalid off-ramp bank details
When you create or update an off-ramp rule (crypto → fiat), theto.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.
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.