JSONDecodeError: Expecting value: line 1 column 1 (char 0) in Python (tested)

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) means the first character of the input is not JSON at all. We reproduced every common cause: almost always the input is empty or is an HTML/text page -- with requests, typically an error, login or proxy page returned with a 4xx/5xx status -- or a file object that was already read to the end.

1. What produces "Expecting value: line 1 column 1 (char 0)"

import io, json

cases = {
    "empty string": "",
    "whitespace / newline only": "\n",
    "HTML error page": "<!DOCTYPE html><html><body>502 Bad Gateway</body></html>",
    "plain-text error": "Internal Server Error",
    "Python dict repr (single quotes)": "{'a': 1}",
    "Python True/None": '{"ok": True, "v": None}',
    "JSONP wrapper": 'callback({"a": 1})',
    "UTF-8 BOM (decoded as utf-8)": "{\"a\": 1}",
    "two JSON objects (JSON Lines)": '{"a": 1}\n{"a": 2}',
}
for label, text in cases.items():
    try:
        json.loads(text)
        print(f"{label:34} OK")
    except json.JSONDecodeError as e:
        print(f"{label:34} {e}")

f = io.StringIO('{"a": 1}')
f.read()                                   # something already consumed the stream
try:
    json.load(f)
except json.JSONDecodeError as e:
    print(f"{'json.load(file already read)':34} {e}")
f.seek(0)
print(f"{'json.load(after seek(0))':34}", json.load(f))
empty string                       Expecting value: line 1 column 1 (char 0)
whitespace / newline only          Expecting value: line 2 column 1 (char 1)
HTML error page                    Expecting value: line 1 column 1 (char 0)
plain-text error                   Expecting value: line 1 column 1 (char 0)
Python dict repr (single quotes)   Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
Python True/None                   Expecting value: line 1 column 8 (char 7)
JSONP wrapper                      Expecting value: line 1 column 1 (char 0)
UTF-8 BOM (decoded as utf-8)       Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)
two JSON objects (JSON Lines)      Extra data: line 2 column 1 (char 9)
json.load(file already read)       Expecting value: line 1 column 1 (char 0)
json.load(after seek(0))           {'a': 1}

Measured: char 0 means the very first character is not the start of a JSON value. In practice that is one of: an empty string, an HTML or plain-text page (an error page, a login page, a proxy's 502), a JSONP wrapper, or a file object that was already read -- json.load() then sees an empty remainder. A later position points inside the document: True/None instead of true/null fail at their own column, single-quoted keys give "Expecting property name enclosed in double quotes", a BOM has its own message, and JSON Lines gives "Extra data".

2. With requests: r.json() on an error page

import json, socket, threading, time, requests

PORT = 8963
def serve():
    srv = socket.socket()
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind(("127.0.0.1", PORT))
    srv.listen()
    while True:
        conn, _ = srv.accept()
        conn.recv(65536)
        page = b"<html><body>502 Bad Gateway</body></html>"
        conn.sendall(b"HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/html\r\n"
                     b"Content-Length: %d\r\n\r\n%s" % (len(page), page))
        conn.close()
threading.Thread(target=serve, daemon=True).start()
time.sleep(0.2)

r = requests.get(f"http://127.0.0.1:{PORT}/api")
try:
    r.json()
except requests.exceptions.JSONDecodeError as e:
    print("r.json() ->", type(e).__name__ + ":", e)
    print("also a json.JSONDecodeError?", isinstance(e, json.JSONDecodeError))
    print("status:", r.status_code, "| Content-Type:", r.headers.get("Content-Type"))
    print("body starts:", r.text[:60])

# Safer pattern: check before decoding
def get_json(url, **kw):
    r = requests.get(url, **kw)
    r.raise_for_status()                               # 4xx/5xx -> HTTPError, not JSONDecodeError
    if "json" not in r.headers.get("Content-Type", ""):
        raise ValueError(f"expected JSON, got {r.headers.get('Content-Type')!r}: {r.text[:200]!r}")
    return r.json()
try:
    get_json(f"http://127.0.0.1:{PORT}/api")
except requests.HTTPError as e:
    print("get_json ->", type(e).__name__ + ":", e)
r.json() -> JSONDecodeError: Expecting value: line 1 column 1 (char 0)
also a json.JSONDecodeError? True
status: 502 | Content-Type: text/html
body starts: <html><body>502 Bad Gateway</body></html>
get_json -> HTTPError: 502 Server Error: Bad Gateway for url: http://127.0.0.1:8963/api

Measured: the server returned a 502 HTML page and r.json() reported it as a JSON problem. requests.exceptions.JSONDecodeError is also a json.JSONDecodeError (and a ValueError), so either except catches it. Checking raise_for_status() and the Content-Type before decoding turns the misleading JSON error into the real one: 502 Server Error: Bad Gateway.

3. Fixes, by cause

Related: JSONDecodeError: Extra data, Invalid control character and Unexpected end of JSON input (JavaScript).

Frequently asked questions

What does 'Expecting value: line 1 column 1 (char 0)' mean?

The decoder found no JSON value at the very first character. We measured it for '', an HTML page, plain text like 'Internal Server Error', a JSONP callback(...) wrapper, and json.load() on a file that was already read.

Why does r.json() fail when the API works in my browser?

Your script's request gets a different response: often a 401/403 login page, a rate-limit page, or a proxy error in HTML. Print r.status_code, r.headers.get('Content-Type') and r.text[:200] to see it.

How do I catch it with requests?

except requests.exceptions.JSONDecodeError -- we measured it is also a json.JSONDecodeError and a ValueError. Better: call r.raise_for_status() first so HTTP errors surface as HTTPError instead.

Why does json.load(f) fail on a valid file?

If anything read the file object first, its position is at the end and json.load() sees an empty string. We measured it failing at char 0 and succeeding after f.seek(0).

What about 'Expecting property name enclosed in double quotes'?

That one means the input started like JSON but used single quotes, as Python's str(dict) produces. JSON needs double quotes; write it with json.dumps().

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5's built-in json module and requests 2.32.3, decoding empty, HTML, plain-text, JSONP, Python-literal, BOM-prefixed and JSON Lines input, plus a local server returning a 502 HTML page. PlainHeaders is our extension; Python and requests are free.