decolua/9router · error

Invalid JSON body

Error message

Invalid JSON body

What it means

HTTP 400 returned by handleFetch when request.json() throws — the body is not valid JSON (empty body, malformed JSON, or a non-JSON content type). The handler parses the body first and fails fast before any auth or validation.

Source

Thrown at src/sse/handlers/fetch.js:29

import { errorResponse, unavailableResponse } from "open-sse/utils/error.js";
import { HTTP_STATUS } from "open-sse/config/runtimeConfig.js";
import * as log from "../utils/logger.js";
import { updateProviderCredentials, checkAndRefreshToken } from "../services/tokenRefresh.js";
import { handleComboChat, getComboModelsFromData } from "open-sse/services/combo.js";
import { assertPublicUrl } from "@/shared/utils/ssrfGuard.js";

/**
 * Handle web fetch (URL extraction) request for the SSE/Next.js server.
 * Provider IS the model. Mirrors handleEmbeddings auth + fallback flow.
 *
 * @param {Request} request
 */
export async function handleFetch(request) {
  let body;
  try {
    body = await request.json();
  } catch {
    log.warn("FETCH", "Invalid JSON body");
    return errorResponse(HTTP_STATUS.BAD_REQUEST, "Invalid JSON body");
  }

  const reqUrl = new URL(request.url);
  // Accept either `provider` or `model` (UI sends `model` since provider IS the model for webFetch)
  const providerInput = body.provider || body.model;
  const targetUrl = body.url;
  const format = body.format;
  const maxCharacters = body.max_characters;

  log.request("POST", `${reqUrl.pathname} | ${providerInput}`);

  // Log API key (masked)
  const apiKey = extractApiKey(request);
  if (apiKey) {
    log.debug("AUTH", `API Key: ${log.maskKey(apiKey)}`);
  } else {
    log.debug("AUTH", "No API key provided (local mode)");

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Send a valid JSON body with Content-Type: application/json
  2. Validate the payload with JSON.stringify before sending
  3. If using curl, quote the JSON: -d '{"provider":"x","url":"https://..."}'
  4. Confirm no middleware/proxy is transforming or dropping the body

Example fix

// before
curl -X POST http://localhost:20128/v1/fetch -d provider=jina url=https://example.com
// after
curl -X POST http://localhost:20128/v1/fetch -H 'Content-Type: application/json' -d '{"provider":"jina","url":"https://example.com"}'
Defensive patterns

Strategy: validation

Validate before calling

const payload = JSON.stringify({ provider, url });
JSON.parse(payload); // fail fast locally before sending
await post('/v1/fetch', payload, { headers: { 'Content-Type': 'application/json' } });

Type guard

function isSerializable(x) {
  try { JSON.stringify(x); return true; } catch { return false; }
}

Try / catch

const res = await post('/v1/fetch', body);
if (res.status === 400 && (await res.text()).includes('Invalid JSON body')) {
  console.error('Request body was not parseable JSON — log the raw body and Content-Type');
}

Prevention

When it happens

Trigger: POST to the fetch endpoint with an empty body, trailing commas, unquoted keys, form-encoded or multipart bodies, or a body stringified incorrectly client-side.

Common situations: Using curl without --data or with unquoted JSON on the shell; forgetting Content-Type: application/json; SDK sending FormData; proxy stripping the body on redirects.

Understand the failure class

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/9ccaee16f9c43974. Report an issue: GitHub.