Harness-engineering api-status-codes

HTTP Status Codes

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
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"
manifest: agents/skills/codex/api-status-codes/SKILL.md
source content

HTTP 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
    200 OK
    for validation errors or internal failures
  • Choosing between
    404 Not Found
    and
    403 Forbidden
    when a resource exists but access is denied
  • Deciding whether to return
    200
    or
    204
    after a successful mutation
  • Explaining why
    422 Unprocessable Entity
    is more appropriate than
    400 Bad Request
    for semantic validation failures
  • 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

  1. 1xx Informational — Provisional responses sent before the final response. Rarely used in REST APIs.

    100 Continue
    is sent by servers to indicate that the initial part of a request was received and the client should proceed.
    101 Switching Protocols
    is used for WebSocket upgrades.

  2. 2xx Success — The request was received, understood, and accepted. The three most important 2xx codes:

    • 200 OK
      — General success with a response body. Use for GET, PATCH, and PUT when returning the updated resource.
    • 201 Created
      — A new resource was created. Must include a
      Location
      header with the URL of the new resource. Use for POST and PUT-to-create.
    • 204 No Content
      — Success with no response body. Use for DELETE and PUT/PATCH when returning the resource is not needed.
  3. 3xx Redirection — Further action is needed to complete the request.

    301 Moved Permanently
    redirects clients and updates bookmarks.
    302 Found
    (temporary redirect) does not update bookmarks.
    304 Not Modified
    is the conditional GET response — see
    api-conditional-requests
    . APIs should avoid redirects in normal operation flows; they complicate client retry logic.

  4. 4xx Client Error — The request contained an error the client must fix before retrying. These are non-retryable without change. Key codes:

    • 400 Bad Request
      — Malformed syntax, invalid parameters, missing required fields.
    • 401 Unauthorized
      — No valid authentication credentials provided. The client should re-authenticate.
    • 403 Forbidden
      — Authentication succeeded but the caller lacks permission. Do not leak resource existence.
    • 404 Not Found
      — Resource does not exist at this URL, or the server is hiding its existence (use
      403
      if you want to reveal it exists).
    • 409 Conflict
      — Request conflicts with current resource state (e.g., duplicate creation, stale optimistic lock).
    • 422 Unprocessable Entity
      — Request is syntactically valid but semantically invalid (e.g., end date before start date).
    • 429 Too Many Requests
      — Rate limit exceeded. Must include
      Retry-After
      header.
  5. 5xx Server Error — The server failed to fulfill a valid request. These are potentially retryable. Key codes:

    • 500 Internal Server Error
      — Unhandled exception or unexpected server failure. Do not expose stack traces.
    • 502 Bad Gateway
      — An upstream service returned an invalid response.
    • 503 Service Unavailable
      — The server is temporarily unable to handle requests. Should include
      Retry-After
      .
    • 504 Gateway Timeout
      — An upstream service did not respond in time.

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

  1. Returning 200 OK for errors.

    { "success": false, "error": "User not found" }
    in a
    200 OK
    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) or
    api-problem-details-rfc
    .

  2. Using 404 when 403 is correct. If a resource exists and the caller lacks permission, returning

    404
    (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." Use
    403
    when the existence of the resource is not sensitive. Use
    404
    only when existence itself must be concealed.

  3. Using 400 for semantic validation errors.

    400 Bad Request
    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 in
    422 Unprocessable Entity
    . This distinction helps clients route errors to the right handler: syntax errors (fix the request format) vs. semantic errors (fix the payload values).

  4. 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:

  • 401 Unauthorized
    means the request lacks valid authentication. The
    WWW-Authenticate
    header tells the client how to authenticate. The fix: provide credentials.
  • 403 Forbidden
    means authentication succeeded but authorization failed. The client is identified but not permitted. The fix: acquire the required permission or role.

The naming is historical — "Unauthorized" was named before authentication and authorization were cleanly separated in practice.

409 vs 422

  • 409 Conflict
    — 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).
  • 422 Unprocessable Entity
    — 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.

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:

  • 400
    for request parameter errors
  • 401
    for invalid API keys
  • 402
    for payment failures (a creative use of the rarely-used "Payment Required" code)
  • 403
    for permission errors
  • 404
    for non-existent resources
  • 409
    for idempotency key reuse with different parameters
  • 429
    for rate limits
  • 500/502/503
    for Stripe infrastructure failures

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

Process

  1. Identify the outcome category: success (2xx), client error (4xx), or server error (5xx).
  2. For 2xx: choose
    201 Created
    +
    Location
    for resource creation,
    204 No Content
    for mutations with no return body,
    200 OK
    otherwise.
  3. 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
    .
  4. For 5xx: return
    500
    for unhandled server failures,
    503
    for intentional degraded-mode responses. Never expose stack traces or internal paths in 5xx bodies.
  5. Run
    harness validate
    to confirm skill files are well-formed.

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
    200 OK
    for error conditions — all errors use appropriate 4xx or 5xx codes.
  • POST creation endpoints return
    201 Created
    with a
    Location
    header.
  • 401
    and
    403
    are used for authentication vs. authorization failure respectively, with documented rationale for any security-through-obscurity
    404
    substitutions.
  • 429
    responses include a
    Retry-After
    header;
    503
    responses include
    Retry-After
    when a recovery time is known.
  • 400
    is reserved for malformed requests; semantic validation failures use
    422 Unprocessable Entity
    .