Skip to main content
https://.thoughtindustries.com

Status codes

HTTP status codes returned by the Thought Industries REST API and how to handle them.

Status codes

The REST API uses standard HTTP status codes to indicate the outcome of each request. The /v2 endpoints emit a compact set of codes; error response bodies vary by endpoint and are not wrapped in a common envelope.

Codes the API emits

CodeMeaningWhen returned
200 OKRequest succeededAll successful responses, including creates and deletes — no 201 or 204 is emitted by /v2 endpoints.
400 Bad RequestMalformed request body or invalid parametersMissing required fields, wrong types, unsupported content type.
401 UnauthorizedMissing or invalid API keySee Authentication. Response body is empty.
404 Not FoundResource does not existA small number of endpoints (for example, redemption codes) return this when a specific record is missing.
422 Unprocessable EntityRequest was valid but the operation could not be completedBusiness-rule violations reported by the underlying GraphQL layer (for example, an invalid status transition).
429 Too Many RequestsRate limit exceededSee Rate limits.
500 Internal Server ErrorUnexpected server failureRetry after a brief delay; contact support if persistent.

/v2 endpoints do not return 201, 204, 403, 405, or 409. Treat 2xx as success and any 4xx/5xx as failure.

Error response bodies

Error bodies are not standardized across endpoints. Expect any of the following shapes depending on the failure:

Plain string body

Many controllers set this.body to a short human-readable string alongside a 4xx status.

Invalid ID or SKU

Rate-limit response (429)

The throttle middleware returns a JSON object with an errors array.

Response: 429 Too Many Requests

{
  "errors": [
    "Rate limit exceeded, retry in 27 seconds"
  ]
}

The response also sets X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After headers.

GraphQL passthrough (422)

When a mutation succeeds at the HTTP layer but fails a business rule, the endpoint returns the raw GraphQL result. Inspect errors[] inside it for details.

{
  "errors": [
    { "message": "Cannot transition user from disabled to disabled", "path": ["UpdateUserStatus"] }
  ]
}

Empty body (401)

Authentication failures return 401 with no body — the failure mode is inferred from the status code alone.

Handling errors in code

Because bodies vary, defensively parse the response before reading fields:

const response = await fetch(url, { headers });

if (!response.ok) {
  const text = await response.text();
  let payload;
  try {
    payload = JSON.parse(text);
  } catch {
    payload = text;
  }

  console.error(`[${response.status}]`, payload);

  if (response.status === 429) {
    const retryAfter = response.headers.get("Retry-After");
    // schedule a retry after `retryAfter` seconds
  }
}
response = requests.get(url, headers=headers)

if not response.ok:
    try:
        payload = response.json()
    except ValueError:
        payload = response.text

    print(f"[{response.status_code}]", payload)

    if response.status_code == 429:
        retry_after = response.headers.get("Retry-After")
        # sleep(int(retry_after)) before retrying
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($httpCode >= 400) {
    $payload = json_decode($response, true);
    if ($payload === null) {
        $payload = $response;
    }
    error_log("[{$httpCode}] " . print_r($payload, true));
}