kind: Offramp. See Autoramp for how Autoramps work in general.
Examples:
- An address that turns incoming USDC on Base into EUR and pays it to a German IBAN
- An address that turns incoming USDC on Solana into USD and pays it to a US account over ACH
- An address that pays a supplier’s bank account abroad over SWIFT
How it works
Register the bank account
POST /api/addresses/fiat, or POST /api/addresses/pix for BRL. Wait until it’s Registered.Create the payout Autoramp
POST /api/autoramps with a stablecoin source, a fiat destination, and the registered account as recipient_account.Share the deposit address
Approved, deposit_rails holds the deposit address. Show it to your customer with the tokens and chains the Autoramp accepts.Stablecoins arrive, fiat goes out
Coverage
abilities.fiat_payout from GET /api/customers/{customer_id}/abilities before you register an account. For example, abilities.fiat_payout.usd.swift_thirdparty tells you whether the customer can pay a third party over SWIFT. See Ability Status.
RTP is listed in the abilities response, but no provider supports RTP payouts, so it’s never Active in production. Sandbox reports it as Active. EUR and GBP have no SWIFT payouts.
Standing Autoramp or exact-out quote
Before you create a payout Autoramp
The customer is Active
status is Active: terms signed, identification approved, and nothing outstanding in GET /api/customers/{customer_id}/required-signings. Otherwise POST /api/autoramps returns 403 with "Customer is not active". See Onboarding.The rail is Active for this customer
Active customer can still be waiting on a specific rail. Read GET /api/customers/{customer_id}/abilities and create the Autoramp only once its rail reads Active. A rail that isn’t Active yet returns 422 with "Customer is unable to complete the action. Consult the customer abilities API". Subscribe to the customer_fiat_abilities webhook instead of polling. See Ability Status.The recipient is registered
recipient_account:- Wallet: see Crypto Addresses. An unknown wallet returns
400with"Recipient wallet not verified for customer". - Bank account: see Fiat Addresses. It must reach
Registered. An account that isn’t registered yet returns400with"recipient fiat account not verified".
Bank account registration
A bank account starts inRegistrationPending and moves to Registered once a payout provider accepts it. Only Registered accounts work as a recipient_account.
Registered: ready for payoutsRegistrationPending: still registering. Some accounts register right away, others wait for the payee to be activatedRegistrationFailed: no provider accepted the account, or registration kept failing for more than 24 hours. Register a corrected account
ownership_verified on the account is true only for an IBAN that MoonPay Enterprise already holds on an internal verified list. It’s false for every other account, including every non-SEPA account, and doesn’t affect registration.
Each status change fires the register_fiat_address_status webhook. Every currency page lists the exact fields and validation for its rail. See Fiat Addresses for listing and deleting accounts.
The sending wallet
Your customer doesn’t have to register the wallet they send stablecoins from, but registering it changes what happens to large deposits and returns:- Under €1,000: a deposit from a wallet you never registered passes the Travel Rule check automatically, if nothing else flags it
- €1,000 or more: the deposit goes to AML review, unless the sending wallet is a self-hosted wallet you registered, or the sending institution’s Travel Rule data matches. See Travel Rule
- Returns: a deposit MoonPay Enterprise can’t process goes back to the sender automatically only when the wallet was registered with a signature. See Verify a self-hosted crypto address. Otherwise the return waits for a manual review
Payout with an exact-out quote
Use this when your customer owes a precise fiat amount. You ask for the output, and MoonPay Enterprise tells you the stablecoin input. Set these in your shell first. Every command on this page reuses them.Request the quote
GET /api/autoramps/quoteSet amount_out to the fiat amount you need to land in the bank. Set amount_in instead to price a known stablecoin amount. Never set both. recipient_account_id is the ID of the registered bank account.Example response
Example response
fee object above is trimmed. See Fees and Invoices for every field.amount_in is what your customer must send: 111.19863 USDC to receive exactly 100 EUR, with the fees itemized.Create the Autoramp from the quote
POST /api/autorampsSubmit the signed quote payload verbatim. Set the payment reference, swift_purpose_code, and batch_payout on the quote request in step 1: they’re part of the signature, and adding them here returns 400 with "Invalid signature".Share the deposit address and amount
Approved, deposit_rails contains the deposit address. Show your customer the address and the exact amount_in.A deposit matches a quote only when its amount and source currency equal the quote’s amount_in. A deposit that matches no active quote, or only an expired one, is returned under the sending wallet rules above.POST /api/autoramps requires an Idempotency-Key header. A missing key returns 400. Only successful responses are cached, so a retry after an error runs again. See Idempotency.Rate locks and expiry
rate_lock_duration_minutes is optional and is capped for the pair. A stablecoin to a different fiat currency (for example USDC to EUR) locks for up to 10 minutes. A stablecoin to its own currency (a USD stablecoin to USD, a EUR stablecoin to EUR) locks for up to 2 days.
rate_expiry_policy decides what happens when funds arrive after the rate lock passes:
Return: the quote is valid only during the rate lock. Late funds are returnedSlippage: the quote stays valid untilexpiry_in_hours. Late funds are repriced at the current rate and execute if the rate moved less thanslippage_tolerance_in_bips. Otherwise they’re returned
GET /api/autoramps/{autoramp_id}/quote and attach it. See Quotes.
Payout with a standing Autoramp
Use this when you want an address that converts whatever arrives, with no amount agreed up front.recipient_account.account_identifier must match a bank account you registered for this customer, exactly as stored. Registration removes spaces from IBANs and dashes from sort codes, so send them that way, or copy the values from GET /api/addresses/fiat. Anything else returns 400 with "recipient fiat account not verified". The payment reference field depends on the rail: see the currency pages, and Autoramp for every request field.
GET /api/autoramps/{autoramp_id} or listen for register_autoramp_status until the Autoramp is Approved, then read the address from deposit_rails:
One address per chain family
deposit_rails shows a single deposit address. Every EVM chain on an Autoramp shares it, so one Autoramp can accept USDC on Ethereum, Base, and Arbitrum. To accept a non-EVM chain such as Solana, create a separate Autoramp for it.
A deposit converts only when its token and chain are listed in source_currencies. A token sent on an EVM chain you didn’t declare isn’t converted, even though the address matches. Show your customer the exact tokens and chains the address accepts.
Errors
Client errors return a plain string body. A bank account that isn’tRegistered for this customer returns 400:
RegistrationPending. See Autoramp for the other common errors.
Batch payouts
Setbatch_payout: true to pay out once a day instead of after every deposit. The Autoramp collects each day’s converted deposits and sends one transfer for the total. BRL Autoramps can’t batch, a batch payout Autoramp can’t be edited, and its fee profile can’t deduct a payout banking fee. See End of Day Settlement.
What can go wrong
Completed means MoonPay Enterprise handed the payout to the bank or rail. When the money reaches the recipient depends on the rail. See Settlement Times by Payment Method, and Trace a payout to give the recipient’s bank a reference.
Fees
Each payout reports its fees in the source stablecoin, with each fee either deducted from the payout or settled later on your invoice. Banking fees per rail come from the Autoramp’s fee profile. See Fees and Fees and Invoices.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.Simulate an incoming deposit
POST /api/sandbox/transaction builds a transaction directly from the route, reusing its currencies, customer, and recipient. amount_in, amount_out, currency_in, currency_out, and state. See Sandbox for the optional fee, fx_rate, and initial_state fields.Drive the transaction to Completed
PendingPayin, which reads as ConversionInProgress in transaction_status. Advance it to trigger the rest of the status webhooks.Check the webhooks you received
transaction event and a transaction_status event with ConversionInProgress. Advancing it sent a second transaction_status event with Completed. The sandbox writes the state you ask for straight onto the transaction, so it skips the intermediate PayoutInProgress status that a live deposit produces.If the customer has a pending EDD review, or the deposit takes the customer over the sandbox volume threshold, the transaction starts in FundsReviewInProgress instead. Approve the pending identification before you advance it.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.Simulate it in the Dashboard
Create the route
Authorize the route
"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.Create the simulated deposit
- Customer: search by name, email, or ID and select the customer who owns the route.
- Ramp: pick it from the dropdown. This field still uses the older wording for a route. Entries are labelled by currency pair, for example .
- Amount: enter the deposit amount in the route’s input currency.
Settle the transaction
Check the result
Move to production
/api/sandbox/* call from your integration. Those endpoints don’t exist in production.