APIs are the contract between your system and its consumers. A well-designed API makes integration straightforward. A poorly designed one creates friction that compounds over time.
Consistent resource naming
Use plural nouns for resources (/users, /orders). Nest resources to show hierarchy (/orders/123/items). Use query parameters for filtering and pagination. Avoid verbs in URLs — HTTP methods already express the action.
Versioning
Put a version prefix on your API path (/v1/orders). Version when you make breaking changes to the contract. Non-breaking additions (new fields, new endpoints) do not require a new version.
Error responses
Return consistent error structures. Include a machine-readable error code, a human-readable message, and a trace ID for debugging.
{
"code": "order_not_found",
"message": "Order 456 was not found",
"trace_id": "abc-123"
}
Use standard HTTP status codes. 400 for validation errors, 404 for not found, 409 for conflicts, 500 for server errors.
Pagination
Always paginate list endpoints. Use cursor-based pagination for large datasets. Return next and previous cursors in the response so clients do not construct URLs.
Validation
Validate input at the API boundary. Return detailed error messages for each invalid field. Never trust client input, even if it comes from your own frontend.