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
- Inspect the thrown message: if it is plain statusText, curl the endpoint to see the full body for a more specific error.
- 401/403 → regenerate the API key from the dashboard and confirm it starts with 'ctx7sk'.
- 429 → reduce request rate / add backoff; note the SDK does not auto-retry HTTP 429.
- 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
- Remember the SDK retries only fetch rejections, not HTTP non-ok — implement 429/5xx backoff yourself.
- Inspect the thrown message to distinguish auth (401/403) from rate-limit (429) from server (5xx) errors.
- Keep the API key current and correctly prefixed ('ctx7sk') to avoid avoidable 401s.
- When debugging, curl the endpoint directly to read the full response body the SDK distilled into one message.
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
- ${fallback} (HTTP ${response.status} from ${response.url}):
- Request did not return a result
- Request did not return a result
- Request did not return a result
- Failed to fetch user info
AI-assisted analysis of upstash/context7@ca15df0443 (2026-08-12).
Data as JSON: /api/errors/062819572cacc109.
Report an issue: GitHub.