Quick answer
Python opened a JSON string at a double quote but hit the end of the input before finding the closing quote. The position points to where the string started. The usual causes are a missing closing quote, a backslash that escapes the closing quote, or truncated data. Build JSON with json.dumps() rather than by hand.
" but never closes before the input ends.The exact error string
The full traceback ends with a line like this:
Traceback (most recent call last):
File "app.py", line 4, in <module>
data = json.loads(text)
json.decoder.JSONDecodeError: Unterminated string starting at: line 1 column 10 (char 9)
You may also see it raised through the requests library as requests.exceptions.JSONDecodeError when response.json() parses a body that was cut off mid-string. Both wrap the same json.decoder.JSONDecodeError.
What “starting at: line 1 column 10 (char 9)” means
Unlike Expecting value at char 0, this error means parsing succeeded for a while. The number is the index where the offending string opened — the last " the parser saw before it ran out of characters. To find the problem, slice the input from that index:
text = '{"name": "Alice}'
print(repr(text[9:])) # '"Alice}' — the value string that was never closed
Cause 1: A missing closing quote
The simplest case — a quote was dropped while hand-editing or hand-building JSON:
import json
text = '{"name": "Alice}' # closing quote after Alice is missing
data = json.loads(text)
# json.decoder.JSONDecodeError: Unterminated string starting at: line 1 column 10 (char 9)
Cause 2: A backslash escapes the closing quote
A backslash starts an escape sequence in JSON. If a value ends with a backslash, that backslash escapes the closing quote — the parser reads \" as a literal quote character and keeps going, looking for a closing quote that never comes:
import json
# The Python literal '...\\"...' is the JSON text: {"end": "value\"}
text = '{"end": "value\\"}'
data = json.loads(text)
# json.decoder.JSONDecodeError: Unterminated string starting at: line 1 column 9 (char 8)
This usually comes from Windows paths or other values that end in a backslash. The fix is to make sure every backslash in the data is itself escaped — which is exactly what json.dumps() does for you:
import json
# Python value is C:\Users\ (one trailing backslash)
path = "C:\\Users\\"
text = json.dumps({"path": path}) # dumps escapes it to {"path": "C:\\Users\\"}
data = json.loads(text) # round-trips cleanly
Cause 3: Truncated or partial data
If a file was written incompletely, a stream was cut off, or a response was only partially read, the closing quote and braces never arrive. The parser reads to the end and reports the last open string:
import json
# Only the first 20 bytes were read / saved
text = '{"name": "Alexand'
data = json.loads(text)
# json.decoder.JSONDecodeError: Unterminated string starting at: line 1 column 10 (char 9)
print(len(text), repr(text[-30:])) # confirm the data ends mid-string
Related: a literal newline raises a different error
It is tempting to blame a multiline value, but a raw newline or tab inside a string does not give “Unterminated string.” JSON forbids raw control characters, so the parser stops at the control character itself with a different message:
import json
# '\n' here is a real newline character, not the two characters backslash-n
text = '{"note": "line one\nline two"}'
data = json.loads(text)
# json.decoder.JSONDecodeError: Invalid control character at: line 1 column 19 (char 18)
The two characters backslash-n (written \\n in a Python string, or coming verbatim from a file) are valid JSON and parse fine — it is the real line break that breaks. Escape newlines as \n, or let json.dumps() do it. See Bad control character in string literal in JSON for that error in detail.
How to debug it
Print the length and the tail of the input, then slice from the reported index to isolate the open string:
import json
try:
data = json.loads(text)
except json.JSONDecodeError as e:
print(f"{e.msg} at char {e.pos}") # the message and the start index
print("length:", len(text))
print("from error:", repr(text[e.pos:e.pos + 40]))
print("tail:", repr(text[-40:]))
The fix: let json.dumps() build the JSON
Almost every version of this error comes from JSON that was typed or concatenated by hand. Serialize Python objects instead — json.dumps() quotes and escapes every string, including newlines, tabs, and backslashes:
import json
record = {
"name": "Alice",
"note": "line one\nline two", # newline is fine — dumps() escapes it
"path": "C:\\Users\\alice",
}
text = json.dumps(record) # guaranteed valid JSON
data = json.loads(text) # round-trips without error
Find the unterminated string instantly
Paste the JSON into the validator — it points to the exact line and column where the string is left open. Everything runs in your browser; nothing is uploaded.
Frequently Asked Questions
What does JSONDecodeError: Unterminated string starting at mean?
It means Python's json module found a string that opened with a double quote but reached the end of the input before finding the closing quote. The position in the message points to where the string started, not where it ended. The usual causes are a missing closing quote, an unescaped newline inside the string, or data that was cut off before it finished.
Does an unescaped newline cause Unterminated string starting at?
No. A literal newline or tab inside a JSON string raises a different error — Invalid control character at — because JSON forbids raw control characters in strings. Unterminated string starting at specifically means the closing quote is missing or was escaped by a trailing backslash. If you have multiline text, escape newlines as \n or build the JSON with json.dumps().
How do I fix Unterminated string starting at in Python?
Look at the character index in the message and inspect the string that starts there. Confirm it has a matching closing quote and that no trailing backslash is escaping that quote, and check that the data was not truncated. The safest fix is to never hand-build JSON: serialize Python objects with json.dumps(), which quotes and escapes every string correctly.
Can truncated or partial data cause this error?
Yes. If a file was written partially, a network response was cut off, or you sliced a JSON string in code, the closing quote and braces may be missing. The parser reads up to the end of what it was given and reports the last open string as unterminated. Print the length and the last 50 characters of the data to check whether it ends where you expect.
What is the difference between Unterminated string and Expecting value?
Expecting value: line 1 column 1 (char 0) means nothing valid was found at the very start — the input was empty or not JSON at all. Unterminated string starting at means parsing began successfully and got partway through a valid string before the input ended without closing it. The first is “not JSON”; the second is “JSON that stops mid-string.”