{"record":{"id":"be0d3b34a50457d1","repo":"PaddlePaddle/PaddleOCR","slug":"expected-a-json-response-body","errorCode":null,"errorMessage":"Expected a JSON response body.","messagePattern":"Expected a JSON response body\\.","errorType":"exception","errorClass":"ResponseFormatError","httpStatus":null,"severity":"error","filePath":"api_sdk/typescript/src/internal/http.ts","lineNumber":167,"sourceCode":"\n  async fetchResource(url: string, signal?: AbortSignal, timeoutMs?: number): Promise<ArrayBuffer> {\n    const resp = await this.fetch(url, { method: \"GET\" }, signal, false, timeoutMs);\n    return resp.arrayBuffer();\n  }\n\n  private async fetchJson<T>(\n    url: string,\n    init: RequestInit,\n    signal?: AbortSignal,\n    withAuth: boolean = true,\n    timeoutMs?: number,\n  ): Promise<T> {\n    const resp = await this.fetch(url, init, signal, withAuth, timeoutMs);\n    let payload: APIResponse<T>;\n    try {\n      payload = await resp.json() as APIResponse<T>;\n    } catch (error) {\n      throw new ResponseFormatError(\"Expected a JSON response body.\", { cause: error });\n    }\n    if (payload.code !== undefined && payload.code !== 0) {\n      throw new APIError(resp.status, payload.msg || \"PaddleOCR official API request failed.\");\n    }\n    if (!payload || typeof payload !== \"object\" || !(\"data\" in payload)) {\n      throw new ResponseFormatError(\"Response body is missing data.\");\n    }\n    return payload.data;\n  }\n\n  private async fetch(\n    url: string,\n    init: RequestInit,\n    signal?: AbortSignal,\n    withAuth: boolean = true,\n    timeoutMs?: number,\n  ): Promise<Response> {\n    const headers: Record<string, string> = {","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/api_sdk/typescript/src/internal/http.ts#L149-L185","documentation":"ResponseFormatError with 'Expected a JSON response body.' means the HTTP request itself succeeded but resp.json() threw while decoding the body — the server returned an empty body or content that is not JSON (HTML, plain text, binary). The underlying decode error is preserved on `cause`. It comes from the private fetchJson<T>() used by most JSON API endpoints.","triggerScenarios":"Any authenticated JSON call (submitJson, getJobStatus, getBatchStatus, token fetch) where the gateway responds 2xx with a non-JSON body: an HTML login/consent page, an empty 204, a captive portal, or a WAF block page that still returns 200.","commonSituations":"Wrong base URL pointing at a marketing page instead of the API host; corporate proxies injecting HTML; misconfigured API key routing the request to a docs page; server-side incidents returning empty bodies; HTTP/2 truncation.","solutions":["Log the actual body: catch the error and print e.cause plus re-run the request with the same headers via curl to see what the server really returned","Verify the API base URL / endpoint configuration — the most common cause is hitting a host that serves HTML","If a proxy or VPN sits between you and the API, exempt the API host or verify it does not rewrite responses","If persistent and reproducible with curl, treat it as a service-side regression and report it with the request ID"],"exampleFix":"try {\n  const status = await client.getJobStatus(jobId);\n} catch (e) {\n  if (e instanceof ResponseFormatError && /Expected a JSON/.test(e.message)) {\n    // body was not JSON: dump cause for the raw decode failure\n    console.error(\"Non-JSON body from server:\", e.cause);\n  }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"async function returnsJsonBody(url: string, init: RequestInit): Promise<boolean> {\n  const r = await fetch(url, init);\n  const ct = r.headers.get(\"content-type\") ?? \"\";\n  return ct.includes(\"application/json\");\n}","tryCatchPattern":"try {\n  const status = await poller.getStatus(jobId);\n} catch (e) {\n  if (e instanceof ResponseFormatError && /Expected a JSON response body/.test(e.message)) {\n    // server sent non-JSON: capture traffic via custom fetchImpl, check base URL\n    throw new Error(`API returned non-JSON body: ${e.cause}`);\n  }\n  throw e;\n}","preventionTips":["Pin the SDK to the documented base URL; never point it at pages that serve HTML","Wrap fetch with a logging fetchImpl in development to catch contract drift early","Test against the real API in CI (or a faithful mock returning the full envelope)"],"tags":["http","json","response-format","typescript"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}