Core Concepts
Transfer
A transfer moves funds between Exness accounts: from one of your accounts either to another of your own accounts, or — for authorized partners — to an account of another Exness client. The destination is always an Exness account; sending funds outside Exness is not part of this API.
- Every created transfer is identified by a
transfer_id. - The amount is always expressed in the currency of the source account.
A created transfer cannot be cancelled or reversed through the API. Check the destination account before sending. If a transfer was sent to a wrong account, contact Customer Support.
Destination type
Every transfer states whose account receives the money in the required destination_type field:
| Value | Destination | Source account |
|---|---|---|
own_account | Another of your own accounts | Any of your supported accounts |
another_client | An account of another Exness client — a trading account on the Exness platform or a wallet. Available to authorized partners. | Always your Partner Reward Wallet; any other source is rejected with TRANSFER_SOURCE_NOT_ALLOWED |
destination_type must match the destination account. If you send own_account with an account that is not yours, or another_client with your own account, the API responds with ACCOUNT_NOT_FOUND — the same answer as for an account that does not exist.
Some account combinations are not supported and are rejected with TRANSFER_NOT_SUPPORTED or TRANSFER_DESTINATION_NOT_SUPPORTED. A transfer to another client can be declined by eligibility checks with OPERATION_NOT_ALLOWED.
Accounts and identifiers
Only accounts on the Exness platform are supported. MT4 and MT5 accounts cannot be used as a source or a destination; they are rejected with TRANSFER_MT_ACCOUNT_NOT_SUPPORTED.
| You need | Where to find it |
|---|---|
| Your wallet numbers | Personal Area → Payments & wallet |
| Your trading account numbers | Personal Area → Trading → Accounts |
| Your Reward Wallet number | Partner Personal Area → Wallet accounts |
| Trading account numbers of your direct clients | Partner Personal Area → Reports → Client accounts, or the Exness Partnership API: GET /v1/partnership/reports/clients/accounts, field client_account. Account numbers of second-level clients are masked. |
| An account of another Exness client | The recipient provides the account number |
Client account lists can include MT4 and MT5 accounts; transfers to them are not supported.
Identifier rules:
Account numbers
Strings. Send them exactly as shown; do not convert them to numbers.
transfer_id
A numeric string returned when a transfer is created. Send it back as a string, exactly as received.
API key scopes
| Scope | Allows |
|---|---|
Internal transfers | Transfers between your own accounts (destination_type: own_account), account balance, transfer status |
Partner transfers | Transfers from your Reward Wallet to another Exness client (destination_type: another_client), account balance, transfer status |
- A transfer creation request that is not covered by the scopes of your API key is rejected with
AUTH_PERMISSION_DENIED. - A transfer whose type is not covered by the scopes of your API key is reported as
TRANSFER_NOT_FOUND— for example, when a key with only theInternal transfersscope requests the status of a transfer to another client. - A transfer is readable by its sender and its recipient: by any of their API keys whose scopes cover the transfer type. Which key created the transfer does not matter — a key added after a rotation reads it too.
AUTH_PERMISSION_DENIEDis returned only for the scopes and the allowed source IPs of the key. An account that is not available to your API key is always reported asACCOUNT_NOT_FOUND, the same as an account that does not exist.
See Payments scopes.
Transfer lifecycle
The creation request is processed synchronously: the response contains either the created transfer or an error. A created transfer can still be in progress; its final status is available through GET /v1/transfers/{transfer_id}.
| Status | Meaning | Final |
|---|---|---|
processing | The transfer is in progress. Check again later. | No |
done | The money has been transferred. | Yes — never changes |
rejected | The transfer was not completed. The funds are not debited from the source account, or they are returned to it. | Yes — never changes |
Transfers usually complete within a few seconds, but processing time can vary with load. We recommend checking the status every 2 seconds while it is processing. Keep all status requests within the request rate limit. Choose how long your integration will keep checking. Stopping these checks does not cancel the transfer.
- The API returns the status only. For the details of a transfer that is not
done, see Transaction History in the Payments & wallet section of your Personal Area.
Transfer amounts and precision
- Amounts are positive fixed-point decimal strings, for example
"100.00", in the currency of the source account. - Sending more decimal places than the currency allows is rejected, not rounded.
- When the source and destination accounts use different currencies, the amount is converted at execution. The creation response returns the amount of the transfer in the source account currency (
source) and in the destination account currency (destination).
Balance
GET /v1/accounts/{account_id}/balance returns balance — the amount available for transfers from the account, as a non-negative decimal string in the account currency, together with the account currency.
Transfer idempotency
- Every transfer creation requires an idempotency key in the
EXN-IDEMPOTENCY-KEYheader: a UUID in its canonical text form, for example0f8fad5b-d9cb-469f-a165-70867728950e. A missing key or a key that is not a valid UUID is rejected withREQUEST_INVALID_PARAMETERS. - Signed
GETrequests send the header with an empty value, and the signedidempotency_keyfield is an empty string. - Retrying the identical request with the same key never executes the transfer twice. If the retry passes the checks of the source account, funds, limits, and eligibility, which run before the key is looked up, the API returns
201with the sametransfer_id; otherwise it returns the error from those checks, even though the transfer exists. The amounts in that response are calculated for the retry and can differ from the amounts of the transfer, for example after an exchange rate change; the transfer itself does not change. Its amounts are shown in Transaction History. - An identical request has the same source account, destination account, and amount. Amounts are compared as numbers, so
100and100.00are the same. - Reusing a key with a different source account, destination account, or amount is rejected with
REQUEST_IDEMPOTENCY_KEY_REUSED. - Idempotency keys are unique per Exness client: a key is bound to your transfer regardless of which of your API keys sent the request.
- Idempotency keys do not expire: a key used to create a transfer stays bound to that transfer, so a retry with the same key never executes it twice, at any time.
- Before every creation request, record durably that it is being sent. A request in flight has an unknown outcome until you record its answer.
- While no request with the key has had an unknown outcome, any other error response of this API means that this request created no transfer.
- Use a new key for every new transfer, including a new attempt after a transfer is
rejected.
After an unknown outcome — a timeout, no response, a non-API response, TRANSFER_OUTCOME_UNKNOWN, SYSTEM_INTERNAL_ERROR, or SYSTEM_TEMPORARY_UNAVAILABLE — resend the same request with the same key; the key stays reserved for the transfer, even after a restart of your system. Only a 201, a transfer shown in Transaction History, or Customer Support confirms the outcome.
If you stop sending a transfer without a 201, retire its key first: never send it again. Use a new key only for a new transfer, or after Customer Support confirms that no transfer was created. See Recovery and Idempotency.
Error model
- Each error response contains exactly one error with
code,error_message, anddescription. code(a stable numeric business code) anderror_message(a stable<CATEGORY>_<REASON>name) are the machine-readable contract. The HTTP status describes the transport layer.descriptionis a short human-readable hint. Its text may change without notice; do not parse it.- Do not retry errors automatically unless the error table allows it. Some errors require you to fix the cause first. See Troubleshooting Your API Requests for retry rules and Recovery and idempotency for transfer creation.
An error response of this API is a JSON body whose code and error_message are a pair from the public error codes. Any other response — an HTML page, or a JSON body from a proxy or load balancer, even one with a code — is a non-API response: it comes from infrastructure between you and the API and says nothing about the outcome of your request. Treat it as no response.
Rate limits
When you exceed a request quota, the API responds with REQUEST_RATE_LIMIT (HTTP 429). Wait before sending another request.