← All articles

API Design Principles in 2026: REST, GraphQL, gRPC and the Patterns That Scale

API design is the contract between your frontend, mobile apps, third-party integrations, and your backend. A well-designed API accelerates development; a poor one creates years of technical debt. This guide covers the API design principles, patterns, versioning strategies, and governance that scale from startup to enterprise.

API design architecture diagram showing REST, GraphQL and gRPC endpoint flows with JSON schema cards

API design is the contract between your frontend, mobile apps, third-party integrations, and your backend. A well-designed API accelerates development; a poor one creates years of technical debt. This guide covers the API design principles, patterns, and governance that scale from startup to enterprise.

REST vs GraphQL vs gRPC β€” The 2026 Verdict

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         API STYLE DECISION MATRIX                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                             β”‚
β”‚  USE REST WHEN:                                                             β”‚
β”‚  βœ… Public/external APIs (widest adoption)                                 β”‚
β”‚  βœ… Simple CRUD resources                                                  β”‚
β”‚  βœ… Team knows HTTP/REST well                                              β”‚
β”‚  βœ… Caching via HTTP semantics important                                   β”‚
β”‚  βœ… Firewall/proxy compatibility needed                                    β”‚
β”‚  βœ… Tooling ecosystem matters (OpenAPI, Postman, etc.)                    β”‚
β”‚                                                                             β”‚
β”‚  USE GRAPHQL WHEN:                                                          β”‚
β”‚  βœ… Complex nested data requirements                                       β”‚
β”‚  βœ… Multiple clients with different data needs                             β”‚
β”‚  βœ… Real-time subscriptions needed                                         β”‚
β”‚  βœ… Frontend controls data shape (over/under-fetching)                    β”‚
β”‚  βœ… Strong TypeScript integration valued                                   β”‚
β”‚                                                                             β”‚
β”‚  USE gRPC WHEN:                                                             β”‚
β”‚  βœ… Internal microservices (performance critical)                         β”‚
β”‚  βœ… Polyglot microservices (Go, Java, Python, etc.)                       β”‚
β”‚  βœ… Streaming/bidirectional communication                                 β”‚
β”‚  βœ… Strict contract-first development                                      β”‚
β”‚  βœ… Low latency, high throughput required                                 β”‚
β”‚                                                                             β”‚
β”‚  USE tRPC WHEN:                                                             β”‚
β”‚  βœ… TypeScript monorepo (frontend + backend)                              β”‚
β”‚  βœ… End-to-end type safety without codegen                                β”‚
β”‚  βœ… Rapid iteration, minimal boilerplate                                  β”‚
β”‚  βœ… Team is TypeScript-native                                             β”‚
β”‚                                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

**Our default for web apps: REST + OpenAPI + TypeScript** β€” broadest compatibility, excellent tooling, team familiarity.

REST API Design Principles

Resource-Oriented URLs

# βœ… GOOD β€” Nouns, plural, hierarchical
GET    /api/v1/users                    # List users
POST   /api/v1/users                    # Create user
GET    /api/v1/users/{id}               # Get user
PATCH  /api/v1/users/{id}               # Partial update
DELETE /api/v1/users/{id}               # Delete user

GET /api/v1/users/{id}/posts # User's posts POST /api/v1/users/{id}/posts # Create post for user GET /api/v1/posts/{id} # Get post (also accessible directly)

# βœ… Sub-resources for relationships GET /api/v1/posts/{id}/comments POST /api/v1/posts/{id}/comments

# βœ… Custom actions as sub-resources (not verbs) POST /api/v1/posts/{id}/publish POST /api/v1/users/{id}/activate POST /api/v1/orders/{id}/cancel

# ❌ BAD β€” Verbs in URL, RPC-style GET /api/v1/getUsers POST /api/v1/createUser POST /api/v1/deleteUser GET /api/v1/getUserPosts?userId=123

HTTP Methods Semantics

| Method | Use Case | Idempotent | Safe | Request Body | Response | |--------|----------|------------|------|--------------|----------| | `GET` | Retrieve resource | βœ… | βœ… | ❌ | 200 + resource | | `POST` | Create resource | ❌ | ❌ | βœ… | 201 + created resource | | `PUT` | Full replace | βœ… | ❌ | βœ… | 200 + updated resource | | `PATCH` | Partial update | ❌* | ❌ | βœ… | 200 + updated resource | | `DELETE` | Delete resource | βœ… | ❌ | ❌ | 204 No Content | | `HEAD` | Metadata only | βœ… | βœ… | ❌ | Headers only | | `OPTIONS` | Capabilities | βœ… | βœ… | ❌ | Allow header |

*PATCH can be idempotent if implemented correctly (e.g., JSON Merge Patch).

Status Codes β€” Use Precisely

# Success
200 OK                    # GET, PUT, PATCH success
201 Created               # POST success (include Location header)
204 No Content            # DELETE, PATCH/PUT with no response body

