Contents

A good REST API is predictable: resources are nouns, HTTP methods and status codes mean what they should, errors share one format, lists paginate the same way everywhere, and changes do not break existing clients. All of this comes from conventions agreed at the start and written into a specification, not from heroics on every new endpoint.

Below are the rules we follow in APIs for web and mobile clients, with example requests and responses.

What makes a REST API easy to use?

A REST API is easy to use when a client can guess how a new endpoint works from the ones it already knows. A mobile developer should not have to read the docs every time to find out how to pass a filter, where the error is or how to fetch the next page.

Predictability rests on a few conventions:

  • resources are plural nouns: /orders, /orders/42/items;
  • the HTTP method defines the action, not the path: not /getOrders but GET /orders;
  • dates, money and identifiers use one format across the whole API;
  • errors, pagination and filters work the same way in every section.

Write these conventions down in a short document before the first line of code, and check them in review just like code style.

How should you name resources and use HTTP methods?

Name resources with nouns and use HTTP methods for their meaning: GET reads, POST creates, PATCH partially updates, PUT replaces, DELETE removes. Nesting in the path expresses ownership, but no deeper than one or two levels.

Operation Request Success response
List orders GET /orders?status=paid&page=2 200 with an array and page metadata
One order GET /orders/42 200 with the order object
Create an order POST /orders 201 with the created object
Change status PATCH /orders/42 200 with the updated object
Delete an order DELETE /orders/42 204 with no body
Action on an order POST /orders/42/cancel 200, or 202 if processing is asynchronous

The last row is the exception to the "no verbs" rule. Actions that are not just a field change — cancelling, resending, paying — are clearer as a sub-resource than hidden inside a PATCH with a magic value.

How should you return errors?

Return errors with the correct HTTP status and a body in one format across the API: a machine-readable code, a human message and per-field details. A client should be able to handle any error with one function without parsing message text.

The RFC 9457 standard (Problem Details for HTTP APIs) is a good basis:

{
  "type": "https://api.example.com/errors/validation",
  "title": "Validation failed",
  "status": 422,
  "errors": {
    "email": ["This email is already registered"],
    "phone": ["Invalid phone number format"]
  }
}

Which status codes to use

  • 400 — the request cannot be parsed: broken JSON, wrong parameter type;
  • 401 — the client is not authenticated; 403 — authenticated but not allowed;
  • 404 — the resource does not exist, or the client must not know it exists;
  • 409 — state conflict: the order is already paid, the version is stale;
  • 422 — the data is understood but fails business validation;
  • 429 — rate limit exceeded, with a Retry-After header;
  • 5xx — a server error; the body contains no stack traces or internals.

A screen with program code Photo: Unsplash

How should pagination, filters and sorting work?

Pagination, filters and sorting go in query parameters with the same names across the API. Page-number pagination works for small lists; feeds and large tables need cursor pagination, which does not skip or duplicate records when new ones are added.

GET /orders?status=paid&created_from=2026-10-01&sort=-created_at&cursor=eyJpZCI6MTAyNH0

A list response always wraps data and metadata the same way:

{
  "data": [{ "id": 1024, "status": "paid", "total": { "amount": 4990, "currency": "USD" } }],
  "meta": { "next_cursor": "eyJpZCI6MTAwMH0", "has_more": true }
}

Note the money: the amount is an integer in the currency's minor units together with a currency code. Floating-point numbers for money lead to rounding errors.

How do you change an API without breaking clients?

You can change an API safely if you add rather than modify: new fields and endpoints are safe, while renaming, removing or retyping a field breaks clients. This matters most for mobile apps — old versions stay on users' phones for months.

Rules that help:

  1. Add new fields without changing the meaning of old ones; clients must ignore unknown fields.
  2. Ship breaking changes as a new version: /v2/orders or a version header.
  3. Mark deprecated fields in the docs and keep them while they are in use.
  4. Describe the API in OpenAPI and check the spec in CI: a breaking change should fail the build.
  5. Log client versions so you know when old behaviour can be switched off.

The API contract matters for architecture too: if the front end and mobile apps talk to the server only through a documented API, the backend can evolve or even be rewritten without touching clients. We covered this in our piece on monoliths and microservices.

In short

  • Resources are nouns, the HTTP method sets the action and status codes mean what they say.
  • All errors share one format, such as RFC 9457, with per-field details.
  • Pagination, filters and sorting work the same everywhere; money is an integer with a currency.
  • Evolve the API by adding; ship breaking changes as a new version.
  • Describe the API in OpenAPI and check the contract automatically.

FAQ

  • Should the API have a version in the URL from day one?

    Adding a /v1 prefix from the start costs nothing and makes a breaking release easier later. But ship a second version as rarely as possible and evolve the API with compatible changes.

  • How should a REST API be documented?

    With an OpenAPI specification generated from code or checked against it in CI. It powers interactive docs, client SDKs and contract tests, so it does not drift from reality.

  • snake_case or camelCase in JSON?

    Either, as long as it is one style for the whole API. We usually pick the style natural to the backend and never mix: switching styles between sections is a common source of client bugs.

  • How do you protect an API from overload?

    Use per-client and per-endpoint rate limits returning 429 with Retry-After, cache frequent reads and cap page sizes. Run heavy operations asynchronously and return 202.