Skip to main content

TheoApiError

All REST API errors are thrown as TheoApiError instances with structured details.
import { Theo, TheoApiError } from "@hitheo/sdk";

try {
  const res = await theo.complete({ prompt: "Hello" });
} catch (err) {
  if (err instanceof TheoApiError) {
    console.log(err.status);              // 401
    console.log(err.message);             // "Theo API error 401: Invalid API key"
    console.log(err.details?.code);       // "invalid_api_key"
    console.log(err.details?.type);       // "authentication_error"
    console.log(err.details?.request_id); // "req_9f2e1a" — share in support tickets
    console.log(err.url);                 // "https://www.hitheo.ai/api/v1/completions"
  }
}

Error Properties

PropertyTypeDescription
statusnumberHTTP status code (0 for network/timeout/cancel)
kindstring"http" | "timeout" | "cancelled" | "network" — branch on this instead of string-matching
messagestringHuman-readable error message
bodystringRaw response body
urlstringRequest URL
detailsobject | nullParsed { message, type, code, request_id }

Error Types

TheoApiError is the base class — every error below is instanceof TheoApiError, so existing catch blocks keep working. Branch on the subclass (or err.kind) for precise handling:
  • TheoTimeoutError (kind: "timeout") — the SDK aborted after timeoutMs (unary / stream connect) or streamIdleTimeoutMs (stream idle). Carries the offending budget in .timeoutMs. Not auto-retried on POSTs.
  • TheoCancelledError (kind: "cancelled") — you aborted via TheoStream.cancel() or an AbortSignal passed to waitForJob().
  • TheoUsageError (extends Error, not TheoApiError) — thrown synchronously for misuse before any network call, e.g. passing mode: "research" | "video" to complete() / stream() (use the async job methods instead).
import { TheoApiError, TheoTimeoutError, TheoCancelledError, TheoUsageError } from "@hitheo/sdk";

try {
  await theo.complete({ prompt });
} catch (err) {
  if (err instanceof TheoUsageError) { /* fix the call shape */ }
  else if (err instanceof TheoTimeoutError) { console.error(`timed out after ${err.timeoutMs}ms`); }
  else if (err instanceof TheoCancelledError) { /* user aborted — not an error */ }
  else if (err instanceof TheoApiError && err.kind === "network") { /* offline / retry later */ }
}

Stream Errors

The SSE error event carries the same envelope as the REST error body — reuse one code path for both.
const stream = theo.stream({ prompt: "..." });

for await (const event of stream) {
  if (event.type === "error") {
    const { message, type, code, request_id } = event.data.error;
    throw new Error(`[${type}/${code}] ${message} (request_id=${request_id})`);
  }
  // ...
}
This is a change from 0.1.x, where stream errors were { message } only. See the Streaming Completions reference for the full envelope schema.

First-Run Diagnostic

Instead of surfacing raw errors, theo.verify() returns a structured diagnosis with a remediation hint:
const result = await theo.verify();
if (!result.authenticated) {
  console.error(result.error);  // underlying error
  console.error(result.hint);   // e.g. "Authentication failed. Confirm your API key..."
}
Call this as the first line of debugging on a 401. See 401 Troubleshooting for the common failure modes.

Automatic Retries

The SDK retries automatically on:
  • 429 — Rate limited (respects Retry-After header up to 60s)
  • 5xx — Server errors (exponential backoff: 1s, 2s, 4s, max 8s) Client errors (400, 401, 403, 404) are not retried. Timeouts are not retried on non-idempotent POSTs (complete, research, video) — re-issuing could double-run and double-bill the request; pair your own retry with an Idempotency-Key if you need one. See Timeouts & Retries for the full model, including the stream idle timeout. Configure:
const theo = new Theo({
  apiKey: "...",
  maxRetries: 3,               // Default: 2
  timeoutMs: 60000,            // Default: 30000 (unary total; stream connect)
  streamIdleTimeoutMs: 180000, // Default: 120000 (between-chunks)
});

Cancellation

TheoStream.cancel() aborts an in-flight stream without throwing. The underlying fetch is aborted via AbortController; subsequent events stop arriving and the iterator ends cleanly.
const stream = theo.stream({ prompt: "..." });
setTimeout(() => stream.cancel(), 3000); // Graceful "Stop generating"
for await (const event of stream) { /* ... */ }