tgpay cryptoAPI
crypto-payapitransferspayouts

API reference: transfers and checks

6 min readUpdated Oct 9, 2026

The payout methods. A transfer sends funds from your app balance to a Telegram user’s wallet, or holds them until a new recipient joins; a check is a claimable link you fund up front. Conventions (auth, envelope, amounts, spend_id) are on the Merchant API reference page.

All three money-out methods obey the app’s payout mode: a new app pays anyone from the start; if the owner tightened it, only listed recipients can be paid (403 recipient_not_allowed) or nothing at all (403 payouts_disabled). The owner sets the mode in the Mini App; the API cannot. What the payouts may be for is set by the Merchant API terms.

transfer

POST /pay/api/transfer — scope payouts, limit 30 per minute. A known recipient receives the payout immediately. An unknown username creates a pending transfer: funds wait in escrow until that user opens the bot with that username. Unclaimed funds are returned to the app balance after the 30-day expiry (expires_at). Confirm the return through status: "returned" before using the funds in your accounting. Forward invite_url yourself — the bot cannot message someone who has never started it. The final status is completed or returned; subscribe to the transfer_completed and transfer_returned webhooks or poll getTransfers.

ParameterTypeRequiredMeaning
user_idintegerexactly one of user_id / usernamethe recipient’s Telegram ID; must already be a user of the app
usernamestringexactly one of user_id / usernamerecipient username, with or without @; an unknown username creates a pending payout
assetstringyesasset code
amountstringyespositive decimal string; also bounded by the platform’s per-transfer minimum and maximum (a US-dollar-equivalent estimate at current rates)
spend_idstringyesidempotency key, 1–64 characters, unique per payout
commentstringnoup to 1024 characters, shown to the recipient
disable_send_notificationbooleannotrue suppresses the recipient notification for an immediate payout; claiming a pending transfer still sends a notification

The result is the transfer object: transfer_id, hash, user_id, recipient_username, asset, amount, amount_minor, spend_id, comment, status (pending / completed / returned), created_at, completed_at, expires_at, invite_url. user_id may be null while waiting for a new recipient; completed_at is null until completion.

Errors: 403 payouts_disabled / 403 recipient_not_allowed (the payout mode), 404 user_not_found (the id is unknown or the recipient is unavailable), 400 invalid_recipient (invalid recipient username), 400 amount_too_small / 400 amount_too_big (outside the per-transfer bounds), 409 insufficient_funds, 400 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 or username, 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. Invalid item structure, the app’s payout mode or its daily limit can also reject the whole request before any payout. Once accepted, results are returned in result.items.

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 single transfer.

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), status (pending / completed / returned), plus offset / count. Returns {"items": [transfer, …]}, newest first.

createCheck

POST /pay/api/createCheck — scope checks, limit 60 per minute. By default, creates a single-use check from your app balance. Anyone with the link, or only the pinned user, can claim it. The recipients extension creates a named list with a separate amount for each recipient. The total is locked at creation (available moves to onhold in getBalance). Check creation has the same amount and daily payout limits as transfers.

ParameterTypeRequiredMeaning
assetstringyesasset code
amountstringunless recipients is setpositive decimal string; with recipients, the total is calculated from the list
pin_to_user_idintegernoonly this Telegram ID may claim
pin_to_usernamestringnoonly 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_idstringnoidempotency key (extension) — use one
recipientsarraynoextension: 1–100 items, each with user_id or username and a positive decimal-string amount. Cannot be combined with a pinned user
passwordstringnoextension: password required to claim; up to 64 characters
expires_inintegernoextension: lifetime in seconds; omitted or 0 = no expiry

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, expires_at, and recipients (null for an ordinary check; otherwise each entry has user_id, username, amount, amount_minor, claimed_at). Each claim fires the opt-in check_activated webhook; a named-list claim also includes activated_slot. Expiry returns the unclaimed remainder; a named-list expiry can send check_expired.

The current response maps every check that is not fully claimed to status: "active", including an expired check. Check expires_at as well; do not interpret active alone as proof the link is still claimable.

Errors: 403 payouts_disabled / 403 recipient_not_allowed (the payout mode — under the allowlist mode every pinned or named recipient must be listed), 400 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 active check and returns its unclaimed remainder to your app balance; returns true. Errors: 404 check_not_found, 409 check_not_active (fully claimed, expired 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.