# Getting started with Tino Uptime

Tino Uptime checks websites, servers, service ports, DNS records, SSL certificates and domain expiry from probes in Vietnam and abroad. When a target fails, Tino Uptime opens an incident and alerts by email, Telegram, Discord, Slack, webhook or Zalo. The service API lets your application manage monitors and read uptime, check results, incidents and alert channels of a Tino Uptime plan.

Updated: 2026-10-24. Sources: Tino's Tino Uptime module and the Tino Uptime OpenAPI.

## Calling the API

| Item | Value |
| --- | --- |
| Address | `https://api.tino.vn/service/<service ID>/uptime/…` |
| Authentication | `Authorization: Bearer <token>`: the Tino Uptime service owner's sign-in token |
| Body | JSON, `Content-Type: application/json` |
| Each route | the Tino Uptime section of the [API Reference](/en/docs/reference#tino-uptime) |

- The body is always JSON. With [Update a monitor](/en/docs/api/uptime-put-monitors-monitor), send only the fields to change.
- Only the parameters listed on each route are relayed. Tino Uptime refuses unknown body fields (422 `validation_failed`).
- Monitor, incident and channel IDs in the path are UUIDs; a malformed one answers 400 `invalid_request`.
- What this API does not cover (status pages, servers, WordPress, CVE alerts) opens in the Tino Uptime dashboard through the `login_url` of the [plan overview](/en/docs/api/uptime-get-overview).

The `/v1` gateway does not expose the Tino Uptime routes yet: app tokens (`tinoat_`) and [personal API tokens](/en/docs/personal-tokens) (`tinopat_`) cannot call them. The sign-in token is the one customers use in the tino.vn client area; the [SDK](/en/docs/sdk) gets it with an OTP sign-in:

```python
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/100002/uptime/monitors", query={"state": "down"}))
```

`100002` is the Tino Uptime service ID: how to find it is below.

## Find the Tino Uptime service

Each Tino account has one Tino Uptime account; the current plan belongs to exactly one service.

1. Identify it by the service's module: [Service details](/en/docs/api/get-service-id) (`GET https://api.tino.vn/service/<id>`, same sign-in token) has `service.acc_module` set to `"tino_uptime"`. Candidates come from the [service list](/en/docs/api/get-service).
2. Call the [plan overview](/en/docs/api/uptime-get-overview) (`GET /service/<id>/uptime`):

| Result | Meaning |
| --- | --- |
| 200 | the customer's current Tino Uptime plan: call the other routes with this ID |
| 404 `service_not_linked` | the service is no longer the current plan (the customer moved to another service): try the next Tino Uptime service |
| 404 `invalid method` | the service is not Tino Uptime or does not exist |
| 404 `service_not_found` | the service belongs to another account |
| 404 `account_not_found` | no Tino Uptime account for the service yet: contact Tino support |

## Plan limits

Limits are in `subscription.limits` of the [plan overview](/en/docs/api/uptime-get-overview), current numbers in [usage](/en/docs/api/uptime-get-usage). Read the limits from the API, do not hard-code them: each plan's numbers may change.

| Field | Meaning |
| --- | --- |
| `max_monitors` | maximum number of monitors |
| `min_interval_s` | shortest check interval (seconds) |
| `raw_retention_days` | days of per-check results you can read |
| `locations` | probe locations you can use: `vn-cmc`, `vn-vt` (Vietnam), `intl` (international) |
| `channels` | alert channel types you can create |

Going over a limit answers 403 `entitlement_exceeded`; `details.resource` names the limit (`monitors`, `min_interval_s`, `locations`, `channels`). While the service is suspended, the Tino Uptime account is read-only: writes answer 403 `account_suspended`. After a downgrade, monitors over the limit are paused (`disabled_reason: paused_by_plan`, counted in `monitors_paused_by_plan`).

## Monitor types

[Create a monitor](/en/docs/api/uptime-post-monitors) takes `name`, `interval_s` (30–86400 seconds, not below `limits.min_interval_s`), `locations` and `settings`. `settings.type` decides the other fields:

| `type` | Required | Optional |
| --- | --- | --- |
| `http` | `url` | `method` (`GET`, `HEAD`, `POST`), `expected_status` (array of HTTP codes, up to 20), `follow_redirects` (default `true`), `max_latency_ms` (1–60000), `headers` (object), `body` (up to 4096 characters), `basic_auth` (`username`, `password`) |
| `keyword` | `url`, `keyword` (1–100 characters) | `mode` (`present`, `absent`), `case_sensitive`, `method` (`GET`, `POST`) and the other `http` options |
| `ssl` | `host` | `port` (443, 465, 993, 995), `warn_days` (1–90, default 14) |
| `domain` | `domain` | `warn_days` (1–120, default 30) |
| `ping` | `host` | — |
| `tcp` | `host`, `port` (1–65535) | — |
| `dns` | `name`, `record_type` (`A`, `AAAA`, `CNAME`, `MX`, `NS`, `TXT`) | `expected` (array of values, up to 20), `resolver` (an IP, or an IP with port 53; empty asks the authoritative name servers) |
| `heartbeat` | — | `grace_s` (0–86400, default 300) |

- `heartbeat` works the other way round: your system calls `settings.ping_url` after each run. `ping_url` is only in the create answer: store it right away.
- Headers, body and the Basic auth password are never returned; `settings` only tells `header_names`, `body_set`, `basic_auth_set`.
- `international_critical: true`: a failure seen only by the international probe also counts as down (default `false`).
- Internal or unsafe targets are refused: 422 `validation_failed` with `details.reason: blocked_destination`. A target checked too often (counting other customers) answers 422 `target_rate_exceeded`: pick a longer interval.

A monitor's `state`: `pending` (no result yet), `up`, `suspect`, `down`, `partial`, `down_international`, `paused` (turned off, stopped by the system per `disabled_reason`, or within `paused_until`).

## Alert channels

[Create a channel](/en/docs/api/uptime-post-channels) with a `kind` from the plan's `limits.channels`; a new channel awaits verification (except an email to the account owner's own verified address). A channel without `opted_in_at` receives nothing.

