Getting started with TinoPage Builder
TinoPage Builder builds websites for businesses with AI: the customer describes the business, the AI picks a template and writes the content, and the customer keeps editing in the TinoPage Builder editor. Websites take contacts and orders and run on custom domains. The service API lets your app read limits, create websites with AI, publish, pause and delete websites, add custom domains, read contacts and orders, and open TinoPage Builder with a one-time sign-in ticket.
Updated: 2026-10-26. Sources: Tino's TinoPage Builder module and the TinoPage Builder API.
Calling the API
| Item | Value |
|---|---|
| Address | https://api.tino.vn/service/<service id>/builder/… |
| Authentication | Authorization: Bearer <token>: the TinoPage Builder service owner's sign-in token; through /v1: an OAuth app token or personal API token with a builder:* scope |
| Body | JSON, Content-Type: application/json |
| Each route | the TinoPage Builder section of the API Reference |
- Only the parameters listed on each route are relayed. Tino checks shapes before calling TinoPage Builder: a website ID that is not a UUID, a malformed domain, or an unknown query or body field answers 400
INVALID_REQUESTwithdetails.fieldnaming the field. - Website IDs in paths are UUIDs; domains in paths are lower case.
- Editing website content (pages, blocks, images, items) and handling contacts and orders happen in TinoPage Builder: open it with a sign-in ticket.
Since 2026-10-27 the TinoPage Builder routes can also be called through the /v1 gateway: https://api.tino.vn/v1/service/<service id>/builder/… with an OAuth app token (tinoat_, scopes builder:read, builder:write) or a personal API token (tinopat_), with the same bodies and answers. Only the sign-in ticket (POST …/builder/login) is not exposed through /v1: it needs the sign-in token.
The sign-in token is the token customers use in the tino.vn client area; the SDK gets one by signing in with an OTP code:
from tino_userapi import TinoClient
client = TinoClient()
client.request_login_otp("you@example.com")
client.verify_login_otp("you@example.com", input("OTP code: "))
print(client.request("GET", "/service/100003/builder/overview"))100003 is the TinoPage Builder service ID: see below for how to find it.
Find the TinoPage Builder service
Each Tino customer has one TinoPage Builder account, bound to the purchased TinoPage Builder service.
- Take candidates from List services (
product_type) or Service details (ptype): the valuetinobuilderpanel. For a service without it, look atacc_module:TinoPage Builder. Compare case-insensitively, ignoring spaces,_and-. - Call Account overview (
GET /service/<id>/builder/overview):
| Result | Meaning |
|---|---|
| 200 | the customer's TinoPage Builder service: call the other routes with this ID |
404 SERVICE_NOT_LINKED | the TinoPage Builder account is bound to another service of the customer: try the next service |
404 SERVICE_NOT_FOUND, invalid method | not a TinoPage Builder service of the signed-in account |
404 ACCOUNT_NOT_FOUND | no TinoPage Builder account for the service yet: contact Tino support |
Service limits
TinoPage Builder is not sold as fixed plans: limits follow the purchased service and live in config.effective of Account overview. Read them from the API instead of hard-coding them: the numbers can change with the service. -1 means unlimited.
| Key | Applies to | Meaning |
|---|---|---|
sites | account | websites (not deleted, drafts included) |
pages_per_site | each website | pages |
items_per_site | each website | items |
domains_per_site | each website | custom domains |
ai_generations_daily | each website | AI content creations and edits in 24 hours |
ai_chat_daily | each website | messages to the AI assistant in 24 hours |
ai_images_daily | each website | AI images in 24 hours |
- Usage:
usageof Account overview (sites,sites_paused,sites_published) andpages,items,domains(used,limit) of each website. - No websites left: creating a website answers 409
LIMIT_EXCEEDEDwithdetails.keysites; no domains left:domains_per_site. - After limits go down or the service is terminated, the account keeps its oldest websites running up to
sites; the others are paused by the system (paused: true,paused_reason: over_limit): the public page says the website is paused and takes no contacts or orders. Raising the limit or deleting websites brings paused websites back, oldest first. Pages, items and domains over a lowered limit are not deleted; only new ones are blocked. - Suspended service: read only. Writes answer 403
ACCOUNT_SUSPENDED;GETand sign-in tickets still work (read-only access). Terminated service: writes and sign-in tickets answer 403ACCOUNT_TERMINATED.
Create a website with AI
Create a website with AI takes intake: the business details the AI uses to pick a template and write the content.
| Field | Required | Max (characters) | Notes |
|---|---|---|---|
business_name | yes | 120 | business name |
industry | yes | 80 | line of business, e.g. bakery, spa, coffee shop |
phone | yes | 20 | Vietnamese number: 0, 84 or +84, then 9 mobile digits (starting 3, 5, 7, 8, 9) or 10 landline digits (starting 2); spaces, dots, dashes and parentheses allowed |
address | — | 200 | address |
city | — | 80 | province or city |
services | — | 300 | main services or products |
hours | — | 100 | opening hours |
zalo | — | 100 | Zalo phone number or link |
email | — | 120 | contact email |
- A field outside the table, a field that is not a string, or one that is too long answers 400
INVALID_REQUEST(details.field:intakeorintake.<field>). A missing required field or an invalid phone or email answers 422VALIDATION_FAILEDwithdetailsperintake.<field>and a reason in Vietnamese. - Creation runs in the background for 1–5 minutes: the create call answers HTTP 202 with
state: running. Poll Website creation progress every few seconds untilstateisdone(site_idis the new website) orfailed(messageis the reason in Vietnamese). - One creation per account at a time: a second one answers 409
CREATION_IN_PROGRESS. AI creation paused: 503AI_UNAVAILABLE. - The result is kept for 1 hour; after that (or after a server restart) progress answers
idle, and the created website stays in List websites. - Progress may also carry
step(queued,design,write,images,build) and, once done,mode(design,merge): for showing progress only; do not branch on them.
Publish, pause and delete websites
| State | Meaning |
|---|---|
status: draft | not published: a new website, or one paused with Pause a website |
status: published | published, running on host (custom domain) or the preview link preview_url |
paused: true | paused by the system (paused_reason: over_limit: the account is over its website count), overrides status |
- Publish a website publishes the latest version, which also reopens a paused website. A
pausedwebsite answers 409SITE_PAUSED: delete another website or raise the website count. - Pause a website takes the website off the web with its content kept.
- Delete a website moves it to the trash (Tino can restore it on request through support), removes its custom domains and frees one of the account's websites.
- Website details has
todo: contacts not yet called back (leads_new) and orders not yet confirmed (orders_new).
Custom domains
- Publish the website first: an unpublished website answers 409
SITE_NOT_PUBLISHED. - Add a custom domain with
hostname(e.g.example.vn,banhang.example.vn): it answers 201 with the domain atstatus: pendinganddns, the records the customer must set where the domain's DNS is managed. - The customer sets the records: CNAME to
dns_target(names@andwwwfor an apex domain, the sub-label for a subdomain) and TXT_tino-verify(valuetino-site-verify=…) proving ownership. Record names are relative to the apex domain. Takedns_targetand the TXT value from the API; do not hard-code them. - TinoPage Builder rechecks
pendingdomains on a schedule (next_check_at); Check DNS again checks now. With both CNAME and TXT in placestatusturnsactiveand an HTTPS certificate is issued automatically (ssl_status:pending,active,error);wwwredirects to the apex domain.
cname_okandtxt_okare the latest check's results;errorsays what is missing (in Vietnamese) and is empty once it passes.checked_atis0001-01-01T00:00:00Zbefore the first check.owner_verified: true: domain ownership is already confirmed, no TXT needed (dnsonly has the CNAME records).- Automatic checks stop after 14 failures (about 10 days);
errorsays so, and Check DNS again tries once more. - No domains left for the website: 409
LIMIT_EXCEEDED(details.key: domains_per_site); domain used by another website: 409DOMAIN_TAKEN; not a domain: 422VALIDATION_FAILED(details.hostname).
Contacts and orders
Contacts and orders is read only: kind=lead (default) is a contact from a website form, kind=order an order; newest first, paged with page, per_page (default 20, at most 50) and meta.
status | Meaning | Filterable |
|---|---|---|
moi | new | yes |
da_lien_he | contacted (contacts) | yes |
xac_nhan | confirmed | yes |
dang_lam | in preparation (food orders) | no |
san_sang | ready (food orders) | no |
dang_giao | out for delivery | yes |
hoan_tat | completed | yes |
huy | cancelled | yes |
- Contact:
id,kind,name,phone,payload(the form content, keys depend on the website's form),source_page,status,created_at. - Order:
id,code,name,phone,address,note,items(name,option,qty,subtotal),shipping,total(nullwhen the website shows no price),payment(COD,Chuyển khoản(bank transfer) or other text),status,status_label(Vietnamese label),created_at,sheet_error(error sending the order to Google Sheets, empty when none). - Unknown status: show
status_labelwhen present, otherwisestatusas is.
Sign-in tickets
A one-time sign-in ticket opens TinoPage Builder on the service's account without the customer signing in again:
target: me(default) opens the dashboard;target: editoropens the editor ofsite_id. Withoutsite_id: the account's oldest website (the dashboard when the account has no website yet).- It answers 201
{url, expires_at}:urlis https on the TinoPage Builder server, single use, valid for 120 seconds. Open it right away in the service owner's browser; do not store it or send it through another channel. Chat apps that preview the link do not use up the ticket. login_urlof Account overview is not a ticket: it opens the TinoPage Builder sign-in page with a Tino account, and the customer signs in.
Safe retries
Every write (create, publish, pause, delete a website; add, check, remove a domain; sign-in ticket) takes idempotency_key (8–64 characters A-Za-z0-9._:-); with DELETE, send it in the query. Make one key per customer click:
- If the connection drops midway, resend the same key with the same content: you get the earlier result and the command does not run twice. TinoPage Builder remembers keys for 24 hours; an earlier 5xx is not remembered, so a resend runs for real.
- The same key with different content, or while the earlier call is still running: 409
IDEMPOTENCY_CONFLICT. Reload the data before trying with a new key. - Without a key, every call is a new command.
Responses
Success: {"data": …}, where data is TinoPage Builder's answer; lists add meta (page, per_page, total, last_page). HTTP 200, 201 when adding a domain or issuing a sign-in ticket, 202 when a website creation is accepted. Times are RFC 3339; website IDs are UUIDs.
Errors
{ "error": ["LIMIT_EXCEEDED"], "message": "Tài khoản đã dùng hết số website được phép. Nâng cấu hình dịch vụ trên Tino để tạo thêm website.", "details": { "key": "sites" }, "request_id": "…" }Branch on error[0], not on message (a Vietnamese sentence that may change). Codes are upper case; invalid method (the route does not exist for this service) only has error. When reporting an error to Tino, include request_id.
| HTTP | Code | Meaning, what to do |
|---|---|---|
| 400 | INVALID_REQUEST | a website ID, domain, query, body or idempotency_key has the wrong shape, details.field names the field: a programming error in the app |
| 401 | unauthorized, invalid_token, token_expired | the sign-in token is wrong or expired: refresh it |
| 403 | ACCOUNT_SUSPENDED | the service is suspended: writes are blocked, GET still works |
| 403 | ACCOUNT_TERMINATED | the service is terminated: writes and sign-in tickets are blocked |
| 404 | SERVICE_NOT_FOUND, invalid method | not a TinoPage Builder service of the signed-in account: go back to the service list |
| 404 | SERVICE_NOT_LINKED | the TinoPage Builder account is bound to another service: find the service again (Find the TinoPage Builder service) |
| 404 | ACCOUNT_NOT_FOUND | no TinoPage Builder account yet: contact Tino support |
| 404 | RESOURCE_NOT_FOUND | the website or domain is not in the account (or was deleted): reload the list |
| 409 | LIMIT_EXCEEDED | a limit of the purchased service is used up, details.key names it: delete something or upgrade the service |
| 409 | CREATION_IN_PROGRESS | a website creation is running: wait for it to finish |
| 409 | SITE_PAUSED | the website is paused by the system for being over the website count: it cannot be published yet |
| 409 | SITE_NOT_PUBLISHED | publish the website before adding a custom domain |
| 409 | DOMAIN_TAKEN | the domain is used by another website |
| 409 | INVALID_ACTION | not possible in the current state, message says why: reload and retry |
| 409 | IDEMPOTENCY_CONFLICT | the same idempotency_key with different content, or the earlier call is still running |
| 422 | VALIDATION_FAILED | invalid data: show the error under the field named in details (intake.<field>, hostname, kind, status, target) |
| 429 | RATE_LIMITED | too fast: retry after a few seconds |
| 503 | AI_UNAVAILABLE | AI website creation is paused: retry later |
| 500, 502, 503 | INTERNAL_ERROR, NETWORK_ERROR, INVALID_RESPONSE, NOT_CONFIGURED | the connection between Tino and TinoPage Builder is interrupted: a Tino-side error, not a token problem. Do not sign out; retry later |