← Blog

The API Design Principles Behind Every Great Developer Experience

Naming conventions, versioning, error handling, pagination, and documentation practices that separate mediocre APIs from ones developers actually enjoy using.

Kiran Babu · 2024-12-30 · Development

The API Design Principles Behind Every Great Developer Experience

A great API is one that developers don't have to read docs for — it's discoverable, predictable, and forgiving. Building APIs that developers enjoy is a craft, and it separates platforms that get adopted from those that don't.

How should you name REST API resources?

  • Plural nouns for resources: /orders, /products, not /order, /getProduct.
  • Nested resources for clear ownership: /users/{id}/orders.
  • Consistent casing: kebab-case for URLs, camelCase for JSON properties.
  • No verbs in URLs — use HTTP methods for actions.

What should an API error response look like?

Errors should tell developers what went wrong AND what to do about it. Use structured error responses: { code, message, details, helpUrl }. Map errors to semantic HTTP status codes. Never return 200 with an error body — it breaks every HTTP client in existence.

422 Unprocessable Entity — an error a developer can act on without opening a ticket
{
  "code": "validation_failed",
  "message": "The order could not be created.",
  "details": [
    { "field": "items[0].quantity", "issue": "must be greater than 0", "received": 0 },
    { "field": "shippingAddress.postalCode", "issue": "required for country US" }
  ],
  "helpUrl": "https://api.example.com/docs/errors/validation_failed",
  "requestId": "req_01HQ8Z3K2"
}
// requestId is the difference between "it's broken" and a support
// conversation that resolves in one message — always include it.

Should you use offset or cursor pagination?

Offset pagination (?page=3&limit=50) is fine for small, static datasets and quietly wrong for everything else: rows inserted while a client is paging shift the offsets, so records get skipped or returned twice. Cursor pagination encodes a stable position instead, and it stays correct under concurrent writes. Return the cursor as an opaque string — the moment integrators start decoding it, its format becomes your public API.

Cursor pagination — request and response shape
GET /v1/orders?limit=50&cursor=eyJpZCI6Im9yZF8xOTIzIn0

200 OK
{
  "data": [ /* 50 orders, newest first */ ],
  "pagination": {
    "nextCursor": "eyJpZCI6Im9yZF8xODcxIn0",   // null on the last page
    "hasMore": true
  }
}
// No total count: computing it costs a full table scan on large
// datasets. Expose it as a separate endpoint if clients genuinely
// need it, rather than taxing every page request.

How do you stop API docs from going stale?

Hand-written API docs drift from the implementation within a release or two — and stale docs are worse than none, because integrators trust them. Generate the reference from the same source the server validates against.

  • Keep an OpenAPI description as the source of truth and generate the reference docs from it, so a shipped endpoint change cannot leave the docs behind.
  • Validate real requests against that schema in CI — a contract test catches the mismatch before integrators do.
  • Give every endpoint a copy-pasteable example with a real (sandbox) payload, not just a field table.
  • Document the error codes as thoroughly as the success paths; that is what developers actually search for at 2am.

How should you version a REST API?

URL versioning (/v1/, /v2/) is still the most practical approach for REST APIs. Deprecation notices via Deprecation and Sunset headers give integrators time to migrate. Document your API lifecycle policy upfront — 12 months minimum support after deprecation announcement.

Related Reading