HTTP Methods Status Codes and Resource Contracts

Client Server and API Design · 40 min

System Design · Module 2 · Lesson 2 of 4 · 40 min

HTTP Methods Status Codes and Resource Contracts

Design a small resource API with clear ownership, validation, success responses and failures a caller can handle.

A resource contract defines the operation, caller permission, valid input and every meaningful outcome.

01 The idea

The problem this lesson solves

Two developers can agree to “build a notes API” and still implement incompatible behavior. One returns a new note id after saving; the other returns success before persistence. One trusts owner_id in the body; the other derives it from authentication. An API contract resolves these disagreements by defining the operation, its authority, the representation and every meaningful outcome.

Use the requirements and ownership vocabulary from module 1. This module follows one request across the API boundary.

ResourceA named object or collection exposed by the API.
Safe methodA method whose requested semantics are read-only.
AuthorizationThe decision that a caller may perform a particular action on a particular resource.

02 Worked example

Create a note and protect its ownership

HTTP Methods Status Codes and Resource Contracts worked diagram01RequestMethod + resource02IdentityAuthenticate caller03PolicyAuthorize + validate04ResponseStatus + representationHTTP Methods Status Codes and Resource Contracts compact diagram01RequestMethod + resource02IdentityAuthenticate caller03PolicyAuthorize + validate04ResponseStatus + representation
Authentication identifies the caller. Authorization checks that caller’s permission on this resource before the operation succeeds.
1
CreatePOST /v1/notes sends a JSON note body; the server derives its owner.
2
Return identity201 Created returns the new note and a Location header.
3
Read as another userThe API checks permission instead of assuming possession of the id grants access.
4
Handle failureReturn documented authentication, permission, validation or service errors without disguising them as success.

03 The method

Understand the mechanism

Design resources around identities and access patterns

A resource is a named thing or conceptual entity exposed through the interface. /v1/notes can represent a collection, while /v1/notes/n7 identifies a particular note. The identifier should remain stable when mutable fields such as title change. A title is a poor durable identifier if users can rename it.

Start with operations the client needs: create a note, retrieve it, change its body, delete it and list accessible notes. For each, identify the caller, target and ownership check. The database access should follow the same identity. A note lookup by id followed by an authorization decision is different from a query already restricted to the authenticated owner.

Resources do not require exposing table names or internal service topology. A client-facing representation may combine several stored fields or omit internal metadata. This separation lets storage evolve while preserving the public contract.

Use versioning and compatibility deliberately. Adding an optional response field is often easier for tolerant clients than renaming an existing field or changing its meaning. New required request fields can break older callers. Define how unknown fields, omitted fields and null values are treated rather than allowing accidental behavior to become the contract.

Match method semantics to intended effects

GET retrieves a representation and should not be designed to perform a user-requested state-changing action. A server may still log access or update internal accounting. The important distinction is the operation the client requested, not whether the server executes literally zero writes.

POST commonly submits to a collection or invokes an operation whose result is determined by the server. PUT supplies a replacement representation at a known target under its contract. PATCH describes a partial change, whose idempotency depends on the patch semantics. DELETE requests removal of the target’s current association or representation according to the service’s rules.

Idempotent means repeating the same request has the same intended effect as making it once. It does not mean every attempt returns an identical status or body. A first DELETE might return 204 and a later attempt 404 after the resource is gone. A “set title to X” patch differs from an “append X to title” patch under repetition.

Method choice communicates useful expectations to clients and intermediaries, but it does not repair incorrect business logic. A handler named PUT that increments a balance on every retry violates its intended semantics. Review what the stored state actually does.

Make success visible and precise

For POST /v1/notes, the request body might contain title and body. The server derives owner_id from the authenticated context. After durable creation under the chosen contract, it returns 201 Created, a representation containing the new id, and Location identifying the resource.

Specify whether returning success means the work is complete or only accepted. A long export request might return 202 with an operation resource the caller can poll. That response must not claim the file already exists. A queue-backed operation needs explicit terminal states, failure reporting and retention of its outcome.

