thairouter
API

Errors

One JSON shape, OpenAI-style types, and clear rules about what is charged.

Error shape

{
  "error": {
    "message": "Insufficient credits: this request needs up to 0.0463 credits",
    "type": "insufficient_quota",
    "code": null
  }
}

message is human-readable and may change; branch on the HTTP status and type. code is reserved and currently always null.

Status codes

StatustypeCauseBilled
400invalid_request_errorBody isn't JSON, messages is missing or empty, or reasoning_effort / reasoning has an invalid value.No
401authentication_errorMissing, malformed, unknown or revoked API key.No
402insufficient_quotaBalance can't cover the reservation. Top up at Credits.No
403permission_errorAccount suspended.No
404invalid_request_errorUnknown or coming-soon model id.No
404not_foundNo such route.No
413invalid_request_errorRequest body over 1,000,000 bytes.No
429rate_limit_errorPer-key limit exceeded. Comes with retry-after: 10.No
4xx / 5xx(upstream)Model server rejected the request; body passed through as-is.No, refunded
502server_errorModel server unreachable.No, refunded
500server_errorUnexpected failure inside the gateway.See note
A 500 after the reservation step is settled by the usual refund path when the failure is upstream. If you see a charge for a failed request in your logs, send us the usage id.

Upstream errors

When vLLM returns a non-2xx, ThaiRouter refunds the reservation, logs the request with status error, and returns the upstream body and status verbatim. Typical causes: a parameter out of range, or a prompt longer than the context window. vLLM's error shape is also OpenAI-style, so the same parsing works.

Retrying

  • 429: wait for retry-after seconds, then retry. The limit is a 60-second sliding window per key.
  • 502: retry with exponential backoff (start around 1 s, cap at ~30 s). Nothing was charged.
  • 402: don't retry automatically; top up first.
  • 4xx other than the above: fix the request. Retrying the same body will fail the same way.
  • Requests are not idempotent. A timeout on a non-streaming call may have completed upstream and been billed; check the ledger before re-sending large jobs.

In the SDKs

Python
from openai import APIStatusError, RateLimitError

try:
    r = client.chat.completions.create(...)
except RateLimitError as e:
    time.sleep(int(e.response.headers.get("retry-after", "10")))
except APIStatusError as e:
    if e.status_code == 402:
        raise SystemExit("top up credits")
    print(e.status_code, e.body["error"]["type"], e.body["error"]["message"])
TypeScript
import OpenAI from "openai";

try {
  const r = await client.chat.completions.create({ ... });
} catch (e) {
  if (e instanceof OpenAI.APIError) {
    console.error(e.status, e.error?.type, e.message);
  } else throw e;
}