When you fetch data from an API and parse it with JSON.parse() or response.json(), one of the most common errors you’ll hit is:
SyntaxError: Unexpected token < in JSON at position 0
The position 0 is the clue: the very first character of the string is <. That character opens an HTML tag, which means the string you are parsing is HTML, not JSON. This guide explains why it happens and how to fix it.
A valid JSON string can only start with a few characters: {, [, ", a digit, t/f/n, or -. The character < never starts valid JSON. So when JSON.parse() sees < at position 0, it knows immediately that the input isn’t JSON — it is almost always the beginning of an HTML document like <!DOCTYPE html> or <html>.
When a server returns a 404 or 500, many frameworks send back an HTML error page. Your code assumes the response is JSON, but it’s actually HTML:
const res = await fetch('/api/user/9999'); // user doesn't exist -> 404
const data = await res.json(); // ❌ Unexpected token < in JSON at position 0
If an endpoint requires authentication and you’re not logged in, the server often redirects to an HTML login page instead of returning a JSON 401:
const res = await fetch('/api/account'); // redirected to /login (HTML)
const data = await res.json(); // ❌
A typo in the URL can return a generic HTML page (or a single-page app’s index.html) instead of a JSON endpoint:
const res = await fetch('/api/users'); // ❌ should be /api/user
A WAF, CDN, or corporate proxy can intercept the request and return an HTML “blocked” or “challenge” page.
Check the status and Content-Type before parsing:
const res = await fetch('/api/user');
if (!res.ok) {
throw new Error(`Request failed: ${res.status}`);
}
const contentType = res.headers.get('content-type');
if (!contentType?.includes('application/json')) {
throw new Error(`Expected JSON, got ${contentType}`);
}
const data = await res.json();
Log the raw body to see what you actually received:
const res = await fetch('/api/user');
const text = await res.text();
console.log(text.slice(0, 200)); // inspect the first 200 characters
If the output starts with <!DOCTYPE html> or <html>, you’ve found the problem: the server sent HTML.
Fix the root cause — correct the URL, handle 401s by logging in first, or configure the server to return JSON error responses instead of HTML pages.
The < is the first character of an HTML tag. Your code tried to parse a string as JSON, but the string is actually HTML — usually an error page or a login redirect.
Most often the request hit a 404 or 500 error page, the server redirected to a login page, or the URL is wrong and returns a web page instead of an API response.
Log the status and the raw body before parsing. If res.ok is false, or the body starts with <!DOCTYPE or <html, you are parsing HTML, not JSON.
"Unexpected end of JSON input" means the JSON was real but empty or truncated. "Unexpected token <" means the content is not JSON at all.