Create a payout
/payoutsSends money from one of the merchant's accounts to an individual Jeton user, or to a bank account or crypto
wallet, the merchant's own included — a withdrawal is a payout.
The rail (SEPA, SWIFT, instant bank transfer, on-chain, internal ledger) is chosen by the server
from the destination and the beneficiary, and reported back in rail — you never name one. For a
bank_account, POST /payouts/requirements tells you beforehand which rail it will be and what that rail
needs.
Always 202: the payout is accepted and executes asynchronously. There is no merchant approval step
for API payouts. A payout held before it is sent reads pending; one on its rail reads processing. Its
id is the txn_… id of its debit in the ledger.
A refusal (insufficient_funds, limit_exceeded, declined, …) is a 400 with its own code. It does
not use up the merchantReference: once refused, the reference may be sent again with a new
Idempotency-Key. A refusal made after the payout was recorded leaves a failed entry in the ledger under the
reference; at most one payout under a reference is ever not failed. Failures after acceptance surface as
status: failed with a failure object. A 500 is never a refusal: retry with the same
Idempotency-Key and you get the payout as it stands, never a second one — or look the reference up with
GET /payouts?merchantReference=, which finds the payout if it was made. A request left unanswered and not
retried for 15 minutes is settled by the platform: answered with the payout if one was made, otherwise refused
with 400 request_not_executed — nothing moved, its merchantReference is free again, and a retry under the same
key gets that answer, so send it again under a new one. The same Idempotency-Key with a different request is
400 idempotency_key_reused, however long after.
This phase ships destination.type: bank_account, jeton_account and crypto_wallet; recipient is
accepted by the schema but refused with 400 validation_failed until it lands. scheduledAt is likewise not
honoured yet, so a payout executes immediately. Its fee is priced the moment before it is sent and reported on
the 202 response; a bank payout is charged exactly that fee.
What the chosen rail needs is checked before anything is written. On swift, bic is required
(validation_failed on destination.bic). On instant_bank_transfer, the payout is checked against the
beneficiary fields its route requires (POST /payouts/requirements); a missing or malformed field is
400 validation_failed listing every such field in errors[], before anything is written, so the same
merchantReference can be sent again once they are filled in. Each error names the field where you send it
(destination.holderAddress.city, destination.additionalFields.taxId) with the code the rest of the API
uses for the same problem — not_empty, minimum_length, maximum_length, regular_expression — or
not_allowed (a value outside the route's allowedValues) and unknown_field (an additionalFields key the
route does not know). A beneficiary the instant route does not serve is 400 payout_rail_unavailable.
Parameters
Idempotency-KeystringheaderrequiredClient-generated unique key for this logical request (UUID recommended). See Idempotency.min length 1 · max length 255
Request body application/jsonrequired
merchantReferencestringrequiredYour own reference for this payout — what your books reconcile against. Reusing it under a newIdempotency-Keyis refused with400 duplicate_merchant_reference(a reference your legacy/v1payouts used included), so a file re-sent the next day cannot pay anyone twice — unless the payout that held it was refused, which frees it. Retrying the SAME request is what theIdempotency-Keyis for.min length 1 · max length 100
amountobjectrequiredThe amount the destination receives. Fees are charged on top and reported infee/totalDebited.valuestringrequiredExample:
149.90pattern ^-?\d{1,18}(\.\d{1,18})?$
currencystringrequiredISO 4217 code for fiat (EUR), asset ticker for crypto (USDT).Example:
EURpattern ^[A-Z0-9]{3,10}$
destinationone ofrequiredWhere the money goes. The server picks the rail from the destination type.One of
BankAccountDestination
A bank account, by IBAN. The platform chooses the rail and reports it in the payout's
rail;POST /payouts/requirementstells you beforehand which rail it will be and what it needs. SEPA and SWIFT need onlyiban,accountHolderNameand, for SWIFT,bic.The
instant_bank_transferrail also needs the beneficiary fields its provider route asks for — which ones depends on the beneficiary's country and scheme. They are named as the legacy jetonbank contract named them (holderEmail,holderAddress.city,bankName); a field the route asks for that has no typed property below goes inadditionalFieldsunder the key requirements report (additionalFields.taxId→"taxId"). A pre-filled value is used when you leave its field out; a value you send always wins.On any other rail these fields are neither checked against a route nor stored — only the size bounds below apply — and on
instant_bank_transfera typed field the route does not ask for is dropped the same way. They are write-only: a payout never reads them back.typestringrequiredAllowed values:
bank_accountibanstringrequiredpattern ^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$
accountHolderNamestringrequiredmin length 1 · max length 70
bicstringRequired when the payout travels over SWIFT; ignored on SEPA.pattern ^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$
countrystringBeneficiary bank country when not derivable from the IBAN.pattern ^[A-Z]{2}$
holderEmailstringwrite-onlymax length 255
holderPhonestringwrite-onlymax length 255
holderAddressPayoutAddresswrite-onlybankNamestringwrite-onlymax length 255
bankCodestringwrite-onlymax length 255
branchCodestringwrite-onlymax length 255
accountNumberstringwrite-onlymax length 255
interbankAccountCodestringwrite-onlymax length 255
accountTypestringwrite-onlyOne of the route'sallowedValues.max length 255
documentIdstringwrite-onlymax length 255
bankAddressPayoutAddresswrite-onlyadditionalFieldsobjectwrite-onlyFields the route asks for that have no typed property, keyed exactly as requirements name them (keys are case-sensitive). A key the route does not know is400 validation_failed(unknown_field) — a typo here would otherwise lose the value silently. A key the destination has a property for (bankName,holderAddress.city, andholderName/iban/bicSwift, which areaccountHolderName/iban/bic) is refused withreserved_field: each value has one place in the request.Example:
{"taxId":"123456789"}
CryptoWalletDestination
An on-chain address. The payout currency is the asset (
amount.currency: USDT), paid from a crypto account holding it; the network is the payout's rail, so there is norailto choose. Settles asynchronously: expectprocessinguntil the chain confirms.networkmust be an active network of the asset —GET /accounts/{id}lists them on every crypto account (networks). Anything else is400 payout_network_unavailable. It may be left out when the asset is active on exactly one network, which is then used; an asset active on several is400 payout_network_required— never a guess, since some networks share an address format and money sent on the wrong one is lost. The address is checked against the network at creation; one that is not valid on it is400 payout_invalid_wallet_address, before anything is priced or sent. Networks that need a memo / destination tag are not supported yet.typestringrequiredAllowed values:
crypto_walletnetworkstringThe chain code (TRX,ETH,BTC) or the token standard it is known by (TRC20,ERC20). Optional when the asset has a single active network. Reads back as the chain code, including when it was left out.Example:
TRXmax length 20
addressstringrequiredSent as-is — case is significant on several chains.Example:
TQ5NMqJjRcuLcrbFn3jsCswDdcs2Vwk7Zimax length 128
JetonAccountDestination
An individual Jeton user, named by exactly one of
customerNumber,emailorphone. Instant, on the internal ledger — there is norailto choose. PrefercustomerNumber: it is not personal data, and it is whatpayer.customerNumberreports on the payments you receive, so you can pay a customer back with the identity they paid you with.Only individuals resolve. An e-mail or phone is looked up as an individual, never as a business, and a business's customer number resolves to nothing — money to another business is a transfer (
POST /transfers). "No such user", "no active account in the payout currency" and "not an individual" are deliberately one answer,400 payout_recipient_not_found, so the endpoint cannot be used to learn who banks here. Itsdetailis one text for every reason, and points atPOST /transfers— a business is paid with a transfer. An API key whose recipients keep failing to resolve is refused with429 payout_recipient_lookup_limitedfor the rest of the window.typestringrequiredAllowed values:
jeton_accountcustomerNumberstringwrite-onlyThe recipient's public Jeton customer number.pattern ^[1-9][0-9]{0,18}$
emailstring (email)write-onlymax length 254
phonestringwrite-onlyInternational form, e.g.+905551112233. Spaces, dashes and brackets are ignored.accountHolderNamestringread-onlyThe individual's name as the ledger shows it, on a payout read back. Never sent; absent on thePOSTresponse.
One of
object
customerNumberstringrequired
object
emailstringrequired
object
phonestringrequired
SavedRecipientDestination
A recipient saved in the portal, addressed by its id instead of repeating the beneficiary details. Resolves to the destination the recipient holds; a business recipient is refused like ajeton_accountone.typestringrequiredAllowed values:
recipientrecipientIdstring (uuid)requiredA uuid (time-ordered, UUIDv7) — the id of something the portal owns: your merchant, a saved recipient. A resource of this API has a prefixed id instead (pay_…,fx_…,evt_…,txn_…,acc_…).Example:
019985c2-4a6e-7c3b-9f10-2d5e8a1b3c4d
sourceAccountIdstringOptional. Left out, your default account in the payout currency (isDefault) is debited — or the only one, when you hold just one.Example:
acc_AAAAAAAAAAcpattern ^acc_[A-Za-z0-9_-]{11}$
descriptionstringInternal note, shown in your portal and statements.max length 140
scheduledAtstring (date-time)Execute at this time instead of now. Not honoured yet.Example:
2026-09-18T10:15:30.123Z
Responses
Payout accepted. The code is always 202, regardless of destination type — read status in the body:
completed(typical forjeton_accountdestinations executed on the internal ledger within the request) → you are done.pending/processing→ pollLocation;Retry-Afteris sent only then.
Inline completion is an optimisation, never a guarantee — never branch on destination type. An idempotent
replay returns THIS original body even if the payout has since progressed; use GET for the current state.
Headers
Locationstring (uri-reference)The resource URL to poll for the current state (RFC 9110 §15.3.3 status monitor).Retry-AfterintegerSeconds to wait before pollingLocation. On a202it is present only while the body'sstatusis not yet terminal — its absence is a second signal that the work is already done.X-Request-IdstringThe request's trace id: the same value a problem body carries astraceId. Quote it when you contact support. A replayed response (Idempotent-Replayed: true) carries the replay's own; its body keeps the original request'straceId.RateLimit-LimitintegerRateLimit-RemainingintegerRateLimit-ResetintegerSeconds until the window resets.Idempotent-Replayedbooleantruewhen this response was replayed from an earlier request with the sameIdempotency-Key.
Schema application/json
idstringrequiredThe ledger id of the payout's debit — the same entryGET /accounts/{accountId}/transactionsshows.Example:
txn_AAAAAAAAB3Ypattern ^txn_[A-Za-z0-9_-]{11}$
merchantReferencestringYour own reference for this payout, as you sent it. Absent on a payout made without one (portal, batch).statusstringrequiredLifecycle of a payout.pendingaccepted, not sent yet (a payout held for review included — the hold is never named) ·processingexecuting on the rail ·completed·failed·cancelledwithdrawn by the platform before it was sent. Terminal:completed,failed,cancelled.Allowed values:
pendingprocessingcompletedfailedcancelledmethodstringrequiredWhich payout product moved the money — the same vocabulary payments use for money coming in. Derived by the server from the destination; it is never sent. It decides the fee, the limits and how the payout behaves:jeton_walletsettles instantly on the internal ledger,bank_transfertravels on a bank rail,cryptosettles on-chain.Allowed values:
jeton_walletbank_transfercryptoamountMoneyrequiredDecimal amount as a string — never a float, never minor units. An amount you send may carry no more decimal places than its currency has (and never more than 8); trailing zeros do not count, so10.100 EURis fine and10.123 EURis400 validation_failedonamount.value. An amount the API returns is written with exactly as many decimal places as its currency has (at most 8):10.10 EUR, never10.1.valuestringrequiredExample:
149.90pattern ^-?\d{1,18}(\.\d{1,18})?$
currencystringrequiredISO 4217 code for fiat (EUR), asset ticker for crypto (USDT).Example:
EURpattern ^[A-Z0-9]{3,10}$
feeobjectrequiredFee charged on top ofamount, in the payout currency. On thePOSTresponse, the fee quoted the moment before the payout was sent — a bank payout is charged exactly that; a read shows what the ledger charged.valuestringrequiredExample:
149.90pattern ^-?\d{1,18}(\.\d{1,18})?$
currencystringrequiredISO 4217 code for fiat (EUR), asset ticker for crypto (USDT).Example:
EURpattern ^[A-Z0-9]{3,10}$
totalDebitedobjectrequiredamount + fee: what leaves the source account.valuestringrequiredExample:
149.90pattern ^-?\d{1,18}(\.\d{1,18})?$
currencystringrequiredISO 4217 code for fiat (EUR), asset ticker for crypto (USDT).Example:
EURpattern ^[A-Z0-9]{3,10}$
destinationone ofrequiredWhere the money went, as the ledger records it: a bank account'siban,accountHolderName,bicandcountry; a crypto wallet'snetworkandaddress; a Jeton payout's individual byaccountHolderName, the name the ledger shows — never by thecustomerNumber,emailorphoneit was sent to, and not on thePOSTresponse.One of
BankAccountDestination
A bank account, by IBAN. The platform chooses the rail and reports it in the payout's
rail;POST /payouts/requirementstells you beforehand which rail it will be and what it needs. SEPA and SWIFT need onlyiban,accountHolderNameand, for SWIFT,bic.The
instant_bank_transferrail also needs the beneficiary fields its provider route asks for — which ones depends on the beneficiary's country and scheme. They are named as the legacy jetonbank contract named them (holderEmail,holderAddress.city,bankName); a field the route asks for that has no typed property below goes inadditionalFieldsunder the key requirements report (additionalFields.taxId→"taxId"). A pre-filled value is used when you leave its field out; a value you send always wins.On any other rail these fields are neither checked against a route nor stored — only the size bounds below apply — and on
instant_bank_transfera typed field the route does not ask for is dropped the same way. They are write-only: a payout never reads them back.typestringrequiredAllowed values:
bank_accountibanstringrequiredpattern ^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$
accountHolderNamestringrequiredmin length 1 · max length 70
bicstringRequired when the payout travels over SWIFT; ignored on SEPA.pattern ^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$
countrystringBeneficiary bank country when not derivable from the IBAN.pattern ^[A-Z]{2}$
holderEmailstringwrite-onlymax length 255
holderPhonestringwrite-onlymax length 255
holderAddressPayoutAddresswrite-onlybankNamestringwrite-onlymax length 255
bankCodestringwrite-onlymax length 255
branchCodestringwrite-onlymax length 255
accountNumberstringwrite-onlymax length 255
interbankAccountCodestringwrite-onlymax length 255
accountTypestringwrite-onlyOne of the route'sallowedValues.max length 255
documentIdstringwrite-onlymax length 255
bankAddressPayoutAddresswrite-onlyadditionalFieldsobjectwrite-onlyFields the route asks for that have no typed property, keyed exactly as requirements name them (keys are case-sensitive). A key the route does not know is400 validation_failed(unknown_field) — a typo here would otherwise lose the value silently. A key the destination has a property for (bankName,holderAddress.city, andholderName/iban/bicSwift, which areaccountHolderName/iban/bic) is refused withreserved_field: each value has one place in the request.Example:
{"taxId":"123456789"}
CryptoWalletDestination
An on-chain address. The payout currency is the asset (
amount.currency: USDT), paid from a crypto account holding it; the network is the payout's rail, so there is norailto choose. Settles asynchronously: expectprocessinguntil the chain confirms.networkmust be an active network of the asset —GET /accounts/{id}lists them on every crypto account (networks). Anything else is400 payout_network_unavailable. It may be left out when the asset is active on exactly one network, which is then used; an asset active on several is400 payout_network_required— never a guess, since some networks share an address format and money sent on the wrong one is lost. The address is checked against the network at creation; one that is not valid on it is400 payout_invalid_wallet_address, before anything is priced or sent. Networks that need a memo / destination tag are not supported yet.typestringrequiredAllowed values:
crypto_walletnetworkstringThe chain code (TRX,ETH,BTC) or the token standard it is known by (TRC20,ERC20). Optional when the asset has a single active network. Reads back as the chain code, including when it was left out.Example:
TRXmax length 20
addressstringrequiredSent as-is — case is significant on several chains.Example:
TQ5NMqJjRcuLcrbFn3jsCswDdcs2Vwk7Zimax length 128
JetonAccountDestination
An individual Jeton user, named by exactly one of
customerNumber,emailorphone. Instant, on the internal ledger — there is norailto choose. PrefercustomerNumber: it is not personal data, and it is whatpayer.customerNumberreports on the payments you receive, so you can pay a customer back with the identity they paid you with.Only individuals resolve. An e-mail or phone is looked up as an individual, never as a business, and a business's customer number resolves to nothing — money to another business is a transfer (
POST /transfers). "No such user", "no active account in the payout currency" and "not an individual" are deliberately one answer,400 payout_recipient_not_found, so the endpoint cannot be used to learn who banks here. Itsdetailis one text for every reason, and points atPOST /transfers— a business is paid with a transfer. An API key whose recipients keep failing to resolve is refused with429 payout_recipient_lookup_limitedfor the rest of the window.typestringrequiredAllowed values:
jeton_accountcustomerNumberstringwrite-onlyThe recipient's public Jeton customer number.pattern ^[1-9][0-9]{0,18}$
emailstring (email)write-onlymax length 254
phonestringwrite-onlyInternational form, e.g.+905551112233. Spaces, dashes and brackets are ignored.accountHolderNamestringread-onlyThe individual's name as the ledger shows it, on a payout read back. Never sent; absent on thePOSTresponse.
One of
object
customerNumberstringrequired
object
emailstringrequired
object
phonestringrequired
SavedRecipientDestination
A recipient saved in the portal, addressed by its id instead of repeating the beneficiary details. Resolves to the destination the recipient holds; a business recipient is refused like ajeton_accountone.typestringrequiredAllowed values:
recipientrecipientIdstring (uuid)requiredA uuid (time-ordered, UUIDv7) — the id of something the portal owns: your merchant, a saved recipient. A resource of this API has a prefixed id instead (pay_…,fx_…,evt_…,txn_…,acc_…).Example:
019985c2-4a6e-7c3b-9f10-2d5e8a1b3c4d
sourceAccountIdstringrequiredOpaque account identifier. Treat as a string; never parse or construct it.Example:
acc_AAAAAAAAAAcpattern ^acc_[A-Za-z0-9_-]{11}$
railstringHow the money travelled within its method.sepa·sepa_instant·swift·instant_bank_transferforbank_transfer,cryptoforcrypto,jeton_internalforjeton_wallet.Allowed values:
sepasepa_instantswiftinstant_bank_transfercryptojeton_internaldescriptionstringfailureobjectPresent whenstatusisfailed.codestringrequiredExample:
insufficient_fundspattern ^[a-z][a-z0-9_]*$
messagestringrequired
updatedAtstring (date-time)requiredThe last change the ledger recorded on the payout. ThePOSTresponse's timestamps are the ledger's too, so a laterGETnever shows an earlierupdatedAt.Example:
2026-09-18T10:15:30.123ZcreatedAtstring (date-time)requiredRFC 3339, UTC, with millisecond precision.Example:
2026-09-18T10:15:30.123ZcompletedAtstring (date-time)RFC 3339, UTC, with millisecond precision.Example:
2026-09-18T10:15:30.123Z