Resource API design

A useful API contract explains what a resource represents and how requests behave during contention, failure, and change. Consistent semantics matter more than a large endpoint catalog.

Handle concurrent writes
Example: conditional invoice update
Current resource/invoices/inv_1042Version 8
RequestPATCH /invoices/inv_1042
If-Match: "invoice-version-7"
412
Precondition Failed

The update was rejected. The invoice remains at version 8.

Retry with the current version

  1. Fetch the current invoice and its validator.
  2. Review the changes before updating the request.
  3. Resubmit with the current validator. If the invoice changes again, repeat the review.

Define resources around stable concepts

Choose resource identities that reflect the business model. An invoice, its approval history, and an export job have different lifecycles and should not become one overloaded response. Keep identifiers stable while allowing display names and descriptive attributes to change. Document who owns each resource lifecycle.

Use HTTP methods according to their semantics. A read should not trigger a business mutation. PUT and DELETE have idempotent intended effects as defined by HTTP, while a POST operation needs its own duplicate handling strategy when retries can occur. Specify accepted content types, size limits, and supported query parameters rather than allowing undefined parser behavior.

Handle concurrency explicitly

Use conditional requests when a write must apply to a known resource version. A strong ETag and If-Match condition can prevent a user from overwriting a concurrent change. HTTP defines strong comparison for If-Match; a failed condition normally produces HTTP 412. An API requiring a missing precondition can use HTTP 428. Evaluate the condition against the current representation at the write boundary.

For long running operations, return a documented job resource or status location and distinguish acceptance from completion. Consumers need to know whether they should poll, subscribe, cancel, or await a callback. A successful acceptance response should never imply that an external action has already finished.

http
PATCH /invoices/inv_1042 HTTP/1.1
Host: api.example.com
Content-Type: application/json
If-Match: "invoice-version-7"

{ "reviewNote": "Contract terms checked" }

Example conditional update using a strong ETag and If-Match.

Make error responses stable and actionable

RFC 9457 defines problem details for HTTP APIs. Use a stable problem type and structured extensions for machine behavior, while a clear human detail explains the specific occurrence. Clients should not parse prose to decide whether to retry or highlight a field.

Keep the HTTP status aligned with the actual response and avoid leaking internal queries, stack traces, credentials, or another tenant's resource existence. Include a safe correlation reference for support. Define validation errors, authentication failures, permission denials, conflicts, rate limits, and temporary service failures as different cases with distinct recovery behavior.

Document and test the API contract

An OpenAPI description can document operations, schemas, parameters, and security requirements. Validate it in delivery workflows and test that the implementation agrees with it. A generated specification is helpful only when examples, required fields, and error responses reflect actual behavior.

Build consumer scenarios covering empty results, maximum payloads, invalid filters, interrupted requests, expired credentials, and conditional write conflicts. Verify authorization on every resource lookup and action. Compatibility review should include semantic changes such as a status redefinition, because unchanged JSON structure can still break a consumer's business logic.

References