HTTP 200 OK

HTTP 200 OK is the standard success response indicating that the request has been fulfilled. The meaning varies by HTTP method: for GET, the resource is returned in the body; for POST, the result of the action is described; for HEAD, only headers are sent without a body. While seemingly simple, proper use of 200 OK — versus more specific success codes like 201 Created or 204 No Content — significantly impacts API design quality, caching behavior, and client-side logic.

Debug HTTP 200 live
Analyze real 200 behavior — headers, caching, CORS, redirects
Open Inspector →

Try it (live endpoint)

Response includes the status code, standard headers (including Content-Type), and a small diagnostic JSON body describing the request and returned status.

Simulator URL (copy in the app after load — not a normal link):

https://httpstatus.com/api/status/200

Example request:

curl -i "https://httpstatus.com/api/status/200"
Try in playground

Meaning

The request has succeeded.

What it guarantees
  • The server accepted the request and produced a final response.
What it does NOT guarantee
  • The underlying business operation is correct across all downstream systems.
  • The response is cacheable unless headers explicitly allow it.

When to use this status

  • GET succeeds and returns a representation of the resource.
  • PUT/PATCH succeeds and returns an updated representation.
  • POST succeeds and returns an immediate result.

When NOT to use this status (common misuses)

Returning 200 for partial failures or errors embedded only in the body.
Clients and monitors treat it as success; failures become silent and harder to alert on.
Returning 200 for creation instead of 201 with Location.
Clients lose a reliable created-resource identifier; SDK behavior becomes inconsistent.
Returning 200 for async acceptance instead of 202.
Clients assume the work is complete and proceed incorrectly.

Critical headers that matter

Content-Type
Defines how clients parse the body.
Clients mis-parse payloads; SDKs and browsers apply wrong decoding.
Cache-Control
Controls cacheability and revalidation.
CDNs/browsers cache dynamic data or fail to cache static content.
ETag / Last-Modified
Enables conditional requests and revalidation.
Unnecessary bandwidth; poor cache consistency.

Tool interpretation

Browsers
Treats as success; caches/revalidates based on headers and validators.
API clients
Deserializes per Content-Type; conditional requests use validators when implemented.
Crawlers / SEO tools
Indexes depending on headers and canonical stability; caches behavior via validators and cache directives.
Uptime monitors
Typically marks success; advanced checks may flag header anomalies or latency.
CDNs / reverse proxies
Caches/revalidates based on Cache-Control, ETag, and Vary; compression and content-type affect behavior.

Inspector preview (read-only)

On this code, Inspector focuses on semantics, headers, and correctness warnings that commonly affect clients and caches.

Signals it will highlight
  • Status semantics vs method and body expectations
  • Header sanity (Content-Type, Cache-Control, Vary) and evidence completeness
Correctness warnings
No common correctness warnings are specific to this code.

Guided Lab outcome

  • Reproduce HTTP 200 OK using a controlled endpoint and capture the full exchange.
  • Practice distinguishing status semantics from transport issues (redirects, caching, proxies).

Technical deep dive

200 OK is defined in RFC 7231 Section 6.3.1 as the most general successful response. The response payload depends on the method: GET returns the target resource representation, HEAD returns the same headers without a body, POST returns the processing result, and TRACE returns the received request message. Caching: 200 responses to GET are cacheable by default (RFC 7234). The response may include Cache-Control, ETag, Last-Modified, and Vary headers to control caching granularity. A critical design consideration: returning 200 for error conditions (e.g., { "success": false, "error": "not found" }) is an anti-pattern that breaks HTTP semantics, caching, and monitoring tools. CDNs cache 200 responses aggressively — returning errors as 200 means your error pages get cached.

Real-world examples

REST API resource retrieval
GET /api/users/42 returns 200 with the user's JSON representation. The response includes ETag: "v3" and Cache-Control: max-age=60, allowing CDNs and browsers to cache the response for 60 seconds and use conditional requests with If-None-Match thereafter.
Search endpoint with no results
GET /api/search?q=xyz returns 200 with an empty results array { "results": [], "total": 0 }. This is correct because the search itself succeeded — the absence of results is not an error. Returning 404 for an empty search would be semantically wrong.
GraphQL API
POST /graphql always returns 200 OK, even for partial errors. GraphQL's error handling is in-band: the response body contains both 'data' (successful fields) and 'errors' (failed fields). The HTTP 200 means the GraphQL engine processed the query; field-level errors are domain concerns.

Framework behavior

Express.js (Node)
Express returns 200 by default: res.json({ data }) automatically sets status 200 and Content-Type: application/json. Override with res.status(201).json({ data }) for creation. Common mistake: using res.json() for errors instead of res.status(4xx).json({ error }).
Django / DRF (Python)
Django returns 200 via HttpResponse or JsonResponse by default: return JsonResponse({'data': result}). For REST, DRF's Response class with status=status.HTTP_200_OK. Django's template views return 200 unless the template raises an exception.
Spring Boot (Java)
Spring MVC returns 200 by default for @ResponseBody/@RestController methods. Override with @ResponseStatus(HttpStatus.CREATED) or ResponseEntity.status(201).body(result). Common pitfall: controller methods that throw exceptions may return 200 if caught by a global @ExceptionHandler that doesn't set the status.
FastAPI (Python)
FastAPI returns 200 by default for all route handlers. Override with response parameter: @app.post('/items', status_code=201). FastAPI auto-validates response models and returns 422 if the response doesn't match the declared response_model.

