Zuplo
API Design

API Design Patterns: REST, Pagination, Versioning & Error Handling

Martyn DaviesMartyn Davies
May 30, 2025
8 min read

Learn essential API design patterns including RESTful resource naming, pagination strategies, versioning approaches, error handling, and rate limiting.

Building an API that developers actually want to use takes more than just exposing endpoints. According to Postman’s 2025 State of the API Report, 82% of organizations have adopted an API-first approach, meaning the bar for API quality and consistency keeps rising. To stand out, you need to deliver an exceptional developer experience, seamless integrations, and adaptable systems.

This guide walks through the API design patterns that matter most — the ones that directly impact usability, scalability, and long-term maintainability.

Why API Design Patterns Matter

API design patterns are reusable solutions to common problems you’ll encounter when building APIs. They create a shared language among your team while delivering strategic benefits that directly impact your bottom line:

  • Improved scalability: Patterns like pagination, caching, and rate limiting help you handle traffic spikes without breaking a sweat.
  • Enhanced maintainability: Consistent patterns make your APIs easier to understand, debug, and evolve over time.
  • Better developer experience: APIs that follow familiar patterns feel natural to use, leading to faster integration and fewer support tickets.
  • Increased adaptability: Flexible patterns like versioning and hypermedia controls let you evolve your API without breaking existing integrations.
  • Reduced development time: Proven solutions save you from reinventing the wheel for every new endpoint.

A programmable API gateway serves as a powerful tool for implementing design patterns through code rather than complex configurations, keeping your APIs consistent without heroic effort.

RESTful Resource Design

REST works because it leverages HTTP naturally and focuses on resources rather than actions. Before committing to REST, it’s worth understanding how REST compares to other architectures like GraphQL — but for most public APIs, REST remains the dominant choice.

The core principles of RESTful design are:

  • Noun-based resource naming: Use /users not /getUsers. Resources are things, not actions.
  • HTTP methods for operations: Let GET, POST, PUT, PATCH, and DELETE express intent.
  • Stateless communication: Each request contains all information needed to process it.
  • Uniform interfaces: Consistent URL structures and response formats across your entire API.

A well-designed RESTful API looks like this:

plaintext
GET    /api/products              # List all products
GET    /api/products/42           # Get product with ID 42
POST   /api/products              # Create a new product
PUT    /api/products/42           # Replace product 42
PATCH  /api/products/42           # Partially update product 42
DELETE /api/products/42           # Delete product 42

For nested resources, keep URLs shallow. Prefer /products/42/reviews over deeply nested paths like /companies/5/stores/12/products/42/reviews. Deep nesting makes URLs fragile and harder to cache. You can learn more about avoiding common pitfalls in RESTful API design.

API Versioning Strategies: URL vs. Header vs. Content Negotiation

Your API will evolve and eventually introduce breaking changes. Versioning ensures you can move forward without leaving existing integrations behind. Understanding different API versioning strategies helps you pick the right approach for your situation.

URL Path Versioning

The most common approach, used by companies like Twilio, SendGrid, and GitHub:

plaintext
https://api.example.com/v1/resources
https://api.example.com/v2/resources

URL path versioning is easy to spot, debug-friendly, and works well with caching. The trade-off is that it adds a prefix to every route.

Header-Based Versioning

Keeps URLs clean by moving the version into a custom header:

plaintext
Api-Version: 2024-01-15

This approach aligns with HTTP standards and is gaining traction for APIs that want cleaner URLs. The downside is that it’s less visible in logs and harder to test in a browser.

Content Negotiation

Uses the Accept header to embed version information:

plaintext
Accept: application/vnd.example.v1+json

This is the most REST-purist approach but adds complexity for API consumers.

Regardless of which strategy you choose, semantic versioning (MAJOR.MINOR.PATCH) communicates exactly what users should expect with each update. And plan your deprecation strategy from day one — a clear sunset policy builds trust with your API consumers.

Rate Limiting Patterns That Protect Your Resources

Rate limiting protects your API from abuse while ensuring fair access for all users. There are three primary algorithms to choose from, each with different trade-offs:

  • Fixed window: Caps requests within a set time window (e.g., 100 requests per minute). Simple to implement but can allow traffic spikes at window boundaries.
  • Sliding window: Tracks requests over a rolling period for smoother control. Eliminates the burst problem at window edges but requires more memory to track individual request timestamps.
  • Token bucket: Allows short bursts of traffic while maintaining an overall rate. Tokens refill at a steady rate and each request consumes one token. This is the most flexible algorithm and is commonly used by AWS and other cloud providers.

Communicate rate limits clearly with standard headers so clients can self-regulate:

plaintext
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 75
X-RateLimit-Reset: 1621872000

When limits are reached, return a 429 Too Many Requests status with a Retry-After header. For a deeper dive on implementation, see our guide to best practices for API rate limiting.

Pagination Patterns: Cursor-Based vs. Offset-Based

Any API that returns collections of data needs a pagination strategy. The two most common approaches have distinct strengths.

Offset-Based Pagination

The simplest approach — clients specify a starting point and page size:

plaintext
GET /api/products?offset=20&limit=10

Offset pagination is easy to understand and lets users jump to any page. However, it has a critical flaw: if records are inserted or deleted between requests, clients can see duplicate results or skip items entirely. Performance also degrades on large datasets because the database must scan past all skipped rows.

Cursor-Based Pagination

Clients pass an opaque cursor that points to a specific record:

plaintext
GET /api/products?cursor=dXNlcjpXMDdRQ1JQQTQ=&limit=10

