Skip to main content
Give your users a liquidation address: a persistent wallet address that accepts stablecoins and settles the proceeds as fiat into a registered bank account. Examples:
  • A liquidation address that turns incoming USDC into EUR and pays out to a Deutsche Bank account
  • A liquidation address that turns incoming USDC into GBP and pays out to a Revolut account

How it works

1
Max registers his bank account and gets a liquidation address owned by MoonPay Enterprise
2
An autoramp ties that address to his bank account: USDC in, EUR out
3
Max sends 1000 USDC to the liquidation address
4
As soon as funds hit the address, MoonPay Enterprise converts them to EUR and pays out to Max’s bank account
5
Max can transfer at any time. The address is persistent.
6
Non-supported assets sent to the address are returned to sender

Two ways to offramp

Most offramp integrations start with an exact-out quote, because the payer usually owes a precise fiat figure. Both are shown below, and a single quote-source autoramp can keep accepting new quotes over time.

Prerequisites

Every step must complete before moving to the next. Sandbox-only steps are marked. Set these in your shell first. Every command on this page reuses them.
1

Customer is Active

Your customer must have Active status: terms signed and identification (KYC/KYB) approved, with nothing outstanding in required-signings. Active alone is not always enough: check that the specific rail you need is Active in the customer’s abilities too, and subscribe to the customer_fiat_abilities webhook to track rail changes instead of polling. See Onramp for a full onboarding walkthrough and Tracking ability changes for the webhook.
2

Register a bank account

Register the destination bank account via POST /api/addresses/fiat. See Fiat Addresses. The account starts in RegistrationPending status.Save the returned fiat address ID. Quotes reference it as recipient_account_id.
3

Sandbox: approve the fiat address

In Sandbox, the fiat address stays in RegistrationPending until you manually advance it:
In production, registration is handled automatically.
4

Register crypto addresses

Register your customer’s wallet addresses via Crypto Addresses for Travel Rule compliance. Funds from unregistered wallets may be delayed for manual review.
5

Create the offramp

Either request an exact-out quote and create a quote-source autoramp, or create a standing autoramp directly. Both are shown below.
6

Sandbox: approve the autoramp

An autoramp is created in Authorized status. In Sandbox you advance it yourself, which also provisions the mock deposit account that a simulated deposit needs:
In production, an autoramp reaches Approved on its own once its deposit account is provisioned and verified.
Quote requests fail if recipient_account_id references a bank account that has not been registered or has not passed verification. Complete the Sandbox approval step above first.

Offramp with an exact-out quote

Use this when your customer owes a precise fiat amount. You ask for the output, MoonPay Enterprise tells you the required stablecoin input.
1

Request the quote

GET /api/autoramps/quoteSet amount_out to the fiat figure you need to land in the bank. Set amount_in instead if you want to price a known stablecoin amount. Never set both.
amount_in is what your customer must send: 111.19863 USDC to receive exactly 100 EUR, with the fee breakdown itemised.
2

Create the autoramp from the quote

POST /api/autorampsSubmit the signed quote payload verbatim.
The quote is digitally signed. Any modification, including changing values or omitting fields, causes the request to fail.
3

Share the deposit address

Once the autoramp is Approved, deposit_rails contains the liquidation address. Show your customer the address and the exact amount_in figure from the quote.A deposit only matches a quote when its amount_in value and source currency match. Deposits that match no active quote, or only an expired one, are returned to sender.
Staged setup. You can create the autoramp with any initial quote to surface the deposit address early (so the payer can whitelist it), then attach the real quote later via POST /api/autoramps/{autoramp_id}/quotes once the amount is known. The initial quote expires unused.

Rate locks and expiry

rate_lock_duration_minutes is optional and clamped to the cap for the pair. Stablecoin to fiat cross-currency pairs (e.g. USDC to EUR) cap at 10 minutes; pegged pairs (a USD-stablecoin to USD, a EUR-stablecoin to EUR) cap at 2 days. rate_expiry_policy decides what happens when funds arrive after the lock expires:
  • Return: validity equals the rate lock. Late funds are refunded.
  • Slippage: validity extends beyond the lock. Late funds execute if the market rate is within slippage_tolerance_in_bips, otherwise they are refunded or requoted.
See Quotes for the full parameter reference, multi-currency input, and attaching quotes over time.

Offramp with a standing autoramp

Use this when you want a persistent address that converts whatever arrives at the current mid-market rate, with no amount agreed up front. POST /api/autoramps
The Idempotency-Key header is required. Send the same key to safely retry a request without creating a duplicate autoramp.

Request fields

The account_identifier inside recipient_account uses the same payment rail discriminator as Fiat Addresses: SEPA (requires iban), ACH / Wire / FEDNOW (require routing_number + account_number), SWIFT (requires bic + account_number), CHAPS / FPS (require sort_code + account_number).

