Menu
AgiCAD Home
API Engineering April 16, 2026 · 11 min read

REST API Design Best Practices: Lessons from 50+ Client Projects

After designing REST APIs for fintech platforms, SaaS products, e-commerce systems, and data aggregators, here are the principles that prevent the most common — and most costly — design mistakes.

APIs are contracts. When an API is designed well, every developer who works with it — internal or external, now or five years from now — can use it intuitively, extend it cleanly, and rely on it under load. When an API is designed poorly, it generates an ongoing tax on every team that touches it: inconsistent naming, ambiguous error responses, brittle versioning, and patterns that make simple things hard.

At AgiCAD, REST APIs are part of almost every project we build. We have made every mistake in this guide at least once. These principles emerged from hard experience — from debugging production incidents, from client onboarding sessions where developers asked "why does this endpoint work this way?", and from post-mortems on migrations that took twice as long as they should have because the original API design created unnecessary coupling.

Design for the Consumer, Not the Database

The most pervasive antipattern we encounter in inherited API codebases is the "database API" — endpoints that map 1:1 to database tables, return raw model data, and expose implementation details that have no place in a public interface. This approach feels fast to implement but creates deep problems:

  • Changing the database schema breaks external clients.
  • Clients receive data they do not need and must filter it themselves.
  • Relationships meaningful to the consumer require multiple sequential requests to assemble.
  • Internal implementation concepts leak into the public interface.

Design from the consumer's perspective. What task is the consumer trying to accomplish? What data does that task require, and in what shape? The translation between the API shape and the underlying data model is your responsibility — not the consumer's.

Resource Naming: Nouns, Not Verbs

REST is built around resources — things, not actions. Endpoint URLs should be nouns that identify resources; HTTP methods carry the semantic meaning of the action.

✓ Good — noun-based resources

GET    /orders              → list orders
GET    /orders/42            → get order 42
POST   /orders               → create order
PUT    /orders/42            → replace order 42
PATCH  /orders/42            → partial update order 42
DELETE /orders/42            → delete order 42

✗ Avoid — verb-based endpoints

POST /getOrder
POST /createOrder
POST /deleteOrder

Use plural nouns consistently (/orders, not /order). Nested resources are appropriate when the relationship is genuinely hierarchical: GET /orders/42/items. Avoid nesting more than two levels deep.

HTTP Status Codes: Use Them Correctly

Returning 200 OK with an error object in the response body is a damaging antipattern — it prevents clients from using standard error handling and forces them to inspect every response body regardless of HTTP status. A practical mapping:

  • 200 OK — successful GET, PUT, PATCH
  • 201 Created — successful POST; include Location header pointing to new resource
  • 204 No Content — successful DELETE with no response body
  • 400 Bad Request — invalid client data; include validation details in response
  • 401 Unauthorized — authentication required or failed
  • 403 Forbidden — authenticated but not authorised
  • 404 Not Found — resource does not exist
  • 409 Conflict — request conflicts with current state (e.g. duplicate creation)
  • 429 Too Many Requests — rate limit exceeded; include Retry-After header
  • 500 Internal Server Error — unexpected server error; never expose stack traces to clients

Consistent Error Response Structure

Every error response in your API should have the same structure. Establish a standard error envelope early and enforce it everywhere:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request contains invalid fields.",
    "details": [
      { "field": "email", "message": "Must be a valid email address." }
    ],
    "request_id": "req_01HX7KZMN4P8FGTQ"
  }
}

The code field should be a machine-readable error type so clients can branch on specific conditions. The request_id enables support teams to trace any error to the exact log entry.

Versioning Strategy

Every API with more than one consumer needs a versioning strategy. Our preferred approach for most projects is URL versioning (/v1/orders): visible, cacheable, easy to route at the infrastructure level. Clients know exactly what version they are using.

Define your deprecation policy before you launch v2. How long will v1 remain supported? How will you notify consumers? Running multiple major API versions indefinitely is expensive — plan for sunset from the start.

Pagination: Design It Early

Any collection endpoint that could return more than 50 items needs pagination. Retrofitting pagination onto a live API is a breaking change. Design your pagination pattern on day one.

We prefer cursor-based pagination over offset-based for most collections. Cursor pagination provides consistent results even with concurrent modifications and is efficient at scale — it uses indexed range queries rather than OFFSET. Offset pagination is simpler to implement and easier for consumers to understand, but suffers on large datasets and with concurrent data modifications.

Authentication and Authorisation

Use OAuth 2.0 / JWT for authentication. Keep access tokens short-lived (15 minutes to 1 hour) and use refresh tokens for long sessions. Validate audience and issuer claims. Never put sensitive data in JWT payloads — the payload is base64 encoded, not encrypted.

Enforce authorisation at the resource level, not just the route level. A user allowed to call GET /orders/42 should only retrieve that order if they own it. Row-level security failures are among the most common vulnerabilities we find in security reviews of inherited codebases.

Documentation: The API Is Only as Good as Its Docs

Undocumented APIs consume enormous developer time in exploration and trial and error. Our standard for client project APIs:

  • OpenAPI 3.x specification maintained alongside code — ideally generated from code annotations.
  • Every endpoint documented with request/response schemas, example values, and error responses.
  • Hosted documentation with a live "Try it" feature accessible to client developers.
  • Changelog maintained for every breaking change, with migration guides.

Summary

  • Design from the consumer's perspective, not the database's.
  • Use nouns for resources, HTTP methods for actions.
  • Use HTTP status codes correctly — never 200 for errors.
  • Standardise error response structure across all endpoints.
  • Version from day one; define your deprecation policy before you need it.
  • Design pagination into collection endpoints before launch.
  • Enforce authorisation at the resource level, not just the route level.
  • Treat documentation as a first-class deliverable.

Good API design has a compounding return. Every hour invested in getting the design right before launch saves five hours of debugging, migration work, and developer confusion down the road.

If you are evaluating API design or planning a new project, reach out to the AgiCAD team — or explore our work in the portfolio.