One pay-out API for local accounts in six markets

The ATLAS pay-out API pays a UPI ID or a bank account in India, and local accounts or cards in five more markets, with the same POST /api/merchant/pay-out. Each pay-out is funded from your USDT balance, and a signed webhook tells you how it ended.

Pay-out methods by market

You can send money out in every market where ATLAS takes pay-ins. In most of them the recipient gets a local bank transfer; India adds UPI, Venezuela adds Pago Móvil, and Europe pays cards as well as accounts.

Pay-out methods by market
MarketPay-out methodsCurrency
India UPI, IMPS INR
South Korea Bank transfer KRW
Argentina Bank transfer to a CBU ARS
Venezuela Pago Móvil / bank transfer VES
Turkey Havale, IBAN TRY
Europe and the UK OCT to Visa and Mastercard cards, SEPA Instant EUR, USD (OCT, EU/EEA/CH)

Europe and the UK: Visa, Mastercard, Apple Pay and Google Pay in, in EUR, or EUR and GBP in the UK; OCT to cards and SEPA Instant out in EUR, with OCT in USD as well in the EU, EEA and Switzerland. Contact us for details.

UPI or IMPS: choosing the rail in India

India has two pay-out rails, and IMPS is the default. Leave paymentMethod out, or send imps, and the recipient is a bank account: recipientAccountNumber, recipientBank and recipientIfsc.

To pay a UPI ID instead, send paymentMethod: "upi" with recipientUpiVpa in handle@psp form. On this rail the VPA is the whole recipient object, and ATLAS stores it in lower case. Mixing the rails returns an error: account fields on a UPI pay-out, or a VPA on an IMPS one, get 400 VALIDATION_ERROR with a message that names the field.

On the UPI rail, ATLAS sends the pay-out to that VPA from a local account in its network. You hear about it through the same pay_out.completed webhook as on IMPS, and the payload echoes recipientUpiVpa. Both rails take whole rupees only; a fractional amount is rejected, never rounded.

Example: a UPI pay-out
POST /api/merchant/pay-out
{  "recipientUpiVpa": "receiver.demo@ybl",  "recipientName": "Demo Receiver",  "amount": 10000,  "currency": "INR",  "geo": 3,  "expiresIn": 1800,  "merchantOrderId": "payout-in-upi-12345",  "clientReference": "customer-7781",  "paymentMethod": "upi"}
201 Created
{  "payOutId": "770e8400-e29b-41d4-a716-446655440222",  "displayId": "PO00000042",  "amountFiat": 10000,  "currency": "INR",  "expiresAt": "2026-09-09T16:00:00Z",  "recipientUpiVpa": "receiver.demo@ybl"}

Recipient details for every market

Every pay-out carries recipientName, amount, currency, geo, expiresIn, a merchantOrderId and a clientReference. The recipient fields depend on the rail, and a field that belongs to another market or rail is rejected with 400 VALIDATION_ERROR.

Recipient fields by rail
RailWhat you send
Argentina, bank transfer recipientCbu (22 digits, with the standard CBU checksum)
Venezuela, Pago Móvil / bank transfer recipientPhone, recipientCedula, recipientBank, recipientAccountNumber
India, IMPS recipientAccountNumber, recipientBank, recipientIfsc
India, UPI recipientUpiVpa and paymentMethod: "upi"
South Korea, bank transfer recipientAccountNumber, recipientBank

Account numbers take 5 to 20 digits, and you can send them with hyphens or spaces. For South Korea, GET /api/merchant/banks?geo=SouthKorea returns the bank directory to fill a selector, with codes such as kb_kookmin_bank. Pass the code or the name as recipientBank. The codes stay the same across releases, so you can store them and map your own labels. The Pago Móvil page covers the Venezuelan recipient.

Pay-outs are funded from your USDT balance

