Payouts

Create a payout

POST/payouts

Sends 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.

Authentication
ApiKeyAuth
Execution
asynchronous

Parameters

  • Idempotency-Keystringheaderrequired
    Client-generated unique key for this logical request (UUID recommended). See Idempotency.

    min length 1 · max length 255

Request body application/jsonrequired

  • merchantReferencestringrequired
    Your own reference for this payout — what your books reconcile against. Reusing it under a new Idempotency-Key is refused with 400 duplicate_merchant_reference (a reference your legacy /v1 payouts 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 the Idempotency-Key is for.

    min length 1 · max length 100

  • amountobjectrequired
    The amount the destination receives. Fees are charged on top and reported in fee / totalDebited.
    • valuestringrequired

      Example: 149.90

      pattern ^-?\d{1,18}(\.\d{1,18})?$

    • currencystringrequired
      ISO 4217 code for fiat (EUR), asset ticker for crypto (USDT).

      Example: EUR

      pattern ^[A-Z0-9]{3,10}$

  • destinationone ofrequired
    Where 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/requirements tells you beforehand which rail it will be and what it needs. SEPA and SWIFT need only iban, accountHolderName and, for SWIFT, bic.

    The instant_bank_transfer rail 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 in additionalFields under 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_transfer a typed field the route does not ask for is dropped the same way. They are write-only: a payout never reads them back.

    • typestringrequired

      Allowed values: bank_account

    • ibanstringrequired

      pattern ^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$

    • accountHolderNamestringrequired

      min length 1 · max length 70

    • bicstring
      Required when the payout travels over SWIFT; ignored on SEPA.

      pattern ^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$

    • countrystring
      Beneficiary bank country when not derivable from the IBAN.

      pattern ^[A-Z]{2}$

    • holderEmailstringwrite-only

      max length 255

    • holderPhonestringwrite-only

      max length 255

    • holderAddressPayoutAddresswrite-only
    • bankNamestringwrite-only

      max length 255

    • bankCodestringwrite-only

      max length 255

    • branchCodestringwrite-only

      max length 255

    • accountNumberstringwrite-only

      max length 255

    • interbankAccountCodestringwrite-only

      max length 255

    • accountTypestringwrite-only
      One of the route's allowedValues.

      max length 255

    • documentIdstringwrite-only

      max length 255

    • bankAddressPayoutAddresswrite-only
    • additionalFieldsobjectwrite-only
      Fields 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 is 400 validation_failed (unknown_field) — a typo here would otherwise lose the value silently. A key the destination has a property for (bankName, holderAddress.city, and holderName / iban / bicSwift, which are accountHolderName / iban / bic) is refused with reserved_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 no rail to choose. Settles asynchronously: expect processing until the chain confirms.

    network must be an active network of the asset — GET /accounts/{id} lists them on every crypto account (networks). Anything else is 400 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 is 400 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 is 400 payout_invalid_wallet_address, before anything is priced or sent. Networks that need a memo / destination tag are not supported yet.

    • typestringrequired

      Allowed values: crypto_wallet

    • networkstring
      The 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: TRX

      max length 20

    • addressstringrequired
      Sent as-is — case is significant on several chains.

      Example: TQ5NMqJjRcuLcrbFn3jsCswDdcs2Vwk7Zi

      max length 128

    JetonAccountDestination

    An individual Jeton user, named by exactly one of customerNumber, email or phone. Instant, on the internal ledger — there is no rail to choose. Prefer customerNumber: it is not personal data, and it is what payer.customerNumber reports 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. Its detail is one text for every reason, and points at POST /transfers — a business is paid with a transfer. An API key whose recipients keep failing to resolve is refused with 429 payout_recipient_lookup_limited for the rest of the window.

    • typestringrequired

      Allowed values: jeton_account

    • customerNumberstringwrite-only
      The recipient's public Jeton customer number.

      pattern ^[1-9][0-9]{0,18}$

    • emailstring (email)write-only

      max length 254

    • phonestringwrite-only
      International form, e.g. +905551112233. Spaces, dashes and brackets are ignored.
    • accountHolderNamestringread-only
      The individual's name as the ledger shows it, on a payout read back. Never sent; absent on the POST response.

    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 a jeton_account one.
    • typestringrequired

      Allowed values: recipient

    • recipientIdstring (uuid)required
      A 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

  • sourceAccountIdstring
    Optional. Left out, your default account in the payout currency (isDefault) is debited — or the only one, when you hold just one.

    Example: acc_AAAAAAAAAAc

    pattern ^acc_[A-Za-z0-9_-]{11}$

  • descriptionstring
    Internal 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 for jeton_account destinations executed on the internal ledger within the request) → you are done.
  • pending / processing → poll Location; Retry-After is 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-Afterinteger
    Seconds to wait before polling Location. On a 202 it is present only while the body's status is not yet terminal — its absence is a second signal that the work is already done.
  • X-Request-Idstring
    The request's trace id: the same value a problem body carries as traceId. Quote it when you contact support. A replayed response (Idempotent-Replayed: true) carries the replay's own; its body keeps the original request's traceId.
  • RateLimit-Limitinteger
  • RateLimit-Remaininginteger
  • RateLimit-Resetinteger
    Seconds until the window resets.
  • Idempotent-Replayedboolean
    true when this response was replayed from an earlier request with the same Idempotency-Key.

