relogiosoft-api-governance-kit

HTTP Status Codes

Category: Design Tags: status-codes, http, 2xx, 3xx, 4xx, 5xx, 200, 201, 204, 400, 401, 403, 404, 409, 422, 500


Summary of Rules


2xx and 3xx Success & Redirect Codes

flowchart TD
    Start["Choosing a 2xx / 3xx Status Code"] --> Redirect{"Do you want to\nredirect the client?"}

    Redirect -- Yes --> SameResource{"Is it to the same\nresource at a new location?"}

    SameResource -- "Yes (same resource)" --> MethodChange{"Can the HTTP method\nchange to GET?"}
    MethodChange -- Yes --> Temporary1{"Is the new location\ntemporary?"}
    Temporary1 -- Yes --> S302["302 Found"]
    Temporary1 -- No --> S301["301 Moved Permanently"]
    MethodChange -- No --> Temporary2{"Is the new location\ntemporary?"}
    Temporary2 -- Yes --> S307["307 Temporary Redirect"]
    Temporary2 -- No --> S308["308 Permanent Redirect"]

    SameResource -- "No (different resource)" --> CreatedForRequest{"Was the new location\ncreated for this request?"}
    CreatedForRequest -- Yes --> S201["201 Created"]
    CreatedForRequest -- No --> S303["303 See Other"]

    Redirect -- No --> LaterCompletion{"Will the request\ncomplete later (async)?"}
    LaterCompletion -- Yes --> S202["202 Accepted"]
    LaterCompletion -- "No, done now" --> CacheUnchanged{"Do you want the\nclient cache unchanged?"}
    CacheUnchanged -- Yes --> S204["204 No Content"]
    CacheUnchanged -- No --> WebServer{"Are you implementing\na web server?"}
    WebServer -- Yes --> S206_304["206 Partial Content\nor 304 Not Modified"]
    WebServer -- No --> S200["200 OK"]

Key 2xx Code Reference

Code Name When to Use
200 OK Successful GET, successful POST query, empty list results
201 Created Resource created via POST; include Location header and created resource ID
202 Accepted Asynchronous request accepted; include Location header to status endpoint
204 No Content Valid response with no body (e.g., void PUT/POST); entity exists but no content to return
206 Partial Content Partial content delivery (web servers / range requests)

Key 3xx Code Reference

Code Name When to Use
301 Moved Permanently Resource has permanently moved; method may change to GET
302 Found Temporary redirect; method may change to GET
303 See Other Redirect to a different resource (e.g., after POST creates a resource)
304 Not Modified Conditional GET; cached response is still valid
307 Temporary Redirect Temporary redirect; method and body must be preserved
308 Permanent Redirect Permanent redirect; method and body must be preserved

4xx Client Error Codes

flowchart TD
    Start["Choosing a 4xx Status Code"] --> Throttled{"Is the client\nbeing rate-limited?"}
    Throttled -- Yes --> S429["429 Too Many Requests"]

    Throttled -- No --> NeedsAuth{"Does the request\nneed authentication?"}
    NeedsAuth -- Yes --> S401["401 Unauthorized"]

    NeedsAuth -- No --> HasAccess{"Does the client have\naccess to this resource?"}
    HasAccess -- No --> S403["403 Forbidden\n(use even if resource\ndoesn't exist — prevents enumeration)"]

    HasAccess -- Yes --> MethodSupported{"Is the HTTP method\nsupported by this resource?"}
    MethodSupported -- No --> S405["405 Method Not Allowed"]

    MethodSupported -- Yes --> HeaderIssue{"Is the problem\na request header?"}
    HeaderIssue -- "Accept" --> S406["406 Not Acceptable"]
    HeaderIssue -- "Content-Length" --> S411["411 Length Required"]
    HeaderIssue -- "If-* (conditional)" --> S412["412 Precondition Failed"]
    HeaderIssue -- "Content-Type" --> S415["415 Unsupported Media Type"]
    HeaderIssue -- "Expect" --> S417["417 Expectation Failed"]

    HeaderIssue -- No --> ConflictPrev{"Is the request\nincompatible with current\nresource state?"}
    ConflictPrev -- Yes --> S409["409 Conflict"]

    ConflictPrev -- No --> PatchInvalid{"Is this a PATCH where\nthe body is valid but would\nresult in invalid resource state?"}
    PatchInvalid -- Yes --> S422["422 Unprocessable Entity\n(PATCH only)"]

    PatchInvalid -- No --> S400["400 Bad Request"]

