Designing RESTful API endpoints with proper HTTP verbs
An API contract tells clients what can be requested, what the server will do, and what comes back. When it follows resource-oriented design and consistent HTTP semantics, integrations become easier to reason about, monitor, cache, and evolve. The endpoint should identify a business resource, while the method should express the operation rather than burying an action in the path.
This matters to teams serving Australian users from Sydney, Melbourne, Brisbane, or cloud regions elsewhere. A date such as 04/10/2026 can be read in different orders until an API uses ISO 8601, and prices need clear precision rules for Australian dollars. Correct method selection also gives order processing, payment retries, and client applications predictable behaviour.
Model resources before choosing routes
Start with nouns representing durable domain concepts: customer, order, invoice, shipment, or access grant. A customer may have many orders, so /customers/{customerId}/orders expresses ownership naturally. By contrast, /getOrders?customerId=123 describes a procedure and makes clients recognise implementation details rather than resources.
URL paths identify resources; the HTTP method identifies what happens to the selected representation. This separation lets collection and individual endpoints share a model and leaves room to change implementation without breaking consumers. Resource design does not require one noun per database table: an invoice document, refund request, and usage report can each be useful representations.
Keep hierarchy shallow where possible. /orders/{orderId}/line-items is usually clearer than /orders/{orderId}/items/{itemId}/options/{optionId}. If an operation is genuinely procedural, expose it as a process resource such as /refund-requests rather than adding /cancelOrder. The request can then have a trackable identity and ordinary status codes.
Choose a verb by its intended effect
Choose a verb from its defined effect on server state, not habit. GET and HEAD are safe and retrieve information without changing business data. POST usually creates a subordinate resource or starts processing. PUT replaces a resource's state, PATCH applies a partial change, and DELETE requests removal. OPTIONS describes communication options and supports CORS preflight. An expensive read still belongs behind GET.
Safe and metadata requests
- GET retrieves a collection or individual representation.
- HEAD returns the same status and headers without a response body.
- OPTIONS reports supported methods and cross-origin capabilities.
State-changing requests
- POST creates a subordinate resource or initiates processing.
- PUT replaces the selected resource with the submitted representation.
- PATCH applies only the fields allowed by the patch contract.
- DELETE requests removal of the selected resource.
GET, HEAD, OPTIONS, PUT, and DELETE are idempotent: repeating a request has no effect beyond the first successful application. POST is generally not, while PATCH is not unless its contract says so. For retryable order or payment creation, accept an Idempotency-Key header, store it with the result, and return the original response when that key reappears.
Idempotency does not mean a request cannot fail. Deleting an already deleted shipment may return 204, though some APIs return 404 to expose current state. Document the choice and apply it consistently. GET must also be safe: a repeated read should not mutate counters, trigger fulfilment, or send a one-time receipt.
Shape URLs and response representations
Represent resources with JSON or another negotiated media type. Specify it through Content-Type and Accept headers. A 201 response for a new order can contain the order and a Location header pointing to /orders/{orderId}. Use 200 for a useful representation, 204 for success without a body, and 304 when a cached representation remains current. Choose one casing and timestamp convention.
Prefer a stable public representation over exposing persistence models directly. This prevents internal changes from leaking password hashes, risk scores, or supplier margins. Mapping entities to DTOs is an application concern; this automapper object mapping guide shows how AutoMapper can keep that transformation explicit in .NET. Keep the contract under test so an accidental property rename is caught.
Make representation details deterministic. Store dates in UTC using RFC 3339, such as 2026-10-04T02:15:00Z, even when an Australian interface displays 4 October as 04/10/2026. Monetary values need a currency and precise decimals rather than binary floating point. For AUD 129.95, use 129.95 with "currency": "AUD" or minor units of 12995, but do not mix conventions.
Make state changes safe and repeatable
PUT and PATCH need precise contracts. PUT expects a complete replacement, so omitted fields may reset to defaults. PATCH sends only changes and therefore requires a merge policy. State whether null clears a value, unknown properties are rejected, and arrays replace or append. These rules stop one client's partial update from silently undoing another client's work.
Concurrency matters when two systems update one order. Return an ETag and require If-Match on updates. If the tag no longer matches, respond 412 rather than overwriting newer data. Last-Modified can support coarse checks, but an ETag changes whenever the representation does. Services without ETags should still document how they handle conflicting updates.
Keep requests stateless wherever possible: the client supplies credentials or a token, and each request carries enough context to act. For long-running work, POST can create a job resource; clients then GET /jobs/{jobId} until it completes. This pattern handles exports, report generation, and bulk imports without inventing a nonstandard /wait action.
Return errors clients can act on
Errors should identify what failed and whether retrying can help. Use 400 for malformed syntax, 401 for missing or invalid authentication, 403 for insufficient permission, 404 for an unavailable resource, and 405 when the route exists but the method does not. Add an Allow header to 405. Use 422 when syntax is valid but values violate business rules, and include the offending field path.
Adopt one error shape, ideally the Problem Details format from RFC 9457, with type, title, status, detail, and a correlation identifier. Never return stack traces, database names, or secrets. Tell consumers that orderId was not found, not merely that a null reference occurred. Log diagnostic detail internally and return a safe reference support staff can trace.
Rate limiting also needs an explicit response. Send 429 with Retry-After when capacity is temporarily constrained. Distinguish 415 Unsupported Media Type from a valid payload that fails validation. Handle stale conditional requests with 412 and conflicting state transitions with 409. Consistent status semantics let mobile clients decide whether to refresh, correct a field, sign in again, or wait.
Secure and scale collection access
Treat authorization as part of the endpoint contract. OAuth 2.0 access tokens and OpenID Connect are common for web and mobile products; service accounts often use client credentials. Validate audience, issuer, expiry, and scope. Return 401 when credentials are absent or invalid, reserving 403 for an authenticated caller without permission. Object-level checks matter too: access to /orders/{orderId} must verify that the order belongs to the permitted customer.
Collections need controls that remain predictable as data grows. Prefer cursor pagination for large or changing datasets and return a next cursor when more results exist. Define permitted filters, sort fields, and maximum page size; reject expensive query combinations before they reach the database. Cacheable reads can carry Cache-Control, ETag, and Last-Modified headers. Prevent shared caches from storing personalised data unless policy explicitly permits it.
Content negotiation and CORS are separate. Accept tells the server which representation the client wants; CORS tells the browser which origins may call it. Handle OPTIONS preflight without requiring a business Authorization header, then allow only the necessary origins, methods, and headers. Teams in Melbourne and Sydney often coordinate several apps and partner integrations, so explicit CORS rules and documented token scopes are easier to support than permissive defaults.
Test, document, and release the contract
Document the API before it stabilises, then treat the documentation as an executable contract. OpenAPI can describe routes, methods, parameters, schemas, responses, security requirements, and examples. Derive contract tests from it and exercise real HTTP semantics in integration tests. Verify that GET remains safe, PUT and DELETE are repeatable, PATCH merges only intended fields, and errors match the documented media type.
Test failure paths as carefully as happy paths. Cover malformed JSON, expired tokens, missing fields, stale ETags, unsupported media types, empty collections, and pagination beyond the final page. Run tests through the same routing, binding, serialization, and status-code pipeline used in production. Include a request identifier in responses and logs, record endpoint and method separately, and measure latency and errors by operation.
Roll out changes with compatibility in mind. Add a version only when a breaking change cannot be delivered through additive fields, tolerant parsing, or a new endpoint. Publish deprecation dates, monitor consumption, and retain the old contract for its stated window. Pick one endpoint in your system today, define its resource, method, idempotency rules, and response codes, then verify that contract with an automated test before exposing it to clients.