DEV Community

zey-netizen
zey-netizen

Posted on

Why Your OpenAI JSON Calls Randomly Fail with "could not parse JSON body" (And How to Fix It)

``You're calling OpenAI from Node.js. 99% of requests work. Then randomly:

BadRequestError: 400 could not parse JSON body

Or:

SyntaxError: Invalid JSON: EOF while parsing an object

Or, if you're using tool calling:

Invalid JSON in tool call arguments

You check your code. Nothing changed. You retry manually — it works. You deploy again. It breaks again.

What's actually happening

These errors are NOT your bugs. They're transient artifacts from:

  1. Proxy / network corruption — the request body arrives at OpenAI partially mangled. OpenAI returns 400.
  2. Streaming truncation — the SDK parses JSON before the stream signals completion. Especially with max_output_tokens set low.
  3. Model-side tool call malformation — the model emits single quotes, trailing commas, or markdown code fences inside function.arguments.

Every one of these is intermittent. So you can't debug it with a stack trace. You just suffer.

The wrong fix

People usually do this:

try {
  const res = await client.chat.completions.create({...});
} catch (e) {
  const res = await client.chat.completions.create({...});
}
Enter fullscreen mode Exit fullscreen mode

Problems:

  • You retry errors that should NOT be retried (invalid API key, rate limit that needs backoff, context length exceeded).
  • You don't sanitize tool call args, so malformed JSON still crashes downstream.
  • You add 20 lines of retry boilerplate per call site.

The right fix: classify then act

Different errors need different handling:

Error Correct action
Transient 400 (could not parse JSON body) Retry with backoff + jitter
Truncated stream (EOF while parsing) Retry, possibly with higher max_tokens
Malformed tool args Sanitize inline, no retry needed
Invalid API key Fail fast, do NOT retry
Rate limit Retry with longer backoff

Doing this manually is ~150 lines of code you'll write badly. Or:

npm install @coder12-z/llm-shield

import { shield } from "@coder12-z/llm-shield";
import OpenAI from "openai";

const client = new OpenAI();

const safeCall = shield(async (prompt) =>
  client.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: prompt }],
    response_format: { type: "json_object" },
  }),
  {
    maxRetries: 3,
    onRetry: ({ attempt, kind }) => console.log(`retry #${attempt} (${kind})`),
  }
);
Enter fullscreen mode Exit fullscreen mode

Three lines of config. Zero dependencies. Works with OpenAI, Anthropic, Gemini, or anything that throws Error objects with status and message.

What it doesn't do

  • It doesn't repair arbitrary JSON strings — for that, use jsonrepair (https://www.npmjs.com/package/jsonrepair).
  • It doesn't call LLMs for you — it wraps your existing calls.
  • No API key. No network calls. No telemetry.

Links

MIT licensed. Feedback welcome.

Top comments (0)