API reference: transfers and checks
The payout methods. A transfer sends funds from your app balance
straight to a Telegram user’s wallet; a check is a claimable link you
fund up front. Conventions (auth, envelope, amounts, spend_id) are on the
Merchant API reference page.
transfer
POST /pay/api/transfer — scope payouts, limit 30 per minute. Settles
instantly and atomically; there is no pending state.
| Parameter | Type | Required | Meaning |
|---|---|---|---|
user_id | integer | yes | the recipient’s Telegram user id. The recipient must already be a user of the app — a payout to an unknown id errors instead of stranding funds |
asset | string | yes | asset code |
amount | string | yes | positive decimal string; also bounded by the platform’s per-transfer minimum and maximum (a US-dollar-equivalent estimate at current rates) |
spend_id | string | yes | idempotency key, 1–64 characters, unique per payout |
comment | string | no | up to 1024 characters, shown to the recipient |
disable_send_notification | boolean | no | true = don’t notify the recipient in Telegram |
The result is the transfer object: transfer_id, hash, user_id,
asset, amount, amount_minor, spend_id, comment, status (always
completed), created_at, completed_at.
Errors: 404 user_not_found (the recipient has never used the app),
409 recipient_blocked, 400 amount_too_small / 400 amount_too_big
(outside the per-transfer bounds), 409 insufficient_funds,
404 unknown_asset, 400 invalid_amount, and the spend_id pair
409 idempotency_conflict / 409 idempotency_in_progress.
transferBatch
POST /pay/api/transferBatch — scope payouts, limit 10 per minute.
An extension for mass payouts: up to 100 transfers in one call.
One parameter: items — an array where each item is a full transfer
parameter set (user_id, asset, amount, spend_id, optional
comment and disable_send_notification). spend_ids must be unique
within the batch, or the whole call fails with 400 duplicate_spend_id
before anything executes.
Items settle independently, in order — one failed item never rolls
back the others. The call returns HTTP 200 with ok: true even when some
items failed, so always check each item:
- success:
{"ok": true, "spend_id": "…", "result": <transfer object>} - failure:
{"ok": false, "spend_id": "…", "error": {"code": …, "name": "…"}}with the same error names as a singletransfer.
Batch items share the idempotency namespace with single transfers:
retrying the whole batch — or re-sending one item as a single transfer
with the same spend_id — replays instead of paying twice.
getTransfers
GET /pay/api/getTransfers — scope read. Filters: asset,
transfer_ids (comma-separated), spend_id (exact match — look up a
payout by your own key), plus offset / count. Returns
{"items": [transfer, …]}, newest first.
createCheck
POST /pay/api/createCheck — scope checks, limit 60 per minute.
Creates a single-use check funded from your app balance; anyone with the
link — or only the pinned user — can claim it into their wallet. The
amount is locked the moment the check is created (it moves from
available to onhold in getBalance).
| Parameter | Type | Required | Meaning |
|---|---|---|---|
asset | string | yes | asset code |
amount | string | yes | positive decimal string |
pin_to_user_id | integer | no | only this Telegram user id may claim |
pin_to_username | string | no | only this @username may claim (the @ is optional; ignored when pin_to_user_id is also set). The username must belong to an existing user of the app |
spend_id | string | no | idempotency key (extension) — use one |
The result is the check object: check_id, hash, asset,
amount, amount_minor, bot_check_url (the t.me claim link),
status (active / activated), pin_to_user_id, created_at,
activated_at. A claim fires the opt-in check_activated
webhook.
Errors: 404 unknown_asset, 400 invalid_amount, 404 user_not_found
(the pinned username matches nobody), 409 insufficient_funds, and the
spend_id pair.
deleteCheck
POST /pay/api/deleteCheck — scope checks. One parameter: check_id.
Cancels an unclaimed check and returns the locked amount to your app
balance; returns true. Errors: 404 check_not_found,
409 check_not_active (already claimed or deleted).
getChecks
GET /pay/api/getChecks — scope read. Filters: asset, check_ids
(comma-separated), status (active / activated), plus offset /
count. Returns {"items": [check, …]}, newest first; deleted checks are
never returned.
Was this article helpful?
Thanks for the feedback.