Bulk Transfers

Request approval for a batch of crypto payouts from the corporation wallet in one call.

Before You Start

Read the following guides before proceeding:

GuideWhy
Getting StartedPlatform overview and setup
Api BasicsRequired headers and request configuration
AuthenticationHow to obtain the corporation token
Wallets and BalancesThe batch is funded from the corporation wallet

Overview

A bulk transfer requests oracle approval for a batch of on-chain payouts from the corporation wallet.
It is one call for up to 50 destinations, and it returns the exact per-recipient amounts to pass to the
contract that executes the batch.

Despite living under bank, this is not a fiat flow. Only crypto recipients are accepted — no
IBAN, no card, no corridor.


Request Approval

POST /api/v1/bulk/transfer

Requires TransactionSu or TransactionCreate.

Request body:

{
  "recipients": [
    {
      "type": "Crypto",
      "currency": "WUSD",
      "amount": 1000.50,
      "crypto": {
        "address": "0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE",
        "network": "Base"
      }
    },
    {
      "type": "Crypto",
      "currency": "WUSD",
      "amount": 250.00,
      "crypto": {
        "address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874",
        "network": "Base"
      }
    }
  ]
}
FieldTypeRequiredDescription
recipientsarrayYes1–50 entries. All must share the same currency
recipients[].typestringYesMust be Crypto. Any other value is rejected
recipients[].currencystringYesToken symbol. Use WUSD or WEUR for unified tokens
recipients[].amountnumberYesAmount to send to this destination
recipients[].cryptoobjectYesDestination details
recipients[].crypto.addressstringYesDestination address
recipients[].crypto.networkstringYesDestination network

Response:

{
  "transaction_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "operation_id": "64120850-73a1-4df5-a074-d463258c9deb",
  "crypto_transfers": [
    {
      "address": "0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE",
      "precise_amount": "1000500000000000000000",
      "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976"
    },
    {
      "address": "0xAAFF0821A09A1Aac28B72dD3Ff410A7ea5FEb874",
      "precise_amount": "250000000000000000000",
      "token_address": "0x0774164DC20524Bb239b39D1DC42573C3E4C6976"
    }
  ]
}
FieldDescription
transaction_idBatch transaction identifier. This is the corporation id
operation_idIdentifier generated for this approval. Unique per call
crypto_transfers[]The approved transfers, one per recipient, in request order
crypto_transfers[].addressDestination address as sent
crypto_transfers[].precise_amountAmount in the token's smallest unit, as a string
crypto_transfers[].token_addressResolved token contract address

transaction_id is the corporation id, so it is the same value on every bulk transfer this
corporation makes. operation_id is what identifies this batch — key your records on that.


How the Token Is Resolved

currency is a token symbol, not a currency code. The handler takes the currency of the
first recipient, finds the token whose symbol matches it and whose supported_chain_ids include
the corporation token's chain, and uses that token for every transfer in the batch.

A currency with no matching token on the chain is rejected with 400 ErrorNotSupported and
Invalid currency.

Every recipient must carry the same currency, and validation enforces it — a mismatch is
rejected with All recipients must use the same currency. To pay in two assets, send two batches.


Amounts Are Truncated to Two Decimal Places

Each amount is converted with two decimal places of precision and then scaled to the token's
decimals. Anything beyond the second decimal is discarded, not rounded.

amount sentAmount approved
1000.501000.50
1000.1291000.12
1000.9991000.99
0.0050.00

A recipient whose amount is below 0.01 is approved for zero. Round to two decimal places on
your side before sending, and compare every crypto_transfers[].precise_amount in the response
against what you intended before executing the batch.


Code

