TL;DR
REST APIs should use HTTP status codes correctly: 200 for successful reads, 201 for successful resource creation, 204 for successful operations without a response body, 400 for client errors, 401 for authentication failures, 404 for missing resources, and 500 for unexpected server errors. Modern PetstoreAPI implements standard HTTP status codes with appropriate semantics and RFC 9457 error responses.
Introduction
An API that returns 200 OK for every request forces clients to inspect response bodies to determine whether an operation succeeded:
- Successful request:
200 OK - Validation error:
200 OKwith an error message - Missing resource:
200 OKwith{"error": "not found"} - Authentication failure:
200 OK
This breaks HTTP semantics. Caches, proxies, monitoring tools, and client retry logic rely on status codes to understand what happened. Returning 200 for errors makes those systems behave incorrectly.
The old Swagger Petstore made several status code mistakes, including returning 200 for resource creation and deletion and omitting important error codes. Modern PetstoreAPI addresses these issues by using appropriate HTTP semantics across its endpoints.
If you’re building or testing REST APIs, Apidog helps you validate status code usage, test error scenarios, and verify that your API follows HTTP standards. You can define expected status codes, run automated tests, and catch incorrect responses before deployment.
In this guide, you’ll learn which HTTP status codes matter for REST APIs, when to use each one, and how Modern PetstoreAPI implements them.
The Status Code Problem
Many APIs treat status codes as an afterthought. The result is broken HTTP semantics and confused clients.
The “200 OK for Everything” Anti-Pattern
GET /users/123
200 OK
{
"id": 123,
"name": "John"
}
The same API might represent an error like this:
GET /users/999
200 OK
{
"error": "User not found"
}
Or return 200 OK for a validation failure:
POST /users
200 OK
{
"error": "Email is required"
}
This approach causes several problems:
- Clients cannot distinguish success from failure without parsing the body.
- HTTP caches may cache error responses.
- Monitoring tools report false positives.
- Retry logic cannot work reliably.
- Generic HTTP clients and middleware receive misleading information.
Why This Happens
Common reasons include:
- Developers are unfamiliar with less common status codes.
- They assume status codes are optional.
- They want to avoid changing existing clients.
- They are copying outdated or incorrect examples.
The solution is to define status code behavior as part of the API contract and test it for every endpoint.
Essential HTTP Status Codes for REST APIs
You do not need to use every HTTP status code. Start with the ones that describe the behavior of most REST endpoints.
Quick Reference
Success: 2xx
-
200 OK— SuccessfulGET,PUT, orPATCHthat returns data -
201 Created— SuccessfulPOSTthat creates a resource -
204 No Content— SuccessfulDELETE,PUT, orPATCHwith no response body
Client errors: 4xx
-
400 Bad Request— Invalid request format or validation error -
401 Unauthorized— Missing or invalid authentication -
403 Forbidden— Authenticated, but not authorized -
404 Not Found— Resource does not exist -
409 Conflict— Resource conflict, such as a duplicate or version mismatch -
422 Unprocessable Entity— Valid request format but invalid business semantics -
429 Too Many Requests— Rate limit exceeded
Server errors: 5xx
-
500 Internal Server Error— Unexpected server error -
502 Bad Gateway— Upstream service error -
503 Service Unavailable— Temporary service unavailability -
504 Gateway Timeout— Upstream service timeout
Success Codes: 2xx
Success codes indicate that the server successfully processed the request. Choose the specific code based on what happened.
200 OK
Use 200 OK for successful GET, PUT, and PATCH requests that return data.
GET /pets/123
200 OK
Content-Type: application/json
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"species": "CAT"
}
Do not use 200 when a POST creates a resource. Use 201 Created instead. For a successful DELETE without a response body, use 204 No Content.
201 Created
Use 201 Created when a POST request creates a new resource.
POST /pets
201 Created
Location: https://petstoreapi.com/pets/019b4132-70aa-764f-b315-e2803d882a24
Content-Type: application/json
{
"id": "019b4132-70aa-764f-b315-e2803d882a24",
"name": "Fluffy",
"species": "CAT"
}
When returning 201:
- Include a
Locationheader containing the new resource URL. - Return the created resource in the response body when appropriate.
- Make it clear to clients that a resource was created rather than updated.
Modern PetstoreAPI returns 201 for POST operations that create resources.
204 No Content
Use 204 No Content when an operation succeeds and there is no response body to return.
DELETE /pets/019b4132-70aa-764f-b315-e2803d882a24
204 No Content
A 204 response:
- Must not include a response body.
- Indicates that the operation succeeded.
- Avoids sending unnecessary response data.
- Is commonly used for
DELETEoperations.
Client Error Codes: 4xx
A 4xx status code indicates that the client needs to change the request before retrying it.
400 Bad Request
Use 400 Bad Request for malformed requests, invalid JSON, incorrect request formats, or missing required fields.
POST /pets
400 Bad Request
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "Request validation failed",
"invalid-params": [
{
"name": "name",
"reason": "Name is required"
}
]
}
Modern PetstoreAPI uses RFC 9457 problem details for error responses.
401 Unauthorized
Use 401 Unauthorized when authentication credentials are missing or invalid.
GET /pets
401 Unauthorized
WWW-Authenticate: Bearer realm="PetstoreAPI"
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/authentication-required",
"title": "Authentication Required",
"status": 401,
"detail": "Valid authentication credentials required"
}
When returning 401:
- Include a
WWW-Authenticateheader. - Allow the client to prompt for credentials or refresh its token.
- Do not use
401when the client is authenticated but lacks permission; use403instead.
403 Forbidden
Use 403 Forbidden when the client is authenticated but does not have permission to perform the operation.
DELETE /pets/019b4132-70aa-764f-b315-e2803d882a24
403 Forbidden
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/insufficient-permissions",
"title": "Insufficient Permissions",
"status": 403,
"detail": "You don't have permission to delete this pet"
}
A useful distinction is:
-
401: “Who are you?” Authentication is missing or invalid. -
403: “I know who you are, but you cannot do that.” Authorization failed.
404 Not Found
Use 404 Not Found when the requested resource does not exist.
GET /pets/nonexistent-id
404 Not Found
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/not-found",
"title": "Not Found",
"status": 404,
"detail": "Pet not found"
}
Do not use 404 for validation errors or ordinary authorization failures. Use 400 for malformed input and 403 for authenticated users without permission.
409 Conflict
Use 409 Conflict when the request conflicts with the current state of a resource.
Common examples include:
- Creating a duplicate resource
- Updating a resource with an outdated version
- Violating a uniqueness constraint
POST /pets
409 Conflict
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/duplicate-resource",
"title": "Duplicate Resource",
"status": 409,
"detail": "A pet with this microchip ID already exists"
}
422 Unprocessable Entity
Use 422 Unprocessable Entity when the request is syntactically valid but violates a business rule.
POST /pets
422 Unprocessable Entity
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/business-rule-violation",
"title": "Business Rule Violation",
"status": 422,
"detail": "Cannot adopt more than 5 pets per household"
}
The difference between 400 and 422 is:
-
400: The request is malformed, such as invalid JSON or incorrect types. -
422: The request is well-formed but violates a business rule.
429 Too Many Requests
Use 429 Too Many Requests when a client exceeds the configured rate limit.
GET /pets
429 Too Many Requests
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 1678886400
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "Rate limit of 100 requests per hour exceeded"
}
Modern PetstoreAPI uses IETF rate limit headers to communicate the client’s current limit and reset time.
Server Error Codes: 5xx
A 5xx status code indicates that the server failed to process an otherwise valid request. Depending on the error, clients may retry the request.
500 Internal Server Error
Use 500 Internal Server Error for unexpected server-side failures.
GET /pets
500 Internal Server Error
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/internal-error",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred"
}
Do not expose the following in production responses:
- Stack traces
- Internal implementation details
- Database errors
- Secrets or infrastructure information
Log those details on the server while returning a safe error response to the client.
503 Service Unavailable
Use 503 Service Unavailable when the service is temporarily unavailable because of maintenance, overload, or another temporary condition.
GET /pets
503 Service Unavailable
Retry-After: 3600
Content-Type: application/problem+json
{
"type": "https://petstoreapi.com/errors/service-unavailable",
"title": "Service Unavailable",
"status": 503,
"detail": "Service temporarily unavailable for maintenance"
}
Include Retry-After when you can tell clients when to retry.
Use related status codes when they describe the failure more accurately:
-
502for an upstream service error -
504for an upstream timeout
How Modern PetstoreAPI Uses Status Codes
Modern PetstoreAPI applies these semantics across its endpoints.
Pet Management Examples
# List pets
GET /pets
200 OK
# Create a pet
POST /pets
201 Created
Location: https://petstoreapi.com/pets/{id}
# Get a pet
GET /pets/{id}
200 OK # Resource found
404 Not Found # Resource does not exist
# Update a pet
PUT /pets/{id}
200 OK # Response includes the updated pet
204 No Content # No response body
# Delete a pet
DELETE /pets/{id}
204 No Content # Deletion succeeded
404 Not Found # Pet does not exist
Standardize Error Responses
All errors use RFC 9457 problem details:
{
"type": "https://petstoreapi.com/errors/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "Request validation failed",
"instance": "/pets",
"invalid-params": [
{
"name": "name",
"reason": "Name must be between 1 and 100 characters"
}
]
}
See the Modern PetstoreAPI error handling documentation for complete examples.
Testing Status Codes with Apidog
Status codes should be part of your API tests, not something you verify manually after deployment.
Define Expected Status Codes
For example, an OpenAPI definition can document the expected responses for POST /pets:
paths:
/pets:
post:
responses:
'201':
description: Pet created
'400':
description: Validation error
'401':
description: Authentication required
'429':
description: Rate limit exceeded
Document every expected response so client developers and test tools know which outcomes to handle.
Test All Scenarios
Create test cases for:
- Successful reads and updates:
200 - Successful resource creation:
201 - Successful deletion or empty updates:
204 - Validation errors:
400,422 - Authentication and authorization failures:
401,403 - Missing resources:
404 - Resource conflicts:
409 - Rate limiting:
429 - Unexpected and temporary server failures:
500,503
Add Automated Assertions
pm.test("Returns 201 for successful creation", () => {
pm.response.to.have.status(201);
pm.response.to.have.header("Location");
});
pm.test("Returns 400 for missing required fields", () => {
pm.response.to.have.status(400);
pm.expect(pm.response.json().type).to.include("validation-error");
});
Run these tests locally and in CI/CD so status code regressions are caught before deployment.
Common Mistakes to Avoid
Mistake 1: Using 200 for POST
# Wrong
POST /pets
200 OK
# Correct
POST /pets
201 Created
Location: https://petstoreapi.com/pets/{id}
Use 201 when the request creates a resource.
Mistake 2: Using 200 for DELETE
# Wrong
DELETE /pets/{id}
200 OK
{
"message": "Deleted successfully"
}
# Correct
DELETE /pets/{id}
204 No Content
Use 200 only when the response needs to return information. Otherwise, 204 clearly communicates successful deletion without a response body.
Mistake 3: Confusing 401 and 403
# Wrong: The user is authenticated but lacks permission
401 Unauthorized
# Correct
403 Forbidden
Use 401 for missing or invalid authentication and 403 for insufficient permissions.
Mistake 4: Using 500 for Client Errors
# Wrong: A validation error returns 500
POST /pets
500 Internal Server Error
# Correct
POST /pets
400 Bad Request
Use 5xx codes only when the server failed. Invalid client input belongs in the 4xx range.
Conclusion
HTTP status codes are part of the HTTP specification and an essential part of your REST API contract.
Use the status code that describes the result:
-
200for successful reads and updates that return data -
201for successful resource creation -
204for successful operations without a response body -
400for malformed requests and validation errors -
401for authentication failures -
403for authorization failures -
404for missing resources -
409for resource conflicts -
422for business rule violations -
429for rate limiting -
500for unexpected server errors -
503for temporary service unavailability
Modern PetstoreAPI demonstrates correct status code usage across its endpoints. Study its REST API documentation to see how these semantics are applied.
Test status codes with Apidog to verify that your API follows HTTP standards and continues to return the expected responses as it evolves.
FAQ
Should I use 200 or 204 for successful DELETE?
Use 204 No Content when the deletion succeeds and there is no response body. Use 200 OK only when you need to return information about the deleted resource or operation.
What’s the difference between 400 and 422?
400 Bad Request means the request is malformed, such as invalid JSON or incorrect types. 422 Unprocessable Entity means the request is well-formed but violates a business rule.
When should I use 401 vs. 403?
401 Unauthorized means the client must authenticate or provide valid credentials. 403 Forbidden means the client is authenticated but does not have permission to perform the operation.
Should I return 404 or 403 for resources users can’t access?
Return 403 when the resource exists but the authenticated user lacks permission. Return 404 when you want to hide the resource’s existence from unauthorized users.
How do I test all status code scenarios?
Create test cases for successful responses, validation errors, authentication failures, authorization failures, missing resources, conflicts, rate limiting, and server errors. Use Apidog to automate these checks and run them in CI/CD.
What status code should I use for rate limiting?
Use 429 Too Many Requests with RateLimit-* headers. Include Retry-After to tell clients when they can retry.
Should I use 500 for all server errors?
Use 500 for unexpected server errors. Use 502 for upstream service failures, 503 for temporary unavailability, and 504 for upstream timeouts.
How does Modern PetstoreAPI handle errors?
Modern PetstoreAPI returns errors using RFC 9457 problem details with appropriate HTTP status codes. See the error handling documentation for examples.
Top comments (0)