Your merchant account keeps USDT in two balances. Completed pay-ins collect in the pay-in balance; the pay-out balance backs the money you send out, and you move USDT across in the portal. When you create a pay-out, the response gives its USDT value, fixed when it is created, and USDT for it is reserved from the pay-out balance while the pay-out is in flight. The reserve is charged only after the pay-out is confirmed.

GET /api/merchant/balance returns the pay-in balance (availableBalance), the pay-out balance (payOutBalance) and the amount held by open pay-outs (reservedBalance), the same figures your dashboard shows. The pay-in balance is also the one that settlement sends to your TRC-20 wallet.

A pay-out sent before your balance is funded can still be accepted. It returns 201 with status AwaitingFunds and waits up to about 12 hours: top up and it moves to Pending, otherwise it expires with pay_out.expired. When a request is turned down with INSUFFICIENT_BALANCE, the message gives the available, required and shortfall amounts.

How a pay-out runs

  1. Create

    POST /api/merchant/pay-out returns 201 Created with payOutId, a displayId such as PO00000001, the fiat and USDT amounts and expiresAt. A 201 means accepted, not paid.

  2. Pending

    The pay-out is funded and waits to be taken for execution.

  3. Claimed

    The transfer is being made.

  4. PendingValidation

    The transfer is done and ATLAS checks the proof against the recipient details.

  5. Completed

    The pay-out is verified, and pay_out.completed arrives with the recipient fields of its rail.

You can follow the same path at any time with GET /api/merchant/pay-out/{payOutId}/status, which also carries a supportNote when the platform attaches one at validation or rejection. Signing and the full request schema are on the H2H API page.

The API docs have the create pay-out request with the recipient fields for each market.

Example: read a pay-out status
GET /api/merchant/pay-out/{payOutId}/status
200 OK
{  "displayId": "PO00000001",  "status": "Completed",  "amountFiat": 5000.00,  "currency": "ARS",  "geo": "Argentina",  "recipientCbu": "0000177501057000189625",  "recipientName": "Demo Receiver",  "clientReference": "customer-7781",  "supportNote": "Proof verified"}

When a pay-out fails or expires

A pay-out can end three ways. Completed is the one you want. Failed means the transfer was rejected at validation, and pay_out.failed carries the reason, for example "Proof did not match the recipient details". Expired means the lifetime ran out before the transfer was made, or an AwaitingFunds pay-out was never funded, and pay_out.expired tells you.

Validation can also send a pay-out back to Pending, to be taken and executed again, so a pay-out whose first check didn't pass can still complete.

Treat a final status as final only after its webhook has arrived and you verify the webhook signature. Webhooks get up to 10 delivery attempts and can repeat, so deduplicate on the event and payOutId. On your side, merchantOrderId is unique per merchant and acts as the idempotency key. If a completed pay-out turns out wrong, open a dispute on it with transactionType set to PayOut.

FAQ

Can I pay out to a UPI ID?

Yes. Send POST /api/merchant/pay-out with paymentMethod: "upi" and the recipient's recipientUpiVpa. The outcome arrives on pay_out.completed or pay_out.failed, as on any other rail, and the UPI page has the details.

What is the default rail for India pay-outs?

IMPS. Leave paymentMethod out or send imps, with the account number, bank and IFSC.

Do you support bulk pay-outs?

There is no batch endpoint. You send one request per pay-out, each with its own merchantOrderId, so a run of many pay-outs can be retried without paying anyone twice.

What happens if my balance is too low?

The pay-out can be accepted as AwaitingFunds and held up to about 12 hours. It goes ahead once you top up, and expires with pay_out.expired if you don't.

How do I know a pay-out has been paid?

Wait for a signature-verified pay_out.completed, or read GET /api/merchant/pay-out/{payOutId}/status. Completed, Failed and Expired are final.

Which banks can I pay in South Korea?

GET /api/merchant/banks?geo=SouthKorea returns the Korean bank directory, with stable codes you pass as recipientBank.