| `kind` | `config` | Verification (`verification.method`) |
| --- | --- | --- |
| `email` | `address` | `email_code`: send the code from the email to [Verify an alert channel](/en/docs/api/uptime-post-channels-channel-verify) (`code`); the code is valid 24 hours |
| `telegram` | — | `telegram_link`: open `telegram_url` (or `telegram_group_url` for a group) within 10 minutes |
| `discord`, `slack`, `webhook` | `url` (https) | `test_message`: call Verify to send a test message; the channel is verified when it gets through |
| `zalo` | `consent: true`; `phone` to verify by OTP, no `phone` to link through the OA | `zalo_otp` or `zalo_link` (send `zalo_link_code` to the OA at `zalo_oa_url`, valid 10 minutes) |

- A webhook channel returns `webhook_secret` (`whsec_…`) once only, in the create answer: store it to check the signature of incoming messages.
- Verification emails: at most 5 an hour per account and 3 a day per address; beyond that 429 `rate_limited`. A wrong code answers 422 `token_invalid`.
- [Send a test message](/en/docs/api/uptime-post-channels-channel-test) works only for a verified, enabled channel; otherwise 409 `conflict`.

## Lists and safe retries

Lists (monitors, incidents, channels, per-check results) use cursor pagination: send the previous page's `next_cursor` as `cursor`; `next_cursor` is `null` at the end. `limit` defaults to 50, at most 100 (per-check results: default 100, at most 1000).

```json
{ "data": { "data": [], "next_cursor": null } }
```

[Create a monitor](/en/docs/api/uptime-post-monitors) and [Create an alert channel](/en/docs/api/uptime-post-channels) take `idempotency_key` (8–128 characters `A-Za-z0-9._:-`). If the connection drops midway, resend the same body with the same key: nothing extra is created. The same key with a different body, or while the earlier call is still running, answers 409 `idempotency_conflict`. Without a key every call is a new command: read the list again before retrying.

## Responses

Success: `{"data": …}`, where `data` is Tino Uptime's answer. HTTP 200, 201 on create, 202 once a test message is queued, 204 on delete (no content). Times are RFC 3339; IDs are UUIDs.

## Errors

```json
{ "error": ["entitlement_exceeded"], "message": "Đã vượt hạn mức của gói.", "details": { "resource": "monitors" }, "request_id": "…" }
```

Branch on `error[0]`, not on `message` (a Vietnamese sentence that may change). With `validation_failed`, `details.field` names the wrong field and `details.reason` the reason when there is one. Errors Tino returns before relaying (`service_not_found`, `invalid method`) only have `error`. When reporting a problem to Tino, include `request_id`.

| HTTP | Code | Meaning, what to do |
| --- | --- | --- |
| 400 | `invalid_request` | malformed ID (not a UUID), malformed `idempotency_key`, or a bad request: a bug on the application side |
| 401 | `unauthorized`, `invalid_token`, `token_expired` | the sign-in token is wrong or expired: refresh it |
| 403 | `account_suspended` | the Tino Uptime account is suspended: writes are blocked, `GET` still works |
| 403 | `entitlement_exceeded` | over a plan limit, `details.resource` names it: upgrade or remove monitors, channels |
| 404 | `service_not_found`, `invalid method` | not a Tino Uptime service of the signed-in account: go back to the service list |
| 404 | `service_not_linked` | the service is no longer the current plan: find the service again ([Find the Tino Uptime service](#find-the-tino-uptime-service)) |
| 404 | `account_not_found` | no Tino Uptime account yet: contact Tino support |
| 404 | `not_found` | the monitor, incident or channel does not exist or was deleted: reload the list |
| 409 | `conflict` | not possible in the current state (e.g. a test message to an unverified channel): reload and retry |
| 409 | `idempotency_conflict` | same `idempotency_key` with a different body, or the earlier call is still running |
| 422 | `validation_failed` | invalid data: show the error under the field named by `details.field` |
| 422 | `target_rate_exceeded` | the target is checked too often (counting other customers): pick a longer interval |
| 422 | `token_invalid` | the email channel's verification code is wrong or expired |
| 429 | `rate_limited` | too fast: retry later |
| 502, 503 | `network`, `invalid_response`, `not_configured`, `unavailable` | the connection between Tino and Tino Uptime is interrupted: a Tino-side error, not a token problem. Do not sign out, retry later |
