Quick answer: HTTP status code 200 means "OK": the server received your request, processed it successfully, and the response body contains the result. It is the standard success code for the web. There is no such thing as "error code 200" in the HTTP spec; when people say that, they almost always mean a response that returned 200 but contains an error message in the body, which is a real and common trap worth understanding.
Let's cover what 200 actually guarantees, then spend most of our time on the interesting part: the ways a 200 can lie to you.
What 200 Actually Guarantees
HTTP status codes come in five families. The 2xx family means success, and 200 is its workhorse:
| Code | Meaning | Typical use |
|---|---|---|
| 200 | OK, result in the body | GET and most API responses |
| 201 | Created | Successful POST that made a resource |
| 204 | Success, no content | Deletes, updates with nothing to return |
| 206 | Partial content | Range requests, resumed downloads |
A 200 tells you the HTTP transaction worked: the server understood the request and produced a response it considers successful. That's all. It says nothing about whether the content of that response is what you wanted.
"Error Code 200": The Soft-Error Trap
Plenty of real systems return 200 with a failure inside. You'll see responses like:
HTTP/1.1 200 OK
Content-Type: application/json
{"status": "error", "message": "invalid API key"}
This happens for a few reasons: legacy API design, gateways that swallow upstream status codes, GraphQL (which returns 200 for almost everything and puts errors in an errors array by design), and frontend frameworks that render an error page with a 200.
The practical consequence: checking response.status == 200 is necessary but not sufficient. Robust clients validate the body too:
resp = requests.get(url, timeout=10)
resp.raise_for_status() # catches real 4xx/5xx
data = resp.json()
if data.get("status") == "error": # catches soft errors hiding behind 200
raise RuntimeError(data.get("message"))
If you've ever debugged a pipeline that "worked" for weeks while writing garbage, a soft error behind a 200 is a likely culprit.
The Scraping Version: 200 but Blocked
Web scrapers meet a special flavor of lying 200. Anti-bot systems frequently return a perfectly valid 200 whose body is a challenge page, a CAPTCHA, or an empty application shell. Your HTTP client reports success; your dataset fills with "Please verify you are human."
Two habits catch this early:
- Validate content, not status. Check that the response contains something you expect (a price element, a title pattern, a minimum length). Treat "200 with 2 KB of HTML from a page that's usually 200 KB" as a failure.
- Log body samples on anomaly. When extraction returns empty, save the HTML. The block page in your logs tells you in seconds what a status-code dashboard never will.
Search engines deal with the same phenomenon under the name soft 404: a page that says "not found" but returns 200. Google explicitly detects and demotes these, which is worth knowing if you run a website; your error pages should return real 404s, not styled 200s.
200 vs. Its Neighbors, Quickly
People often ask how 200 relates to the codes around it. The short map: 3xx means "look elsewhere" (redirects), 4xx means "your request is the problem" (404 not found, 429 too many requests), and 5xx means "the server is the problem" (500 crash, 502 bad gateway). A healthy request flow often traverses a 301 redirect before landing on the final 200.
One more that surprises people: a page can load in your browser with content while the status is 404 or 500. Browsers render whatever body arrives regardless of code. Status codes are for machines; bodies are for humans.
The Bottom Line
200 means the HTTP layer succeeded, nothing more. Trust it for what it is, then verify the body, because the errors that cost real debugging time are the ones wearing a success code. If you're fetching pages programmatically and want the "is this actually the real content" problem handled for you, that's part of what a managed fetch layer like link.sc does: it detects challenge pages and empty shells instead of handing you a hollow 200.
Want fetches that succeed in substance, not just status code? Try link.sc free: 500 requests a month, clean Markdown out.