Brainby arc-labs/docs
Recipe

Manual rate-limit backoff

Catch a 429 / 503, honor Retry-After, and retry — taking over from the SDK's built-in auto-retry when you need to.

The HTTP client already retries transport failures and 503 responses for the idempotent verbs (encode, whoami, capabilities) with exponential backoff, honoring a server Retry-After. For most callers that is the right behavior — you do nothing. The non-idempotent verbs (recall, forget, link, unlink, plan, reason) run exactly once and are never auto-retried.

When you want different behavior — enqueue the write, log it, or fall through to a degraded path — catch the typed error yourself. Every HTTP failure throws a BrainHttpError carrying status, code, and message (status: 0 means a transport failure or timeout).

import { BrainHttpClient, BrainHttpError } from '@brain-db/sdk';

const brain = new BrainHttpClient({ apiKey: process.env.BRAIN_API_KEY! });

async function encodeOrEnqueue(text: string) {
  try {
    return await brain.encode({ text });
  } catch (err) {
    if (err instanceof BrainHttpError && (err.status === 429 || err.status === 503)) {
      // Enqueue for a worker to retry later instead of blocking here.
      await jobQueue.enqueue('brain-encode', { text, notBefore: Date.now() + 1000 });
      return null;
    }
    throw err; // 4xx (bad request, auth) and everything else: propagate.
  }
}
from brain_db_sdk import BrainHttpClient, BrainHttpError

brain = BrainHttpClient(os.environ['BRAIN_API_KEY'])


def encode_or_enqueue(text: str):
    try:
        return brain.encode(text)
    except BrainHttpError as err:
        if err.status in (429, 503):
            job_queue.enqueue('brain-encode', {'text': text, 'not_before': time.time() + 1})
            return None
        raise  # 4xx and everything else: propagate.

Manual retry with backoff

If you want retry behavior but with your own logging, do it yourself. Honor a server Retry-After when present, otherwise back off exponentially:

async function recallWithRetry(query: string, attempts = 3) {
  let delay = 250;
  for (let i = 0; i < attempts; i++) {
    try {
      return await brain.recall({ query });
    } catch (err) {
      const retryable = err instanceof BrainHttpError && (err.status === 429 || err.status === 503);
      if (!retryable || i === attempts - 1) throw err;
      console.warn(`brain ${err.status}, retrying in ${delay}ms`, { attempt: i });
      await new Promise((r) => setTimeout(r, delay));
      delay *= 2;
    }
  }
  throw new Error('unreachable');
}

On the wire client

The wire client (BrainClient) raises a ServerError whose category is ResourceExhausted (rate/quota) or Unavailable (shard busy), and those carry a retryAfterMs hint. Use the isRetryable(err) helper to branch, or let the built-in withRetry combinator drive it. See Errors and Retries.

Don't loop indefinitely. If the server is genuinely overloaded the retry budget is cooperation — a few attempts with backoff is the polite ceiling. After that, propagate the error and let the caller degrade.

Was this page helpful?

On this page