Skip to main content
The API moves money between fiat and crypto. Your customers can buy tokens with fiat (onramp), sell tokens for fiat (offramp), exchange one token for another on the same chain (swap), or send a token from one supported chain to another (cross-chain transfer). Every transfer runs between two payment accounts: a bank account or a wallet.
Use the sandbox for development. All examples in these guides work against https://api.sand.etherfuse.com. If you have not set up your sandbox account, see Initial Setup.
v2 endpoints sit at the root of the API host, with no /ramp prefix. Send your API key in the Authorization header with no Bearer prefix. See Authentication. Payment accounts, quotes and orders belong to an organization, and their routes start with /organization/{organizationId}. Webhook subscriptions belong to the organization that owns the API key. See Organizations in the route.

Conventions

Organizations in the route

Payment account, quote and order routes take the organization the call acts for as {organizationId}, for example POST /organization/{organizationId}/payment_account.
  • Use your own organization’s id, from GET /me, to act for yourself.
  • Use a customer organization’s id to act for that customer. You get it when you create the organization with POST /organization, or from GET /organizations.
  • Your API key can act for its own organization and for its direct customer organizations. Any other id returns 404 not-found.
Webhook subscriptions and GET /orders stay at the partner level and do not take an organization id. One subscription covers your organization and every customer organization. GET /orders lists the orders of your organization and all its customer organizations.

The flow

1

Register payment accounts

A payment account is a bank account or a wallet that money moves from or to. Register each one with POST /organization/{organizationId}/payment_account.A bank account states its market with country and currency, and its identifier in accountDetails:
A wallet sends only accountDetails of kind address, with its chain:
The accountDetails.kind decides which fields accountDetails carries:A missing field or an extra one returns 400 validation-error, with one entry per field in errors.Registration info. A bank account route requires holder details such as accountHolderName and taxId, and some routes need more. We fill each field from the verified identity of the organization in the route when we hold it. To register a customer’s bank account, pass the customer’s organization id. When a required field is still missing, the request returns 422 registration-info-required:
required lists every field the route needs and missing lists the ones you still have to send. Both are JSON Pointers into your request. Add the missing fields under registrationInfo and send the request again. A taxId you send must match the verified one, or you get 422 holder-tax-id-mismatch. You can also send every registration field you have: we check each value’s format and ignore the fields this route does not use. To see the lists before you register, call GET /organization/{organizationId}/payment_account/requirements.A new registration returns 201. Registering the same account again returns the account the organization already holds, with 200. If another organization holds the account, you get 409 payment-account-unavailable.A new account starts pending. We register it with the payment provider, then move each rail direction to ready or rejected. The account’s status becomes ready when any direction is ready, and rejected when every direction is rejected. To retry a rejected direction, fix the details (usually registrationInfo) and register the same account again. We send it to the provider again, and the direction goes back to pending.
2

Check the account's rails

The response lists the rails the account can move money on, keyed by rail. Each rail reports the account’s readiness per direction: source for money leaving the account, destination for money arriving.
  • A direction’s status is pending, ready, rejected or disabled. You can quote a direction only when it is ready.
  • A direction is null when the rail cannot carry money that way for this account.
  • minAmount and maxAmount appear when the rail has a known limit.
  • isDefault marks the rail a quote uses when you do not name one.
Subscribe to paymentAccountUpdated to hear when a direction becomes ready, or read the account again with GET /organization/{organizationId}/payment_account/{id}.
3

Get a quote

Price a transfer between two registered accounts with POST /organization/{organizationId}/quote. Both accounts must belong to the organization in the route. One side must be a wallet: fiat to crypto, crypto to fiat, a same-chain swap, or the same token sent to another chain.
Take each side’s asset from GET /supported_assets: a market’s asset (its currency code) for a fiat side, a token’s asset for a crypto side. Pass paymentAccountId to list only what one account can send or receive.The quote returns sourceAmount, destinationAmount, the rails each side uses, rateBeforeFees, rateAfterFees, the total fee as feeBps and feeAmount, and the fee lines in fees[]. Each fee line has a type (transactionFee, partnerFee, walletSponsor or bridge), an amount and an asset. Quotes that convert between currencies also return midMarketRate. The order created from the quote returns the same fee lines.A quote expires 2 minutes after it is created (expiresAt). Your organization must be approved to quote, or you get 409 organization-not-approved.
4