Key 4xx Code Reference

Code Name When to Use
400 Bad Request Malformed request, invalid headers, invalid query parameters
401 Unauthorized Authentication required or not provided; also for step-up auth requests
403 Forbidden Authenticated but not authorised; also use when resource existence must not be revealed
404 Not Found Resource does not exist at the given URI (not for permission denial)
405 Method Not Allowed HTTP method not supported on this endpoint
406 Not Acceptable Requested Accept type cannot be produced
409 Conflict Request conflicts with current resource state (e.g. duplicate creation)
410 Gone Resource permanently removed (use after soft delete or sunset)
411 Length Required Content-Length header required
412 Precondition Failed If-Match / If-Unmodified-Since condition failed
415 Unsupported Media Type Content-Type not supported
422 Unprocessable Entity PATCH only — valid body, but would result in invalid resource
429 Too Many Requests Rate limit exceeded

5xx Server Error Codes

flowchart TD
    Start["Choosing a 5xx Status Code"] --> RetryAfter{"Should the client\nretry after a delay?"}
    RetryAfter -- Yes --> S503["503 Service Unavailable\n(add Retry-After header)"]

    RetryAfter -- No --> UpstreamIssue{"Is it a problem with\nan upstream / dependent service?"}
    UpstreamIssue -- Yes --> UpstreamResponding{"Is the upstream\nservice responding?"}
    UpstreamResponding -- "Yes (bad response)" --> S502["502 Bad Gateway"]
    UpstreamResponding -- "No (timeout)" --> S504["504 Gateway Timeout"]

    UpstreamIssue -- No --> NotImplemented{"Is the functionality\nnot yet implemented?"}
    NotImplemented -- Yes --> S501["501 Not Implemented"]
    NotImplemented -- No --> S500["500 Internal Server Error\n(application code only)"]

Key 5xx Code Reference

Code Name When to Use
500 Internal Server Error Unhandled application error. Application code MUST use this code only.
501 Not Implemented Requested functionality is not implemented
502 Bad Gateway Upstream service returned an invalid response (gateway/proxy use)
503 Service Unavailable Service temporarily unavailable; include Retry-After header
504 Gateway Timeout Upstream service did not respond in time (gateway/proxy use)

Note: 502, 503, and 504 are for web servers, gateways, and load balancers — not application code. Application code MUST only use 500.


Specific Scenarios

204 vs 404: Empty Results

Situation Correct Code
Collection query returns zero results 200 with empty array []
URL is valid, entity exists, no body content (void PUT/POST) 204 No Content
URL is valid but the entity does not exist 404 Not Found

401 vs 403

Situation Correct Code
No authentication credentials provided 401 Unauthorized
Credentials provided, valid, but insufficient permissions 403 Forbidden
Resource exists but authenticated client has no access 403 Forbidden
Resource does not exist AND client should not know it exists 403 Forbidden (prevents enumeration)

Always use 403 to prevent enumeration: mixing 403 and 404 based on resource existence allows attackers to determine which resources exist.

400 vs 404 for Invalid Parameters

Parameter Type Correct Code
Invalid header value 400 Bad Request
Invalid query string parameter 400 Bad Request
Invalid path segment (e.g. unknown tenant) 404 Not Found

400 vs 409 for Duplicate Creation

If a POST would create a resource that already exists:

422 for JSON PATCH

201 and 202 with Location Header

Feature Unavailability

When a feature is unavailable for a specific tenant/region but exists for others, 404 is appropriate:

GET /entities/uk/123/premium-feature  → 200 (available in uk)
GET /entities/ie/456/premium-feature  → 404 (not available in ie)

410 Gone would be wrong here because it implies permanent removal — the feature may become available in future. 404 correctly signals that the resource does not exist at this path for this context.