How to Return 201 Created From a POST Endpoint (REST Status Codes)
A Flask create endpoint returns a bare 200 OK by default, so a React UI that only treats res.status === 201 as success never shows its confirmation - the todo saves but the "Added!" message never fires. The fix is one line: return a (body, status) tuple so a successful create sends 201 Created. Status codes are an API contract - 201 for create, 200 for read, and a 400 bad request in a REST API for bad input.
Why a create should return 201, not 200
HTTP status codes are the contract between an API and everything that consumes it - browsers, caches, tests, and other services all branch on the number, not on the response body. REST assigns a specific code to each outcome:
200 OK- a generic success, used for reads and updates201 Created- a new resource was created (often with aLocationheader)204 No Content- success with an empty body, typical for a delete400 Bad Request- the classic 4xx REST API error for invalid client input404 Not Found- the resource does not exist409 Conflict- the request conflicts with current state
Flask returns 200 by default. So a create endpoint that just returns a
body silently sends the wrong code - the body looks right, but the
contract is broken.
The bug: a bare return sends 200
The create handler builds the todo correctly but returns only the body,
so Flask defaults to 200 OK:
@app.post("/api/todos")
def create_todo():
title = (request.get_json(silent=True) or {}).get("title", "")
todo_id = next(_next_id)
TODOS[todo_id] = {"id": todo_id, "title": title}
# BUG: returns a default 200 OK. A create should be 201 Created.
return jsonify(TODOS[todo_id])
The React frontend only accepts 201 as success:
const res = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'buy milk' }),
});
// Only treat 201 Created as success.
setStatus(res.status === 201 ? 'Added!' : `Unexpected status ${res.status}`);
The server saves the todo, the network call succeeds, but because the
status is 200 and not 201, the check fails and the user sees
"Unexpected status 200" instead of "Added!". The client is right to be
strict - the API is the side that broke the convention.
The fix: return a (body, status) tuple
In Flask you set the status explicitly by returning a tuple whose second element is the code:
@app.post("/api/todos")
def create_todo():
title = (request.get_json(silent=True) or {}).get("title", "")
todo_id = next(_next_id)
TODOS[todo_id] = {"id": todo_id, "title": title}
return jsonify(TODOS[todo_id]), 201
That single , 201 is the whole fix. The body is unchanged - only the
status code moves from 200 to 201, which is exactly what the client
checks.
Verify with curl
Print just the status code so you can confirm the contract without reading the body:
pkill -f "python3 app.py"; python3 app.py > /tmp/flask.log 2>&1 &
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8000/api/todos \
-H 'Content-Type: application/json' -d '{"title":"buy milk"}'
This should print 201. Reload the React page, click "Add todo", and the
status line now reads "Added!". The lesson generalizes: pick the code
that matches the outcome - 201 for a create, 200 for a read, 400
for a bad request - because clients depend on it even when the body is
fine.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Returning 201 Created from a create endpoint with a Flask (body, status) tuple
- Mapping REST outcomes to the right status code (201 create, 200 read, 400 bad request)
- Verifying a status code with curl -w '%{http_code}' before trusting the body
FAQ
What status code should a POST endpoint return when it creates a resource?
201 Created is the correct code for a successful create. It signals that a new resource now exists, unlike a generic 200 OK which is meant for reads and updates. A create response often also carries a Location header pointing at the new resource.
How do I return a 201 status code in Flask?
Return a tuple where the second element is the code: return jsonify(todo), 201. Without that second value Flask defaults to 200, so the status must be set explicitly on every create branch.
What is the difference between 200 and 201?
200 OK is a generic success used for reads and updates. 201 Created specifically means a new resource was created by the request, typically from a POST. Clients and tests branch on the difference, so a create should return 201 even though the body may look identical to a 200.
When should a REST API return a 400 bad request instead of 201?
A REST API returns 400 Bad Request when the client sends invalid input - a missing required field, malformed JSON, or a bad value - because that is a 4xx client error. It returns 201 Created only when the request was valid and a new resource was actually created. Validate the input first, then create and return 201.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →