A maintainable HTTP API is a contract between independent systems. Good design lets a client understand what a request means, which outcomes are safe to retry, how to recover from an error and how the contract will evolve. URL style matters, but predictable semantics and operational behaviour matter more.
HTTP already defines methods, status codes, conditional requests, content negotiation and caching. Reusing those semantics reduces private conventions and helps clients, gateways and observability tools behave correctly. RFC 9110 defines HTTP as a stateless request/response protocol with a uniform interface to resources (RFC 9110 — HTTP Semantics).
Not every JSON-over-HTTP interface is strictly REST, and a service does not become RESTful merely by using plural nouns. This guide focuses on practical resource-oriented HTTP APIs without claiming conformance to every architectural constraint associated with REST.

Model resources and business transitions
Start with business concepts, identities and state changes. A resource is something a client can identify and exchange a representation of, such as:
/customers/{customerId};/orders/{orderId};/orders/{orderId}/items; or/orders/{orderId}/cancellations.
Use stable identifiers that do not expose a storage layout or sensitive sequence without a reason. Keep paths understandable, but do not force every complex action into a verb-free fiction. A cancellation can be represented as a subordinate resource when it has its own status, reason, timestamp and permissions.
Separate the API representation from database rows. The representation should express the contract, not leak internal column names, join tables or fields that may later change. Decide whether absence, null, an empty collection and a default value mean different things, then document them.
Respect HTTP method semantics
RFC 9110 defines GET, HEAD, OPTIONS and TRACE as safe methods: the client is not requesting a state change. Safe does not mean that the server performs no logging or accounting; it means the requested semantics are read-only. GET must not trigger a destructive business action through a query parameter.
Idempotent methods can be repeated with the same intended effect. PUT and DELETE are idempotent by definition, while POST is not generally idempotent. Idempotency does not require byte-identical responses or prohibit audit timestamps; it constrains the requested effect.
A practical mapping is:
| Intent | Common method | Notes |
|---|---|---|
| Retrieve a representation | GET | Safe; define cache and authorisation behaviour |
| Create under a server-selected URI | POST to a collection | Return the resulting status and location where appropriate |
| Replace the state of a known resource | PUT | Define complete-replacement semantics explicitly |
| Apply a partial modification | PATCH | Document the patch media type and conflict rules |
| Remove a resource or make it unavailable | DELETE | Repeated requests should preserve the intended removed state |
If clients may retry a POST after a network failure, design an idempotency mechanism. A client-generated key can bind repeated attempts to one operation, but the server must define scope, expiry, payload comparison, storage and concurrent-arrival behaviour. Do not label a request idempotent without implementing the guarantee.
Use status codes to describe the HTTP outcome
Choose the most specific standard status that matches the result. Common examples include:
200 OKfor a successful response with a representation;201 Createdwhen a new resource is created;202 Acceptedwhen processing is accepted but not complete;204 No Contentwhen success has no response content;400 Bad Requestfor malformed or invalid request content;401 Unauthorizedwhen authentication is required or invalid;403 Forbiddenwhen the authenticated principal is not allowed;404 Not Foundwhere the target is unavailable, including deliberate concealment where appropriate;409 Conflictfor a conflict with current resource state;412 Precondition Failedfor a failed conditional request;422 Unprocessable Contentfor syntactically valid content that cannot be processed as supplied;429 Too Many Requestsfor rate limiting; and500-class statuses for server-side failure.
Do not return 200 with { "success": false } for every failure. HTTP-aware clients and monitoring should be able to classify the response without first decoding a private envelope.
Give errors one predictable shape
RFC 9457 defines Problem Details for HTTP APIs using the application/problem+json media type. Its members can include type, status, title, detail and an occurrence-specific instance; an API can add extension fields for structured validation information (RFC 9457 — Problem Details for HTTP APIs).
For example:
{
"type": "https://api.example.test/problems/invalid-request",
"title": "The request contains invalid fields",
"status": 422,
"detail": "Correct the listed fields and submit again.",
"instance": "/problems/01J8EXAMPLE",
"errors": [
{ "pointer": "/email", "code": "invalid_format" }
]
}
Keep human text safe for display, but give clients stable machine-readable types or codes. Do not expose stack traces, SQL, filesystem paths, secrets or internal hostnames. Put a correlation identifier in the response and logs so support can investigate without revealing internals.

Design collections, filters and pagination explicitly
Collections grow. Define pagination before a client depends on an unbounded response. Offset pagination is easy to understand but can duplicate or skip records as data changes and can become expensive at large offsets. Cursor pagination can provide stable traversal when the cursor encodes a deterministic order, but cursors should be opaque to clients and protected from tampering.
Document:
- default and maximum page size;
- stable sort keys and tie-breakers;
- filter syntax and allowed combinations;
- whether total counts are exact, estimated or omitted;
- links or tokens for next and previous pages; and
- behaviour when records change between requests.
Reject unsupported filters rather than silently ignoring them. Bound expensive search and aggregation paths to protect the service.
Protect concurrent updates
Two clients can read the same resource and overwrite each other's change. HTTP conditional requests provide a standard control. The server can return an ETag; a client sends If-Match with an update, and the server returns 412 Precondition Failed if the representation changed. RFC 9110 specifies the evaluation order for request preconditions.
Do not invent a last-write-wins policy accidentally. Choose whether conflicts should be rejected, merged or represented as a business workflow. Test simultaneous requests, retries and partial failures.
Define caching rather than inheriting surprises
GET responses can be cacheable, including by browsers and intermediaries. Use Cache-Control, validators and Vary according to whether content is public, private, personalised or dependent on request headers. RFC 9111 describes when caches may store and reuse a response (RFC 9111 — HTTP Caching).
Authenticated does not automatically mean uncacheable, but shared caching of personalised content requires careful, explicit controls. Test that one user's response cannot be served to another. Mutation responses should invalidate or update relevant cached representations through a defined strategy.
Treat authentication and authorisation as separate decisions
Authenticate the caller using a mechanism appropriate to the client type and threat model. Authorise every operation and object at the server; possession of a valid token does not grant access to every resource. Scope credentials narrowly, rotate secrets, validate token audience and issuer, and require protected transport.
Apply input limits, schema validation and safe parsing. Rate limits can protect capacity but are not a complete denial-of-service strategy. Log security-relevant decisions without storing access tokens or unnecessary personal data.
For browser clients, assess cross-origin policy and request-forgery risks based on where credentials are stored and automatically sent. CORS is a browser read-control mechanism, not authentication.

Document and test the contract
An OpenAPI description can make operations, parameters, schemas and responses reviewable and can support generated documentation or tests. Keep it in the same change workflow as implementation, and verify that deployed behaviour matches the description. Generated clients do not resolve ambiguous business semantics.
Test:
- valid and invalid representations;
- authentication and object-level authorisation;
- each documented status and problem type;
- pagination boundaries and concurrent data changes;
- timeouts, retries and duplicate requests;
- conditional updates;
- cache behaviour; and
- backward compatibility with supported clients.
Evolve deliberately
Prefer additive changes: new optional fields, new resources and new operations. Clients should usually ignore unknown response fields, while servers should reject unknown or invalid request fields according to the documented policy. Changing a field's type or meaning is breaking even if its name remains.
When a breaking change is unavoidable, define the supported versions, migration guide, telemetry, deprecation notice and removal date. A version number in the path does not replace lifecycle management. Keep old versions secure during their support window and remove them only after checking actual client use.
For help designing or reviewing a web API contract and implementation plan, see Ozlin Info's web development services or contact Ozlin Info.
Related reading: Testing web applications: a risk-based strategy that scales.
General-information disclaimer
This article provides general technical information only. A production API design must reflect its clients, data, threat model, performance, compatibility and contractual requirements. Standard HTTP semantics do not by themselves make an API secure or reliable.
AI-assistance disclosure
AI tools assisted with source discovery, outlining and copyediting. A human reviewer must verify the protocol statements, examples, security guidance, service claims and publication decision before release. No compatibility, security or availability outcome is guaranteed.

Primary sources checked
- RFC 9110 — HTTP Semantics
- RFC 9111 — HTTP Caching
- RFC 9457 — Problem Details for HTTP APIs
- RFC 5789 — PATCH Method for HTTP
- OpenAPI Specification — Current Published Version
Source access date: 29 August 2026.

