JSON / API Response Debugging workflow

Why an API Returns HTML Instead of JSON

Trace the common reasons an API client receives an HTML page instead of JSON, including redirects, expired sessions, proxy errors and wrong routes.

Quick Answer

An API usually returns HTML instead of JSON when the request is not reaching the JSON endpoint you think it is reaching. Common causes include authentication redirects, frontend fallback routes, reverse proxy error pages, incorrect base URLs, missing Accept headers and server errors rendered as HTML.

Example Scenario

The failure often appears as SyntaxError: Unexpected token < in JSON at position 0. The first character is the clue: the body starts with an HTML tag, not a JSON object or array. The parser is not the root problem. The client is receiving a browser page, login screen or error document from a path that the developer expected to be an API route.

Step-by-Step Explanation

  1. Open the Network panel and confirm the final request URL.
  2. Check whether the response followed a redirect before returning HTML.
  3. Inspect the status code, Content-Type and first line of the body.
  4. Compare the failing request with a known-good API request.
  5. Check authentication headers, cookies and environment-specific base URLs.
  6. Fix the route, auth flow or proxy behavior before changing JSON parsing code.

The Body Usually Tells the Truth

When an API client receives HTML, the fastest clue is the first line of the response body. A login page may start with <!doctype html>. A single-page application fallback may return the same index.html file for every unknown path. A proxy error may include a gateway message. None of these are valid JSON, even if the URL contains /api/ or the status code looks harmless.

Copy the first 200 characters of the response body before changing code. If the text contains <html, <body, a form element, a script tag or a framework error page, the next step is route and response debugging, not JSON syntax debugging.

This small preview also protects you from a misleading stack trace. The stack trace points at the line where parsing failed, but the body preview points at the system that produced the wrong response. That difference matters when the frontend code, API server, proxy and identity provider are all owned by different teams.

Authentication Redirects Are a Frequent Cause

A session-based application may redirect unauthenticated users to a login page. Browser navigation handles that naturally, but an API client expecting JSON sees the login HTML and fails at parsing. This is especially common when local development uses cookies and production uses bearer tokens, or when a staging environment expires sessions aggressively.

Look for 301, 302, 303 or 307 entries before the final response. Also inspect whether credentials, cookies or Authorization headers are present. If the final URL is a login route, the API client did not receive an API error object; it received a user-facing page.

The fix should happen at the contract boundary. A browser page can still redirect a person to sign in, but an API route should normally return 401 with a compact JSON body. That keeps clients predictable and stops authentication failures from masquerading as broken JSON.

Frontend Fallback Routes Can Mask Missing API Routes

Many frontend frameworks serve index.html for unknown paths so client-side routing can work. That is useful for pages, but it can hide a missing API route. A typo such as /api/userss may return the app shell rather than a JSON 404 if the server configuration sends unknown paths to the frontend.

A good API deployment should separate frontend fallback behavior from API routes. Unknown API paths should return a JSON or problem+json error body with an appropriate status code. If they return the app shell, client debugging becomes much harder.

This issue often appears only after deployment. Local development may proxy /api cleanly to the backend, while production may serve the frontend and API through the same domain. Test at least one deliberately wrong API path in each environment. The response should look like an API error, not like the application homepage.

Proxy and Gateway Errors May Be Rendered as HTML

Reverse proxies and load balancers often produce HTML error pages for 502, 503 and 504 responses. The upstream API may be down, but the client receives a proxy-generated document. This can look like an API parsing issue when the real cause is deployment, timeout or upstream availability.

Check response headers for server, via, x-request-id or gateway-specific values. Those headers often reveal whether the response came from the application server, a proxy layer or a CDN.

A useful habit is to save the failing response body together with the request id. The body shows the shape of the error, while the request id helps backend or infrastructure logs find the matching event. Without both pieces, teams can spend time debating whether the bug lives in parsing code or the serving layer.

Compare Browser, Curl and Application Requests

If the Network panel shows HTML but the endpoint works in another tool, compare the actual requests rather than the URL alone. Cookies, Accept headers, Authorization headers, origin headers and query parameters can all change server behavior. A URL copied into a browser may return a page, while the same URL with a bearer token may return JSON.

Curl is helpful because it makes redirects and headers visible. Run one request with redirects disabled and one with redirects enabled. If the first response is a redirect and the second is HTML, you have a navigation-style response path, not a JSON API response path.

Also check whether the frontend is calling a relative URL from an unexpected base path. A page deployed under /app may turn api/users into /app/api/users instead of /api/users. That tiny path difference is enough to hit the frontend router, a static file handler or a completely unrelated service.

What to Fix

Do not fix this by wrapping every JSON parse call in a silent catch. That hides the symptom but does not restore the API contract. Instead, make the request land on the intended JSON route, make auth failures return API-friendly errors, or branch client behavior when the response is clearly not JSON.

After fixing the route or auth issue, validate the response body with the JSON Formatter & Validator and compare the failing response with a successful one. That gives you a concrete before-and-after record instead of a vague parser failure.

A stronger long-term fix is to add one integration test that asserts API errors are returned as JSON. The test does not need to cover every endpoint. Even a single wrong-route or unauthenticated-request case catches the class of configuration mistakes that send HTML into API clients.

Code Examples

Log the final URL and Content-Type
const response = await fetch('/api/account', { credentials: 'include' });
const text = await response.text();

console.log('final url:', response.url);
console.log('status:', response.status);
console.log('content-type:', response.headers.get('content-type'));
console.log(text.slice(0, 200));
Fail clearly when HTML is returned
function assertJsonResponse(response, body) {
  const type = response.headers.get('content-type') || '';
  if (!type.includes('json')) {
    throw new Error(
      'Expected JSON from ' + response.url + ' but received ' + type + ': ' + body.slice(0, 120)
    );
  }
}
Return JSON for API auth failures
// API route behavior
if (!request.user) {
  return Response.json(
    { error: 'unauthorized', message: 'Sign in or send a valid token.' },
    { status: 401 }
  );
}

Common Mistakes

  • Assuming every /api/ URL is handled by the API server.
  • Ignoring redirects because the final response has a body.
  • Treating an HTML login page as a JSON syntax problem.
  • Letting frontend fallback routes handle missing API endpoints.
  • Catching parser errors without logging status, Content-Type and body preview.

FAQ

Why does the response start with <!doctype html>?

That usually means the server returned an HTML document such as a login page, app shell or error page instead of JSON.

Can this happen with a 200 status?

Yes. A frontend fallback or misconfigured route can return index.html with a 200 status.

Should APIs return HTML for authentication errors?

Browser pages may return HTML, but API routes should usually return a structured JSON error and an appropriate status code.