# Client Errors (4xx) 400 Bad Request # Invalid JSON, validation failed 401 Unauthorized # Missing/invalid auth 403 Forbidden # Auth valid but insufficient permissions 404 Not Found # Resource doesn't exist 409 Conflict # Resource conflict (duplicate email, etc.) 422 Unprocessable Entity # Valid JSON but semantic errors 429 Too Many Requests # Rate limited

# Server Errors (5xx) 500 Internal Server Error # Unexpected error 503 Service Unavailable # Maintenance, overload

Error Response Format (RFC 7807)

{
  "type": "https://api.example.com/errors/validation-error",
  "title": "Validation Failed",
  "status": 422,
  "detail": "The request body failed validation",
  "instance": "/api/v1/users",
  "errors": [
    {
      "field": "email",
      "code": "invalid_format",
      "message": "Invalid email format"
    },
    {
      "field": "password",
      "code": "too_short",
      "message": "Password must be at least 8 characters"
    }
  ],
  "trace_id": "req_abc123"
}

Request/Response Standards

# List Response (paginated)
{
  "data": [...],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 150,
    "total_pages": 8
  },
  "links": {
    "first": "/api/v1/users?page=1",
    "last": "/api/v1/users?page=8",
    "prev": null,
    "next": "/api/v1/users?page=2"
  }
}

# Single Resource { "data": { "id": "usr_abc123", "type": "user", "attributes": { "name": "John Doe", "email": "john@example.com", "created_at": "2024-01-15T10:30:00Z" }, "relationships": { "posts": { "data": [ {"type": "post", "id": "post_abc"}, {"type": "post", "id": "post_def"} ], "links": { "self": "/api/v1/users/usr_abc123/relationships/posts", "related": "/api/v1/users/usr_abc123/posts" } } } }, "included": [...] }

Versioning Strategy

GET /api/v1/users
GET /api/v2/users          # Breaking changes in v2

Header Versioning (Internal/Microservices)

Accept: application/vnd.example.v2+json

Versioning Rules

  1. **Major versions in URL** (`/v1/`, `/v2/`) β€” Breaking changes only
  2. **Never break existing versions** β€” Add new fields, never remove/rename
  3. **Deprecation policy** β€” 12 months notice, `Deprecation` header, `Sunset` header
  4. **Document migration path** β€” Clear upgrade guide for each version
# Deprecation headers
Deprecation: true
Sunset: Sat, 01 Jan 2025 00:00:00 GMT
Link: <https://api.example.com/v2/users>; rel="successor-version"

Filtering, Sorting, Pagination

Filtering

# Simple equality
GET /api/v1/users?status=active&role=admin

# Operators GET /api/v1/users?age[gte]=18&age[lte]=65 GET /api/v1/posts?title[cont]=typescript GET /api/v1/users?status[in]=active,pending

# Complex (RQL-style) GET /api/v1/users?filter=(age>18;role=admin;name~john)

Sorting

# Single field
GET /api/v1/users?sort=created_at
GET /api/v1/users?sort=-created_at    # Descending

# Multiple fields GET /api/v1/users?sort=-created_at,name

Pagination β€” Cursor-Based (Preferred)

# First page
GET /api/v1/users?limit=20

# Next page (using cursor) GET /api/v1/users?limit=20&cursor=eyJpZCI6InVzcl9hYmMifQ==

# Response { "data": [...], "meta": { "has_more": true, "next_cursor": "eyJpZCI6InVzcl9kZWYifQ==" } }

**Why cursor over offset?**

  • βœ… Consistent performance (no OFFSET scan)
  • βœ… No skipped/duplicated items on concurrent inserts
  • βœ… Works with real-time data

Authentication & Authorization

API Keys (Server-to-Server)

Authorization: Bearer sk_live_abc123...
# Or custom header
X-API-Key: sk_live_abc123...

JWT Bearer Tokens (User-Facing)

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

OAuth 2.0 / OIDC (Third-Party Access)

# Authorization Code Flow (web apps)
GET /oauth/authorize?client_id=...&redirect_uri=...&scope=read:users&response_type=code

# Client Credentials (server-to-server) POST /oauth/token grant_type=client_credentials&client_id=...&client_secret=...&scope=read:users

Scopes/Permissions

{
  "scopes": [
    "read:users",
    "write:users",
    "read:posts",
    "write:posts",
    "admin:users"
  ]
}

Rate Limiting

Standard Headers (IETF Draft)

# Response headers
RateLimit-Limit: 100
RateLimit-Remaining: 99
RateLimit-Reset: 1699900000
Retry-After: 60

Rate Limit Tiers

| Tier | Requests/Minute | Burst | Use Case | |------|-----------------|-------|----------| | Free | 60 | 10 | Public API exploration | | Starter | 300 | 50 | Small apps | | Pro | 1,000 | 200 | Production apps | | Enterprise | 10,000+ | Custom | High-volume |

Webhooks

Webhook Design

# Registration
POST /api/v1/webhooks
{
  "url": "https://app.example.com/webhooks",
  "events": ["user.created", "order.completed", "payment.failed"],
  "secret": "whsec_abc123..."
}

