--- url: /api/account.md description: 'Balances, sub-accounts and transfers between them, and transaction history.' --- # Account Your business has a primary account and can have sub-accounts, each with its own balances, history, deposit details and bank accounts. Use sub-accounts to keep funds apart, for example by desk, product or client, and move funds between them with a transfer. ## Choosing the account a request acts on Every signed request acts on one account: * Without `X-VALR-SUB-ACCOUNT-ID`, it acts on your primary account, whose ID is `0`. * With `X-VALR-SUB-ACCOUNT-ID` set to a sub-account's `id`, it acts on that sub-account. Include the ID in the signature too; see [Authentication](/guides/authentication). A key limited to specific sub-accounts can only act on those, so it must always send the header. Endpoints that manage sub-accounts, or that cover your whole business, such as balances for all accounts, are called on your primary account by a key that isn't limited to sub-accounts. ## Common tasks * **See every balance at once:** [Get balances for all accounts](/operations/getV1AccountBalancesAll) returns each account's balances in one call. Balances of your payments accounts are under [Payouts](/api/payouts). * **Create a sub-account:** [Create a sub-account](/operations/postV1AccountSubaccount) returns its ID. Mark accounts that hold your clients' money with the `CLIENT_FUNDS` tag. * **Move funds:** [Transfer between accounts](/operations/postV1AccountSubaccountsTransfer) moves funds between your primary account and sub-accounts. It's processed asynchronously, so confirm it from balances or transaction history. * **Reconcile:** transaction history is per account, newest first. An account's history doesn't include its sub-accounts' transactions; list them together with [List transactions for all sub-accounts](/operations/getV1AccountTransactionhistorySubaccounts). ## Before you start | Bundle | Allows | |---|---| | `TREASURY_READ` | Read balances, history, deposit details, sub-accounts and withdrawals | | `TREASURY_WRITE` | Everything in `TREASURY_READ`, plus create, rename and delete sub-accounts and transfer between accounts | ## Endpoints ### Balances Account balances. | Endpoint | Request | Needs | |---|---|---| | [Get balances](/operations/getV1AccountBalances) | `GET /v1/account/balances` | `ACCOUNT_BALANCES_READ` | | [Get balances for all accounts](/operations/getV1AccountBalancesAll) | `GET /v1/account/balances/all` | `ACCOUNT_BALANCES_READ` | ### Sub-accounts Your business's primary account and its sub-accounts: list, create, rename and delete sub-accounts, and move funds between your accounts. Your primary account's ID is `0`. To call another endpoint on a sub-account, send the sub-account's `id` in the `X-VALR-SUB-ACCOUNT-ID` header; without it, requests act on your primary account. A key limited to some sub-accounts can only act on those, so it must always send the header. | Endpoint | Request | Needs | |---|---|---| | [List sub-accounts](/operations/getV1AccountSubaccounts) | `GET /v1/account/subaccounts` | `SUB_ACCOUNTS_READ` | | [Get a sub-account](/operations/getV1AccountSubaccountByAccountPublicId) | `GET /v1/account/subaccount/{accountPublicId}` | `SUB_ACCOUNTS_READ` | | [Create a sub-account](/operations/postV1AccountSubaccount) | `POST /v1/account/subaccount` | `SUB_ACCOUNTS_MANAGE` | | [Rename a sub-account](/operations/putV1AccountSubaccount) | `PUT /v1/account/subaccount` | `SUB_ACCOUNTS_MANAGE` | | [Delete a sub-account](/operations/deleteV1AccountSubaccount) | `DELETE /v1/account/subaccount` | `SUB_ACCOUNTS_MANAGE` | | [Transfer between accounts](/operations/postV1AccountSubaccountsTransfer) | `POST /v1/account/subaccounts/transfer` | `TRANSFERS_CREATE` | ### Transaction history Transactions and trades on your accounts, newest first. Unless an endpoint says otherwise, it reads the account you call it on: your primary account, or the sub-account selected with `X-VALR-SUB-ACCOUNT-ID`. An account's history doesn't include its sub-accounts' transactions. | Endpoint | Request | Needs | |---|---|---| | [List transactions](/operations/getV1AccountTransactionhistory) | `GET /v1/account/transactionhistory` | `TRANSACTION_HISTORY_READ` | | [List transactions for all sub-accounts](/operations/getV1AccountTransactionhistorySubaccounts) | `GET /v1/account/transactionhistory/subaccounts` | `TRANSACTION_HISTORY_READ` | --- --- url: /guides/ai-support.md description: >- Ask questions about the API with AI-powered support, and machine-readable docs for coding assistants --- # AI support ![Ask AI](/img/ask-ai.png) To explore our docs with AI support, [open the VALR support centre's AI assistant](https://support.valr.com/hc/en-us?askai=true). ## Docs for coding assistants and agents This site publishes its documentation in forms that coding assistants and AI agents can read directly: | | | |---|---| | [`/llms.txt`](/llms.txt) | An index of every page, with a short description of each | | [`/llms-full.txt`](/llms-full.txt) | All the documentation in one markdown file | | [`/openapi.json`](/openapi.json) | The OpenAPI specification for every endpoint on this site | Point your assistant at `llms-full.txt` for the full context, or at `llms.txt` to let it fetch only the pages it needs. Every endpoint's page there includes the permission it needs, its errors and a ready-to-run example request. --- --- url: /api.md description: VALR Business API reference for institution API keys --- # API Reference | | | |---|---| | **Base URL** | `https://api.valr.com` | | **Protocol** | HTTPS only | | **Content-Type** | `application/json` | | **Authentication** | Signed requests with an institution API key ([guide](/guides/authentication)) | | **Permissions** | Each endpoint needs one permission, granted by your key's bundles ([bundles](/guides/permissions)) | | **Errors** | Listed per endpoint ([errors](/guides/errors)) | | **OpenAPI spec** | [Download openapi.json](/openapi.json) | Each endpoint page shows the permission it needs, the bundles that grant it, its request and response, the errors it can return, and code samples that sign the request when they run. ## Categories | Category | | |---|---| | [Account](/api/account) | Balances, sub-accounts and transfers between them, and transaction history. | | [Wallets](/api/wallets) | Receive and send crypto and fiat, manage saved crypto addresses and linked bank accounts, and track every deposit and withdrawal. | | [Collections](/api/collections) | Collect bank deposits from your payers with unique deposit references, then follow each payment to settlement. | | [Payouts](/api/payouts) | Pay many bank beneficiaries in one batch from your payout account, and track every payout to completion. | | [Webhooks](/api/webhooks) | Get an HTTPS notification when something happens on your business's accounts, instead of polling the API. | | [Public data](/api/public-data) | Exchange status, server time, currencies and pairs, and market prices, with no API key needed. | --- --- url: /api-docs/deleteV2PaymentsBulkWithdrawalsBeneficiariesById.md description: 'DELETE /v2/payments/bulk-withdrawals/beneficiaries/{id}' --- # Archive a beneficiary `DELETE /v2/payments/bulk-withdrawals/beneficiaries/{id}` Archives a beneficiary so it no longer appears in the active list. Its bank and account number stay reserved: creating or paying the same recipient again restores it. **Requires** `DISBURSEMENTS_DELETE`, granted by `DISBURSEMENTS_WRITE` ([bundles](/guides/permissions)) Sign the request as described in [Authentication](/guides/authentication). ## Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | id | path | string | Yes | Beneficiary ID. | ## Responses ### 204 No content ## Example request Signs the request when it runs. Set `VALR_API_KEY` and `VALR_API_SECRET` first. ```bash #!/usr/bin/env bash # Needs VALR_API_KEY and VALR_API_SECRET. Set VALR_SUBACCOUNT_ID to act on a sub-account. METHOD=DELETE REQUEST_PATH='/v2/payments/bulk-withdrawals/beneficiaries/{id}' BODY='' TIMESTAMP=$(($(date +%s) * 1000)) SIGNATURE=$(printf '%s' "${TIMESTAMP}${METHOD}${REQUEST_PATH}${BODY}${VALR_SUBACCOUNT_ID:-}" \ | openssl dgst -sha512 -hmac "$VALR_API_SECRET" | awk '{print $NF}') HEADERS=(-H "X-VALR-API-KEY: $VALR_API_KEY" -H "X-VALR-SIGNATURE: $SIGNATURE" -H "X-VALR-TIMESTAMP: $TIMESTAMP") [ -n "$VALR_SUBACCOUNT_ID" ] && HEADERS+=(-H "X-VALR-SUB-ACCOUNT-ID: $VALR_SUBACCOUNT_ID") curl -X "$METHOD" "https://api.valr.com$REQUEST_PATH" "${HEADERS[@]}" ``` ```python import hashlib import hmac import os import time import requests API_KEY = os.environ["VALR_API_KEY"] API_SECRET = os.environ["VALR_API_SECRET"] SUBACCOUNT_ID = os.environ.get("VALR_SUBACCOUNT_ID", "") method = "DELETE" path = "/v2/payments/bulk-withdrawals/beneficiaries/{id}" body = "" # send exactly the string you sign timestamp = str(int(time.time() * 1000)) signature = hmac.new( API_SECRET.encode(), (timestamp + method + path + body + SUBACCOUNT_ID).encode(), hashlib.sha512 ).hexdigest() headers = {"X-VALR-API-KEY": API_KEY, "X-VALR-SIGNATURE": signature, "X-VALR-TIMESTAMP": timestamp} if SUBACCOUNT_ID: headers["X-VALR-SUB-ACCOUNT-ID"] = SUBACCOUNT_ID response = requests.request(method, "https://api.valr.com" + path, headers=headers) print(response.status_code, response.text) ``` ## Errors | HTTP | Code | Name | When | |---|---|---|---| | 400 | `BAD_REQUEST` | `BAD_REQUEST` | A query parameter or field is invalid. | | 400 | `-11` | `INVALID_REQUEST` | The beneficiary ID isn't a valid UUID. | | 403 | `-16098` | `PAYMENTS_USER_ACCESS_NOT_CONFIGURED` | Payments haven't been set up for your business yet. | | 404 | `NOT_FOUND` | `NOT_FOUND` | No active beneficiary with this ID exists on your account; it may already be archived. | | 500 | `INTERNAL_ERROR` | `INTERNAL_ERROR` | Unexpected error in the payments service. | | 502 | `-16097` | `EXCHANGE_API_ERROR` | The payments service failed to process the request. | | 502 | `EXCHANGE_API_ERROR` | `EXCHANGE_API_ERROR` | The payments service couldn't complete the request. Try again later. | | 503 | `-16099` | `PAYMENTS_BACKEND_PROXY_DISABLED` | Payments endpoints are temporarily unavailable. | Plus the [errors common to all signed endpoints](/guides/errors#common-to-all-endpoints). --- --- url: /guides/authentication.md description: Institution API keys and how to sign requests --- # Authentication ## Institution API keys Your business creates institution API keys in VALR Business. Creating a key needs a team member with permission to manage API keys, and confirmation by email and two-factor authentication. When you create a key you choose: * **Bundles**: what the key can do ([permissions & bundles](/guides/permissions)). * **Sub-accounts** (optional): limit the key to specific sub-accounts. Without a limit, the key can act on your primary account and any sub-account. * **IP addresses** (optional): only accept requests from these addresses or ranges. * **Endpoints** (optional): only allow these endpoints. The API key and secret are shown once. Keys don't expire; revoke a key in VALR Business when you no longer need it. ::: danger Keep the secret secret Anyone with your API secret can sign requests as your business. Never send it in a request, commit it to source control or paste it into a web page. ::: ## Signing a request Every request is signed with HMAC-SHA512, using your API secret as the key, over this string: ``` timestamp + METHOD + path + body + subAccountId ``` | Part | Value | |---|---| | `timestamp` | Current time in Unix milliseconds; the same value you send in `X-VALR-TIMESTAMP` | | `METHOD` | HTTP method in upper case, e.g. `POST` | | `path` | Path including the query string, e.g. `/v1/account/balances?excludeZeroBalances=true` | | `body` | The exact JSON string you send, or an empty string | | `subAccountId` | The sub-account ID you send in `X-VALR-SUB-ACCOUNT-ID`, or an empty string | Send these headers: | Header | Value | |---|---| | `X-VALR-API-KEY` | Your API key | | `X-VALR-SIGNATURE` | The signature, as lower-case hex | | `X-VALR-TIMESTAMP` | The timestamp you signed | | `X-VALR-SUB-ACCOUNT-ID` | Optional: act on this sub-account | Sign each request just before you send it: requests with an old timestamp are rejected, and each signature can only be used once. Every endpoint's code samples sign the request for you; set `VALR_API_KEY` and `VALR_API_SECRET` in your environment and run them. ## Primary account and sub-accounts Without `X-VALR-SUB-ACCOUNT-ID`, a request acts on your primary account. Some endpoints, such as payments and deposit references, only work on the primary account and with a key that isn't limited to sub-accounts. Those endpoints say so, and return `401` with code `-93` otherwise. ## Verify your implementation | | GET example | POST example | |---|---|---| | Secret | `4961b74efac86b25cce8fbe4c9811c4c7a787b7a5996660afcc2e287ad864363` | same | | Timestamp | `1558014486185` | `1558017528946` | | Method | `GET` | `POST` | | Path | `/v1/account/balances` | `/v1/orders/market` | | Body | *(empty)* | `{"customerOrderId":"ORDER-000001","pair":"BTCUSDC","side":"BUY","quoteAmount":"80000"}` | | Signature | `9d52c181ed69460b49307b7891f04658e938b21181173844b5018b2fe783a6d4c62b8e67a03de4d099e7437ebfabe12c56233b73c6a0cc0f7ae87e05f6289928` | `09f536e3dfdad58443f16010a97a0a21ad27486b7b8d6d4103170d885410ed77f037f1fa628474190d4f5c08ca12c1acc850901f1c2e75c6d906ec3b32b008d0` | --- --- url: /api-docs/postV2PaymentsBulkWithdrawalsBatchesByIdCancel.md description: 'POST /v2/payments/bulk-withdrawals/batches/{id}/cancel' --- # Cancel a disbursement batch `POST /v2/payments/bulk-withdrawals/batches/{id}/cancel` Cancels a batch that is pending approval or approved, and marks its withdrawals that haven't been submitted as `SKIPPED`. Batches created with an API key are executed immediately, so they usually can't be cancelled. **Requires** `DISBURSEMENTS_UPDATE`, granted by `DISBURSEMENTS_WRITE` ([bundles](/guides/permissions)) Sign the request as described in [Authentication](/guides/authentication). ## Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | id | path | string | Yes | Batch ID. | ## Responses ### 200 OK | Property | Type | Required | Description | |---|---|---|---| | id | string (uuid) | Yes | - | | label | string | Yes | - | | currency | string | Yes | - | | totalAmount | string | Yes | Sum of the withdrawal amounts, as a decimal string. | | withdrawalCount | integer (int32) | Yes | - | | status | string | Yes | Batch status: `PENDING_APPROVAL`, `APPROVED`, `PROCESSING`, `COMPLETED`, `PARTIALLY_COMPLETED`, `FAILED` or `CANCELLED`. | | uploadedBy | string | Yes | ID of the user or API key that created the batch. | | uploadedByName | object | Yes | - | | approvedBy | string | Yes | ID of the user or API key that approved the batch (for a batch created with an API key, that key); `null` until approved. | | approvedByName | object | Yes | - | | approvedAt | string (date-time) | Yes | - | | createdAt | string (date-time) | Yes | - | | updatedAt | string (date-time) | Yes | - | | completedAt | string (date-time) | Yes | - | | feeQuote | object | Yes | Fee for the batch's pending withdrawals, or the fee charged once none are pending. Only returned by `GET /v2/payments/bulk-withdrawals/batches/{id}`; `null` elsewhere, and when the fee can't be quoted. | **`uploadedByName` properties:** | Property | Type | Required | Description | |---|---|---|---| | name | string | Yes | - | | surname | string | Yes | - | **`approvedByName` properties:** | Property | Type | Required | Description | |---|---|---|---| | name | string | Yes | - | | surname | string | Yes | - | **`feeQuote` properties:** | Property | Type | Required | Description | |---|---|---|---| | fee | string | Yes | Fee as a decimal string. `null` means the fee is unknown, which isn't the same as no fee. | | currency | string | Yes | - | | withdrawalCount | integer (int32) | Yes | Number of withdrawals the fee covers. | | grossTotal | string | Yes | Sum of those withdrawals' amounts. | | requiredTotal | string | Yes | Gross total plus fee: the available balance your payout account needs. | | feeGroups | DisbursementFeeGroupForDisplay\[] | Yes | Breakdown by fee type; `null` when the quote is the fee already charged. | **`feeGroups` properties:** | Property | Type | Required | Description | |---|---|---|---| | actionType | string | Yes | `FAST_WITHDRAWAL` for banks that take real-time payments, otherwise `REGULAR_WITHDRAWAL`. | | withdrawalCount | integer (int32) | Yes | - | | grossTotal | string | Yes | - | **Example:** ```json { "id": "7c1e9a3b-5d2f-4b6a-8e0c-2a4b6c8d0e1f", "label": "November rent", "currency": "ZAR", "totalAmount": "45000.00", "withdrawalCount": 3, "status": "CANCELLED", "uploadedBy": "2e4a6c8e-0b1d-4f3a-9c5e-7a9b1d3f5a7c", "uploadedByName": { "name": "Lerato", "surname": "Mokoena" }, "approvedBy": null, "approvedByName": null, "approvedAt": null, "createdAt": "2026-10-22T14:03:11.250Z", "updatedAt": "2026-10-23T08:40:27.733Z", "completedAt": null, "feeQuote": null } ``` ## Example request Signs the request when it runs. Set `VALR_API_KEY` and `VALR_API_SECRET` first. ```bash #!/usr/bin/env bash # Needs VALR_API_KEY and VALR_API_SECRET. Set VALR_SUBACCOUNT_ID to act on a sub-account. METHOD=POST REQUEST_PATH='/v2/payments/bulk-withdrawals/batches/{id}/cancel' BODY='' TIMESTAMP=$(($(date +%s) * 1000)) SIGNATURE=$(printf '%s' "${TIMESTAMP}${METHOD}${REQUEST_PATH}${BODY}${VALR_SUBACCOUNT_ID:-}" \ | openssl dgst -sha512 -hmac "$VALR_API_SECRET" | awk '{print $NF}') HEADERS=(-H "X-VALR-API-KEY: $VALR_API_KEY" -H "X-VALR-SIGNATURE: $SIGNATURE" -H "X-VALR-TIMESTAMP: $TIMESTAMP") [ -n "$VALR_SUBACCOUNT_ID" ] && HEADERS+=(-H "X-VALR-SUB-ACCOUNT-ID: $VALR_SUBACCOUNT_ID") curl -X "$METHOD" "https://api.valr.com$REQUEST_PATH" "${HEADERS[@]}" ``` ```python import hashlib import hmac import os import time import requests API_KEY = os.environ["VALR_API_KEY"] API_SECRET = os.environ["VALR_API_SECRET"] SUBACCOUNT_ID = os.environ.get("VALR_SUBACCOUNT_ID", "") method = "POST" path = "/v2/payments/bulk-withdrawals/batches/{id}/cancel" body = "" # send exactly the string you sign timestamp = str(int(time.time() * 1000)) signature = hmac.new( API_SECRET.encode(), (timestamp + method + path + body + SUBACCOUNT_ID).encode(), hashlib.sha512 ).hexdigest() headers = {"X-VALR-API-KEY": API_KEY, "X-VALR-SIGNATURE": signature, "X-VALR-TIMESTAMP": timestamp} if SUBACCOUNT_ID: headers["X-VALR-SUB-ACCOUNT-ID"] = SUBACCOUNT_ID response = requests.request(method, "https://api.valr.com" + path, headers=headers) print(response.status_code, response.text) ``` ## Errors | HTTP | Code | Name | When | |---|---|---|---| | 400 | `BAD_REQUEST` | `BAD_REQUEST` | A query parameter or field is invalid. | | 400 | `-11` | `INVALID_REQUEST` | The batch ID isn't a valid UUID. | | 403 | `-16098` | `PAYMENTS_USER_ACCESS_NOT_CONFIGURED` | Payments haven't been set up for your business yet. | | 404 | `NOT_FOUND` | `NOT_FOUND` | No batch with this ID exists on your account. | | 500 | `INTERNAL_ERROR` | `INTERNAL_ERROR` | Unexpected error in the payments service. | | 502 | `-16097` | `EXCHANGE_API_ERROR` | The payments service failed to process the request. | | 502 | `EXCHANGE_API_ERROR` | `EXCHANGE_API_ERROR` | The payments service couldn't complete the request. Try again later. | | 503 | `-16099` | `PAYMENTS_BACKEND_PROXY_DISABLED` | Payments endpoints are temporarily unavailable. | Plus the [errors common to all signed endpoints](/guides/errors#common-to-all-endpoints). --- --- url: /api/collections.md description: >- Collect bank deposits from your payers with unique deposit references, then follow each payment to settlement. --- # Collections Collections let your payers pay you by bank transfer, with each deposit matched to the payer who made it. You give every payer a unique deposit reference; when a deposit quoting that reference arrives, it's credited to your collections account, matched to the reference and recorded as a payment. Collected funds are then settled to your business, either in scheduled payout runs or instantly. ## How a collection works 1. **Create a deposit reference** for each payer, one at a time or in bulk, and give it to them. You can record the amount you expect and the payer's name and email to help you reconcile. 2. **The payer deposits**, quoting the reference. The deposit is credited and matched to the reference. If a payer quotes the wrong reference, link the deposit to the right one yourself. 3. **Follow the payment.** Each deposit becomes a payment with a step-by-step timeline: received, converted where your business converts collected funds, and settled. 4. **Settlement.** Collected funds are paid out to your business's settlement wallet, either in runs on a payout schedule set up with VALR or as instant payouts. Each settlement lists the payments it carried. To hear about deposits as they're credited, subscribe a [webhook](/api/webhooks) to `FIAT_DEPOSIT_CREDITED`: its `reference` is the deposit reference the deposit matched. ## Before you start * **API key:** the `COLLECTIONS_WRITE` bundle creates, updates and deletes deposit references and reads collections. `PAYMENTS_READ` reads collections without changing anything. * **Account:** collections work on your business as a whole. Call these endpoints without `X-VALR-SUB-ACCOUNT-ID`, with a key that isn't limited to specific sub-accounts; otherwise they return `401` with code `-93`. * **Formats:** amounts are decimal strings. Paged lists return `items`, `total`, `limit` and `offset`. Page through payments and settlements with `limit` and `offset`, and through deposit references and deposits with `limit` and `skip`. ## Endpoints ### Getting paid Deposits your business receives, from payers or to fund disbursements, and how each one is converted and paid out. | Endpoint | Request | Needs | |---|---|---| | [List payments](/operations/getV2Payments) | `GET /v2/payments` | `COLLECTIONS_READ` | | [Get a payment](/operations/getV2PaymentsById) | `GET /v2/payments/{id}` | `COLLECTIONS_READ` | | [List collection activity](/operations/getV2PaymentsCollectionsHistory) | `GET /v2/payments/collections/history` | `COLLECTIONS_READ` | ### Deposit references Unique references your payers quote when they deposit, so each deposit can be matched to a payer. | Endpoint | Request | Needs | |---|---|---| | [Get a deposit reference](/operations/getV2WalletFiatByCurrencyPaymentReferencesByReferenceCode) | `GET /v2/wallet/fiat/{currency}/payment/references/{referenceCode}` | `COLLECTIONS_READ` | | [Delete a deposit reference](/operations/deleteV2WalletFiatByCurrencyPaymentReferencesByReferenceCode) | `DELETE /v2/wallet/fiat/{currency}/payment/references/{referenceCode}` | `COLLECTIONS_DELETE` | | [Update a deposit reference](/operations/putV2WalletFiatByCurrencyPaymentReferencesByReferenceCode) | `PUT /v2/wallet/fiat/{currency}/payment/references/{referenceCode}` | `COLLECTIONS_UPDATE` | | [Create a deposit reference](/operations/postV2WalletFiatByCurrencyPaymentReferences) | `POST /v2/wallet/fiat/{currency}/payment/references` | `COLLECTIONS_CREATE` | | [List deposit references](/operations/getV2WalletFiatByCurrencyPaymentReferences) | `GET /v2/wallet/fiat/{currency}/payment/references` | `COLLECTIONS_READ` | | [Create deposit references in bulk](/operations/postV2WalletFiatByCurrencyPaymentBulkReferences) | `POST /v2/wallet/fiat/{currency}/payment/bulk-references` | `COLLECTIONS_CREATE` | | [Get a bulk reference job](/operations/getV2WalletFiatByCurrencyPaymentBulkReferencesByJobId) | `GET /v2/wallet/fiat/{currency}/payment/bulk-references/{jobId}` | `COLLECTIONS_READ` | | [List bulk reference job items](/operations/getV2WalletFiatByCurrencyPaymentBulkReferencesByJobIdItems) | `GET /v2/wallet/fiat/{currency}/payment/bulk-references/{jobId}/items` | `COLLECTIONS_READ` | | [List credited deposits](/operations/getV2WalletFiatByCurrencyPaymentDeposits) | `GET /v2/wallet/fiat/{currency}/payment/deposits` | `COLLECTIONS_READ` | | [Unlink a deposit from its reference](/operations/deleteV2WalletFiatByCurrencyPaymentDepositsByPublicIdReference) | `DELETE /v2/wallet/fiat/{currency}/payment/deposits/{publicId}/reference` | `COLLECTIONS_DELETE` | | [Link a deposit to a reference](/operations/putV2WalletFiatByCurrencyPaymentDepositsByPublicIdReference) | `PUT /v2/wallet/fiat/{currency}/payment/deposits/{publicId}/reference` | `COLLECTIONS_UPDATE` | ### Collection schedules Schedules that pay collected funds out at set times, the payments queued for each run, and the runs themselves. | Endpoint | Request | Needs | |---|---|---| | [List queued scheduled payments](/operations/getV2PaymentsCollectEvents) | `GET /v2/payments/collect/events` | `COLLECTIONS_READ` | | [Get a scheduled payment](/operations/getV2PaymentsCollectEventsById) | `GET /v2/payments/collect/events/{id}` | `COLLECTIONS_READ` | | [List processed scheduled payments](/operations/getV2PaymentsCollectHistory) | `GET /v2/payments/collect/history` | `COLLECTIONS_READ` | | [Get a payout run](/operations/getV2PaymentsCollectRunsById) | `GET /v2/payments/collect/runs/{id}` | `COLLECTIONS_READ` | | [List a payout run's payments](/operations/getV2PaymentsCollectRunsByIdPayments) | `GET /v2/payments/collect/runs/{id}/payments` | `COLLECTIONS_READ` | | [List payout runs](/operations/getV2PaymentsCollectRunsHistory) | `GET /v2/payments/collect/runs/history` | `COLLECTIONS_READ` | | [List payout schedules](/operations/getV2PaymentsCollectSchedules) | `GET /v2/payments/collect/schedules` | `COLLECTIONS_READ` | | [Get upcoming scheduled payouts](/operations/getV2PaymentsCollectUpcoming) | `GET /v2/payments/collect/upcoming` | `COLLECTIONS_READ` | ### Settlements Payouts of collected funds, made instantly or by a scheduled run, and the payments each one paid out. | Endpoint | Request | Needs | |---|---|---| | [List settlements](/operations/getV2PaymentsCollectSettlements) | `GET /v2/payments/collect/settlements` | `COLLECTIONS_READ` | | [Get a settlement](/operations/getV2PaymentsCollectSettlementsById) | `GET /v2/payments/collect/settlements/{id}` | `COLLECTIONS_READ` | | [List a settlement's payments](/operations/getV2PaymentsCollectSettlementsByIdPayments) | `GET /v2/payments/collect/settlements/{id}/payments` | `COLLECTIONS_READ` | | [Get the settlement wallet](/operations/getV2PaymentsCollectSettlementsWallet) | `GET /v2/payments/collect/settlements/wallet` | `COLLECTIONS_READ` | --- --- url: /api-docs/postV2PaymentsBulkWithdrawalsBeneficiaries.md description: POST /v2/payments/bulk-withdrawals/beneficiaries --- # Create a beneficiary `POST /v2/payments/bulk-withdrawals/beneficiaries` Saves a beneficiary you can pay out to. If an archived beneficiary has the same bank and account number, it's restored with these details and keeps its original ID. **Requires** `DISBURSEMENTS_CREATE`, granted by `DISBURSEMENTS_WRITE` ([bundles](/guides/permissions)) Sign the request as described in [Authentication](/guides/authentication). ## Request Body **Content-Type:** `application/json` | Property | Type | Required | Description | |---|---|---|---| | bank | string | Yes | Bank code from `GET /v2/payments/bulk-withdrawals/banks`, e.g. `FNB`. Case-insensitive. | | accountHolder | string | No | - | | accountNumber | string | No | - | | branchCode | string | No | - | | accountType | string | No | Account type: `CHEQUE`, `SAVINGS`, `BOND` or `TRANSMISSION`. Case-insensitive. | | email | string | No | - | | type | string | No | `INDIVIDUAL` or `BUSINESS`. Case-insensitive. | | tradingName | string | No | - | | reference | string | No | Default payment reference for this beneficiary. It isn't applied to payouts automatically: send it as the withdrawal's `reference`. | | country | string | No | Country of registration as an ISO 3166 alpha-2 code, e.g. `ZA`. | ## Responses ### 200 OK | Property | Type | Required | Description | |---|---|---|---| | id | string (uuid) | Yes | - | | bank | string | Yes | - | | bankDisplayName | string | Yes | - | | accountHolder | string | Yes | - | | accountNumberMasked | string | Yes | Account number with all but the last four digits masked. | | accountNumber | string | Yes | - | | branchCode | string | Yes | - | | accountType | string | Yes | - | | email | string | Yes | - | | type | string | Yes | `INDIVIDUAL`, `BUSINESS`, or `null` if never set, which is possible for beneficiaries saved from a batch. | | tradingName | string | Yes | - | | reference | string | Yes | Default payment reference for this beneficiary. It isn't applied to payouts automatically. | | country | string | Yes | - | | createdAt | string (date-time) | Yes | - | | updatedAt | string (date-time) | Yes | - | | lastPaidAt | string (date-time) | Yes | When a payout to this beneficiary was last attempted; `null` if never. | | deletedAt | string (date-time) | Yes | When the beneficiary was archived; `null` if it's active. | | deletedBy | string | Yes | ID of the user or API key that archived the beneficiary. | **Example:** ```json { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "bank": "FNB", "bankDisplayName": "FNB/RMB", "accountHolder": "Thandi Nkosi", "accountNumberMasked": "*******0327", "accountNumber": "62845190327", "branchCode": "250655", "accountType": "CHEQUE", "email": "thandi@example.com", "type": "INDIVIDUAL", "tradingName": null, "reference": "SALARY", "country": "ZA", "createdAt": "2026-09-01T10:12:30.004Z", "updatedAt": "2026-09-01T10:12:30.004Z", "lastPaidAt": null, "deletedAt": null, "deletedBy": null } ``` ## Example request Signs the request when it runs. Set `VALR_API_KEY` and `VALR_API_SECRET` first. ```bash #!/usr/bin/env bash # Needs VALR_API_KEY and VALR_API_SECRET. Set VALR_SUBACCOUNT_ID to act on a sub-account. METHOD=POST REQUEST_PATH='/v2/payments/bulk-withdrawals/beneficiaries' BODY='{"bank":"","accountHolder":"","accountNumber":"","branchCode":"","accountType":"","email":"","type":"","tradingName":"","reference":"","country":""}' TIMESTAMP=$(($(date +%s) * 1000)) SIGNATURE=$(printf '%s' "${TIMESTAMP}${METHOD}${REQUEST_PATH}${BODY}${VALR_SUBACCOUNT_ID:-}" \ | openssl dgst -sha512 -hmac "$VALR_API_SECRET" | awk '{print $NF}') HEADERS=(-H "X-VALR-API-KEY: $VALR_API_KEY" -H "X-VALR-SIGNATURE: $SIGNATURE" -H "X-VALR-TIMESTAMP: $TIMESTAMP") [ -n "$VALR_SUBACCOUNT_ID" ] && HEADERS+=(-H "X-VALR-SUB-ACCOUNT-ID: $VALR_SUBACCOUNT_ID") curl -X "$METHOD" "https://api.valr.com$REQUEST_PATH" "${HEADERS[@]}" -H "Content-Type: application/json" --data "$BODY" ``` ```python import hashlib import hmac import os import time import requests API_KEY = os.environ["VALR_API_KEY"] API_SECRET = os.environ["VALR_API_SECRET"] SUBACCOUNT_ID = os.environ.get("VALR_SUBACCOUNT_ID", "") method = "POST" path = "/v2/payments/bulk-withdrawals/beneficiaries" body = "{\"bank\":\"\",\"accountHolder\":\"\",\"accountNumber\":\"\",\"branchCode\":\"\",\"accountType\":\"\",\"email\":\"\",\"type\":\"\",\"tradingName\":\"\",\"reference\":\"\",\"country\":\"\"}" # send exactly the string you sign timestamp = str(int(time.time() * 1000)) signature = hmac.new( API_SECRET.encode(), (timestamp + method + path + body + SUBACCOUNT_ID).encode(), hashlib.sha512 ).hexdigest() headers = {"X-VALR-API-KEY": API_KEY, "X-VALR-SIGNATURE": signature, "X-VALR-TIMESTAMP": timestamp} if SUBACCOUNT_ID: headers["X-VALR-SUB-ACCOUNT-ID"] = SUBACCOUNT_ID headers["Content-Type"] = "application/json" response = requests.request(method, "https://api.valr.com" + path, headers=headers, data=body.encode()) print(response.status_code, response.text) ``` ## Errors | HTTP | Code | Name | When | |---|---|---|---| | 400 | `-113` | `INVALID_JSON` | The body isn't valid JSON. | | 400 | `-11` | `INVALID_REQUEST` | The body is empty or a field fails validation; `validationErrors` lists each failing field. | | 400 | `BAD_REQUEST` | `BAD_REQUEST` | A query parameter or field is invalid. | | 403 | `-16098` | `PAYMENTS_USER_ACCESS_NOT_CONFIGURED` | Payments haven't been set up for your business yet. | | 409 | `CONFLICT` | `CONFLICT` | An active beneficiary with this bank and account number already exists; update it instead. | | 500 | `INTERNAL_ERROR` | `INTERNAL_ERROR` | Unexpected error in the payments service. | | 502 | `-16097` | `EXCHANGE_API_ERROR` | The payments service failed to process the request. | | 502 | `EXCHANGE_API_ERROR` | `EXCHANGE_API_ERROR` | The payments service couldn't complete the request. Try again later. | | 503 | `-16099` | `PAYMENTS_BACKEND_PROXY_DISABLED` | Payments endpoints are temporarily unavailable. | Plus the [errors common to all signed endpoints](/guides/errors#common-to-all-endpoints). --- --- url: /api-docs/postV2WalletFiatByCurrencyPaymentReferences.md description: 'POST /v2/wallet/fiat/{currency}/payment/references' --- # Create a deposit reference `POST /v2/wallet/fiat/{currency}/payment/references` Creates a unique deposit reference for a payer. Give the reference to the payer; deposits that quote it are matched to it. You can record the amount you expect and the payer's name and email to help you reconcile deposits. **Requires** `COLLECTIONS_CREATE`, granted by `COLLECTIONS_WRITE` ([bundles](/guides/permissions)) Sign the request as described in [Authentication](/guides/authentication). ## Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | currency | path | string | Yes | Fiat currency of the reference, e.g. `ZAR`. | ## Request Body **Content-Type:** `application/json` | Property | Type | Required | Description | |---|---|---|---| | expectedAmount | string | No | Amount you expect the payer to deposit, as a decimal string greater than zero. | | name | string | No | Payer's name. | | email | string | No | Payer's email address. | ## Responses ### 201 Created | Property | Type | Required | Description | |---|---|---|---| | referenceCode | string | Yes | The reference your payer quotes when depositing. | | currency | string | Yes | - | | expectedAmount | string | No | - | | createdAt | integer (int64) | Yes | When the reference was created, in Unix milliseconds. | | name | string | No | - | | email | string | No | - | | matchedDeposits | UserMatchedDepositInfo\[] | Yes | Deposits matched to this reference. | | matchedDepositCount | integer (int32) | Yes | - | **`matchedDeposits` properties:** | Property | Type | Required | Description | |---|---|---|---| | publicId | string (uuid) | Yes | - | | amount | string | Yes | - | | effectiveDate | string | Yes | - | | creditedAt | integer (int64) | Yes | - | | isOverride | boolean | Yes | `true` if this deposit is set as the reference's override deposit. | ## Example request Signs the request when it runs. Set `VALR_API_KEY` and `VALR_API_SECRET` first. ```bash #!/usr/bin/env bash # Needs VALR_API_KEY and VALR_API_SECRET. Set VALR_SUBACCOUNT_ID to act on a sub-account. METHOD=POST REQUEST_PATH='/v2/wallet/fiat/{currency}/payment/references' BODY='{"expectedAmount":"","name":"","email":""}' TIMESTAMP=$(($(date +%s) * 1000)) SIGNATURE=$(printf '%s' "${TIMESTAMP}${METHOD}${REQUEST_PATH}${BODY}${VALR_SUBACCOUNT_ID:-}" \ | openssl dgst -sha512 -hmac "$VALR_API_SECRET" | awk '{print $NF}') HEADERS=(-H "X-VALR-API-KEY: $VALR_API_KEY" -H "X-VALR-SIGNATURE: $SIGNATURE" -H "X-VALR-TIMESTAMP: $TIMESTAMP") [ -n "$VALR_SUBACCOUNT_ID" ] && HEADERS+=(-H "X-VALR-SUB-ACCOUNT-ID: $VALR_SUBACCOUNT_ID") curl -X "$METHOD" "https://api.valr.com$REQUEST_PATH" "${HEADERS[@]}" -H "Content-Type: application/json" --data "$BODY" ``` ```python import hashlib import hmac import os import time import requests API_KEY = os.environ["VALR_API_KEY"] API_SECRET = os.environ["VALR_API_SECRET"] SUBACCOUNT_ID = os.environ.get("VALR_SUBACCOUNT_ID", "") method = "POST" path = "/v2/wallet/fiat/{currency}/payment/references" body = "{\"expectedAmount\":\"\",\"name\":\"\",\"email\":\"\"}" # send exactly the string you sign timestamp = str(int(time.time() * 1000)) signature = hmac.new( API_SECRET.encode(), (timestamp + method + path + body + SUBACCOUNT_ID).encode(), hashlib.sha512 ).hexdigest() headers = {"X-VALR-API-KEY": API_KEY, "X-VALR-SIGNATURE": signature, "X-VALR-TIMESTAMP": timestamp} if SUBACCOUNT_ID: headers["X-VALR-SUB-ACCOUNT-ID"] = SUBACCOUNT_ID headers["Content-Type"] = "application/json" response = requests.request(method, "https://api.valr.com" + path, headers=headers, data=body.encode()) print(response.status_code, response.text) ``` ## Errors | HTTP | Code | Name | When | |---|---|---|---| | 400 | `-113` | `INVALID_JSON` | The body isn't valid JSON. | | 400 | `-11` | `INVALID_REQUEST` | The body is empty or a field fails validation; `validationErrors` lists each failing field. | | 400 | `-15721` | `SCOPED_DEPOSIT_ACCOUNT_CURRENCY_MISMATCH` | Your collections account doesn't support this currency. | | 429 | `-15116` | `UNIQUE_DEPOSIT_REFERENCE_RATE_LIMIT_EXCEEDED` | You've reached the daily limit for creating references. | | 429 | `-15119` | `UNIQUE_DEPOSIT_REFERENCE_OVERALL_LIMIT_EXCEEDED` | You've reached the maximum number of references. | | 503 | `-15117` | `UNIQUE_DEPOSIT_REFERENCES_FEATURE_DISABLED` | Deposit references are temporarily unavailable. | Plus the [errors common to all signed endpoints](/guides/errors#common-to-all-endpoints). --- --- url: /api-docs/postV2PaymentsBulkWithdrawalsBatches.md description: POST /v2/payments/bulk-withdrawals/batches --- # Create a disbursement batch `POST /v2/payments/bulk-withdrawals/batches` Creates a batch of ZAR payouts and executes it straight away: batches created with an API key skip the approval step, so the fee is charged and the payouts are submitted immediately, and the key needs the `DISBURSEMENTS_APPROVE` permission as well (the `DISBURSEMENTS_WRITE` bundle includes it). Your payout account must have enough available balance for the payouts plus the fee. `X-VALR-Idempotency-Key` is optional. Send one to make retries safe: repeating a key returns the original batch's ID with status 200. Without it, every request creates a new batch. **Requires** `DISBURSEMENTS_CREATE` (granted by `DISBURSEMENTS_WRITE`) and `DISBURSEMENTS_APPROVE` (granted by `DISBURSEMENTS_WRITE`) ([bundles](/guides/permissions)) Sign the request as described in [Authentication](/guides/authentication). ## Parameters | Name | In | Type | Required | Description | |---|---|---|---|---| | X-VALR-Idempotency-Key | header | string | No | Your unique key for this batch: up to 128 letters, digits, `:`, `_`, `.` or `-`. Reuse it only to retry the same request. | ## Request Body **Content-Type:** `application/json` | Property | Type | Required | Description | |---|---|---|---| | withdrawals | BulkWithdrawalItemRequest\[] | Yes | The payouts to make, one per recipient. | | label | string | No | Your label for the batch, up to 64 characters. Defaults to the creation time in South African time, e.g. `23-10-26-10:15`. | **`withdrawals` properties:** | Property | Type | Required | Description | |---|---|---|---| | bank | string | Yes | Bank code from `GET /v2/payments/bulk-withdrawals/banks`, e.g. `FNB`. Case-insensitive. | | accountHolder | string | No | Name on the recipient's bank account, 4 to 64 characters. | | accountNumber | string | No | Recipient's account number, 4 to 18 digits. | | branchCode | string | No | Recipient's branch code, 6 digits. | | accountType | string | No | Account type: `CHEQUE`, `SAVINGS`, `BOND` or `TRANSMISSION`. Case-insensitive. | | amount | number | No | Amount to pay in ZAR, greater than zero with at most two decimal places. | | reference | string | No | Payment reference for this payout: up to 35 letters, digits, spaces, `/`, `'` or `-`. | | email | string | No | Recipient's email address. Saved on their beneficiary, not sent with the payout. | | type | string | No | `INDIVIDUAL` or `BUSINESS`. Saved on the recipient's beneficiary. Case-insensitive. | | tradingName | string | No | Recipient's trading name. Saved on their beneficiary, not sent with the payout. | | country | string | No | Recipient's country of registration as an ISO 3166 alpha-2 code, e.g. `ZA`. Saved on their beneficiary. | ## Responses ### 200 A batch with this idempotency key already exists. Its ID is returned and nothing new is created. | Property | Type | Required | Description | |---|---|---|---| | id | string (uuid) | Yes | Batch ID. | **Example:** ```json { "id": "5b9a3c1e-2f4d-4a7b-9c8e-1d2f3a4b5c6d" } ``` ### 202 Batch created and executed. Check its progress with `GET /v2/payments/bulk-withdrawals/batches/{id}`; individual payouts can still fail. | Property | Type | Required | Description | |---|---|---|---| | id | string (uuid) | Yes | Batch ID. | **Example:** ```json { "id": "5b9a3c1e-2f4d-4a7b-9c8e-1d2f3a4b5c6d" } ``` ## Example request Signs the request when it runs. Set `VALR_API_KEY` and `VALR_API_SECRET` first. ```bash #!/usr/bin/env bash # Needs VALR_API_KEY and VALR_API_SECRET. Set VALR_SUBACCOUNT_ID to act on a sub-account. METHOD=POST REQUEST_PATH='/v2/payments/bulk-withdrawals/batches' BODY='{"withdrawals":"","label":"