Merchant API reference
The conventions every method shares, plus the read-only catalog methods. Method-by-method pages: invoices and refunds, transfers and checks, subscriptions, webhooks.
The API is Crypto Bot-compatible: an existing Crypto Bot integration works after changing only the base URL and the token. Everything beyond that contract is marked extension below.
A machine-readable OpenAPI 3.1 spec of the whole API — every method, object, error name, and webhook — is published alongside these pages: crypto-pay-openapi.yaml · crypto-pay-openapi.json. Feed it to code generators, API clients, or your AI tools.
Base URL and authentication
All methods live at https://crypto.tgpaybot.com/pay/api/<methodName>.
Authenticate every request with the token in the TgCryptoPay-API-Token
header (Crypto-Pay-API-Token is accepted as a compatibility alias; if both
are sent, the canonical one wins). A missing, invalid, or revoked token — or
a token of a deleted app — returns 401 unauthorized.
The token has the form <app_id>:<secret> and is shown once, at creation or
rotation; the server stores only its hash. See
Getting started as a developer.
Tokens and scopes
Two kinds of credentials authenticate identically:
- The main token — full access to every method, and the only key that signs webhooks.
- Restricted tokens (extension) — up to 10 live per app, created in More → Merchant API under Restricted tokens, each with a label and a subset of scopes. Revoking one is instant and doesn’t touch the others; rotating the main token doesn’t touch them either.
| Scope | Methods it unlocks |
|---|---|
| (any valid token) | getMe, getCurrencies, getExchangeRates |
read | getBalance, getStats, getInvoices, getChecks, getTransfers, getSubscriptionPlans, getSubscriptions |
invoices | createInvoice, deleteInvoice |
refunds | refundInvoice |
payouts | transfer, transferBatch |
checks | createCheck, deleteCheck |
subscriptions | createSubscriptionPlan, archiveSubscriptionPlan, cancelSubscription |
Scopes are additive and independent — invoices does not imply read, so a
server that only creates invoices can hold a token that can’t read anything.
Calling a method the token doesn’t cover returns 403 scope_required.
getMe reports the current token’s scopes (null for the main token) and
token_name, so you can always check what you’re holding.
Requests and responses
- Read methods are
GETwith query-string parameters. Money-moving methods arePOSTonly — parameters as a JSON body, form-urlencoded, or query params (the body wins on conflict);multipart/form-datais rejected. - Every response is JSON with the same envelope:
success
{"ok": true, "result": …}, error{"ok": false, "error": {"code": <HTTP status>, "name": "<error_name>"}}. Branch onerror.name— it’s the stable machine-readable string. - One edge case: a type-invalid query value on a GET method (e.g.
offset=abc) returns HTTP 422 with a{"detail": …}body outside the envelope. Send well-typed query values.
Amounts
- Crypto amounts are decimal strings in whole-coin units (
"10.5") — never JSON numbers. Responses also carryamount_minor(extension): the integer minor-unit value as a string, because wei-scale integers overflow a JavaScriptNumber. - Per-asset
decimalscome fromgetCurrencies— drive your amount math from there instead of hardcoding. - Fiat amounts have at most 2 decimal places.
- Rates are fixed-point strings, never numbers.
Pagination
List methods (getInvoices, getChecks, getTransfers,
getSubscriptions) take offset (default 0) and count (default 100, max
1000; getSubscriptions max 500) and return {"items": […]}, newest
first. ID filters (invoice_ids, check_ids, transfer_ids) are
comma-separated integer lists. Timestamps everywhere are ISO 8601 strings.
Idempotency: spend_id
Methods that move money take a caller-generated spend_id key (1–64
characters): required on transfer and each transferBatch item, optional
on createCheck and refundInvoice (extension — use it anyway). Retrying
with the same key and the same parameters replays the original result
instead of moving funds twice, so a timed-out request is always safe to
retry. The same key with different parameters returns
409 idempotency_conflict; a retry while the original is still executing
returns 409 idempotency_in_progress.
Rate limits
Per app, shared across all its tokens; exceeding returns
429 rate_limited:
| Method | Limit |
|---|---|
createInvoice, createCheck | 60 per minute |
refundInvoice, transfer | 30 per minute |
transferBatch | 10 per minute |
Read methods are not rate-limited. During platform maintenance, write
methods return 503 maintenance while reads keep working.
Catalog and account methods
getMe
GET /pay/api/getMe — no parameters. Returns the app’s identity:
app_id, name, payment_processing_bot_username, webhook_url,
webhook_events (the extended webhook types the app opted into), and the
token introspection fields scopes / token_name described above.
getBalance
GET /pay/api/getBalance — no parameters. Returns an array with one row
per supported asset, even at zero: currency_code, available (spendable
balance), onhold (funds locked in your outstanding checks), and
amount_minor.
getCurrencies
GET /pay/api/getCurrencies — no parameters. The authoritative list of
what the API supports: crypto rows (is_blockchain: true) and the fiat
currencies invoices can be priced in (is_fiat: true). Each row carries
code, name, decimals, and the is_stablecoin flag.
getExchangeRates
GET /pay/api/getExchangeRates — no parameters. Crypto-to-fiat quotes:
source, target, rate (a fixed-point string), and is_valid — false
means the whole table is served from a stale cache; treat those rates as
indicative only.
getStats
GET /pay/api/getStats — optional start_at / end_at (ISO 8601; the
default window is the last 24 hours). Returns volume (USD value of paid
invoices in the window), conversion (paid/created, percent),
unique_users_count, created_invoice_count, paid_invoice_count, and
the effective window bounds. An unparsable date returns
400 invalid_date.
Errors every method can return
| HTTP | error.name | When |
|---|---|---|
| 401 | unauthorized | missing, invalid, or revoked token |
| 403 | scope_required | the token lacks the method’s scope |
| 400 | invalid_request | unparsable or invalid parameters (POST) |
| 429 | rate_limited | rate limit exceeded |
| 503 | maintenance | platform maintenance (write methods) |
| 500 | internal_error | unexpected server error |
Method-specific errors are listed on each method’s page.
Was this article helpful?
Thanks for the feedback.