# Delivery POST https://app.example.com/webhooks Content-Type: application/json X-Webhook-Signature: sha256=abc123... X-Webhook-Timestamp: 1699900000 X-Webhook-Id: wh_abc123

{ "id": "evt_abc123", "type": "user.created", "created_at": "2024-01-15T10:30:00Z", "data": { "object": { "id": "usr_abc", "email": "..." } } }

Webhook Best Practices

  • βœ… **HMAC signature verification** (`X-Webhook-Signature`)
  • βœ… **Timestamp validation** (reject >5 min old)
  • βœ… **Idempotency keys** (`X-Webhook-Id` for deduplication)
  • βœ… **Retry with exponential backoff** (1s, 2s, 4s, 8s, 16s, 32s, max 5 retries)
  • βœ… **Dead letter queue** for failed deliveries
  • βœ… **Test endpoint** in developer dashboard

OpenAPI/Swagger Documentation

# openapi.yaml
openapi: 3.1.0
info:
  title: Example API
  version: 1.0.0
  description: |
    Example API for managing users and posts.
  contact:
    name: API Support
    email: api@example.com
  license:
    name: MIT

servers:

description: Production

description: Staging

  • url: https://api.example.com/v1
  • url: https://api-staging.example.com/v1

paths: /users: get: summary: List users parameters:

in: query schema: { type: string, enum: [active, inactive, pending] } responses: '200': description: List of users content: application/json: schema: $ref: '#/components/schemas/UserListResponse' '401': { $ref: '#/components/responses/Unauthorized' } '429': { $ref: '#/components/responses/RateLimited' }

  • $ref: '#/components/parameters/page'
  • $ref: '#/components/parameters/limit'
  • name: status

components: parameters: page: name: page in: query schema: { type: integer, minimum: 1, default: 1 } limit: name: limit in: query schema: { type: integer, minimum: 1, maximum: 100, default: 20 }

SDK Generation

# Generate TypeScript SDK from OpenAPI
npx @openapi-generator-cli generate \
  -i openapi.yaml \
  -g typescript-axios \
  -o packages/api-client

# Generated usage import { UsersApi, Configuration } from '@example/api-client';

const api = new UsersApi(new Configuration({ basePath: 'https://api.example.com/v1', accessToken: 'sk_live_...' });

const users = await api.listUsers({ page: 1, limit: 20 }); // users.data is typed: User[]

Testing Strategy

// Contract testing with Pact
// Consumer (frontend) defines expectations
// Provider (backend) verifies against contract

// pact/consumer/UserApiConsumer.test.ts import { PactV3, MatchersV3 } from '@pact-foundation/pact';

const { like, eachLike } = MatchersV3;

const provider = new PactV3({ consumer: 'WebApp', provider: 'UserApi', });

describe('User API', () => { it('returns users list', () => { return provider .given('users exist') .uponReceiving('a request for users') .withRequest({ method: 'GET', path: '/api/v1/users', query: { page: '1', limit: '20' }, headers: { Authorization: 'Bearer token' }, }) .willRespondWith({ status: 200, headers: { 'Content-Type': 'application/json' }, body: { data: eachLike({ id: like('usr_abc123'), name: like('John Doe'), email: like('john@example.com'), }), meta: { page: 1, per_page: 20, total: 100 }, }, }) .executeTest(async (mockServer) => { const response = await fetch(`${mockServer.url}/api/v1/users`); expect(response.ok).toBe(true); }); }); });

API Governance Checklist

Design Review Checklist

  • [ ] Resources named with plural nouns
  • [ ] HTTP methods used correctly
  • [ ] Status codes accurate
  • [ ] Error format consistent (RFC 7807)
  • [ ] Versioning strategy documented
  • [ ] Filtering/sorting/pagination consistent
  • [ ] Auth strategy appropriate
  • [ ] Rate limits documented
  • [ ] OpenAPI spec complete
  • [ ] Breaking change policy documented
  • [ ] Deprecation policy documented
  • [ ] SDKs generated and published
  • [ ] Contract tests in CI

API Lifecycle

Design β†’ Review β†’ Implement β†’ Contract Test β†’ Deploy (Staging) 
  β†’ Integration Test β†’ Deploy (Production) β†’ Monitor β†’ Deprecate

The UI Designer API Standard

Every API we build:

  • βœ… **REST + JSON** with OpenAPI 3.1 spec
  • βœ… **Cursor-based pagination** (not offset)
  • βœ… **RFC 7807 error format**
  • βœ… **Cursor-based pagination** for lists
  • βœ… **Idempotency keys** for mutations
  • βœ… **Idempotency keys** for webhooks
  • βœ… **Rate limiting** with standard headers
  • βœ… **OpenAPI 3.1** spec + generated SDKs
  • βœ… **Contract testing** (Pact) in CI
  • βœ… **Versioning** in URL (`/v1/`, `/v2/`)
  • βœ… **Deprecation headers** + 12-month notice

Ready to Design Better APIs?

**Book a free API architecture review** β€” we'll audit your current API, identify design issues, and give you a prioritized improvement roadmap.

ui-designer.in/api-design