Python JSONDecodeError Expecting Value Line 1 Column 1
Why json.loads or response.json() raises Expecting value at line 1 column 1 in Python, how to see what the text really is, and a checklist for each cause.

The traceback ends with json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) and the code was perfectly sensible a minute ago. This is the most common JSON error in Python, and it nearly never means that your JSON is malformed. It means the parser was given something that is not JSON at all: nothing, a web page, a prefix, a Python object that was converted to a string. This article is a short procedure for finding which, with the specific messages each cause produces, the three habits that prevent the error from being confusing, and the cases where the parser is not at fault.
What the message says
Expecting value is raised when the parser needs the start of a value (an object, array, string, number, true, false or null) and finds something else. line 1 column 1 (char 0) is the position where that happened: the first character of the text. So the document does not begin with anything JSON can begin with.
The first step is to look at the text itself, not the traceback. Put this just above the call that fails:
print(repr(text[:200]), len(text))repr matters. It shows an empty string as '', a newline as \n, a byte order mark as , and keeps invisible characters visible. Whatever it prints, you now know what the parser saw, and the rest of this article is a lookup by that output.
Causes, from most to least common
An empty string. json.loads("") fails with Expecting value: line 1 column 1 (char 0), and so does a string of whitespace (for " \n" the position moved to line 2 column 1, char 3, the end of the text). The repr is '' with a length of 0. This is what you get from an empty file, an API call that returned no body, an environment variable that was not set (os.environ.get("CONFIG", "")) or a message queue payload that was blank.
HTML where JSON was expected. The repr starts '<!doctype html>...' or '<html>'. Python reports the same position as for an empty string, because < cannot start a value. A server returned an error page, a login page, a captive portal, a proxy's block page or a 404 from the wrong URL. The payload is human-readable text that tells you what happened, so read it.
A file that was already read. Passing a file object to json.load after something else consumed it fails the same way, because the read starts at the end. In my test, f.read() followed by json.load(f) raised Expecting value: line 1 column 1 (char 0) even though the file was fine. The fix is f.seek(0) or to read once and keep the text. A common version of this bug is printing the file for debugging with print(f.read()) and then loading it.
The wrong text, converted to a string. json.loads(str(obj)) on a dict gives single quotes (a different message, at char 1; see JSON parse error with single quotes instead of double quotes). json.loads(str(None)) gives Expecting value because the text is None, not null. json.loads("undefined") does the same. The repr starts with a Python or JavaScript word.
A prefix in front of the document. Some APIs prepend )]}', to guard against script inclusion, a log line, or callback( in JSONP. The repr shows the prefix. If a )]}' prefix is documented, strip exactly that and then parse; for JSONP, strip the function wrapper. Expecting value at char 0 in that case is expected, because the first character really is not a JSON start.
A value that Python does not accept. {"a": undefined} and {"a": +1} fail at the value, not at char 0, with Expecting value: line 1 column 7 (char 6). Position 0 stays reserved for text that is wrong from the first character.
The same problem with requests
Most readers meet this error through a web call:
import requests
r = requests.get("https://example.test/api/items")
data = r.json()r.json() parses the response body, and in the version of requests I used (2.34.2) its requests.exceptions.JSONDecodeError subclasses the standard one and prints the same message. Against a local test server, a 200 page of HTML and a 204 with no body both failed with Expecting value: line 1 column 1 (char 0). When it fails, the response is the evidence:
print(r.status_code, r.headers.get("Content-Type"), len(r.content))
print(repr(r.text[:200]))Three results explain most failures. A status of 204 with a length of 0 means the server answered correctly and sent no body, so there is nothing to parse. A status of 404, 500 or 503 with text/html means you are reading an error page, so the real fault is the request. A **status of 200 with text/html** and a page full of forms means a redirect to a login or consent page, often because the cookie or token expired, or the URL points to the website and not to the API.
Handle those cases before parsing. A small wrapper that names the problem saves the next person an hour:
import requests
def fetch_json(url, **kw):
r = requests.get(url, timeout=10, **kw)
ctype = r.headers.get("Content-Type", "")
if r.status_code == 204 or not r.content:
return None
if "json" not in ctype:
raise ValueError(f"{url} returned {r.status_code} {ctype!r}: {r.text[:120]!r}")
r.raise_for_status()
return r.json()The check on Content-Type is a heuristic. A badly configured server can send JSON as text/plain, and then the wrapper rejects a good response, but the error message includes the first 120 characters, so the situation is plain. I prefer a loud mismatch to a silent guess. The same pattern for error responses from APIs is in reading JSON from an API with requests and handling errors.
When the position is not zero
If the message says line 1 column 1 the text is not JSON from the start. When it names a later position, the cause is something else, and the neighboring articles apply:
Expecting valueat a later column: a bad token where a value belongs (undefined,NaNin a strict reader, a trailing comma in older Pythons). See finding the exact line of a JSON syntax error.Extra data: valid JSON followed by more text, as in Python JSONDecodeError extra data and concatenated objects.Unexpected UTF-8 BOM (decode using utf-8-sig): a byte order mark, covered in why JSON parse fails on a byte order mark.Expecting property name enclosed in double quotes: single quotes or a trailing comma.
The same condition in other languages reads differently: Node says Unexpected token '<', "<html>" is not valid JSON for the HTML case and Unexpected end of JSON input for an empty string, which is described in unexpected token in JSON at position 0 and unexpected end of JSON input.
Three habits that make the error short
Log the evidence at the boundary. The code that receives external text should log status, content type and the first 200 characters when parsing fails. The traceback alone cannot tell you any of that.
Parse once, from a variable. Reading a response or a file into a variable, then parsing the variable, avoids the consumed-handle bug and gives you something to print.
Fail with context.
raise ValueError(f"bad JSON from {source}: {text[:100]!r}") from ereplaces a library message with one that tells the on-call engineer what to look at.
When the JSON is fine and the call is wrong
Sometimes the parser is right and the real defect is upstream, and no change to the JSON handling helps.
The service is returning errors. A wrapper that retries on JSONDecodeError treats an outage as bad data. Check the status first and retry on the status, not on the parse failure.
The request is for the wrong thing. A URL that points at a documentation page returns HTML with a 200. If the status is fine and the content is a page, the URL or the Accept header is wrong. Many APIs choose a representation from Accept: application/json, and the default of requests does not set it.
The data is not JSON on purpose. A CSV endpoint, a stream of JSON Lines or an XML feed is not parseable by json.loads, and a .json name does not change that. A file that contains one object per line fails with Extra data on line 2; use a line-by-line reader, as in reading and writing JSON files in Python.
The text is truncated. If the response was cut off, the failure is not at char 0, but a proxy that returns an empty 200 on timeout produces Expecting value at char 0 from a call that "worked". Check Content-Length against len(r.content) when the empty responses are intermittent.
For the exception class and its attributes, see the Python json module reference, and for what a media type of application/json promises, see RFC 8259.


