Authentication
Bearer tokens, scopes, storage, and rotation.
The Tourical API uses Bearer tokens that you generate in the operator dashboard, under Settings → Developer access. Each token is bound to one tenant and one set of scopes.
Token format
tk_<env>_<26 alphanumeric chars>tk_is the human prefix — useful in code grep and secret-scanning rules.<env>islivefor production-issued tokens,testfor sandbox.- The body is 26 base32 characters carrying 130 bits of entropy. That's well above the practical guess threshold; you don't need to additionally restrict by IP.
Example: tk_live_a3f9bc4n7vqzr2k8mp5tx9w1
The dashboard only ever shows you the full plaintext once. After that, only the first ~12 characters are stored for UI hints. There is no recovery path — if you lose the plaintext, revoke and re-create.
Sending the token
Pass it as a Bearer token on every request:
Authorization: Bearer tk_live_a3f9bc4n7vqzr2k8mp5tx9w1Missing, malformed, revoked, or expired tokens all return 401 Unauthorized as problem+json, with WWW-Authenticate: Bearer and a detail string that says which case you hit.
Scopes
Scopes follow the resource:action shape, like GitHub. There is no implicit hierarchy — trips:write does not grant trips:read. Add both if you need both.
| Scope | Grants |
|---|---|
trips:read | Read trips, days, segments. |
trips:write | Create and update trips. |
bookings:read | Read bookings and their snapshots. |
bookings:write | (Reserved — booking writes go through the storefront today.) |
clients:read | Read CRM contacts. |
clients:write | Create and update CRM contacts. |
payments:read | Read booking payment ledger entries. |
libraries:read | Read the hotels, activities, vehicles, guides catalogs. |
webhooks:read | List webhook subscriptions and recent deliveries. |
webhooks:write | Create, delete, and test webhook subscriptions. |
A request whose token doesn't carry the required scope returns 403 Forbidden, type .../errors/scope-required, with a required_scope field naming the scope that is missing.
Storing the token
A handful of patterns that work; one anti-pattern that doesn't:
| Where | Verdict |
|---|---|
| Vault, 1Password, AWS/GCP Secrets | ✓ Read at process start; never write to disk. |
| GitHub Actions encrypted secrets | ✓ Audit who can view; rotate on contractor offboarding. |
.env.local on a dev machine | ✓ For dev tokens (tk_test_…). Add .env* to .gitignore. |
| Server-side cookie | ✓ If your backend proxies API calls for a SPA. HttpOnly + Secure. |
| Browser localStorage or JS bundle | ✗ Never for an integration token. Treat any token shipped to a browser as compromised. |
| Plain-text on a shared spreadsheet | ✗ Never. Rotate immediately if this happened. |
The token is the credential. Anyone who can read it can act as your tenant inside its scopes.
The API Explorer on this site is the one deliberate exception, and it is not a pattern to copy: you paste a token into your own browser to fire a call by hand. It keeps the token in sessionStorage — this tab only — and offers localStorage as an explicit opt-in. Use a tk_test_… token there where you can, and clear it when you are done.
Plan tiers
API access is gated by your subscription tier. On a plan without API access, every /v1/* request returns 403 Forbidden with type .../errors/plan-required, a required_plans array listing the plans that do include it, and current_plan showing where you are today.
Rotation
To rotate:
- Create the new token with the same scopes.
- Roll it out — deploy the new value to your integration's secrets store.
- Verify in your logs that traffic is going through the new token.
- Revoke the old one from the dashboard. Revocation takes effect immediately (the row is
revokedAt-soft-deleted; audit log entries that reference its id still resolve).
There is no overlap window beyond what your deployment takes. Tokens are cheap to mint.
Leak response
If a token may have leaked (committed to a public repo, posted in chat, included in a customer email):
- Revoke it now from the operator dashboard. The next call on the leaked token fails with
401andtype.../errors/unauthorized(detail: "Token has been revoked."). - Check
lastUsedAtandlastUsedIpon the dashboard's token row. If activity happened from an unexpected IP after the suspected leak time, escalate to your incident channel. - Mint a new token with the same scopes.
- Audit what the leaked token could do with its scope set; e.g. a
trips:readleak is informational, aclients:writeleak warrants checking for unexpected CRM rows since the leak window. - Rotate the source that leaked (purge git history, regenerate API consumer secrets on your side, etc.).
The Tourical token format is grep-friendly — tk_(live|test)_[a-z0-9]{26} matches any token. Add that pattern to your CI's secret scanner if you don't already.
Why not OAuth?
Bearer tokens cover the only auth pattern we currently support: server-to-server integration on behalf of one tenant. If you need delegated auth on behalf of a Tourical user (e.g. you're building a third-party app that several different operators install), tell us — we're tracking that path but it isn't shipped.