Create the order

Create the order with POST /organization/{organizationId}/order and the quoteId. The response is 201 with the full order. The order’s id is the quoteId.Sending the same quoteId again returns the existing order with 200, even after the quote has expired. Retry a timed-out request with the same quoteId and you never create a second order. See Idempotency.fundingInstructions tells the customer how to fund the order: the rail, our reference for the order, and a fundingDeadline. An order with no funds by the fundingDeadline moves to canceled. You can also cancel an unfunded order yourself with POST /organization/{organizationId}/order/{id}/cancel. fundingInstructions.destination holds where to send the funds (a CLABE on spei, a BR Code on pix, an address on a chain). It is absent until it is available. Subscribe to orderUpdated to receive the order when it arrives, or read the order again with GET /organization/{organizationId}/order/{id}.If a funding destination expires before the customer pays, call POST /organization/{organizationId}/order/{id}/funding_instructions/refresh. To cancel an order before it is funded, call POST /organization/{organizationId}/order/{id}/cancel.
5

Track the order

An order’s status is one of:orderUpdated delivers the full order on each status change. See Webhooks.Authorization. Some transfers need the account holder’s approval before money moves, for example an offramp from an embedded wallet or the claim into one. The order’s authorization then holds what to sign, and the status does not change while it waits. Sign authorization.method.message and send it with POST /organization/{organizationId}/order/{id}/authorization. authorization is null when nothing is needed.

Quote types

The two payment accounts decide the type of transfer. You do not send a type. Quote any asset to any other asset between two payment accounts: if we support the route, the quote returns a price, and if we do not, it returns an error that says why.

Rails

A rail is the network that moves the money. A bank account moves on a fiat rail (spei for MXN, pix for BRL). A wallet moves on its chain (stellar, solana, base, polygon, monad or avalanche). Each account’s rails map shows which rails it can use and in which direction. Name a rail in the quote with sourceRail or destinationRail, or leave it out to use the account’s default rail. Each chain has its own asset identifier format and setup. See the chain guides: Solana, Stellar, Base, Polygon, Monad, Avalanche.
Always discover assets with GET /supported_assets (or the public GET /lookup/stablebonds). Do not hardcode identifiers. Identifiers, especially Stellar issuers, differ between sandbox and production and can change.

KYC status

Your customers complete KYC before they transact. See the KYC overview for the flows. Read a customer’s KYC status with GET /organization/{organizationId}/kyc, and a business’s KYB status with GET /organization/{organizationId}/kyb. To hear when it changes, subscribe to kycUpdated with POST /webhook_subscription. See the kycUpdated payload.

Fees

The platform fee is 0 bps for same-currency transfers except a stablebond offramp, which pays an exit fee (5 bps for CETES). Cross-currency transfers use a volume-based FX fee. Partner, wallet setup and bridge fees may also apply. Every quote and order itemizes fees in fees[]. See Fees.

Sandbox

In sandbox, POST /organization/{organizationId}/order/{id}/fiat_received simulates the fiat deposit for an order funded from a SPEI or PIX account, so you can test an onramp without sending money. See the Sandbox Reference.

Resources

The API manages these resources:
  • Organizations (id): your organization and the customer organizations you create with POST /organization. A personal organization completes KYC; a business completes KYB.
  • Members (memberId): the people who belong to a business organization, each named by the sub in their JWT. See Add organization member.
  • Payment accounts (id): bank accounts and wallets, registered with POST /organization/{organizationId}/payment_account.
  • Quotes (quoteId): a priced transfer between two payment accounts. Valid for 2 minutes.
  • Orders (id, equal to the quoteId): a transfer in progress.
  • Webhook subscriptions: the URLs we send events to.

Migrating from v1

The V1 → V2 guide lists endpoint replacements, request field changes, order statuses and webhook changes.

Troubleshooting

See Errors for every problem type and what to do about it, and Authentication for 401 issues.