Skip to main content
Send payments between internal and external accounts, with automatic exchange rate handling when the currencies differ.

Overview

Every payment goes through POST /quotes, whether or not the currencies differ. A quote prices the transfer — the amounts, the fees, and, when the currencies differ, the exchange rate — and creates the transaction that carries the money. What varies is when you execute it:
  • In one request. Set immediatelyExecute and Grid creates and executes the quote together. Use this when you don’t need to put rate or fee details in front of your user before the money moves.
  • In two steps. Create the quote, show your user what the transfer will cost, then call execute before the quote expires. Use this whenever your UX surfaces rates or fees — which includes same-currency transfers, where there is no exchange rate but there can still be fees worth showing.
Either way the request shape is the same, and the payment rail is chosen from the destination account. The same endpoint sends to UMA addresses by giving the quote a UMA_ADDRESS destination. See Sending payments for that flow.

Prerequisites

Before sending payments, ensure you have:
  • An active internal account with sufficient balance
  • A verified external account for the destination
  • Valid API credentials with appropriate permissions
  • A webhook endpoint configured to receive payment status updates (recommended)
If you don’t have these set up yet, review the Internal Accounts and External Accounts guides first.

Checking limits before you quote

Every corridor accepts amounts only within a range. You can read that range before you create a quote, so an out-of-range amount surfaces in your own UI rather than as a 400 AMOUNT_OUT_OF_RANGE on POST /quotes. Which endpoint you use depends on what you know:
These bounds are approximate. They are priced off a cached rate that refreshes about every five minutes, and only POST /quotes locks a rate and a final amount. Treat what you read here as a pre-flight check for your UI, not a guarantee that the quote will succeed.

Across a corridor

Use the exchange rates endpoint when you know the currencies but not yet the recipient — populating a currency picker, or validating an amount as the sender types it.
Success (200 OK)
minSendingAmount and maxSendingAmount are in the smallest unit of sourceCurrency — on this corridor, $1.00 to $100,000.00. Omit destinationCurrency to get every corridor available from a source currency, each with its own bounds and rail. Repeat the parameter to compare a few: ?sourceCurrency=USD&destinationCurrency=INR&destinationCurrency=GBP.
Pass sendingAmount to price a specific amount. fees.total varies with the amount sent, so the default (10000) is only representative.

For a specific recipient

Once you have a destination, look it up. The lookup prices the corridor against that particular recipient and returns a lookupId you can carry into the quote.
Success (200 OK)
Each entry carries bounds on both sides of the conversion. min and max are in the smallest unit of that entry’s currency — here MX$20.00 to MX$1,850,000.00. minSendingAmount and maxSendingAmount are in the smallest unit of the response’s sendingCurrency — $1.09 to $100,000.00 — which is the pair you want when your UI collects an amount in the sender’s currency. Pass sendingCurrency when your customer holds more than one currency. Without it the bounds are priced against the customer’s default currency, which may not be the one they intend to send from. GET /receiver/uma/{address} returns the same shape for a UMA recipient, with receiverUmaAddress in place of accountId. A UMA recipient commonly supports several currencies, so expect more than one entry in supportedCurrencies, each with its own bounds.
minSendingAmount and maxSendingAmount are omitted when Grid cannot resolve a sending-side bound for that currency. Fall back to dividing min and max by estimatedExchangeRate, and treat the result as looser than the real limit — the sending leg can impose a bound of its own that the converted receiving bound doesn’t reflect.
Reuse the lookupId on POST /quotes to price against the same lookup. It is required for UMA destinations.
Clearing these bounds doesn’t guarantee the quote succeeds. Cumulative limits are enforced at quote time and surface separately as DAILY_VOLUME_LIMIT_EXCEEDED (HTTP 429), and a per-transaction ceiling can surface as TRANSACTION_SIZE_LIMIT_EXCEEDED. Handle both alongside AMOUNT_OUT_OF_RANGE.

Send a payment

1

Get account IDs

Retrieve the internal account (source) and external account (destination) IDs:
Note the id fields from both the internal and external accounts you want to use.
2

