Skip to main content

API Design Best Practices Complete Guide 2026

Created: March 6, 2026 CalmOps 3 min read

Introduction

Well-designed APIs are crucial for developer experience and system maintainability. This guide covers API design best practices.

REST API Design

URL Structure

RESTful URL Patterns:
├── Resources (nouns, plural)
│   ├── /users
│   ├── /orders
│   └── /products
├── Nested Resources
│   ├── /users/{id}/orders
│   └── /orders/{id}/items
├── Actions
│   ├── POST /users/{id}/activate
│   └── POST /orders/{id}/cancel
└── Filtering
    ├── /users?status=active
    ├── /products?category=electronics&price<100
    └── /orders?date_from=2026-01-01

HTTP Methods

HTTP Method Usage:
├── GET - Retrieve resources
│   └── GET /users - List users
│   └── GET /users/123 - Get user
├── POST - Create new resources
│   └── POST /users - Create user
├── PUT - Replace entire resource
│   └── PUT /users/123 - Replace user
├── PATCH - Partial update
│   └── PATCH /users/123 - Update user fields
└── DELETE - Remove resources
    └── DELETE /users/123 - Delete user

Response Format

// Success Response
{
  "data": {
    "id": "123",
    "type": "user",
    "attributes": {
      "name": "John Doe",
      "email": "[email protected]"
    }
  },
  "meta": {
    "request_id": "abc-123"
  }
}

// Error Response
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid email format",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email"
      }
    ]
  }
}

Authentication

API Keys

# API Key authentication
class APIKeyAuth:
    def __init__(self, header_name='X-API-Key'):
        self.header_name = header_name
    
    def authenticate(self, request):
        api_key = request.headers.get(self.header_name)
        if not api_key:
            return None
        
        user = self.validate_key(api_key)
        return user
    
    def validate_key(self, key):
        # Look up key in database
        return api_key_db.get(key)

JWT Tokens

import jwt
from datetime import datetime, timedelta

def create_access_token(user_id):
    """Create JWT access token"""
    payload = {
        'sub': user_id,
        'exp': datetime.utcnow() + timedelta(hours=1),
        'iat': datetime.utcnow()
    }
    return jwt.encode(payload, SECRET_KEY, algorithm='HS256')

def verify_token(token):
    """Verify JWT token"""
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload['sub']
    except jwt.ExpiredSignatureError:
        return None

Versioning

URL Versioning

API Versioning Strategies:
├── URL Path (Recommended)
│   ├── /v1/users
│   └── /v2/users
├── Header
│   ├── Accept: application/vnd.api+json;version=2
│   └── X-API-Version: 2
└── Query Parameter
    ├── /users?version=2
    └── Not recommended

Rate Limiting

# Rate limiting with Redis
class RateLimiter:
    def __init__(self, redis_client):
        self.redis = redis_client
    
    def is_allowed(self, identifier, limit, window):
        """Check if request is allowed"""
        key = f"rate_limit:{identifier}"
        
        current = self.redis.get(key)
        if current is None:
            self.redis.setex(key, window, 1)
            return True
        
        if int(current) >= limit:
            return False
        
        self.redis.incr(key)
        return True

Documentation

OpenAPI/Swagger

OpenAPI Specification:
openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users:
    get:
      summary: List users
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'

Conclusion

Good API design improves developer experience and system maintainability. Follow these practices consistently.

Comments

Share this article

Scan to read on mobile