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

  1. Log the error body included in the message (truncatedBody) to see what the server actually returned and identify the interfering proxy or middleware
  2. Inspect the upstream service/proxy for misconfigured error handlers that set Content-Type: application/json on non-JSON error bodies
  3. Retry the request if error.retryable is true (transient status like 502/503/429) — the body may be intact on a subsequent attempt
  4. Check for proxies or VPNs intercepting requests (corporate auth walls returning HTML); bypass or authenticate the proxy
  5. 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

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.

Related errors


AI-assisted analysis of upstash/context7@80e681a507 (2026-09-08). Data as JSON: /api/errors/5c3274384fa80ece. Report an issue: GitHub.