HTTP Clients And APIs

API Status Code Design

An API status policy maps domain outcomes to HTTP semantics consistently across endpoints.

Why This Matters

Clients use status, headers, and bodies together to decide whether to render data, correct input, authenticate, retry, poll, or stop.

Working Model

A method and domain outcome determine the response. The same error category should not alternate between success envelopes and unrelated 4xx codes.

Practical Rules

  • Use 201 plus Location for completed creation.
  • Use 202 plus a status URL for accepted unfinished work.
  • Use 409 for state conflicts and document the 400 versus 422 convention.
  • Pair 429 and temporary 503 responses with useful retry policy.
  • Document every response in OpenAPI.

Failure Modes

  • Returning 200 with success: false.
  • Sending a body with 204.
  • Using 500 for validation.
  • Treating every provider failure as retryable.

Verification

  • Build a method/outcome matrix.
  • Contract-test status, headers, and body together.
  • Test unknown and temporary failures.
  • Review consistency across endpoints.

What You Should Be Able To Do

After this lesson, you should be able to explain status selection for CRUD, async work, validation, conflict, authentication, retry, and error contracts, choose a suitable approach for a real PHP project, and verify the result instead of relying on assumptions.

Build A Status Policy Before Endpoints Drift

An API status policy maps repeatable domain outcomes to HTTP responses. Without a policy, one endpoint returns 400 for validation, another returns 422, another wraps the error in 200, and clients learn endpoint-specific folklore. Consistency is the feature: clients can write one authentication handler, one validation renderer, one retry policy, and one conflict workflow.

Start by listing outcomes, not codes. Typical outcomes include completed creation, completed update, accepted asynchronous work, malformed request, validation failure, missing authentication, denied authorization, missing resource, state conflict, precondition failure, rate limit, temporary dependency failure, and unexpected defect. Map each to status, headers, and body shape.

CRUD And Mutation Outcomes

Creation that completes during the request should normally use 201 Created and identify the new resource. If the API creates work rather than the final resource, use 202 Accepted with a status URL or operation ID. If an update succeeds and returns the updated representation, use 200 OK; if no body is useful, use 204 No Content.

State conflicts should not become validation errors. If the request is well-formed but cannot apply because the resource is in the wrong state, 409 Conflict tells clients they may need to refresh state or present a conflict message. If the client supplied an entity tag or version precondition and it failed, 412 Precondition Failed is more precise.

Validation Policy

Choose a clear boundary between 400 and 422 if your API uses both. A common convention is 400 for malformed JSON, invalid query syntax, unsupported parameters, or wrong content type, and 422 for syntactically valid JSON that violates business validation. Some APIs use 400 for all client input errors. Either convention can work; inconsistency is the problem.

Validation bodies should be structured enough for clients. Include a stable error code and field-level details where possible. Do not leak internal validator class names or database constraint names as the public contract.

Authentication And Authorization

401 Unauthorized means authentication is missing, invalid, or expired. It should trigger authentication handling, not a generic form-error path. 403 Forbidden means the identity is known but not allowed to perform the action. 404 Not Found may intentionally hide a resource from unauthorized callers when the product chooses not to reveal existence.

Apply the same hiding policy across endpoints. If one endpoint returns 403 for another tenant's order and another returns 404, clients and attackers both receive inconsistent signals.

Retry And Temporary Failure

429 Too Many Requests should carry rate-limit information when available. 503 Service Unavailable is appropriate for temporary overload or dependency unavailability. 502 and 504 may appear at gateways. Clients need to know whether retry is useful, whether to back off, and whether the original operation may already have happened.

For non-idempotent operations, status policy and idempotency policy are connected. A timeout or 503 after submitting a payment or order may require a status lookup rather than retrying the same request without an idempotency key. Document this behavior in endpoint descriptions.

Error Body Consistency

Use one error envelope across the API unless a protocol requires otherwise. A stable envelope might contain code, message, fields, and request_id. The status code tells the broad HTTP category; the body tells the application-specific reason. Avoid mixing booleans such as success: false into otherwise status-driven APIs.

The public body should be safe. Logs can contain exception class and stack trace under access controls; clients should receive stable machine codes and user-safe messages. Every error response should be documented in OpenAPI for endpoints where clients can reasonably handle it.

Verification Matrix

Create a table with methods on one axis and outcomes on the other. For each cell the endpoint supports, specify status, required headers, body shape, retry behavior, and cache behavior. Then write contract tests for representative endpoints. Tests should assert that 204 has no body, 201 includes Location where promised, 401 and 403 differ correctly, and temporary failures include retry guidance only when truthful.

