~/TechPurAI
~/tutorials/django-rest-framework/status-codes
beginner·part 6 of 22·2 min read

Status codes done properly with rest_framework.status

Updated Aug 16, 2026Python · Django

Every response so far has used a status code without much explanation of why that specific one. This part is a deliberate pause on that — the codes that matter for a real API, what each one actually tells a client, and where getting it wrong causes real problems downstream.

The success codes: 200, 201, 204

python
from rest_framework import status

Response(serializer.data)                                    # 200 by default
Response(serializer.data, status=status.HTTP_201_CREATED)     # a POST that created something
Response(status=status.HTTP_204_NO_CONTENT)                   # a DELETE that succeeded

200 OK is Response's default and fits a GET or a successful update. 201 Created specifically means "a new resource now exists" — the correct code for a successful POST to a list endpoint, distinct from 200 so a client can tell "I fetched something" apart from "I made something new" without inspecting the body. 204 No Content means success with deliberately no body — the standard response for a DELETE, since there's nothing left to describe once the object is gone.

The client-error codes: 400, 401, 403, 404

python
Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)   # invalid input
Response(status=status.HTTP_401_UNAUTHORIZED)                      # not authenticated at all
Response(status=status.HTTP_403_FORBIDDEN)                         # authenticated, not allowed
Response(status=status.HTTP_404_NOT_FOUND)                         # doesn't exist (or you can't see it)

400 and 404 get confused constantly, and the distinction matters: 400 means the request itself was malformed — a missing required field, invalid data — the same shape every time regardless of what exists in the database. 404 means the request was fine, but the specific resource asked for isn't there. 401 versus 403 is a similarly common mix-up: 401 Unauthorized really means "unauthenticated" — no valid credentials at all — while 403 Forbidden means the credentials are valid but don't grant permission for this specific action. Part 15 and 16 (authentication, permissions) return exactly these two codes for exactly these two situations.

Why this actually matters to a client

A frontend or another service consuming this API makes real decisions based on the status code, not just the body: a 401 might trigger a redirect to a login screen, while a 403 might show "you don't have access" without prompting a re-login — sending 403 for a plain unauthenticated request means a client never knows to prompt for login at all. Getting the code wrong doesn't just look sloppy in a spec; it breaks the client-side logic built to react to it correctly.

Common mistake

Returning 200 OK for everything, including errors, with the actual problem only described in the response body. Every well-behaved API client — and every piece of HTTP tooling, from browser devtools to monitoring dashboards — relies on the status code to know whether a request succeeded at all, before it ever looks at the body.

Next: APIView — a class-based alternative to @api_view for when a resource's logic is complex enough to want more structure.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous5. Request and Response: DRF's upgrades over Django's own objectsnext →7. Class-based views: APIView