For a note update, include a version or ETag when concurrent editing matters. A client can submit an If-Match condition so the server refuses an update based on an old representation rather than silently overwriting a newer edit. The database transition must enforce the condition; checking a version and then writing unconditionally reintroduces a race.

A response should contain the information needed for the next step. Returning only “success” forces the client to guess the new identity or perform an extra ambiguous search. Returning internal stack traces provides the wrong kind of detail and can expose implementation information without helping the caller recover.

Separate authentication, authorization and validation

Authentication establishes the caller’s identity. Authorization decides whether that caller may perform this operation on this resource. Validation checks whether the submitted data satisfies the operation’s shape and business constraints. Passing one does not imply passing the others.

An authenticated user may still lack permission to edit n7. A well-formed note body may still exceed the account’s policy limit. A request that supplies owner_id=another_user must not gain authority merely because the value is syntactically valid.

Authorization applies to every object access, including list filters, nested fields and bulk operations. Knowing or guessing an id is not permission. Decide whether unauthorized access reveals existence; some APIs use a missing response to avoid disclosing a protected resource. Keep that policy consistent across endpoints.

HTTPS protects the transport under its trust model. It does not ensure the logged-in account may access the requested note. Likewise, CORS is a browser access mechanism rather than a substitute for server-side authentication. An API must enforce authority for callers that do not use a browser too.

Design errors as part of the interface

A useful error response combines an appropriate HTTP status with a stable machine-readable code and a safe human-readable explanation. For example, a conflicting edit can identify version_conflict and explain that the caller should fetch the current representation. The client should not need to parse prose to determine its recovery action.

Distinguish malformed input, missing or invalid authentication, insufficient permission, missing resources, state conflicts, quota exhaustion and service failure. These conditions have different remedies. Retrying an unchanged malformed body will not repair it; retrying an unavailable dependency may help if the operation is retry-safe and still within its deadline.

Do not return 200 for every outcome and bury the real result in a string field. Infrastructure, clients and monitoring often rely on status semantics. Conversely, do not assume a success status proves business correctness: a confirmation for the wrong note is still an application defect.

Set limits explicitly. Maximum body size, page size and field lengths protect both reliability and predictability. Return a documented error when the request exceeds them. Silent truncation can corrupt the caller’s understanding of what was stored.

Evolve and test the contract through examples

Write a complete exchange for the happy path and one for each important failure. This exposes omissions that a list of endpoint names misses. A create exchange needs request content type, identity context, body, status, Location and response fields. An update conflict needs a recovery path.

Then test changes against existing clients. If a field becomes optional, an old client may still assume it always exists. If a list’s ordering changes, cursor behavior and UI pagination may break even though the JSON shape is unchanged. Compatibility includes semantics, not only syntax.

Contract tests should exercise permissions and repeated requests as well as serialization. Create as A, attempt to read or edit as B, update with a stale version, submit an oversized body, and repeat a request after a response loss. These cases connect the interface to the invariants introduced earlier.

You have a usable API design when a caller can determine what to send, what success means, what each failure means, and whether and how to retry. The implementation can change internally while those promises remain stable.

Guided tutorial

  1. Write the creation exchange

    Draft POST /v1/notes with title and body, an authenticated caller A, and Content-Type: application/json. Return 201 with id n7 and Location: /v1/notes/n7 after the save commits. Leave owner_id out of the caller-controlled authority decision. Explain which component supplies it.

  2. Try a different caller

    Repeat GET and PATCH for n7 as caller B. Decide whether the contract returns forbidden or deliberately hides existence. Apply the same policy to the list endpoint so a bulk listing does not reveal objects the direct endpoint protects.

  3. Prevent an overwritten edit

    Assume n7 is version 4. Two clients read it. Client one writes version 5. Client two submits an update conditional on version 4 and must receive a conflict/precondition outcome instead of overwriting version 5. Describe the conditional database update that makes the check atomic.

  4. Build an outcome table

    Include valid creation, malformed body, expired authentication, unauthorized access, stale version and unavailable storage. For every row, write a status, stable error code if needed and caller action. Mark which requests can be retried unchanged and which require new input or authentication.