Review logs and metrics too. Operators should be able to distinguish validation volume, auth failures, conflicts, rate limits, dependency failures, and defects. A dashboard that only groups by 4xx and 5xx misses product signals.

Rolling Out A Status Policy

Existing APIs often contain inconsistent status behavior. Do not change every endpoint at once without considering clients. First, document the current behavior with contract tests. Then define the target policy and identify differences that could break callers. Changing a validation response from 200 to 422 is correct only if clients are prepared for it or the API version allows the break.

For public APIs, introduce status corrections through a new version, a compatibility header, or a staged migration when necessary. For internal APIs, coordinate client updates and monitor error handling after deployment. A status-code fix can still be a breaking change because clients may have built retries, messages, or redirects around the old behavior.

Start with new endpoints and high-risk inconsistencies. Stop returning success envelopes for new failures. Make authentication, authorization, validation, and conflict responses consistent. Then migrate older endpoints with release notes. Keep a temporary compatibility layer only when it has an owner and removal date.

Examples Of Common Decisions

A create-order endpoint might return 201 Created with a Location header when the order is fully persisted. If fraud review or payment confirmation continues asynchronously, 202 Accepted with an operation URL is more honest. Returning 201 before the order exists teaches clients to assume durability that the server has not provided.

An update endpoint with optimistic concurrency should distinguish malformed input, validation, and conflict. Invalid JSON is 400. A syntactically valid body with an unacceptable shipping date may be 422 under that convention. A stale version or failed If-Match is 412 or 409, depending on whether the API uses HTTP preconditions or an application-level version field.

A delete endpoint can return 204 after a successful deletion, but repeated deletion needs a policy. Some APIs return 404 because the resource is gone. Others make delete idempotent and return 204 if the desired final state already holds. Either can work when documented consistently.

Client Documentation

Status policy should be visible in OpenAPI and client guides. Document which errors are safe to retry, which require user correction, which require authentication refresh, and which require reading current resource state. Generated clients can model response types, but humans still need prose explaining what to do.

Include examples for at least one validation error, one conflict, one authentication failure, one rate-limit response, and one temporary failure. Example responses should use realistic headers and request IDs. They should not include stack traces or internal exception names.

Review Checklist

Before approving an endpoint, ask: Does the method match the operation? Does success use the right 2xx response? Are validation, auth, permission, absence, conflict, and temporary failure distinct? Does 204 omit the body? Does 201 identify the created resource? Does retry guidance avoid duplicating non-idempotent work? Are all documented responses tested?

After this review, the status code should help clients make their next decision without parsing a private implementation detail. That is the practical measure of a good API status policy.

When changing old behavior, monitor by endpoint and client. A mobile application may keep an older error parser for months, while a server-to-server client can update quickly. Logs should show the response code, application error code, client identifier where safe, and request ID. That evidence tells the team whether the rollout is working or whether compatibility handling must remain longer.

After this lesson, you should be able to design a consistent API status policy for CRUD, async work, validation, auth, conflicts, rate limits, temporary failures, and defects; document the policy in OpenAPI; and verify status, headers, body, retry behavior, and observability as one contract.

Practice

Practice: Map CRUD Outcomes

Create a status matrix for product create, read, update, and delete.

Your answer must:

  • state the intended outcome;
  • show the commands, data flow, or implementation shape;
  • identify at least one unsafe alternative;
  • explain how the result will be verified.
Show solution

Use documented outcomes such as 201 with Location for creation, 200 for representations, 204 only for empty success, 404 for unavailable resources, and 409 for version conflicts.

The important part is not memorising one command or vendor screen. The solution makes the invariant, failure behavior, and verification evidence explicit.

Practice: Design An Async Status Contract

Design an export endpoint whose work continues in a queue.

Your answer must:

  • state the intended outcome;
  • show the commands, data flow, or implementation shape;
  • identify at least one unsafe alternative;
  • explain how the result will be verified.
Show solution

Return 202 with a job identifier and status URL, expose pending/running/succeeded/failed states, and return the completed resource separately when ready.

The important part is not memorising one command or vendor screen. The solution makes the invariant, failure behavior, and verification evidence explicit.

Practice: Standardize API Errors

Unify malformed JSON, validation, authentication, authorization, conflict, and server failures.

Your answer must:

  • state the intended outcome;
  • show the commands, data flow, or implementation shape;
  • identify at least one unsafe alternative;
  • explain how the result will be verified.
Show solution

Choose stable statuses, machine-readable error codes, safe messages, field errors where relevant, request IDs, and a documented Problem Details-compatible shape.

The important part is not memorising one command or vendor screen. The solution makes the invariant, failure behavior, and verification evidence explicit.