> ## Documentation Index
> Fetch the complete documentation index at: https://docs.etherfuse.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How it works

> How an Etherfuse integration fits together: conventions, payment accounts, quotes, orders and webhooks.

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.

<Info>
  **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/initial-setup).
</Info>

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](/v2/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](#organizations-in-the-route).

## Conventions

| Topic | Rule |
| - | - |
| Keys | camelCase, for example `sourceAmount`. |
| Enum values | camelCase, for example `debitCard`, `pixKey`, `orderUpdated`. We reject other spellings, such as `debit_card`. |
| Amounts | Decimal strings, for example `"1000.50"`. Never JSON numbers. Basis-point fields such as `feeBps` are integers. |
| Timestamps | ISO 8601 in UTC, for example `2026-09-28T17:05:02Z`. |
| Unknown fields | Request bodies ignore fields we do not define, so we can add fields without breaking your integration. Two objects are stricter: `accountDetails` rejects fields its `kind` does not declare, and `registrationInfo` rejects names that are not registration fields. |
| Errors | [RFC 9457 problem details](/v2/errors) with content type `application/problem+json`. |
| Lists | `pageSize` and `pageNumber` query parameters and one page envelope. See [Pagination](/v2/pagination). |

### 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`](/v2/api-reference/organizations/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`](/v2/api-reference/organizations/create-organization), or from [`GET /organizations`](/v2/api-reference/organizations/list-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`](/v2/api-reference/orders/list-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

```mermaid theme={null}
flowchart LR
    A["Register payment accounts<br/>bank account, wallet"] --> B["Check each account's rails"]
    B --> C["Create a quote<br/>valid for 2 minutes"]
    C --> D["Create the order<br/>from the quoteId"]
    D --> E["Customer funds the order<br/>before fundingDeadline"]
    E --> F["We deliver to the destination"]
    D -. "orderUpdated" .-> W(["Your webhook endpoint"])
    E -. "orderUpdated" .-> W
    F -. "orderUpdated" .-> W
```

<Steps>
  <Step title="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`](/v2/api-reference/payment-accounts/register-payment-account).

    A bank account states its market with `country` and `currency`, and its identifier in `accountDetails`:

    ```json theme={null}
    {
      "kind": "bank",
      "label": "Operating account",
      "country": "MX",
      "currency": "MXN",
      "accountDetails": { "kind": "clabe", "clabe": "012180000000000002" }
    }
    ```

    A wallet sends only `accountDetails` of kind `address`, with its `chain`:

    ```json theme={null}
    {
      "kind": "wallet",
      "label": "Treasury wallet",
      "accountDetails": {
        "kind": "address",
        "address": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF",
        "chain": "stellar"
      }
    }
    ```

    The `accountDetails.kind` decides which fields `accountDetails` carries:

    | `accountDetails.kind` | Fields |
    | - | - |
    | `clabe` | `clabe`: an 18-digit CLABE with a valid check digit. |
    | `pixKey` | `pixKey`: 1 to 77 characters. |
    | `address` | `address` and `chain` (`stellar`, `solana`, `base`, `polygon`, `monad` or `avalanche`). |

    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`:

    ```json theme={null}
    {
      "type": "https://docs.etherfuse.com/ramp/v2/errors/registration-info-required",
      "title": "Registration information required",
      "status": 422,
      "detail": "required registration information was not supplied",
      "instance": "tag:etherfuse.com,2026-08-28:R2-10022/0b1c2d3e-4f50-4a6b-8c7d-9e0f1a2b3c4d",
      "required": ["#/registrationInfo/accountHolderName", "#/registrationInfo/taxId"],
      "missing": ["#/registrationInfo/taxId"]
    }
    ```

    `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`](/v2/api-reference/payment-accounts/get-registration-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`.
  </Step>

  <Step title="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.

    ```json theme={null}
    {
      "id": "3f9a2c1e-5b7d-4e8f-9a01-2b3c4d5e6f70",
      "status": "pending",
      "label": "Operating account",
      "rails": {
        "spei": {
          "currency": "MXN",
          "source": { "isDefault": true, "status": "ready" },
          "destination": { "isDefault": true, "status": "pending" }
        }
      },
      "createdAt": "2026-09-28T17:04:11.482Z"
    }
    ```

    * 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`](/v2/webhooks) to hear when a direction becomes `ready`, or read the account again with [`GET /organization/{organizationId}/payment_account/{id}`](/v2/api-reference/payment-accounts/get-payment-account).
  </Step>

  <Step title="Get a quote">
    Price a transfer between two registered accounts with [`POST /organization/{organizationId}/quote`](/v2/api-reference/quotes/create-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.

    ```json theme={null}
    {
      "source": { "type": "stored", "id": "3f9a2c1e-5b7d-4e8f-9a01-2b3c4d5e6f70" },
      "destination": { "type": "stored", "id": "8c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e" },
      "sourceAmount": "1000",
      "sourceAsset": "MXN",
      "destinationAsset": "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
    }
    ```

    Take each side's asset from [`GET /supported_assets`](/v2/api-reference/assets/list-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`.
  </Step>

  <Step title="Create the order">
    Create the order with [`POST /organization/{organizationId}/order`](/v2/api-reference/orders/create-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](/v2/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`](/v2/api-reference/orders/cancel-order). `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`](/v2/webhooks) to receive the order when it arrives, or read the order again with [`GET /organization/{organizationId}/order/{id}`](/v2/api-reference/orders/get-order).

    If a funding destination expires before the customer pays, call [`POST /organization/{organizationId}/order/{id}/funding_instructions/refresh`](/v2/api-reference/orders/refresh-funding-instructions). To cancel an order before it is funded, call [`POST /organization/{organizationId}/order/{id}/cancel`](/v2/api-reference/orders/cancel-order).
  </Step>

  <Step title="Track the order">
    An order's `status` is one of:

    | Status | Meaning |
    | - | - |
    | `created` | Waiting for the source funds. |
    | `funded` | Source funds received. |
    | `claimable` | Delivered, waiting for the customer to claim the funds. `claimInstructions` holds what they need, and [`POST /organization/{organizationId}/order/{id}/claim`](/v2/api-reference/orders/prepare-claim) prepares the claim. |
    | `completed` | Delivered to the destination. |
    | `failed` | Could not be completed. `failure` holds a `code` and a `message`. |
    | `canceled` | Canceled before completion, either by you or because the funding deadline passed. |
    | `refunded` | The source funds were returned. |

    ```mermaid theme={null}
    stateDiagram-v2
        [*] --> created: order created
        created --> funded: source funds received
        created --> canceled: you cancel, or fundingDeadline passes
        funded --> completed: delivered
        funded --> claimable: delivered, needs a claim
        claimable --> completed: customer claims
        funded --> failed: could not be completed
        failed --> refunded: source funds returned
        completed --> [*]
        canceled --> [*]
        failed --> [*]
        refunded --> [*]
    ```

    `orderUpdated` delivers the full order on each status change. See [Webhooks](/v2/webhooks).

    **Authorization.** Some transfers need the account holder's approval before money moves, for example an offramp from an [embedded wallet](/v2/guides/embedded-wallets) 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`](/v2/api-reference/orders/submit-authorization). `authorization` is null when nothing is needed.
  </Step>
</Steps>

## 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.

| Source | Destination | Transfer |
| - | - | - |
| Bank account | Wallet | Onramp: the customer buys tokens with fiat. |
| Wallet | Bank account | Offramp: the customer sells tokens for fiat. |
| Wallet | Wallet on the same chain | Swap: the customer exchanges one token for another. |
| Wallet | Wallet on another chain | Cross-chain transfer: the customer sends a token from one supported chain to another. |

```mermaid theme={null}
flowchart TD
    S{"Source account"} -- "bank account" --> SB{"Destination account"}
    S -- "wallet" --> SW{"Destination account"}
    SB -- "wallet" --> ON["Onramp"]
    SB -- "bank account" --> X1["Not supported:<br/>fiat to fiat"]
    SW -- "bank account" --> OFF["Offramp"]
    SW -- "wallet, same chain" --> SWAP["Swap"]
    SW -- "wallet, other chain,<br/>same token" --> CC["Cross-chain transfer"]
    SW -- "wallet, other chain,<br/>different token" --> X2["Not supported:<br/>cross-chain swap"]
```

## 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](/v2/guides/chains/solana), [Stellar](/v2/guides/chains/stellar), [Base](/v2/guides/chains/base), [Polygon](/v2/guides/chains/polygon), [Monad](/v2/guides/chains/monad), [Avalanche](/v2/guides/chains/avalanche).

<Tip>
  **Always discover assets with [`GET /supported_assets`](/v2/api-reference/assets/list-supported-assets)** (or the public [`GET /lookup/stablebonds`](/v2/api-reference/lookup/list-stablebonds)). Do not hardcode identifiers. Identifiers, especially Stellar issuers, differ between sandbox and production and can change.
</Tip>

## KYC status

Your customers complete KYC before they transact. See the [KYC overview](/v2/guides/onboarding) for the flows.

Read a customer's KYC status with [`GET /organization/{organizationId}/kyc`](/v2/api-reference/kyc/get-kyc-status), and a business's KYB status with [`GET /organization/{organizationId}/kyb`](/v2/api-reference/kyb/get-kyb-status). To hear when it changes, subscribe to `kycUpdated` with [`POST /webhook_subscription`](/v2/api-reference/webhook-subscriptions/create-webhook-subscription). See the [`kycUpdated` payload](/v2/api-reference/webhooks/kycupdated).

## 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](/v2/fees).

## Sandbox

In sandbox, [`POST /organization/{organizationId}/order/{id}/fiat_received`](/v2/api-reference/sandbox/simulate-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](/v2/test-environment).

## Resources

The API manages these resources:

* **Organizations** (`id`): your organization and the customer organizations you create with [`POST /organization`](/v2/api-reference/organizations/create-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](/v2/api-reference/members/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](/v2/migration-guide) lists endpoint replacements, request field changes, order statuses and webhook changes.

## Troubleshooting

See [Errors](/v2/errors) for every problem type and what to do about it, and [Authentication](/v2/authentication) for `401` issues.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.