SDK
Errors, retries, and timeouts
How the SDK reports failures, which ones to retry, and how to set deadlines.
This page shows how @qualiaso/sdk reports API errors and network failures, gives you a retry helper, and shows how to set timeouts. For what each error code means, see Handle errors and rate limits.
What returns and what throws
| Failure | What the SDK does |
|---|---|
The API returns an error status, such as 401 or 422 | Returns it in error. Does not throw. |
| The request never gets a response, such as a network failure or DNS error | Throws. |
The request times out through an AbortSignal | Throws. |
error holds the parsed error body, so the code and message are one level down, in error.error:
{
"error": {
"code": "unauthorized",
"message": "Invalid API key",
"requestId": "req_x2f9kq7wz1mv8c3jtr0hbnyd"
}
}Branch on the status
response is set on every result. Branch on response.status, which works with every SDK version:
const { data, error, response } = await qualia.createActor({
body: { email: "anna@ridgeline.co" },
});
if (error) {
switch (response.status) {
case 401:
throw new Error("Qualia rejected the API key. Check QUALIA_API_KEY.");
case 402:
// Out of credits. Queue the signup and send it after you add credits.
break;
case 422:
// The message names the invalid field.
console.error(error.error.message);
break;
case 429:
// Wait for Retry-After seconds, then retry. See the helper below.
break;
default:
console.error(`Qualia returned ${response.status}`, error);
}
} else {
console.log(data.actorId);
}Throw on errors instead
Pass throwOnError: true to make a call throw on any error status. The thrown value is the parsed error body. It is a plain object without a stack trace:
try {
const { data } = await qualia.createActor({
body: { email: "anna@ridgeline.co" },
throwOnError: true,
});
console.log(data.actorId);
} catch (err) {
// An error body such as { error: { code, message, requestId } },
// or an Error from a network failure or timeout.
console.error(err);
}Retry transient failures
Retry 429 responses, 5xx responses, and network failures. Do not retry other 4xx responses, because the same request gets the same error. Retrying createActor is safe: each email maps to one actor.
This helper wraps any SDK call. It waits for Retry-After on a 429, and backs off 1, 2, then 4 seconds otherwise:
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export async function withRetry<T extends { response: Response }>(
call: () => Promise<T>,
maxAttempts = 4,
): Promise<T> {
for (let attempt = 1; ; attempt++) {
const backoffMs = 1000 * 2 ** (attempt - 1);
let result: T;
try {
result = await call();
} catch (err) {
// Network failure or timeout.
if (attempt >= maxAttempts) throw err;
await sleep(backoffMs);
continue;
}
const { status, headers } = result.response;
const retryable = status === 429 || status >= 500;
if (!retryable || attempt >= maxAttempts) return result;
const retryAfterSeconds = Number(headers.get("Retry-After"));
await sleep(retryAfterSeconds > 0 ? retryAfterSeconds * 1000 : backoffMs);
}
}Use it like this:
const { data, error, response } = await withRetry(() =>
qualia.createActor({ body: { email: "anna@ridgeline.co" } }),
);Set a timeout
The SDK uses fetch, so it accepts an AbortSignal. Pass signal to give one call a deadline:
const { data, error } = await qualia.createActor({
body: { email: "anna@ridgeline.co" },
signal: AbortSignal.timeout(5_000),
});When the deadline passes, the call throws a TimeoutError.
To give every call the same deadline, pass a custom fetch when you create the client. This replaces any signal you pass to a single call:
import { Qualia } from "@qualiaso/sdk";
export const qualia = new Qualia({
auth: () => process.env.QUALIA_API_KEY!,
fetch: (input, init) =>
fetch(input, { ...init, signal: AbortSignal.timeout(10_000) }),
});Next steps
- Handle errors and rate limits. Every error code, the rate limit, and credits.
- Get briefing results. Poll for briefings with a helper that handles
429.