const response = await fetch(`${baseUrl}/api/v1/bulk/transfer`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${corporationToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    recipients: payouts.map((p) => ({
      type: 'Crypto',
      currency: 'WUSD',
      amount: Number(p.amount.toFixed(2)),  // amounts are truncated past 2 decimals
      crypto: { address: p.address, network: 'Base' }
    }))
  })
});
const approval = await response.json();
response = requests.post(
    f"{base_url}/api/v1/bulk/transfer",
    headers={
        "Authorization": f"Bearer {corporation_token}",
        "Content-Type": "application/json",
    },
    json={
        "recipients": [
            {
                "type": "Crypto",
                "currency": "WUSD",
                "amount": round(p["amount"], 2),  # amounts are truncated past 2 decimals
                "crypto": {"address": p["address"], "network": "Base"},
            }
            for p in payouts
        ]
    },
)
approval = response.json()
recipients := make([]map[string]interface{}, 0, len(payouts))
for _, p := range payouts {
    recipients = append(recipients, map[string]interface{}{
        "type":     "Crypto",
        "currency": "WUSD",
        "amount":   math.Round(p.Amount*100) / 100, // amounts are truncated past 2 decimals
        "crypto":   map[string]string{"address": p.Address, "network": "Base"},
    })
}

body, _ := json.Marshal(map[string]interface{}{"recipients": recipients})

req, _ := http.NewRequest("POST", baseURL+"/api/v1/bulk/transfer", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+corporationToken)
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var approval BulkTransferResponse
json.NewDecoder(resp.Body).Decode(&approval)

What Happens Next

This endpoint approves the batch — it does not move funds. The approval authorises the funds oracle
to execute the transfers listed in crypto_transfers, which are then submitted from the corporation
wallet on-chain.

Completed transfers appear in GET /api/v1/activity/feed, and balance changes are delivered to
POST {your_webhook_base_url}/v2/webhooks/balances.

There is no bulk-transfer status endpoint and no bulk-transfer webhook. Reconcile through the activity
feed using the destination addresses and amounts.


Limitations

  • Crypto destinations only. Sepa, Ach, Card and every other recipient type is rejected.
  • 50 recipients per call.
  • One currency per call.
  • Two decimal places of precision, truncated.
  • No cancel endpoint. An approved batch is revoked only through Wirex.

Error Handling

{
  "error_reason": "ErrorInvalidField",
  "error_description": "All recipients must use the same currency",
  "error_category": {
    "category": "CategoryValidationFailure",
    "http_status_code": 400
  },
  "error_details": [
    { "key": "field", "details": "recipients[1].currency" },
    { "key": "issue", "details": "currency_mismatch" }
  ]
}

Bulk validation errors name the offending entry by index — recipients[1].currency — so the failing
recipient is identifiable without bisecting the batch.

Validation Errors (400)

Error ReasonError DetailsDescriptionResolution
ErrorMissingFieldfield: recipientsrecipients is requiredSend at least one recipient
ErrorInvalidFieldissue: max_exceeded, max: 50recipients exceeds maximum allowedSplit into batches of 50
ErrorInvalidFieldfield: recipients[i].typeOnly crypto recipients are supportedSet type to Crypto
ErrorInvalidFieldissue: currency_mismatchAll recipients must use the same currencySend one currency per batch
ErrorMissingFieldfield: cryptocrypto is required for selected typeAdd the crypto object
ErrorMissingFieldfield: crypto.addresscrypto.address is requiredAdd the destination address
ErrorMissingFieldfield: crypto.networkcrypto.network is requiredAdd the destination network
ErrorNotSupportedfield: currencyInvalid currencyNo token with that symbol exists on the corporation's chain

Permission Errors (403)

Error ReasonDescriptionResolution
ErrorPermissionDeniedUser does not have required permissionsRequires TransactionCreate or TransactionSu

Server Errors (500)

Error ReasonDescriptionResolution
ErrorGeneralFailed to get service tokensThe token catalogue was unavailable. Retry
ErrorGeneralFailed to request bulk transfer approvalThe funds oracle rejected the batch — most often insufficient balance for the total. Check the wallet balance and retry

Did this page help you?