Cursor pagination avoids duplicates and skipped records, and performs consistently regardless of how deep into the dataset you’ve paginated. The trade-off is that clients can’t jump to an arbitrary page — they must traverse sequentially. This is the preferred pattern for real-time feeds, large datasets, and any API where data changes frequently.

Include helpful metadata in your pagination responses:

JSONjson
{
  "data": [
    { "id": 1, "name": "Product A" },
    { "id": 2, "name": "Product B" }
  ],
  "pagination": {
    "hasMore": true,
    "nextCursor": "dXNlcjpXMDdRQ1JQQTQ=",
    "prevCursor": "dXNlcjpXMDdRQ0xPVTM="
  }
}

Caching Patterns for API Performance

Strategic caching is what separates high-performance APIs from those that crumble under load.

Use HTTP headers to control caching behavior at multiple layers:

plaintext
Cache-Control: max-age=3600, public
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 May 2025 13:28:00 GMT

Support conditional requests with If-None-Match and If-Modified-Since headers. When the content hasn’t changed, the server returns a 304 Not Modified response with no body, saving bandwidth and reducing latency.

Layer your caching strategy for maximum impact:

  • Client-side caching: Browsers and API clients store responses locally based on Cache-Control headers.
  • Gateway-level caching: An API gateway can cache responses at the edge, closer to your users, for faster response times.
  • Server-side caching: Use in-memory stores like Redis to cache expensive database queries or computed results.

Authentication and Authorization Patterns

Protecting your API requires battle-tested authentication methods:

  • API keys: The simplest option, ideal for identifying callers and tracking usage. Best suited for server-to-server communication where you can keep keys secret.
  • OAuth 2.0: The standard for delegated authorization. Users grant applications limited access to their resources without sharing credentials.
  • JWT (JSON Web Tokens): Compact, self-contained tokens that carry claims about the user. Commonly used alongside OAuth 2.0 for stateless authentication.
  • Mutual TLS (mTLS): Both client and server verify each other’s certificates. Provides the strongest authentication guarantees for service-to-service communication.

Authorization goes beyond identity — it determines what an authenticated user can do. Use role-based access control (RBAC) or attribute-based access control (ABAC) to enforce fine-grained permissions at the API gateway level. For more detail, see our API security best practices guide.

Error Handling With RFC 7807 Problem Details

Great error handling turns frustrating failures into actionable guidance. Instead of inventing your own error format, use the Problem Details standard (RFC 9457, which supersedes the original RFC 7807). This gives consumers a consistent, machine-readable structure they can parse programmatically.

A Problem Details response includes:

  • type: A URI identifying the error type
  • title: A short, human-readable summary
  • status: The HTTP status code
  • detail: A human-friendly explanation specific to this occurrence
  • instance: A URI identifying this specific error occurrence

Here’s what a well-structured error response looks like:

JSONjson
{
  "type": "https://api.example.com/errors/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "The email field is not in a valid format.",
  "instance": "/api/users/signup",
  "errors": [
    {
      "field": "email",
      "message": "Must be a valid email address"
    }
  ],
  "trace": {
    "requestId": "f7a8b99c-9e66-4ae9-b3e2-c3b6e8f66a4a",
    "timestamp": "2025-05-30T14:22:00Z"
  }
}

Zuplo has built-in support for Problem Details through its HttpProblems helper, which generates RFC 7807-compliant responses from any policy or handler with a single function call. For more patterns, see our guide on best practices for API error handling.

Idempotency Patterns for Safe Retries

Idempotency ensures that sending the same request multiple times produces the same result — critical for payment processing, order creation, and any operation where duplicates cause real-world problems.

HTTP has built-in idempotency for GET, PUT, and DELETE methods. The challenge is making POST requests idempotent for operations that create new resources. The standard approach is idempotency keys:

plaintext
POST /api/orders
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000

The server stores the result of the first request associated with this key. If the same key appears again (e.g., due to a network retry), the server returns the stored result instead of processing the request a second time. Stripe popularized this pattern, and it has since become a widely adopted standard for financial and e-commerce APIs.

HATEOAS and Hypermedia-Driven APIs

HATEOAS (Hypermedia as the Engine of Application State) helps APIs evolve without breaking clients by including discoverable links in every response:

JSONjson
{
  "departmentId": 10,
  "name": "Engineering",
  "links": [
    { "rel": "self", "href": "/management/departments/10" },
    { "rel": "employees", "href": "/management/departments/10/employees" },
    { "rel": "update", "href": "/management/departments/10", "method": "PUT" }
  ]
}

The benefits of hypermedia-driven APIs are significant:

  • Dynamic discovery: Clients navigate by following server-provided links rather than hardcoding URLs.
  • Client-server decoupling: Backend URL restructuring doesn’t break clients because they follow links, not memorize paths.
  • Self-describing responses: Each response provides context about what actions are available, reducing documentation burden.
  • Graceful evolution: New capabilities are exposed by adding new links without changing existing ones.

GitHub’s REST API is a well-known example of HATEOAS in practice, enabling clients to discover related repositories and actions dynamically.

Bringing It All Together

Well-designed APIs using these patterns become true business assets. They speed up development, reduce technical debt, and enable integrations that would otherwise require custom solutions.

The key is consistency: pick patterns that work for your use case, document them thoroughly, and apply them uniformly across every endpoint. A programmable API gateway can enforce many of these patterns — rate limiting, authentication, caching, and error formatting — at the infrastructure level, so your backend services can focus on business logic.

Ready to see how these patterns work in practice? Start building with Zuplo and implement professional API design patterns with a programmable gateway.