upstash/context7 · error · Context7Error

errorBody.error || errorBody.message || res.statusText

Error message

errorBody.error || errorBody.message || res.statusText

What it means

Thrown by HttpClient.request() when the final response (after retrying only fetch-level rejections) is non-ok. It parses the body as JSON and throws Context7Error with errorBody.error, errorBody.message, or res.statusText as the message. Important: the retry loop only retries when fetch() throws (network errors); HTTP non-ok responses are NOT retried, so 429/5xx surface immediately.

Source

Thrown at packages/sdk/src/http/index.ts:176

        if (requestOptions.signal?.aborted) {
          throw error_;
        }
        error = error_ as Error;
        if (i < this.retry.attempts) {
          await new Promise((r) => setTimeout(r, this.retry.backoff(i)));
        }
      }
    }
    if (!res) {
      throw error ?? new Error("Exhausted all retries");
    }

    if (!res.ok) {
      const errorBody = (await res.json().catch(() => ({}))) as {
        error?: string;
        message?: string;
      };
      throw new Context7Error(errorBody.error || errorBody.message || res.statusText);
    }

    const contentType = res.headers.get("content-type");

    if (contentType?.includes("application/json")) {
      const body = await res.json();
      return { result: body as TResult };
    } else {
      const text = await res.text();
      const headers = this.extractTxtResponseHeaders(res.headers);
      return { result: text as TResult, headers };
    }
  }

  private extractTxtResponseHeaders(headers: Headers): TxtResponseHeaders | undefined {
    const page = headers.get("x-context7-page");
    const limit = headers.get("x-context7-limit");
    const totalPages = headers.get("x-context7-total-pages");

View on GitHub (pinned to ca15df0443)

Solutions

  1. Inspect the thrown message: if it is plain statusText, curl the endpoint to see the full body for a more specific error.
  2. 401/403 → regenerate the API key from the dashboard and confirm it starts with 'ctx7sk'.
  3. 429 → reduce request rate / add backoff; note the SDK does not auto-retry HTTP 429.
  4. 5xx → retry with exponential backoff at the call site; if persistent, check the status page.

Example fix

// before — single shot, non-ok is fatal
const libs = await client.exec(new SearchLibraryCommand(q, lib));

// after — wrap and branch on status surfaced via the message
try {
  const libs = await client.exec(new SearchLibraryCommand(q, lib));
} catch (e) {
  if (e instanceof Context7Error && /429|Too Many Requests/.test(e.message)) {
    await sleep(backoffMs); // caller-managed backoff for HTTP 429
    return retry();
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// The SDK does not retry HTTP non-ok (4xx/5xx); budget for it at the call site.
async function callWithHttpRetry<T>(fn: () => Promise<T>, retries = 3): Promise<T> {
  for (let i = 0; ; i++) {
    try {
      return await fn();
    } catch (e) {
      const transient = e instanceof Context7Error &&
        (/\b5\d\d\b|internal|bad gateway|service unavailable|gateway timeout/i.test(e.message));
      if (!transient || i >= retries) throw e;
      await new Promise((r) => setTimeout(r, Math.min(1000 * 2 ** i, 8000)));
    }
  }
}

Type guard

import { Context7Error } from "@error";
function isContext7Error(e: unknown): e is Context7Error {
  return e instanceof Error && (e as Context7Error).name === "Context7Error";
}
function isRateLimited(e: unknown): boolean {
  return isContext7Error(e) && /429|too many requests/i.test(e.message);
}

Try / catch

try {
  const libs = await client.exec(new SearchLibraryCommand(q, lib));
} catch (e) {
  if (isContext7Error(e)) {
    if (isRateLimited(e)) await sleep(2000);          // HTTP 429 — caller handles backoff
    else if (/^4\d\d|unauthor|forbidden|not found/i.test(e.message)) throw new Error(`Fix request: ${e.message}`);
    else await sleep(1000);                             // assume transient 5xx
    return retry();
  }
  throw e;
}

Prevention

When it happens

Trigger: 401/403 (invalid/expired API key), 404 (wrong endpoint or path), 429 (rate limited — not retried), 400 (malformed query params), 5xx (server error). The message is the server's `error`/`message` field if present, otherwise the bare HTTP statusText (e.g. 'Too Many Requests').

Common situations: Wrong API key or one missing the 'ctx7sk' prefix that the server rejected; query/libraryName params the server rejected; exceeded the plan's rate limit; transient 5xx during an incident; pointed the SDK at a wrong base URL returning 404.

Related errors


AI-assisted analysis of upstash/context7@ca15df0443 (2026-08-12). Data as JSON: /api/errors/062819572cacc109. Report an issue: GitHub.