Text and data

JSON Unexpected token <: why your API returns HTML

If JSON parsing fails at <, inspect the response body before fixing your JSON parser. The request may have received an HTML login page, a missing-route page or a proxy error. JSON Formatter checks pasted JSON; it cannot change what your API returned.

A JSON parser accepts valid JSON or reports the first syntax error.
A parse error is evidence about the received body, not proof that the API route was reached.

Reproduce the mismatch with a harmless example

The following body begins with an HTML doctype. A JSON parser rejects it immediately because a JSON value cannot start with <. Error wording varies by browser and JavaScript runtime; the first character is the useful clue.

In the browser Network panel, select the failing request and inspect Response, not only Preview. Record its original URL, final URL, status and Content-Type. Redact tokens and cookies before sharing evidence.

Received body:
<!doctype html><title>Sign in</title>

Expected body:
{"items":[],"next":null}

JSON.parse("<!doctype html>");
// SyntaxError: wording depends on the runtime

Use headers and redirects to identify the cause

A 200 status does not prove JSON was returned. An authentication redirect can end on a successful HTML login page, and a single-page application can serve index.html for an unknown /api route. A gateway can also return its own HTML error document.

Use HTTP Headers and Redirect Checker for a public endpoint without credentials. Those checks are separate requests from Cloudflare; they do not reproduce your browser’s authenticated session or POST body. For private APIs, inspect your own request locally.

Common evidence and next action
EvidenceLikely explanationNext check
Final URL is /loginAuthentication flowInspect session/auth handling locally
200 plus text/html at /api/itemsFallback route or wrong hostInspect API routing and deployment base URL
502 plus HTMLGateway or upstream failureCheck proxy and application logs
404 plus HTMLMissing API routeConfirm method, path and deployed version

Handle the response deliberately

Fix the route, authentication contract or upstream problem rather than replacing < with another character. An Accept header expresses the representation you want; it cannot force an HTML-only route to become a JSON API.

Check both response status and representation in client code. The example below omits logging the body because it could contain private data. An API can return JSON errors on non-success statuses; handle those according to its documented contract.

After changing the server or request, retry with the same method and credentials. A formatter successfully parsing a manually corrected sample is not proof that the live response is fixed.

const response = await fetch("/api/items", {
  headers: { Accept: "application/json" }
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const type = response.headers.get("content-type") || "";
if (!/\bapplication\/(?:[\w.-]+\+)?json\b/i.test(type)) {
  throw new Error("Expected a JSON response");
}
const data = await response.json();

References

Use the linked specifications and product documentation to check behavior in your own environment.

Continue troubleshooting

Related tools

Frequently asked questions

Does setting Accept guarantee a JSON body?

No. The server must implement the requested representation and error contract.

Can a 200 response contain a login page?

Yes. The final redirected response can be a successful HTML login page.

Can PageUtil inspect my authenticated request?

No. Public network checks do not carry your browser session or private request body.

Need help with a tool or found a bug?

Report an issue directly to engineering. No accounts required — all client data remains private on your device.

Report an Issue →