Tino Developers
View .md

Getting started with TinoPay

TinoPay is Tino's bank-transfer collection service: your customers transfer money to a linked bank account, or to a dedicated virtual account (VA); TinoPay records the transaction, sends a webhook to your system and posts notifications to Telegram, Zalo or Lark. Bank accounts, virtual accounts, transactions, webhooks and notifications live in the TinoPay system (https://pay.tino.vn); the service's merchant API on api.tino.vn relays to it. TinoPay also has a public API called with an API token and no-code integrations (Google Sheets, WordPress / WooCommerce, HostBill, Sapo).

Updated: 2026-10-25. Sources: Tino's TinoPay module and the TinoPay API v1 documentation.

Setup

  1. Order a TinoPay service on tino.vn: each service is one merchant.
  2. Link a bank account. For a BIDV business account, send a link request; staff set up the service with BIDV, and the account then appears in the bank account list.
  3. Create virtual accounts when you need to separate money by order, shop or customer: the VA number is the bank account's VA prefix (first_va_number) plus a suffix you choose.
  4. Set payment codes (pay_codes) if you want TinoPay to extract order codes from transfer descriptions.
  5. Create a webhook to receive every transaction: see Receiving TinoPay webhooks.
  6. To post every transaction to a chat: see Telegram, Zalo and Lark notifications. To read transactions yourself through TinoPay's public API: create an API token.

Calling the merchant API

ItemValue
Addresshttps://api.tino.vn/service/<service id>/tinopay/…
AuthenticationAuthorization: Bearer <token>: the TinoPay service owner's sign-in token; through /v1: an OAuth app token or personal API token with a tinopay:* scope
BodyJSON, Content-Type: application/json
Each routethe TinoPay section of the API Reference
  • PATCH requires a JSON body: the API does not read form bodies (application/x-www-form-urlencoded) on PATCH. With PATCH, send only the fields to change.
  • Only the parameters listed on each route are forwarded; anything else is dropped.
  • IDs in the path after the service ID (bank account, virtual account, webhook, log, transaction, notification app, API token) contain only letters, digits and -, up to 64 characters; a malformed ID returns 404 RESOURCE_NOT_FOUND.
  • Timeouts: Tino waits up to 30 seconds for TinoPay per attempt and retries up to 3 times (1 and 2 seconds apart) on network errors or 5xx for requests that are safe to retry, so when TinoPay is slow one request can take about 95 seconds. Set your timeout to at least 100 seconds (or show a pending state with a cancel option). A proxy in front may cut earlier and return 504 with an HTML body, not JSON: handle it like the 502 connection errors. Finding a Zalo bot's chats always takes about 20 seconds.

Since 2026-10-27 the TinoPay routes can also be called through the /v1 gateway: https://api.tino.vn/v1/service/<service id>/tinopay/… with an OAuth app token (tinoat_, scopes tinopay:read, tinopay:write) or a personal API token (tinopat_), with the same bodies and answers. Creating and deleting TinoPay API tokens and linking receiving bank accounts need tinopay:owner: personal tokens with an IP restriction only, OAuth apps never get it. Plugins and third-party software (a WooCommerce plugin, for example) should ask for access through Sign in with Tino rather than ask for the customer's sign-in token.

The sign-in token (the token a customer uses in the tino.vn client area) still calls https://api.tino.vn/service/<service id>/tinopay/… directly; the SDK gets one with a one-time code sign-in:

from tino_userapi import TinoClient

client = TinoClient()
client.request_login_otp("you@example.com")
client.verify_login_otp("you@example.com", input("One-time code: "))
print(client.request("GET", "/service/100001/tinopay/transactions", query={"type": "in"}))

100001 is the TinoPay service ID: see below for finding it.

Finding the TinoPay service

A Tino account has at most one TinoPay service, which is one merchant.

  1. Identify it by product type, not by group or name (TinoPay sits in the "Dịch vụ khác" (other services) group, which other services share):
Route (same sign-in token)FieldTinoPay value
GET https://api.tino.vn/service (List services)product_type"tinopay"
GET https://api.tino.vn/service/<id>service.ptype"tinopaypanel"
  1. When opening its management, call Merchant details (GET /service/<id>/tinopay):
ResultMeaning
200the TinoPay service is running: the merchant can be managed
403 service_not_activethe TinoPay service is pending, cancelled or locked (Pending, Terminated, Cancelled, Fraud): show only the service status
404 invalid methodthe service is not TinoPay or does not exist: a service without TinoPay has no TinoPay routes
404 service_not_founda TinoPay service of another account
404 MERCHANT_NOT_FOUNDthe service is active but the merchant is still being set up; contact Tino support if it takes too long

