Create a transfer
Sends money from a customer's account, crypto wallet or card to an account, crypto wallet, crypto address, card or external account. The call returns once the transfer is recorded; it then runs in the background. Follow it with GET /v1/transactions/{transaction_id} or the transfer.status.* webhooks.
A 200 can still be transfer_status: paused. Read requirements, upload documents with POST /v1/transfer/documents if asked, then call POST /v1/transfer/{id}/resume. Put rail inside destination. Send at most 2 decimal places. Most errors are a 400 with no code.
Headers
1 to 255 printable ASCII characters. A retry with the same key within 24 hours returns the first response, including a 400. After you correct a request, send a new key.
Request Body
A card source can only be withdrawn to its own card account.
Possible values: internal_account, internal_crypto_wallet, card
ID of the account, crypto wallet or card (UUID version 4)
Accepted but ignored. The transfer uses the source's own currency.
Possible values: USD, EUR, GBP
Asset sent, such as USDC or USDT. Defaults to the network's usual asset.
One of the following variants:
Possible values: external_account
An active external account
Payout rail. ach, wire and rtp are US domestic rails; wire is not an international wire. Leave it out, or send globalbank, for local bank and mobile money payouts.
Possible values: ach, wire, rtp, sepa, swift, fps, globalbank
Possible values: internal_account
An active account. For a card source, its own card account.
Possible values: internal_crypto_wallet
Asset received. Between two wallets of the same customer, USDT to USDC or back converts the asset.
Possible values: USDC, USDT
Possible values: crypto_wallet
Must match the source's network, except for USDC moved between networks
Possible values: avalanche, ethereum, polygon, solana, stellar, tron, sui, base
Memo for addresses that need one, such as some stellar exchange addresses
Possible values: card
ID of the card to fund
Positive amount in major units of the source currency, with at most 2 decimal places. Send it as a string; a number is accepted but can lose precision.
The customer sending the transfer (UUID version 4)
Optional note for your records
Your unique reference. On tenants whose limits are enforced, a reused reference returns the first transfer, or a 409 when the request differs. Send it so that a retry does not pay out twice.
Your data, often JSON text, returned on the transaction
Note the receiving bank shows the payee. Plain ASCII, at most 140 characters. ACH allows 10 letters, digits or spaces. A US wire allows 35 letters, digits, spaces and / - ? : ( ) . , ' +. Other rails ignore it.
Up to 5 IDs from POST /v1/transfer/documents for this customer, each unused. Needed when a payout requires a document; otherwise the transfer pauses for one.
Sender details. Some payout routes need them; without them the transfer pauses with sender_details_required.
Alpha-2 code
YYYY-MM-DD
YYYY-MM-DD
YYYY-MM-DD, not in the future
Alpha-2 code
Your record of how you verified the sender. When your limits policy uses attested tiers, the transfer counts against this remitter instead of the customer.
Your ID for the sender
Who verified the sender
Each value must be clear
Request Examples
{
"source": {
"source_type": "internal_account",
"id": "b9c2cb0d-e09a-496f-8655-d8c4b7aaf2f8"
},
"destination": {
"destination_type": "external_account",
"id": "70c337ad-e3e8-4a90-9de7-7f3e6bc2aac8"
},
"amount": "250.00",
"customer_id": "bb6c6022-f1d8-492f-80f0-7547134bb42f",
"client_reference": "inv-114",
"description": "Invoice 114",
"additional_information": {
"remitter_full_name": "John Doe",
"remitter_country": "US",
"remitter_date_of_birth": "1990-06-25",
"remitter_address": {
"address_line1": "123 Main Street",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US"
}
}
}
{
"source": {
"source_type": "internal_account",
"id": "b9c2cb0d-e09a-496f-8655-d8c4b7aaf2f8"
},
"destination": {
"destination_type": "external_account",
"id": "70c337ad-e3e8-4a90-9de7-7f3e6bc2aac8",
"rail": "wire"
},
"amount": "1200.00",
"customer_id": "bb6c6022-f1d8-492f-80f0-7547134bb42f",
"client_reference": "inv-115",
"payment_rail_reference": "INV 115"
}
{
"source": {
"source_type": "internal_account",
"id": "b9c2cb0d-e09a-496f-8655-d8c4b7aaf2f8"
},
"destination": {
"destination_type": "internal_account",
"id": "915a6953-49a1-4a78-b29d-56d401d9f22a"
},
"amount": "10.00",
"customer_id": "bb6c6022-f1d8-492f-80f0-7547134bb42f",
"client_reference": "move-0042"
}
{
"source": {
"source_type": "internal_account",
"id": "b9c2cb0d-e09a-496f-8655-d8c4b7aaf2f8"
},
"destination": {
"destination_type": "crypto_wallet",
"blockchain_network": "polygon",
"wallet_address": "0x25a4c9853e761753ae18d6871731e305b8b0b2a4"
},
"amount": "2.00",
"customer_id": "bb6c6022-f1d8-492f-80f0-7547134bb42f",
"client_reference": "send-0043"
}
{
"source": {
"source_type": "internal_crypto_wallet",
"id": "67e169c0-547f-410d-8e18-f616b6822d70"
},
"destination": {
"destination_type": "card",
"id": "3397bab8-0f2b-4721-b15b-8c71860f01f9"
},
"amount": "30.00",
"customer_id": "04dfb2e5-1274-4214-b5fd-3415fde7dc18",
"client_reference": "card-fund-1234"
}
Responses
| Status | Description |
|---|---|
200 |
The transfer is recorded, or paused |
400 |
Invalid request, insufficient balance, or a source, destination or external account problem |
409 |
client_reference was used for a different transfer, or the same one is still being set up (retry after 1 second) |
422 |
The sender is blocked, a supporting document is required, or the Idempotency-Key was used with a different body |
503 |
Limits cannot be checked right now. Retry. |
200 response
Why the transfer paused
Possible values: per_transfer, daily, weekly, monthly, annual, annual_requires_approval, screening, sender_details_required, supporting_document_required
What to send on resume: remitter_verification, screening_clear, supporting_document, a sender detail such as remitter_full_name, or customer_email (update the customer, then resume). admin_review means only Gravv can release it.
Possible values: per_transfer, daily, weekly, monthly, annual
Allowance left in that window, in USD
When the pause expires, 72 hours by default. The transfer then fails.
{
"data": {
"amount": "250",
"amount_currency": "USD",
"reference": "3f0c6a8e-6b1d-4c1e-9a55-0d2f7b9e4a11",
"date_created": "2026-10-13T09:15:22.481Z",
"transfer_status": "pending",
"transaction_id": "8d4e2b6c-1f3a-4e7b-b0c9-5a6d7e8f9a01",
"local_amount": "382500",
"local_currency": "NGN",
"fee": "1.5",
"fee_currency": "USD",
"requires_approval": false
},
"error": null
}
{
"data": {
"amount": "5000",
"amount_currency": "USD",
"reference": "c1a2b3c4-d5e6-4f70-8a91-b2c3d4e5f607",
"date_created": "2026-10-13T09:15:22.481Z",
"transfer_status": "paused",
"transaction_id": "0e1f2a3b-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
"local_amount": null,
"local_currency": null,
"fee": "0",
"fee_currency": "USD",
"requires_approval": false,
"status": "awaiting_remitter_information",
"park_reason": "remitter_information",
"pause_reason": "daily",
"requirements": [
{
"type": "remitter_verification",
"tier": "T2",
"levels": [
"enhanced"
],
"description": "resume with remitter_verification at one of these levels"
}
],
"breached_window": "daily",
"remaining_usd": "1200.00",
"expires_at": "2026-10-16T09:15:22Z"
},
"error": null
}
400 response
What went wrong
{
"data": null,
"error": "insufficient balance"
}
{
"data": null,
"error": "payee 70c337ad-e3e8-4a90-9de7-7f3e6bc2aac8 is not active: pending"
}
409 response
Such as ERROR_CODE_DUPLICATE_TRANSACTION
{
"data": null,
"error": "client_reference has already been used for a different transfer (the amount, currency, source, destination or remitter differ); use a new client_reference for a new transfer",
"code": "ERROR_CODE_DUPLICATE_TRANSACTION"
}
422 response
Such as ERROR_CODE_DUPLICATE_TRANSACTION
{
"data": null,
"error": "transfers are not permitted for this sender",
"code": "ERROR_CODE_SUBJECT_BLOCKED"
}
503 response
Such as ERROR_CODE_DUPLICATE_TRANSACTION
{
"data": null,
"error": "transfer limits are temporarily unavailable, please retry",
"code": "ERROR_CODE_LIMITS_UNAVAILABLE"
}
Authorization
- ApiKeyAuth