Create the quote

Specify the source and destination accounts and the amount to lock:
cURL
Success (201 Created)
Same-currency transfers use this exact request. The two currencies simply match, and the quote comes back with an exchangeRate of 1 — the fee fields are still populated. Add "immediatelyExecute": true to create and execute in this one request and skip the next two steps.
Locked currency side determines which amount is fixed:
  • SENDING: Lock the sending amount (receiving amount calculated based on exchange rate)
  • RECEIVING: Lock the receiving amount (sending amount calculated based on exchange rate)
The paymentRail field is optional. If omitted, Grid selects a default rail for the destination. Specify a rail (e.g., ACH, ACH_SAME_DAY, WIRE, RTP, FEDNOW) when you need to control which payment network processes the transfer. ACH_SAME_DAY is never selected for you: it requests same-business-day settlement and is priced separately. It is capped at 1,000,000perentrybytheNACHAsame−daylimit(risingto1,000,000 per entry by the NACHA same-day limit (rising to 10,000,000 on 2027-09-17); a payout above that is rejected, so send it over ACH instead.
remittanceInformation is optional. Use it to send a reference that travels with the payment to the recipient (max 80 characters). This populates the ACH Addenda record, FedNow/RTP remittance information, or wire OBI field depending on the payment rail.
purposeOfPayment is optional. Some destinations require it, and some rails carry it on the payment itself. A business payout to China also needs supporting documents for its purpose. See Supporting documents.
Amounts are specified in the smallest unit of the currency (cents for USD, pence for GBP, etc.). For example, 12550 represents $125.50 USD.
3

Review the quote

Before executing, check that:
  • The exchange rate is acceptable
  • Fees are as expected
  • The receiving amount meets requirements
  • The quote hasn’t expired (check expiresAt)
Quote expiration depends on the corridor but is typically ~5 minutes or greater. If expired, create a new quote to get an updated exchange rate.
Skip this step by setting immediatelyExecute on the quote. A same-currency quote has no exchange rate to review, but check feesIncluded if your UX shows the customer what the transfer costs.
4

Execute the quote

Confirm and execute the quote to initiate the transfer:
cURL
The quote comes back with status PROCESSING and the same transactionId it carried at creation — unless the customer requires Strong Customer Authentication, in which case it returns PENDING_AUTHORIZATION and the transfer waits on that.
Once executed, the quote creates a transaction and the transfer begins processing. The transactionId can be used to track the payment.
Real-time funding sources: If your quote uses a real-time funding source (USDC, BTC, RTP, or FedNow), you don’t call the execute endpoint. Instead, send a payment to the account specified in the quote’s paymentInstructions. Grid detects the deposit and processes the transfer automatically.
5

Monitor completion

After execution, a transaction is created and progresses through PENDING → PROCESSING → COMPLETED or FAILED. You’ll receive OUTGOING_PAYMENT.<STATUS> webhooks as the transaction progresses:
If a transaction fails, Grid initiates a refund automatically. You’ll receive OUTGOING_PAYMENT.REFUND_PENDING followed by OUTGOING_PAYMENT.REFUND_COMPLETED or OUTGOING_PAYMENT.REFUND_FAILED. The transaction’s refund object tracks the refund status and reference.
For the full state diagram, refund object details, and all webhook scenarios (including bank returns and manual cancellations), see the Transaction Lifecycle guide.

Transaction statuses

For the full state diagram including refund tracking and edge cases, see the Transaction Lifecycle guide.

Supporting documents

A business payout to China needs supporting documents, such as an invoice or a contract. That is a CNY bank transfer to an external account with beneficiaryType: "BUSINESS". The payout’s purposeOfPayment decides which documents you need.
To create a business beneficiary account, see the China tab in External Accounts. Other payouts don’t take documents. A quote with documentIds for any other destination returns 400 INVALID_INPUT.The payout must use one of the purposes below. Any other purposeOfPayment returns 400 INVALID_INPUT, including GOODS_OR_SERVICES and SERVICE_CHARGES. A payment for services uses the purpose that names the service.Each row is one document to supply. Upload one file for each requirement of your purpose. Where a requirement lists more than one type, the types are alternatives for that one file. Declare any one of them as the file’s documentType.The four service purposes share one set of requirements. For example, an ACCOUNTING_SERVICES payout needs three files: a contract, an invoice, and either a purchase order or a delivery slip.
The requirement ID names the document to supply. The document type names what a file is. 400 DOCUMENTS_REQUIRED reports missing documents by requirement ID.
Limits
  • A quote accepts at most 3 documents.
  • Each file fills one requirement. A requirement that accepts several types still takes one file.
  • Each file is a PDF, JPEG, or PNG, from 1 to 8,000,000 bytes. Grid detects the format from the file contents, not the file name.
  • A document can be used for 24 hours after upload, until its expiresAt.
  • A document can be used on one quote only.
  • A document belongs to the customer in its customerId. Only that customer’s quotes can use it. Omit customerId when the platform itself is the sender. The document can then be used only on the platform’s own quotes.
1

Upload each file

Upload one file per request with POST /payment-documents. You can send the requests in parallel.
cURL
Success (201 Created)
Keep the id of each document. To check whether a document can still be used, call GET /payment-documents/{paymentDocumentId}.
2

Create the quote with the document IDs

Pass the IDs in documentIds, along with the purposeOfPayment they support. A request with documentIds must carry an Idempotency-Key header.
cURL
Grid attaches every document to the payment before it returns the quote. The quote lists their IDs in its documentIds field. For a document’s details, such as its type and its ATTACHED status, call GET /payment-documents/{paymentDocumentId}. With immediatelyExecute: true, Grid attaches the documents before it executes the quote. Otherwise, execute the quote as in Send a payment.
If documentIds doesn’t fill every requirement of the purpose, POST /quotes returns 400 DOCUMENTS_REQUIRED. details.missingRequirements lists the requirement ID of each missing document.
Upload the missing documents, then send the quote request again with a new Idempotency-Key. Treat each requirement ID as an opaque value. Grid may add new ones as requirements change.Other errors on a quote with documents:A request that fails with a 409, 410, or 424 created no quote. A retry with the same Idempotency-Key runs the request again.
Grid does not check what a document says. The payout partner reviews each document after Grid attaches it. To avoid a rejected or delayed payout:
  • Every document must carry the beneficiary’s stamp. A contract must be stamped by both parties.
  • The invoice amount must match the transaction amount.
  • The invoice currency must match the payout currency.
  • Sender and beneficiary details in the documents must match the details you send through the API.
A successful attachment means the payout partner received the file, not that it approved it. The partner may later request more information about the payment, with a deadline of 15 calendar days.

Payout timing

How long a payout takes is determined by the rail it settles over. Timings below are typical end-to-end times measured from quote execution:
These are typical times, not guarantees. Instant rails run continuously — including weekends and holidays — but still depend on the receiving institution. The slower bank rails settle on banking hours, so a transfer submitted after a bank’s cutoff, on a weekend, or on a local holiday starts on the next banking day. Compliance review on a given payment can extend any of these.

Strong Customer Authentication (EU customers)

Customers in SCA-regulated regions (in practice the EU: EUR / USDC) must confirm payments with Strong Customer Authentication. When it applies, the quote comes back PENDING_AUTHORIZATION carrying an scaChallenge that you authorize before the transfer is released; for every other customer nothing changes. See Per-transaction authorization for the full walkthrough.

Checking Payment Status

Configure a webhook endpoint to receive real-time notifications when payment status changes:
See the Webhooks guide for complete webhook implementation details including signature verification.

Best Practices

Quote expiration depends on the corridor (typically ~5 minutes or greater). Always check expiration before executing:
Always include meaningful descriptions to help with reconciliation:
This makes it easier to match payments in your accounting system and provides context when reviewing transactions.
Always save transaction and quote IDs for audit trails and support:

Next Steps