Schema application/json

  • idstringrequired
    The ledger id of the payout's debit — the same entry GET /accounts/{accountId}/transactions shows.

    Example: txn_AAAAAAAAB3Y

    pattern ^txn_[A-Za-z0-9_-]{11}$

  • merchantReferencestring
    Your own reference for this payout, as you sent it. Absent on a payout made without one (portal, batch).
  • statusstringrequired
    Lifecycle of a payout. pending accepted, not sent yet (a payout held for review included — the hold is never named) · processing executing on the rail · completed · failed · cancelled withdrawn by the platform before it was sent. Terminal: completed, failed, cancelled.

    Allowed values: pendingprocessingcompletedfailedcancelled

  • methodstringrequired
    Which 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_wallet settles instantly on the internal ledger, bank_transfer travels on a bank rail, crypto settles on-chain.

    Allowed values: jeton_walletbank_transfercrypto

  • amountMoneyrequired
    Decimal 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, so 10.100 EUR is fine and 10.123 EUR is 400 validation_failed on amount.value. An amount the API returns is written with exactly as many decimal places as its currency has (at most 8): 10.10 EUR, never 10.1.
    • valuestringrequired

      Example: 149.90

      pattern ^-?\d{1,18}(\.\d{1,18})?$

    • currencystringrequired
      ISO 4217 code for fiat (EUR), asset ticker for crypto (USDT).

      Example: EUR

      pattern ^[A-Z0-9]{3,10}$

  • feeobjectrequired
    Fee charged on top of amount, in the payout currency. On the POST response, the fee quoted the moment before the payout was sent — a bank payout is charged exactly that; a read shows what the ledger charged.
    • valuestringrequired

      Example: 149.90

      pattern ^-?\d{1,18}(\.\d{1,18})?$

    • currencystringrequired
      ISO 4217 code for fiat (EUR), asset ticker for crypto (USDT).

      Example: EUR

      pattern ^[A-Z0-9]{3,10}$

  • totalDebitedobjectrequired
    amount + fee: what leaves the source account.
    • valuestringrequired

      Example: 149.90

      pattern ^-?\d{1,18}(\.\d{1,18})?$

    • currencystringrequired
      ISO 4217 code for fiat (EUR), asset ticker for crypto (USDT).

      Example: EUR

      pattern ^[A-Z0-9]{3,10}$

  • destinationone ofrequired
    Where the money went, as the ledger records it: a bank account's iban, accountHolderName, bic and country; a crypto wallet's network and address; a Jeton payout's individual by accountHolderName, the name the ledger shows — never by the customerNumber, email or phone it was sent to, and not on the POST response.

    One of

    BankAccountDestination

    A bank account, by IBAN. The platform chooses the rail and reports it in the payout's rail; POST /payouts/requirements tells you beforehand which rail it will be and what it needs. SEPA and SWIFT need only iban, accountHolderName and, for SWIFT, bic.

    The instant_bank_transfer rail 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 in additionalFields under 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_transfer a typed field the route does not ask for is dropped the same way. They are write-only: a payout never reads them back.

    • typestringrequired

      Allowed values: bank_account

    • ibanstringrequired

      pattern ^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$

    • accountHolderNamestringrequired

      min length 1 · max length 70

    • bicstring
      Required when the payout travels over SWIFT; ignored on SEPA.

      pattern ^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$

    • countrystring
      Beneficiary bank country when not derivable from the IBAN.

      pattern ^[A-Z]{2}$

    • holderEmailstringwrite-only

      max length 255

    • holderPhonestringwrite-only

      max length 255

    • holderAddressPayoutAddresswrite-only
    • bankNamestringwrite-only

      max length 255

    • bankCodestringwrite-only

      max length 255

    • branchCodestringwrite-only

      max length 255

    • accountNumberstringwrite-only

      max length 255

    • interbankAccountCodestringwrite-only

      max length 255

    • accountTypestringwrite-only
      One of the route's allowedValues.

      max length 255

    • documentIdstringwrite-only

      max length 255

    • bankAddressPayoutAddresswrite-only
    • additionalFieldsobjectwrite-only
      Fields 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 is 400 validation_failed (unknown_field) — a typo here would otherwise lose the value silently. A key the destination has a property for (bankName, holderAddress.city, and holderName / iban / bicSwift, which are accountHolderName / iban / bic) is refused with reserved_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 no rail to choose. Settles asynchronously: expect processing until the chain confirms.

    network must be an active network of the asset — GET /accounts/{id} lists them on every crypto account (networks). Anything else is 400 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 is 400 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 is 400 payout_invalid_wallet_address, before anything is priced or sent. Networks that need a memo / destination tag are not supported yet.

    • typestringrequired

      Allowed values: crypto_wallet

    • networkstring
      The 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: TRX

      max length 20

    • addressstringrequired
      Sent as-is — case is significant on several chains.

      Example: TQ5NMqJjRcuLcrbFn3jsCswDdcs2Vwk7Zi

      max length 128

    JetonAccountDestination

    An individual Jeton user, named by exactly one of customerNumber, email or phone. Instant, on the internal ledger — there is no rail to choose. Prefer customerNumber: it is not personal data, and it is what payer.customerNumber reports 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. Its detail is one text for every reason, and points at POST /transfers — a business is paid with a transfer. An API key whose recipients keep failing to resolve is refused with 429 payout_recipient_lookup_limited for the rest of the window.

    • typestringrequired

      Allowed values: jeton_account

    • customerNumberstringwrite-only
      The recipient's public Jeton customer number.

      pattern ^[1-9][0-9]{0,18}$

    • emailstring (email)write-only

      max length 254

    • phonestringwrite-only
      International form, e.g. +905551112233. Spaces, dashes and brackets are ignored.
    • accountHolderNamestringread-only
      The individual's name as the ledger shows it, on a payout read back. Never sent; absent on the POST response.

    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 a jeton_account one.
    • typestringrequired

      Allowed values: recipient

    • recipientIdstring (uuid)required
      A 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

  • sourceAccountIdstringrequired
    Opaque account identifier. Treat as a string; never parse or construct it.

    Example: acc_AAAAAAAAAAc

    pattern ^acc_[A-Za-z0-9_-]{11}$

  • railstring
    How the money travelled within its method. sepa · sepa_instant · swift · instant_bank_transfer for bank_transfer, crypto for crypto, jeton_internal for jeton_wallet.

    Allowed values: sepasepa_instantswiftinstant_bank_transfercryptojeton_internal

  • descriptionstring
  • failureobject
    Present when status is failed.
    • codestringrequired

      Example: insufficient_funds

      pattern ^[a-z][a-z0-9_]*$

    • messagestringrequired
  • updatedAtstring (date-time)required
    The last change the ledger recorded on the payout. The POST response's timestamps are the ledger's too, so a later GET never shows an earlier updatedAt.

    Example: 2026-09-18T10:15:30.123Z

  • createdAtstring (date-time)required
    RFC 3339, UTC, with millisecond precision.

    Example: 2026-09-18T10:15:30.123Z

  • completedAtstring (date-time)
    RFC 3339, UTC, with millisecond precision.

    Example: 2026-09-18T10:15:30.123Z