git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/api-status-codes" ~/.claude/skills/intense-visions-harness-engineering-api-status-codes-1f1cb0 && rm -rf "$T"
agents/skills/codex/api-status-codes/SKILL.mdHTTP Status Codes
HTTP STATUS CODES ARE THE RESPONSE CONTRACT BETWEEN SERVER AND CLIENT — CORRECT CODE SELECTION ENABLES ERROR HANDLING, RETRY LOGIC, AND MONITORING WITHOUT PARSING RESPONSE BODIES. MISUSING STATUS CODES FORCES CLIENTS TO TREAT 200 OK AS AN AMBIGUOUS SIGNAL THAT MUST BE INSPECTED FOR HIDDEN FAILURES.
When to Use
- Designing the response contract for a new API endpoint
- Reviewing a PR that returns
for validation errors or internal failures200 OK - Choosing between
and404 Not Found
when a resource exists but access is denied403 Forbidden - Deciding whether to return
or200
after a successful mutation204 - Explaining why
is more appropriate than422 Unprocessable Entity
for semantic validation failures400 Bad Request - Selecting the correct code for rate limiting, service unavailability, and conflict scenarios
- Building error monitoring dashboards that distinguish client errors (4xx) from server errors (5xx)
- Implementing retry logic that behaves correctly for 429, 503, and 500 responses
Instructions
Key Concepts
-
1xx Informational — Provisional responses sent before the final response. Rarely used in REST APIs.
is sent by servers to indicate that the initial part of a request was received and the client should proceed.100 Continue
is used for WebSocket upgrades.101 Switching Protocols -
2xx Success — The request was received, understood, and accepted. The three most important 2xx codes:
— General success with a response body. Use for GET, PATCH, and PUT when returning the updated resource.200 OK
— A new resource was created. Must include a201 Created
header with the URL of the new resource. Use for POST and PUT-to-create.Location
— Success with no response body. Use for DELETE and PUT/PATCH when returning the resource is not needed.204 No Content
-
3xx Redirection — Further action is needed to complete the request.
redirects clients and updates bookmarks.301 Moved Permanently
(temporary redirect) does not update bookmarks.302 Found
is the conditional GET response — see304 Not Modified
. APIs should avoid redirects in normal operation flows; they complicate client retry logic.api-conditional-requests -
4xx Client Error — The request contained an error the client must fix before retrying. These are non-retryable without change. Key codes:
— Malformed syntax, invalid parameters, missing required fields.400 Bad Request
— No valid authentication credentials provided. The client should re-authenticate.401 Unauthorized
— Authentication succeeded but the caller lacks permission. Do not leak resource existence.403 Forbidden
— Resource does not exist at this URL, or the server is hiding its existence (use404 Not Found
if you want to reveal it exists).403
— Request conflicts with current resource state (e.g., duplicate creation, stale optimistic lock).409 Conflict
— Request is syntactically valid but semantically invalid (e.g., end date before start date).422 Unprocessable Entity
— Rate limit exceeded. Must include429 Too Many Requests
header.Retry-After
-
5xx Server Error — The server failed to fulfill a valid request. These are potentially retryable. Key codes:
— Unhandled exception or unexpected server failure. Do not expose stack traces.500 Internal Server Error
— An upstream service returned an invalid response.502 Bad Gateway
— The server is temporarily unable to handle requests. Should include503 Service Unavailable
.Retry-After
— An upstream service did not respond in time.504 Gateway Timeout
Worked Example
A GitHub REST API interaction demonstrating status code precision across a repository lifecycle:
Create a repository (POST → 201 Created):
POST /user/repos Authorization: Bearer ghp_... Content-Type: application/json { "name": "my-project", "private": true }
HTTP/1.1 201 Created Location: https://api.github.com/repos/alice/my-project Content-Type: application/json { "id": 123456, "name": "my-project", "full_name": "alice/my-project", ... }
Create duplicate repository (422 Unprocessable Entity — GitHub's choice):
POST /user/repos Content-Type: application/json { "name": "my-project", "private": true }
HTTP/1.1 422 Unprocessable Entity Content-Type: application/json { "message": "Repository creation failed.", "errors": [{ "resource": "Repository", "code": "custom", "field": "name", "message": "name already exists on this account" }] }
Note: GitHub returns
422 rather than 409 for duplicate names — a documented choice that treats name uniqueness as a semantic constraint rather than a state conflict.
Fetch without credentials (401 Unauthorized):
GET /repos/alice/my-project
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="GitHub" Content-Type: application/json { "message": "Requires authentication" }
Fetch a private repo with wrong credentials (403 Forbidden):
GET /repos/alice/private-project Authorization: Bearer ghp_wrong_token
HTTP/1.1 404 Not Found
GitHub returns
404 (not 403) to avoid leaking that the private repository exists — a security pattern called "security through obscurity on existence."
Rate limit exceeded (429 Too Many Requests):
HTTP/1.1 429 Too Many Requests Retry-After: 60 X-RateLimit-Limit: 5000 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1714780800 Content-Type: application/json { "message": "API rate limit exceeded for ghp_..." }
Anti-Patterns
-
Returning 200 OK for errors.
in a{ "success": false, "error": "User not found" }
body breaks monitoring, alerting, and client error handling. 5xx/4xx rates in logs become meaningless. Clients must parse every body to detect failure. Fix: use the appropriate 4xx or 5xx code with an error body conforming to RFC 9457 (Problem Details) or200 OK
.api-problem-details-rfc -
Using 404 when 403 is correct. If a resource exists and the caller lacks permission, returning
(to hide existence) is a security decision that should be explicit and documented — not a default. It prevents clients from distinguishing "wrong URL" from "wrong permissions." Use404
when the existence of the resource is not sensitive. Use403
only when existence itself must be concealed.404 -
Using 400 for semantic validation errors.
signals a malformed request (unparseable JSON, missing Content-Type, invalid URL parameter). Semantic failures — start date after end date, referenced resource does not exist, business rule violation — belong in400 Bad Request
. This distinction helps clients route errors to the right handler: syntax errors (fix the request format) vs. semantic errors (fix the payload values).422 Unprocessable Entity -
Returning 500 for client-caused failures. An API that throws a 500 when the request body contains unexpected values is a server bug, but returning 500 to the client incorrectly signals that the server is at fault. Validate inputs early, return 400/422 for client errors, and reserve 5xx for genuine server-side failures.
Details
The 401 vs 403 Distinction
This distinction is frequently confused:
means the request lacks valid authentication. The401 Unauthorized
header tells the client how to authenticate. The fix: provide credentials.WWW-Authenticate
means authentication succeeded but authorization failed. The client is identified but not permitted. The fix: acquire the required permission or role.403 Forbidden
The naming is historical — "Unauthorized" was named before authentication and authorization were cleanly separated in practice.
409 vs 422
— The request is valid but conflicts with the current state of the target resource. Use for optimistic concurrency failures (stale ETag), duplicate unique-key violations where idempotency is expected, or state machine violations (e.g., closing an already-closed order).409 Conflict
— The request is syntactically and structurally valid but fails semantic validation. Use for business rule violations, cross-field validation failures, and references to non-existent related resources.422 Unprocessable Entity
Real-World Case Study: Stripe Error Taxonomy
Stripe's API maps all errors to HTTP status codes with machine-readable error codes in the body:
for request parameter errors400
for invalid API keys401
for payment failures (a creative use of the rarely-used "Payment Required" code)402
for permission errors403
for non-existent resources404
for idempotency key reuse with different parameters409
for rate limits429
for Stripe infrastructure failures500/502/503
This precise taxonomy allows Stripe SDK clients to switch on
error.type for business logic while using the HTTP status code for transport-level decisions (retry vs. no-retry). APIs that follow this pattern report 30-40% fewer support tickets related to error handling ambiguity.
Source
- MDN — HTTP Response Status Codes
- RFC 9110 — HTTP Semantics, Section 15
- RFC 9457 — Problem Details for HTTP APIs
- RFC 6585 — Additional HTTP Status Codes (429)
Process
- Identify the outcome category: success (2xx), client error (4xx), or server error (5xx).
- For 2xx: choose
+201 Created
for resource creation,Location
for mutations with no return body,204 No Content
otherwise.200 OK - For 4xx: distinguish authentication failure (401), authorization failure (403), not found (404), state conflict (409), semantic validation failure (422), and rate limiting (429). For 429 and 503, always include
.Retry-After - For 5xx: return
for unhandled server failures,500
for intentional degraded-mode responses. Never expose stack traces or internal paths in 5xx bodies.503 - Run
to confirm skill files are well-formed.harness validate
Harness Integration
- Type: knowledge -- this skill is a reference document, not a procedural workflow.
- No tools or state -- consumed as context by other skills and agents.
- related_skills: api-http-methods, api-error-contracts, api-problem-details-rfc, api-rest-maturity-model
Success Criteria
- No endpoint returns
for error conditions — all errors use appropriate 4xx or 5xx codes.200 OK - POST creation endpoints return
with a201 Created
header.Location
and401
are used for authentication vs. authorization failure respectively, with documented rationale for any security-through-obscurity403
substitutions.404
responses include a429
header;Retry-After
responses include503
when a recovery time is known.Retry-After
is reserved for malformed requests; semantic validation failures use400
.422 Unprocessable Entity