Merchant status

Servicedata.statusRead (GET)Write (create, update, delete, resend)
Activeactiveyesyes
Suspendedsuspendedyes403 MERCHANT_SUSPENDED
—terminated403 MERCHANT_TERMINATED403 MERCHANT_TERMINATED

When data.status is suspended (or data.suspended_at is not null): tell the customer the service is suspended (paying the invoice reactivates it) and lock every write action. While suspended, bank transactions are still recorded but no webhooks are sent; after reactivation the bank accounts return to their previous state, and missed transactions can be reconciled with the transaction history using webhook_status=none.

Responses

Success: {"data": …}; paginated lists also carry meta. HTTP 200, 201 when something is created, 202 when a webhook resend is queued.

{ "data": [], "meta": { "page": 1, "per_page": 20, "total": 135, "last_page": 7 } }

Paginated lists accept page and per_page (up to 100).

API tokens

An API token calls TinoPay's public API, e.g. GET https://pay.tino.vn/api/transactions (header Authorization: Bearer <token>), the same data as the API Access page of the TinoPay web app.

  • Create an API token ({name}) returns the 64-character token only once, in the 201 response: let the customer copy it, and do not store or log it. TinoPay keeps only a hash, so the list has no token.
  • Not retried on network errors (each call creates a token): read the list again and revoke any extra token. Revoking takes effect immediately.

Errors

{ "error": ["VA_NUMBER_EXISTS"], "message": "Số VA đã tồn tại.", "details": { "account_number": "9624QA01" }, "request_id": "…" }

Branch on error[0], not on message (Vietnamese text that may change); use message only as a fallback. With VALIDATION_FAILED, details holds the per-field errors. Errors Tino returns before forwarding (service_not_found, service_not_active, invalid method, RESOURCE_NOT_FOUND for a malformed ID or a webhook or notification app of another merchant) carry only error. When reporting an error to Tino, include request_id.

HTTPCodeMeaning, what to do
400INVALID_REQUESTwrong method or body: a programming error in the app
401unauthorized, invalid_token, token_expiredthe sign-in token is invalid or expired: refresh it
403service_not_activethe service is neither Active nor Suspended: show only the service status
403MERCHANT_SUSPENDEDthe merchant is suspended: writes are blocked, GET still works
403MERCHANT_TERMINATEDthe merchant is terminated: every route is blocked
404service_not_found, invalid methodnot a TinoPay service of the signed-in account: go back to the service list
404MERCHANT_NOT_FOUNDthe merchant is still being set up
404BANK_ACCOUNT_NOT_FOUNDthe bank account does not exist or does not belong to the merchant
404RESOURCE_NOT_FOUNDthe virtual account, webhook, log, transaction, notification app or API token does not exist, was deleted or does not belong to the merchant; malformed ID. Reload the list
409VA_NUMBER_EXISTSthe virtual account number already exists
409EMAIL_TAKENthe email belongs to another Tino customer
409IDEMPOTENCY_CONFLICTrare: the previous action is still being processed, retry in a few seconds
422VALIDATION_FAILEDinvalid data; details lists the errors per field
429RATE_LIMITEDover 300 requests per minute per merchant: retry after a minute
500INTERNAL_ERRORTinoPay-side error: retry later, report it to Tino with request_id
502NETWORK_ERROR, NOT_CONFIGURED, UNEXPECTED_RESPONSE, MISSING_API_KEY, INVALID_KEY, IP_NOT_ALLOWEDthe connection between Tino and TinoPay is interrupted: a Tino-side error, not a token or session problem. Do not drop the token or sign the customer out; retry later, and report it to Tino with request_id if it persists
504(HTML body)a proxy cut a slow request before the answer: handle it like 502, retry later

Codes not in the table: show message (when present) or a generic message, with request_id.

Notes

  • POST is not deduplicated for you: repeating a POST (for example creating a virtual account) is a new request. Read the list again before retrying.
  • The merchant's name, email and phone are not synced with the customer's Tino profile: they change only through Update merchant details.
  • A webhook's token is the customer's secret: when showing it in a UI, mask it and reveal it only when the customer asks.
  • Finding chats and sending test messages for notifications, and creating API tokens, are not retried on network errors: let the user try again.