A good API is a long-term contract, not merely a set of endpoints. It should be predictable for clients, safe under retries, observable in production, and flexible enough to evolve without breaking existing integrations.
1. Design around resources, not actions
Use nouns that represent domain concepts:
GET /orders/123
POST /orders
PATCH /orders/123
For domain actions that are not simple field updates, use a meaningful sub-resource such as POST /orders/123/cancellations.
2. Use HTTP methods according to their semantics
GETreads without changing business state.POSTcreates a resource or starts a non-idempotent operation.PUTreplaces a representation and should be idempotent.PATCHapplies a partial update.DELETEremoves or deactivates a resource according to the contract.
3. Return meaningful status codes
Use 200 for successful reads or updates, 201 for creation, 202 for accepted asynchronous work, and 204 when no response body is needed. Distinguish validation, authentication, authorization, conflicts, rate limits, and server failures.
4. Standardize error responses
{
"error": {
"code": "validation_failed",
"message": "The request contains invalid fields.",
"fields": {"email": ["A valid email is required."]},
"request_id": "req_01..."
}
}
A stable machine-readable code lets clients react without parsing human text. A request ID connects client reports to server logs.
5. Plan pagination, filtering, and sorting early
Large collections need explicit limits and deterministic ordering. Cursor pagination usually behaves better than page numbers when records change frequently.
GET /orders?status=paid&sort=-created_at&limit=50&cursor=...
6. Model long-running work asynchronously
Return 202 Accepted with a job resource rather than holding a connection open:
POST /exports
GET /jobs/job_123
Define progress, completion, failure, expiration, and cancellation behavior.
7. Preserve compatibility
Prefer additive changes. Do not silently rename fields, change types, or reinterpret existing values. Version when a breaking change is unavoidable and publish a migration and retirement timeline.
8. Apply security in layers
- Authenticate every protected request and authorize the specific resource.
- Validate input and constrain response fields.
- Use TLS, rate limiting, least-privilege credentials, and secret rotation.
- Protect against object-level authorization failures.
- Use idempotency keys for retry-sensitive creation and payment operations.
9. Describe the contract with OpenAPI
An OpenAPI document supports documentation, generated clients, mock servers, contract tests, and review. Keep it synchronized with implementation in CI.
10. Design for observability
Record structured logs, latency, error rates, request volume, dependency health, and trace context. Avoid logging credentials or sensitive payloads.
Release checklist
- Resources and methods are consistent.
- Errors and status codes are documented.
- Pagination has deterministic ordering.
- Authorization is tested at object level.
- Retries and idempotency are defined.
- OpenAPI and contract tests are current.
- Metrics, logs, and alerts are ready.




No comments yet. Be the first to share your thoughts.