Request

Capturing the ID into $AUTORAMP_ID lets you paste the remaining commands on this page straight into the same shell.
The account_identifier you provide in recipient_account must match a bank account you’ve already registered via the Fiat Addresses API.

Response

The example above is trimmed to the fields most integrations need. The full Autoramp object also returns kind, source, recipient, customer_id, source_currencies, destination_currency, is_third_party, fee_profile_id, batch_payout, and quotes. The legacy deposit_account field is deprecated.
Shared EVM deposit address. An offramp gets one deposit address per source chain, and every EVM chain on the same autoramp shares one address, so the EVM entries in deposit_rails repeat the same string. Only the token and chain combinations listed in source_currencies are converted: a deposit arriving on an EVM chain you did not declare is not swept, even though the address matches. Declare every chain you want to accept when you create the autoramp. Non-EVM chains such as Solana still get their own distinct deposit address.

Error response

Validation failures return the error as a plain string body, not a structured object. An account_identifier that does not match a bank account already registered and verified for this customer returns 400:
A bank account that is registered but has not reached Registered status returns 422 with "recipient account has not yet been activated". Show the user their liquidation address along with the supported assets for it (e.g. EURC, USDC). Non-supported assets are returned to sender.

Test the full flow in Sandbox

Creating the is half the integration. Simulate a deposit to confirm your webhook handler and reconciliation logic work before you take real money.
1

Simulate an incoming deposit

POST /api/sandbox/transaction builds a transaction directly from the route, reusing its currencies, customer, and recipient.
The response carries amount_in, amount_out, currency_in, currency_out, and state. See Sandbox for the optional fee, fx_rate, and initial_state fields.
2

Drive the transaction to Completed

The simulated transaction starts in Pending. Advance it to trigger the rest of the status webhooks.
3

Check the webhooks you received

Your endpoint should have received a transaction event when the deposit landed, followed by transaction_status events as it moved through FundsReviewInProgress, ConversionInProgress, PayoutInProgress, and Completed.Read transaction_status, not the deprecated status field, for the current state. Payload shapes and the full status table are in Monitoring payments; signature verification is in Webhooks.
On an offramp, Completed means the payout was sent to the bank. Arrival depends on the rail: SEPA and ACH settle on their own schedules. See Transaction Status.

Simulate it in the Dashboard

Register and approve the bank account first: My Address → Linked Bank Accounts in the App view, then Developer → Sandbox → Fiat Accounts and set the entry under Unverified Fiat Accounts to Registered. Until you do, the offramp has nowhere to pay out.
You can run this same flow without writing a single API call. The Partner Dashboard covers every step, from creating the route to settling a simulated deposit.
1

Create the route

Switch the sidebar to the App view and pick your customer in the customer selector at the top. Open Operations → Routes, then click on the card and choose .
2

Authorize the route

Go to Developer → Sandbox and open the Autoramps tab. Your new route appears under Unverified Autoramps. Set its status to Authorized using the dropdown on the right.
The dropdown offers Created, Authorized, and Rejected. Pick Authorized: that provisions the mock deposit account, which is what the simulated deposit needs. Sending "Approved" to PUT /api/sandbox/autoramp/{id} provisions the same account and also moves the route to Approved, so use the API call if you want to see the Approved status your integration waits for.
3

Create the simulated deposit

Still under Developer → Sandbox, click New Transaction in the top right. On the Create New Sandbox Transaction screen:
  1. Customer: search by name, email, or ID and select the customer who owns the route.
  2. Ramp: pick it from the dropdown. This field still uses the older wording for a route. Entries are labelled by currency pair, for example .
  3. Amount: enter the deposit amount in the route’s input currency.
Click Execute Transaction. This bypasses the normal deposit flow and builds the transaction straight from the route configuration.
4

Settle the transaction

Back on the Sandbox page, open the Transactions tab. Your transaction is listed under Pending Transactions with Accept and Reject buttons. Choose Accept to complete it, or use the Change state dropdown to move it to a specific state and test how your integration reacts.
5

Check the result

Open the transaction from Operations → Transactions to see amounts, fees, and . Webhooks fire exactly as they do on the API path, so this is a good way to exercise your endpoint before you write any integration code.
Use Reset on the Sandbox page to clear all customers, wallets, fiat accounts, transactions, and autoramps and start from a clean state.

Move to production

Remove every /api/sandbox/* call from your integration. Those endpoints do not exist in production.

Quotes

Full quote reference: expiry policies, multi-currency input, attaching quotes over time

Fiat Addresses

Register bank accounts per payment rail and track registration status

Onramp

The reverse route: a named virtual account that settles into stablecoins

Transaction Status

Status mapping, settlement times, and the polling API

Webhooks

Signature verification, payload schemas, and retry behaviour

End of Day Settlement

How batched payouts and daily settlement work