tinovn/tino-sdk

Official PHP SDK for the Tino UserAPI (https://api.tino.vn)

Maintainers

Package info

github.com/tinovn/tino-sdk

Language:Python

pkg:composer/tinovn/tino-sdk

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-08-04 05:26 UTC

This package is not auto-updated.

Last update: 2026-08-04 06:50:25 UTC


README

Bản tiếng Việt

Official SDKs and API contract documentation for the Tino UserAPI — the customer API behind tino.vn that manages services, domains, invoices and support tickets for a Tino account.

Base URL   https://api.tino.vn
Auth       Authorization: Bearer <JWT>
Surface    257 operations · 18 resource groups
🐍 Python sdk/python — 3.8+, zero dependencies
🟩 Node.js sdk/nodejs — 18+, ESM, TypeScript definitions, zero dependencies
🐘 PHP sdk/php — 7.4+, PSR-4, zero dependencies
📖 Docs docs/ — every endpoint with request, response and SDK call
🤖 Agent skill skills/tino-userapi — drop-in skill for Paperclip, Claude Code and Codex

Quick start

from tino_userapi import TinoClient

client = TinoClient()
client.request_login_otp("you@example.com")          # code is sent by Zalo or email
client.verify_login_otp("you@example.com", "123456")

print(client.billing.get_balance())
print(client.domains.list())
client.support.create_ticket(dept_id=2, subject="Hello", body="…")
import { TinoClient } from '@tinovn/tino-sdk';

const client = new TinoClient();
await client.requestLoginOtp('you@example.com');
await client.verifyLoginOtp('you@example.com', '123456');

console.log(await client.billing.getBalance());
console.log(await client.domains.list());
require 'vendor/autoload.php';

$client = new Tino\UserApi\TinoClient();
$client->requestLoginOtp('you@example.com');
$client->verifyLoginOtp('you@example.com', '123456');

print_r($client->billing->getBalance());
print_r($client->domains->list());

All three expose the same surface: client.<group>.<operation>(), one group per section of the API. See docs/api-contract/README.md for the full list.

Why use the SDK instead of raw HTTP

The Tino UserAPI has sharp edges. The SDKs absorb them:

  • Errors arrive with HTTP 200. {"error":["unauthorized"]} comes back as a 200 OK. The SDKs inspect the body and raise a typed error (TinoAuthError, TinoNotFoundError, TinoValidationError, …) instead of handing you a "successful" response.
  • Tokens rotate. A refresh invalidates both the old refresh token and the old access token. The SDKs swap the pair atomically, retry the failed call once, and call your on_token_refresh hook so you can persist the new credentials.
  • Query encoding is PHP-flavoured. filter[hide_cancelled]=1, key[]=a&key[]=b, booleans as 1/0. Pass native structures; the SDK encodes them.
  • Pagination is zero-based going in, one-based coming out. Documented, not silently "fixed", so your code stays predictable.
  • Some responses are binary. PDFs and certificates are exposed as raw bytes rather than mangled UTF-8.

Repository layout

spec/
  tino-userapi-catalog.json    canonical catalog — single source of truth (257 operations)
  response-samples.json        redacted live responses used as documentation samples
docs/
  getting-started.md           base URL, login flows, token lifecycle
  conventions.md               errors, pagination, filters, data types, unsafe GETs
  api-contract/                per-endpoint reference, one chapter per resource group
sdk/
  python/  nodejs/  php/       hand-written core + generated resource layer
tools/
  build-catalog.py             specification + corrections -> catalog
  generate-sdk.py              catalog -> resource layer for all three languages
  generate-docs.py             catalog + samples -> docs/api-contract/
  sanitize-samples.py          live captures -> publishable samples (PII stripped)
skills/
  tino-userapi/                agent skill (SKILL.md + references)
examples/                      runnable end-to-end scripts

How the contract was built

Every operation is described from three angles: the API's formal specification, the way Tino's own client calls it in production, and what the live service actually answers — with the live service as the tie-breaker.

195 operations were executed against https://api.tino.vn with a real account and their responses captured, redacted and embedded in the documentation. The sweep covered the write endpoints too — orders, tickets, DNS records, VM power actions, cancellations — on a sandbox account, with each step restoring what it changed. The remainder are marked spec only: they need data the sandbox does not have, or they end the session.

The catalog is committed, so the SDKs and the reference can be regenerated from a fresh clone without any additional input.

Regenerating

python3 tools/build-catalog.py <specification.json>   # -> spec/tino-userapi-catalog.json
python3 tools/generate-sdk.py                         # catalog -> sdk/*/resources
python3 tools/generate-docs.py                        # catalog + samples -> docs/api-contract

The catalog is committed, so generate-sdk.py and generate-docs.py run on a fresh clone. build-catalog.py is only needed when a new specification export lands.

Hand-written SDK code (transport, auth, errors, client) is never touched by the generator; only the per-endpoint resource layer is generated, which is what keeps the three languages in step.

Releasing

Tagging publishes all three packages:

git tag v1.0.0 && git push origin v1.0.0

.github/workflows/release.yml verifies that the tag matches the version in every package, runs all three test suites, checks that the generated code still matches the catalog, and only then publishes. PyPI uses Trusted Publishing, so no API token is stored; npm needs an NPM_TOKEN secret; Packagist picks up tags through the GitHub integration once the repository has been submitted.

Status

Version 1.0.0. The catalog and both generated layers are complete; response samples cover the 195 operations that could be exercised against a sandbox account. Contributions that add verified samples for the remaining operations are welcome — see tools/sanitize-samples.py for the redaction rules that publishable samples must pass.

License

MIT — see LICENSE.