qualia
SDK

Errors and retries

How the SDK reports failures, when to retry, and how to set timeouts.

The SDK never throws by default. Every method returns { data, error, response } where exactly one of data or error is populated.

The error envelope

Every error response from /v1 carries this shape:

{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key.",
    "requestId": "req_abc..."
  }
}

Include the requestId when filing a support ticket; it correlates the request with our logs.

Branching on error

error carries the full response body the API returned, fully typed per status code. Branch on the status to decide what to do next.

// app/signup-handler.ts
import { Qualia } from "@qualiaso/sdk";

const qualia = new Qualia({
  auth: () => process.env.QUALIA_API_KEY!,
});

const { data, error, response } = await qualia.createActor({
  body: { email: "alex@example.com" },
});

if (error) {
  if (response.status === 401) {
    // Wrong or missing API key. Surface a config error and stop.
    return;
  }
  if (response.status === 422) {
    // Validation failed. Inspect `error.message` for the offending field path.
    return;
  }
  // 5xx from us, or other 4xx. Inspect `error` for details.
  throw new Error(`createActor failed: ${response.status}`);
}

console.log(data.actorId);

If you would rather throw on every non-2xx, pass throwOnError: true per call:

// app/signup-handler.ts
const { data } = await qualia.createActor({
  body: { email: "alex@example.com" },
  throwOnError: true,
});

The thrown value is the same error object you would have read from the destructured tuple.

Retry policy

Retry on transient failures only:

  • HTTP 5xx responses.
  • Network errors (fetch rejected before a response was received).

Do not retry 4xx responses; the request was rejected for a reason your input must fix.

A reasonable default is exponential backoff with three attempts (200ms, 800ms, 2s) and a 10s ceiling.

// app/with-retry.ts
async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
  let lastError: unknown;
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (e) {
      lastError = e;
      if (i < attempts - 1) {
        await new Promise((r) => setTimeout(r, 200 * 4 ** i));
      }
    }
  }
  throw lastError;
}

Timeouts

The SDK uses the global fetch, which honors AbortSignal. Set a per-call deadline with AbortSignal.timeout:

// app/signup-handler.ts
const { data, error } = await qualia.createActor({
  body: { email: "alex@example.com" },
  signal: AbortSignal.timeout(5_000),
});

For a process-wide default, swap the underlying fetch when you instantiate:

// app/qualia.ts
const qualia = new Qualia({
  auth: () => process.env.QUALIA_API_KEY!,
  fetch: (url, init) =>
    fetch(url, { ...init, signal: AbortSignal.timeout(10_000) }),
});

Next

  • Errors guide. Full table of HTTP statuses and stable error codes.