Skip to main content

How to Design a REST API That Developers Love

·APIScout Team
api designrest apideveloper experiencebest practicesapi architecture

How to Design a REST API That Developers Love

The best APIs share a quality: developers figure them out without reading the docs. Resource naming is intuitive. Error messages explain what went wrong and how to fix it. Pagination works consistently. Authentication is a single header. Here's how to design APIs that developers reach for.

1. Resource Naming

Use Nouns, Not Verbs

Resources are things, not actions. HTTP methods provide the verbs.

✅ GET    /users          → List users
✅ POST   /users          → Create user
✅ GET    /users/123      → Get user
✅ PUT    /users/123      → Update user
✅ DELETE /users/123      → Delete user

❌ GET /getUsers
❌ POST /createUser
❌ POST /deleteUser/123

Use Plural Nouns

Consistency matters more than grammar debates. Use plurals everywhere.

✅ /users
✅ /orders
✅ /products

❌ /user
❌ /order/123   (mixing singular and plural)

Nest for Relationships

Sub-resources express relationships. Keep nesting to 2 levels max.

✅ GET /users/123/orders          → User's orders
✅ GET /users/123/orders/456      → Specific order

❌ GET /users/123/orders/456/items/789/reviews  → Too deep

Use kebab-case

✅ /api/order-items
✅ /api/user-profiles

❌ /api/orderItems
❌ /api/order_items

2. HTTP Methods and Status Codes

Methods

MethodPurposeIdempotentSafe
GETRead resource(s)YesYes
POSTCreate resourceNoNo
PUTReplace resource (full)YesNo
PATCHUpdate resource (partial)NoNo
DELETERemove resourceYesNo

Status Codes

Use specific codes. Don't return 200 for everything.

CodeMeaningWhen to Use
200OKSuccessful GET, PUT, PATCH, DELETE
201CreatedSuccessful POST (include Location header)
204No ContentSuccessful DELETE with no response body
400Bad RequestInvalid input (validation errors)
401UnauthorizedMissing or invalid authentication
403ForbiddenAuthenticated but not authorized
404Not FoundResource doesn't exist
409ConflictDuplicate resource, state conflict
422Unprocessable EntitySemantically invalid input
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server failure

3. Error Handling

The Error Response

Every error should include a machine-readable code, human-readable message, and actionable detail.

{
  "error": {
    "code": "validation_error",
    "message": "The request body contains invalid fields.",
    "details": [
      {
        "field": "email",
        "code": "invalid_format",
        "message": "Must be a valid email address."
      },
      {
        "field": "age",
        "code": "out_of_range",
        "message": "Must be between 13 and 120."
      }
    ],
    "request_id": "req_abc123",
    "documentation_url": "https://api.example.com/docs/errors#validation_error"
  }
}

Error Design Rules

  1. Machine-readable codesvalidation_error, not_found, rate_limit_exceeded
  2. Human-readable messages — explain what happened in plain English
  3. Field-level details — for validation errors, tell the client which fields failed and why
  4. Request ID — for debugging and support tickets
  5. Documentation link — point to docs explaining the error

4. Pagination

{
  "data": [...],
  "pagination": {
    "has_more": true,
    "next_cursor": "eyJpZCI6MTIzfQ=="
  }
}

Request: GET /users?limit=20&cursor=eyJpZCI6MTIzfQ==

Pros: Consistent results when data changes. Performs well at any depth. Cons: Can't jump to page 5 directly. Can't show "page 3 of 10."

Offset-Based

{
  "data": [...],
  "pagination": {
    "total": 500,
    "limit": 20,
    "offset": 40,
    "has_more": true
  }
}

Request: GET /users?limit=20&offset=40

Pros: Simple. Can jump to any page. Can show total count. Cons: Inconsistent results if data changes between pages. Slow at deep offsets (OFFSET 10000).

Recommendation: Cursor-based for most APIs. Offset-based only when random access is required.

5. Filtering and Sorting

GET /users?status=active&role=admin          → Filter
GET /users?sort=created_at&order=desc        → Sort
GET /users?fields=id,name,email              → Sparse fields
GET /users?search=john                       → Search

Keep It Consistent

  • Use the same parameter names across all endpoints
  • sort for field name, order for direction (asc/desc)
  • limit and cursor/offset for pagination
  • fields for sparse fieldsets
  • search for full-text search

6. Versioning

Start with URL path versioning: /api/v1/users. It's the most understood approach.

Rules:

  • Add fields without incrementing version (additive changes are not breaking)
  • Increment version for breaking changes (field removal, type changes, behavior changes)
  • Support old versions for 12+ months after deprecation announcement
  • Document what constitutes a "breaking change" for your API

7. Authentication

Use Authorization: Bearer <token> header. Don't put tokens in URLs (they leak in logs, referrers, and browser history).

✅ Authorization: Bearer sk_live_abc123
❌ GET /users?api_key=sk_live_abc123

8. Response Envelope

Consistent Wrapping

Wrap all responses in a consistent envelope:

{
  "data": { ... },
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

For lists:

{
  "data": [ ... ],
  "pagination": { ... },
  "meta": { ... }
}

9. Learn from the Best

APIWhat They Do Well
StripeConsistent naming, excellent errors, idempotency keys, expandable objects
GitHubDiscoverable (HATEOAS links), consistent pagination, excellent docs
TwilioResource-oriented, consistent patterns across all products
SlackSimple methods, clear error codes, rate limit transparency
OpenAIClean JSON, streaming support, clear model selection

Designing APIs? Explore API design tools, gateways, and best practices on APIScout — comparisons, guides, and developer resources for building great APIs.

Comments