Error handling
Catch the right error per client — BrainHttpError for HTTP, the BrainError hierarchy for the wire — decide retry vs surface, and know which of the nine categories are transient.
- A Brain client.
- Familiarity with HTTP statuses 400/401/403/404/409/503.
A wrapper around Brain calls that classifies a failure as retry / surface / alert using the SDK's own taxonomy, without hard-coding a status list.
Two clients, two taxonomies
The error shape depends on which client you use. This guide is the task recipe; the exhaustive class lists and code tables live in the reference pages, so branch on the SDK's own helpers rather than re-implementing them.
Full references: API errors, TypeScript errors, Python errors.
Handling HTTP errors
Every BrainHttpClient failure is one BrainHttpError. status === 0 means no response arrived (transport or timeout). Branch on status/code; treat message as diagnostic text only.
import { BrainHttpError } from "@brain-db/sdk";
try {
await brain.encode({ text });
} catch (e) {
if (!(e instanceof BrainHttpError)) throw e;
if (e.status === 0 || e.status === 503) {
// transport/timeout or engine.unavailable — retriable (see below)
} else if (e.status === 401 || e.status === 403) {
// fix the credential or permissions — do not retry
} else {
// 400 / 404 / 409 / 502 — surface; the request or state is wrong
}
throw e;
}from brain_db_sdk.http import BrainHttpError
try:
brain.encode(text=text)
except BrainHttpError as e:
if e.status in (0, 503):
... # transport/timeout or engine.unavailable — retriable
elif e.status in (401, 403):
... # fix the credential or permissions — do not retry
else:
... # 400 / 404 / 409 / 502 — surface
raiseuse brain_db_sdk::http::EncodeInput;
if let Err(e) = brain
.encode(&EncodeInput { text: text.to_string(), ..Default::default() })
.await
{
match e.status {
0 | 503 => { /* transport/timeout or engine.unavailable — retriable (see below) */ }
401 | 403 => { /* fix the credential or permissions — do not retry */ }
_ => { /* 400 / 404 / 409 / 502 — surface; the request or state is wrong */ }
}
return Err(e.into());
}The edge maps Brain's internal taxonomy onto a small stable set: bad_request (400), unauthorized (401), forbidden (403), not_found (404), conflict (409), engine.unavailable (503, retriable), engine.error (502). Only 503 and status 0 are worth a blind retry.
Handling wire errors
The wire client separates failure layers so you can react to each differently. Catch BrainError at the boundary; catch a subclass only where you have a specific recovery.
import { ServerError, ErrorCategoryWire, isActAsDenied, BrainError } from "@brain-db/sdk";
try {
await client.encode(req);
} catch (e) {
if (isActAsDenied(e)) throw e; // wrong-scoped credential — fix it
if (e instanceof ServerError && e.category === ErrorCategoryWire.Validation) {
throw e; // bad input — surface to caller
}
if (e instanceof BrainError) {
// ProtocolError / ConnectionClosed / BrainTimeout / ServerError — see below
}
throw e;
}from brain_db_sdk import ServerError, ActAsDenied, BrainTimeout, BrainError
try:
client.encode(req)
except ActAsDenied:
raise # lacks can_act_as / namespace outside allowlist
except ServerError as e:
print(f"{e.code:#06x}", e.category, e.retry_after_ms)
raise
except BrainTimeout:
... # bump request_timeout, or retry
except BrainError:
raiseuse brain_db_sdk::BrainError;
use brain_db_sdk::wire::types::ErrorCategoryWire;
if let Err(e) = client.encode(&req).await {
match &e {
// Authorization covers act_as denied — a wrong-scoped credential; fix it.
BrainError::Server { category: ErrorCategoryWire::Authorization, .. } => return Err(e.into()),
// bad input — surface to caller
BrainError::Server { category: ErrorCategoryWire::Validation, .. } => return Err(e.into()),
// Protocol / Closed / Timeout / other Server verdicts — see below
_ => {}
}
return Err(e.into());
}ServerError.category is one of the nine wire categories. Only two are transient — ResourceExhausted and Unavailable (which may carry retryAfterMs). The other seven (Protocol, Authentication, Authorization, Validation, NotFound, Conflict, Internal) will not succeed on a blind retry.
Let the SDK classify retryability
Don't hard-code a status list — use the SDK's own predicate so a category you didn't enumerate still classifies correctly.
import { isRetryable } from "@brain-db/sdk";
try {
return await client.recall(req);
} catch (e) {
if (isRetryable(e)) return retryWithBackoff(() => client.recall(req));
throw e;
}from brain_db_sdk import is_retryable, with_retry
try:
return client.recall(req)
except Exception as e:
if is_retryable(e):
return with_retry(lambda: client.recall(req))
raiseuse brain_db_sdk::{with_retry, RetryPolicy};
match client.recall(&req).await {
Ok(answer) => Ok(answer),
Err(e) if e.is_retryable() => with_retry(&RetryPolicy::default(), || client.recall(&req)).await,
Err(e) => Err(e),
}isRetryable / is_retryable returns true for a connection drop, a timeout, or a ResourceExhausted / Unavailable verdict — and false for validation and auth failures. See Retries and cancellation.
Page on Internal / engine.error and persistent Unavailable; log auth and validation failures at WARN — those are your inputs and credentials, not an incident. Never retry a Conflict: a same-id-different-params write will keep conflicting until you fix the request.
Was this page helpful?
Idempotency
Two mechanisms keep writes safe to repeat — request_id idempotency (same id, same response) and content dedup (BLAKE3 fingerprint, was_deduplicated).
Retries and cancellation
Configure the HTTP client's built-in retry policy, retry wire calls safely with the request-id contract, and bound calls with timeouts — the SDKs take no external AbortSignal.