Internal transfers

Move money between two accounts your customers hold at Gravv, without a payee.

An internal transfer (a book transfer) moves money between two accounts at Gravv, for example from a customer's operating account to their payroll account. You do not need a payee, and you do not choose a rail.

Example. The operating account holds 40,000.00 USD and the payroll account 1,000.00 USD. You move 15,000.00 USD. Afterward, the operating account holds 25,000.00 USD (less your fee, if your plan sets one) and the payroll account 16,000.00 USD.

Before you start

  • Both accounts are active. Check each one with GET /v2/accounts/{id}.
  • The source account holds the amount plus any fee.
  • A bank-held account can only move money to another account held at the same bank. Compare bank_code on the two accounts. It cannot pay a card, a crypto wallet or an account at another bank. Wallet-backed accounts have no such limit.

Send the transfer

Send the source account, the destination account and the amount. Leave out rail.

curl --request POST \
     --url https://api.gravv.xyz/v1/transfer \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: 6f1a9c2e-4b7d-4e3a-9c5f-8d2b1a0e7c41' \
     --data '
{
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "source": {
    "source_type": "internal_account",
    "id": "c56a4180-65aa-42ec-a945-5fd21dec0538"
  },
  "destination": {
    "destination_type": "internal_account",
    "id": "e4d909c2-90d0-4b6a-9c1d-3f2e5a7b8c90"
  },
  "amount": "15000.00",
  "description": "Fund payroll October 15",
  "client_reference": "book-2026-10-13-01"
}
'
{
  "data": {
    "amount": "15000",
    "amount_currency": "USD",
    "date_created": "2026-10-13T23:05:53.17901Z",
    "fee": "0",
    "fee_currency": "USD",
    "local_amount": null,
    "local_currency": null,
    "reference": "1a7c795a-a3f2-4d45-ba57-2441b7c310b7",
    "requires_approval": false,
    "transaction_id": "94d7f1d0-9769-4762-a09c-ef8d172a8d1e",
    "transfer_status": "pending"
  },
  "error": null
}

Gravv then completes the transfer in the background. The transaction has transaction_type internal_transfer and channel internal. Gravv sends the transfer.status.pending webhook, then transfer.status.completed.

What can go wrong

Response Why Fix
400 destination.id is required for an internal account transfer No destination id Add destination.id
400 the destination account was not found Wrong id, or an id from the other environment Check the id. Sandbox ids do not exist in live
400 invalid transfer input The destination is not active Wait until it is active
400 this account can only send to a bank account or to another account held at the same bank A bank-held source paying an account at another bank, a card or a wallet Pick a destination at the same bank
400 amount must have at most 2 decimal places More than 2 decimals from a bank-held account Send at most 2 decimals. A wallet-backed account accepts a third decimal at this step, but the transfer fails later
400 insufficient balance Amount plus fee is more than the balance Fund the source, then send with a new Idempotency-Key

To move a customer's money to a card, see Fund cards. To move it on-chain, see Crypto transfers. The full recipe is Move money between your customer's own accounts.

Was this page helpful?