HTTP status codes — the ones that matter in API design
Beginner5 min read·lv-10-002
interview
Concept
HTTP status codes communicate the result of a request to the client. Using the right status code is essential for API design — clients (browsers, mobile apps, API consumers) use status codes to decide how to handle responses.
Success (2xx):
200 OK: Standard success. GET responses, PUT/PATCH success.201 Created: Resource successfully created (POST). IncludeLocationheader with the new resource URL.202 Accepted: Request received, but processing is asynchronous (job dispatched).204 No Content: Success but no response body. DELETE operations, PATCH that don't return the updated resource.
Client errors (4xx):
400 Bad Request: Malformed request syntax, missing required fields. Different from 422.401 Unauthorized: Authentication required (not authenticated). Despite the name, it's about authentication, not authorization.403 Forbidden: Authenticated but not authorized. Permission denied.404 Not Found: Resource doesn't exist.405 Method Not Allowed: HTTP method not allowed for this endpoint.409 Conflict: Conflict with current state (duplicate entry, version mismatch).422 Unprocessable Entity: Validation failed — request was well-formed but semantically invalid.429 Too Many Requests: Rate limit exceeded. IncludeRetry-Afterheader.
Server errors (5xx):
500 Internal Server Error: Unhandled exception.502 Bad Gateway: Upstream service failed.503 Service Unavailable: Maintenance mode or overload. IncludeRetry-After.
Code Example
php
<?php
// Status code constants (from Symfony HttpFoundation)
use Symfony\Component\HttpFoundation\Response;
Response::HTTP_OK; // 200
Response::HTTP_CREATED; // 201
Response::HTTP_NO_CONTENT; // 204
Response::HTTP_BAD_REQUEST; // 400
Response::HTTP_UNAUTHORIZED; // 401
Response::HTTP_FORBIDDEN; // 403
Response::HTTP_NOT_FOUND; // 404
Response::HTTP_UNPROCESSABLE_ENTITY; // 422
Response::HTTP_TOO_MANY_REQUESTS; // 429
// Practical usage in controllers
class OrderController extends Controller
{
// 201 Created — resource created
public function store(Request $request): JsonResponse
{
$order = Order::create($request->validated());
return response()->json(
new OrderResource($order),
201,
['Location' => route('orders.show', $order)]
);
}
// 204 No Content — deleted successfully
public function destroy(Order $order): \Illuminate\Http\Response
{
$order->delete();
return response()->noContent(); // 204
}
// 202 Accepted — async processing
public function exportRequest(Request $request): JsonResponse
{
ExportOrdersJob::dispatch(auth()->id());
return response()->json(['message' => 'Export queued'], 202);
}
// 409 Conflict — duplicate entry
public function store(Request $request): JsonResponse
{
if (Order::where('idempotency_key', $request->idempotency_key)->exists()) {
return response()->json(['message' => 'Duplicate request'], 409);
}
// ...
}
}
// abort() helper — throws HttpException → exception handler renders it
abort(404); // 404
abort(403, 'You cannot access this resource'); // 403
abort_if(!$user->isAdmin(), 403); // conditional abort
abort_unless($user->owns($order), 403); // inverse condition
// HTTP exceptions in exception handler
// ValidationException → 422 (automatic)
// ModelNotFoundException → 404 (via renderable in Handler)
// AuthenticationException → 401 (via unauthenticated() in Handler)
// AuthorizationException → 403 (via Handler)