Errors
The public API is REST over JSON, so failures come back as standard HTTP status codes with a JSON
body describing what went wrong. The SDK turns every non-2xx into a typed
TrueTickError carrying status and code.
Status codes
| HTTP | SDK code | Meaning & typical cause |
|---|---|---|
400 | http_error | Invalid argument — malformed body/params. Also: a precondition isn't met (e.g. start blocked on zero balance or unverified email, or a server over its disk quota) |
401 | unauthorized | Missing/invalid/revoked x-api-key. Check the key |
403 | forbidden | The key is valid but lacks the scope for this operation. See scopes |
404 | not_found | Unknown id/path — or a resource owned by a different account (we return 404, not 403, so foreign ids don't leak existence) |
409 | http_error | Conflict — e.g. an id that already exists |
429 | rate_limited | Rate limit exceeded, or the node/region is at capacity / low on disk, or an account limit was hit (see below) |
5xx | server_error | Internal error — transient; retry with backoff |
The SDK maps unlisted statuses to http_error (or server_error for 5xx). Branch on the numeric
status for anything finer-grained than the named codes.
At capacity and quota
Because TrueTick doesn't oversell, the platform will honestly refuse work it can't do well, rather
than degrading every server on a box. These show up as 429 (Too Many Requests):
| Message | When |
|---|---|
node at capacity | The node/region has no free slot for this RAM tier. Try the other region or retry later |
node is low on disk | Not enough free disk to admit the server right now |
server limit reached (N) | You've hit the max servers per account — delete one or contact support |
api key limit reached (max 25 per account) | Revoke an unused key first |
And as 400 (precondition not met):
| Message | When |
|---|---|
server is over its disk quota — delete files, it's rechecked every few minutes | The server exceeded its disk quota; free space and it's re-admitted automatically |
top up your wallet to start this server | Wallet balance is ≤ 0 |
verify your email to start this server | Email verification is required and pending |
At-capacity is the honesty moat in action. A truthful "no room right now" is the price of
guaranteeing every placed server its full RAM and CPU. Query /v1/regions before
placing, and handle the 429 by falling back to another region or backing off.
Rate limits
The public API is rate-limited per key at 120 requests/minute (token bucket, burst 120).
Over-limit requests get 429. Handle it with exponential backoff:
import { TrueTickError } from "@truetick/sdk";
async function withRetry<T>(fn: () => Promise<T>, tries = 5): Promise<T> {
for (let i = 0; ; i++) {
try {
return await fn();
} catch (e) {
if (e instanceof TrueTickError && e.status === 429 && i < tries - 1) {
await new Promise((r) => setTimeout(r, 2 ** i * 500)); // 0.5s, 1s, 2s, 4s…
continue;
}
throw e;
}
}
}Live log streams have separate concurrency caps (3 per key, 10 per account),
also surfaced as 429.
Retries & idempotency
There is no idempotency-key header on the public API — design retries around each operation's natural semantics:
- Reads (
GET) andDELETEare idempotent — safe to retry freely. - Lifecycle calls (
start/stop/restart) converge on a target state; treatstatefrom a follow-upGETas the source of truth rather than assuming a single call "took." - Create is not idempotent: a server id derives from the name, and creating one that already exists conflicts. Guard against duplicate creates on your side (check existence, or use a unique name per attempt — handy for ephemeral CI servers).
- Top-ups are deduplicated server-side (the payment webhook is idempotent), so a retried checkout won't double-credit your wallet.
For transient 5xx and 429, retry with exponential backoff (as above). For 400/401/403/404,
fix the request — retrying as-is will fail the same way.
Parsing note
Large integers (ramMb, balanceMicros, sizeBytes) are JSON-encoded as strings. The SDK coerces
them to numbers; in raw HTTP, wrap with Number(...) before doing math.