HTTP contract sketch

POST /v1/notes → 201 Created + Location
GET /v1/notes/n7 → 200 or documented failure
DELETE /v1/notes/n7 → deletion outcome
error body: {"code":"permission_denied","message":"..."}
owner_id comes from authentication, not the request body

Check your result

Creation returns a resource identity and Location. Reading another user’s note requires an explicit permission decision. Changing owner_id in the request body must not transfer authority.

04 Cheat sheet

Review the decisions

Use this table to explain the mechanism and its limit without memorizing a product name.

Choice or conceptRememberCheck
GETRead a resourceSafe and idempotent semantics
POSTCreate or invokeNeeds an explicit retry contract
PUTReplace at a known resourceIdempotent intended effect
DELETERemove a resourceRepeated status codes may differ

05 Where & why

Check the primary references

Use official references for protocol and implementation details. The questions here are teaching prompts.

Primary reference
HTTP semantics, methods and status codes

Read HTTP semantics, methods and status codes

Primary reference
PostgreSQL table constraints

Read PostgreSQL table constraints

State the implementation version and the workload assumptions when defending your own design.

06 Interview questions

Explain the reasoning out loud

Answer before opening the explanation. The questions progress from vocabulary to decisions and failure behavior.

What does Resource mean?
A named object or collection exposed by the API.
What does Safe method mean?
A method whose requested semantics are read-only.
What does Authorization mean?
The decision that a caller may perform a particular action on a particular resource.
Is DELETE idempotent if the second response differs?
It can be. Idempotency concerns the intended effect on the resource, not identical responses.
Does an unpredictable id authorize access?
No. The API must still check whether the authenticated caller may act on the resource.
What does 202 establish?
It normally indicates acceptance for processing, not successful completion. The contract needs an operation status or completion mechanism.
Why use structured error codes?
Clients need stable categories to decide whether to authenticate again, repair input, stop or retry. Human-readable wording can evolve separately.
Can PATCH always be retried?
No. Setting a field to a value differs from incrementing it. The patch contract must specify retry behavior.

07 Practice problems

Practice a new case

Solve the new cases before revealing hints. Complete worked solutions follow the set.

Create outcome

Easy
Choose a success response for creating a task and identify the new resource.
Show the hint
Creation should return an identity.

Permission failure

Easy
Choose the failure category when a logged-in reader tries to delete another owner’s private note.
Show the hint
Identity is already established.

Retry semantics

Medium
Compare retrying set likes=10 with retrying increment likes by 1.
Show the hint
Think about the second intended effect.

Async work

Medium
Design the initial response and follow-up lookup for a report that takes 30 seconds.
Show the hint
The first response cannot honestly mean completed.

Bound a request

Medium
List three limits for a list-and-search endpoint.
Show the hint
Bound both input and output work.

Review a mutable redirect

Hard
Specify a redirect contract when an owner may change the destination later.
Show the hint
The client may cache a prior redirect.

Worked solutions

1. Create outcome

201 Created with a body containing the task id and a Location header is a clear contract.

2. Permission failure

Return the documented permission failure, commonly 403, or a deliberate privacy-preserving 404 policy. Enforce it on the server.

3. Retry semantics

Setting the same value is idempotent under that contract; an increment repeats its effect unless deduplicated.

4. Async work

Return 202 with an operation id and status URL. Define pending, succeeded, failed and expiry behavior for the status resource.

5. Bound a request

Limit page size, query length/complexity and execution time. Also cap body size and enforce authorization filters on all pages.

6. Review a mutable redirect

Use a documented temporary redirect and explicit cache policy consistent with the update visibility requirement. Define the update response, authorization, invalidation path and behavior of already-in-flight reads.