vercel/next.js · error · ApiError

Invalid body

Error message

Invalid body

What it means

Thrown by parseBody() while reading the raw request body for an API route / Server Action. It is a 400 ApiError raised when getRawBody() rejects for any reason OTHER than the 'entity.too.large' size-limit error (which separately yields a 413). It signals the incoming request body could not be consumed at all -- malformed Content-Encoding, unsupported/invalid charset, a prematurely-closed/truncated stream, or a bad encoding parameter.

Source

Thrown at packages/next/src/server/api-utils/node/parse-body.ts:52

  try {
    contentType = parse(req.headers['content-type'] || 'text/plain')
  } catch {
    contentType = parse('text/plain')
  }
  const { type, parameters } = contentType
  const encoding = parameters.charset || 'utf-8'

  let buffer

  try {
    const getRawBody =
      require('next/dist/compiled/raw-body') as typeof import('next/dist/compiled/raw-body')
    buffer = await getRawBody(req, { encoding, limit })
  } catch (e) {
    if (isError(e) && e.type === 'entity.too.large') {
      throw new ApiError(413, `Body exceeded ${limit} limit`)
    } else {
      throw new ApiError(400, 'Invalid body')
    }
  }

  const body = buffer.toString()

  if (type === 'application/json' || type === 'application/ld+json') {
    return parseJson(body)
  } else if (type === 'application/x-www-form-urlencoded') {
    const qs = require('querystring') as typeof import('querystring')
    return qs.decode(body)
  } else {
    return body
  }
}

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Inspect the exact error cause: the raw-body error is re-thrown, so check server logs for the underlying message to determine if it's an encoding, charset, or stream issue.
  2. Verify the client sends a valid Content-Type with a supported charset (e.g. 'application/json; charset=utf-8') and a consistent Content-Encoding.
  3. If a reverse proxy or middleware is involved, confirm it is not truncating or double-encoding the request body.
  4. Ensure the client is not closing the connection before the full body is transmitted (increase client-side timeout for large uploads).

Example fix

// before: client sends gzip body without server decompression
// fetch(url, { body: gzip(json), headers: { 'content-encoding': 'gzip' } })

// after: send uncompressed JSON or decompress in middleware
// fetch(url, { body: json, headers: { 'content-type': 'application/json' } })
Defensive patterns

Strategy: validation

Validate before calling

// Before calling parseBody, sanity-check the content-type/encoding.
// parseBody itself reads content-type; validate upstream in middleware:
export function isValidBodyRequest(req) {
  const ct = req.headers['content-type'] || ''
  if (!ct) return false
  const enc = req.headers['content-encoding']
  // reject encodings you don't handle
  if (enc && !['identity'].includes(enc.toLowerCase())) return false
  return true
}

Type guard

function isReadableBody(req): req is import('http').IncomingMessage & { readable: true } {
  return Boolean(req && typeof (req as any).on === 'function' && (req as any).readable !== false)
}

Try / catch

try {
  const body = await parseBody(req, limit)
} catch (e) {
  if (e instanceof ApiError && e.statusCode === 400) {
    res.status(400).json({ error: 'Malformed request body' })
  } else if (e instanceof ApiError && e.statusCode === 413) {
    res.status(413).json({ error: 'Body too large' })
  } else {
    throw e
  }
}

Prevention

When it happens

Trigger: A POST request hits an API route or Server Action handler that calls parseBody(req, limit). The Content-Type header carries a charset/encoding that raw-body rejects, the client sends a body with a content-encoding the server can't decode (e.g. gzip without decompression), or the connection drops mid-stream so getRawBody throws a non-size error. The catch at parse-body.ts:48 checks e.type === 'entity.too.large'; anything else falls through to this 400.

Common situations: A proxy/CDN injects or strips Content-Encoding causing the body to be unintelligible; a client posts an empty body with a charset like 'utf-32'; reverse-proxy timeouts truncate the upload; or a misconfigured fetch sets a wrong charset parameter. Also seen when the content-type header itself is unparseable (handled by the fallback at line 36-38) yet the body is still unreadable.

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/94aa15b34713feac. Report an issue: GitHub.