Debugging guide

  1. If you get 200 but unexpected data, check the Vary header — you might be seeing a cached response for a different Accept/Authorization combination
  2. Monitor for '200 OK' responses with error-like bodies — use structured logging to flag { success: false } patterns in 200 responses
  3. Check Content-Type matches expectations — 200 with text/html when you expected application/json usually means you're hitting a proxy or login page
  4. For APIs returning 200 for everything, inspect the response body's status field — some APIs use HTTP 200 with in-band error codes
  5. Use DevTools Network tab → sort by Status → look for 200s with unexpectedly large or small response sizes as indicators of wrong responses

Code snippets

Node.js
const express = require('express');
const app = express();

// GET - 200 with caching headers
app.get('/api/users/:id', async (req, res) => {
  const user = await db.findUser(req.params.id);
  if (!user) return res.status(404).json({ error: 'User not found' });

  res.set('Cache-Control', 'private, max-age=60');
  res.set('ETag', `"${user.version}"`);
  res.json(user); // 200 is implicit
});

// Search - 200 even with empty results
app.get('/api/search', async (req, res) => {
  const results = await db.search(req.query.q);
  res.json({ results, total: results.length }); // 200 OK
});
Python
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

@app.get('/api/users/{user_id}')
async def get_user(user_id: int):
    user = await db.find_user(user_id)
    if not user:
        raise HTTPException(status_code=404, detail='User not found')
    # 200 is the default status code
    return {
        'id': user.id,
        'name': user.name,
        'email': user.email
    }

@app.get('/api/search')
async def search(q: str = ''):
    results = await db.search(q)
    return {'results': results, 'total': len(results)}  # 200 OK
Java (Spring)
@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        return userRepo.findById(id)
            .map(user -> ResponseEntity.ok()  // 200
                .cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
                .eTag(String.valueOf(user.getVersion()))
                .body(user))
            .orElseThrow(() -> new ResponseStatusException(
                HttpStatus.NOT_FOUND, "User not found"));
    }
}
Go
func getUserHandler(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	user, err := db.FindUser(id)
	if err != nil {
		http.Error(w, `{"error":"User not found"}`,
			http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Header().Set("Cache-Control", "private, max-age=60")
	w.Header().Set("ETag", fmt.Sprintf(`"%d"`, user.Version))
	// 200 is the default WriteHeader
	json.NewEncoder(w).Encode(user)
}

FAQ

Should a POST request return 200 or 201?
Return 201 Created when the POST creates a new resource (with a Location header pointing to it). Return 200 OK when the POST performs an action that doesn't create a resource — like a search, a login, or a computation. The key distinction is whether something new was created and can be accessed at a URL.
Is it correct to return 200 with an empty body?
Technically yes, but 204 No Content is more semantically correct for successful requests with no response body. 200 with an empty body can confuse clients that expect content. Use 204 for DELETE operations, PUT updates that don't return the resource, and actions that succeed without data to return.
Why do some APIs return 200 for error conditions?
This is generally considered an anti-pattern, but some APIs (notably GraphQL) intentionally return 200 for all responses because they handle errors at the application protocol layer rather than the transport layer. For REST APIs, using proper HTTP status codes (4xx/5xx) is strongly recommended because it enables standard error handling, monitoring, caching, and retry logic.
How does 200 OK interact with browser caching?
200 responses to GET requests are cacheable by default per HTTP/1.1. The browser respects Cache-Control, Expires, ETag, and Last-Modified headers. Without these headers, browsers apply heuristic caching (typically 10% of the age since Last-Modified). To prevent caching, explicitly set 'Cache-Control: no-store'. To enable conditional revalidation, include ETag or Last-Modified.

Client expectation contract

Client can assume
  • A final HTTP response was produced and processed by the server.
Client must NOT assume
  • The change is durable across all downstream systems.
Retry behavior
Retries are generally unnecessary; treat as final unless domain rules require revalidation.
Monitoring classification
Success
Use payload and header checks to avoid false positives; cacheability depends on Cache-Control/ETag/Vary.

Related status codes

201 Created
The request has been fulfilled and resulted in a new resource being created.
103 Early Hints
Used to return preliminary response headers (typically Link headers for preloading resources) before the final HTTP response.
204 No Content
The server successfully processed the request and is not returning any content.
304 Not Modified
Indicates that the resource has not been modified since the version specified by the request headers (If-None-Match or If-Modified-Since). The client should use its cached copy.

Explore more

Related guides
Related tools
Related utilities