Errors

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

HTTPSDK codeMeaning & typical cause
400http_errorInvalid 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)
401unauthorizedMissing/invalid/revoked x-api-key. Check the key
403forbiddenThe key is valid but lacks the scope for this operation. See scopes
404not_foundUnknown id/path — or a resource owned by a different account (we return 404, not 403, so foreign ids don't leak existence)
409http_errorConflict — e.g. an id that already exists
429rate_limitedRate limit exceeded, or the node/region is at capacity / low on disk, or an account limit was hit (see below)
5xxserver_errorInternal 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):

MessageWhen
node at capacityThe node/region has no free slot for this RAM tier. Try the other region or retry later
node is low on diskNot 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):

MessageWhen
server is over its disk quota — delete files, it's rechecked every few minutesThe server exceeded its disk quota; free space and it's re-admitted automatically
top up your wallet to start this serverWallet balance is ≤ 0
verify your email to start this serverEmail 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) and DELETE are idempotent — safe to retry freely.
  • Lifecycle calls (start/stop/restart) converge on a target state; treat state from a follow-up GET as 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.