upstash/context7 · error · Context7JSONParseError
invalid_json_response
invalid_json_response
Error message
Unable to parse response body: ${truncatedBody} What it means
Context7JSONParseError raised from throwResponseError: the server returned a non-OK HTTP response whose body was advertised as application/json but whose raw text failed JSON.parse. The library throws this instead of the usual Context7Error so the invalid body is preserved and the mismatch between declared content-type and actual payload is surfaced to the caller. retryable is inherited from whether the status was a transient/retryable status.
Source
Thrown at packages/sdk/src/http/response.ts:73
metadata: Context7ResponseMetadata,
retryable: boolean
): Promise<never> {
const rawBody = await response.text();
let errorBody: { error?: string; message?: string } = {};
if (rawBody) {
try {
const parsed: unknown = JSON.parse(rawBody);
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
const { error, message } = parsed as Record<string, unknown>;
errorBody = {
error: typeof error === "string" ? error : undefined,
message: typeof message === "string" ? message : undefined,
};
}
} catch (cause) {
if (response.headers.get("content-type")?.includes("application/json")) {
throw jsonParseError(rawBody, metadata, cause, retryable);
}
}
}
throw new Context7Error(errorBody.message || errorBody.error || response.statusText, {
code: errorBody.error ?? "http_error",
status: response.status,
requestId: metadata.requestId,
rateLimit: metadata.rateLimit,
retryable,
});
}
async function parseJson(response: Response, metadata: Context7ResponseMetadata): Promise<unknown> {
const rawBody = await response.text();
try {
return JSON.parse(rawBody);
} catch (cause) {View on GitHub (pinned to 80e681a507)
Solutions
- Log the error body included in the message (truncatedBody) to see what the server actually returned and identify the interfering proxy or middleware
- Inspect the upstream service/proxy for misconfigured error handlers that set Content-Type: application/json on non-JSON error bodies
- Retry the request if error.retryable is true (transient status like 502/503/429) — the body may be intact on a subsequent attempt
- Check for proxies or VPNs intercepting requests (corporate auth walls returning HTML); bypass or authenticate the proxy
- If you control the server, fix the error path to emit valid JSON or drop the application/json content-type when sending plain text
Example fix
// before: assume all error responses are well-formed JSON
try {
const res = await client.request({ path: ['v1', 'search'], body: q });
} catch (e) {
console.error(e.message);
}
// after: distinguish JSON parse failures on error responses and inspect the body
try {
const res = await client.request({ path: ['v1', 'search'], body: q });
} catch (e) {
if (e.name === 'Context7JSONParseError') {
console.error('Server returned non-JSON error body:', e.message); // shows truncated body
} else {
throw e;
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// cannot inspect the error body before the call, but you can pre-flight the endpoint
const probe = await fetch(baseUrl);
const ct = probe.headers.get('content-type') ?? '';
if (!ct.includes('application/json')) console.warn('endpoint not serving JSON'); Type guard
function isJsonParseError(e: unknown): e is Context7JSONParseError {
return e instanceof Context7JSONParseError;
} Try / catch
try {
await client.request({ path: ['v1', 'search'], body: q });
} catch (e) {
if (isJsonParseError(e) && e.retryable) {
await sleep(1000);
return retryRequest(); // transient status — body may be intact next attempt
}
if (isJsonParseError(e)) {
// inspect e.message for the truncated body; likely proxy/gateway interference
}
throw e;
} Prevention
- Check error.retryable before retrying; only transient statuses are marked retryable
- Inspect the truncated body in the message to spot HTML/proxy injection early
- Verify no corporate proxy or VPN intercepts requests to the API host
- Alert on this error in production — it usually signals gateway/server misconfiguration, not client bugs
When it happens
Trigger: Any HttpClient.request call that receives response.ok === false (handled in packages/sdk/src/http/index.ts:94-100) where the error response carries Content-Type: application/json but the body is not valid JSON — e.g. an empty body, an HTML error page served with a JSON content-type, or a truncated response from a proxy.
Common situations: API gateway / reverse proxy (nginx, Cloudflare, ALB) returning error pages with a json content-type header; server crashing mid-error-response so the body is cut off; misconfigured server middleware that sets the content-type before serializing nothing; requests routed to a wrong origin that always claims JSON; corporate proxies injecting HTML login pages.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- errorBody.error || errorBody.message || res.statusText
- Failed to fetch ${item.path}: ${fileResponse.status}
- describeConnectionError(error, url)
- await describeErrorResponse(response, fallback)
- Skipped ${mcpPath}: could not parse (${err instanceof Error
AI-assisted analysis of upstash/context7@80e681a507 (2026-09-08).
Data as JSON: /api/errors/5c3274384fa80ece.
Report an issue: GitHub.