API Design Guide
Build APIs that developers love to use. A comprehensive guide to API design covering REST principles, naming conventions, versioning strategies, error handling, and authentication patterns.
Why Design-First Wins
The design-first approach means writing your API specification before a single line of server code. Teams that adopt design-first ship faster, produce more consistent APIs, and create better developer experiences. Here is why.
Benefits of Design-First Development
- Catch breaking changes before they reach production
- Enable parallel frontend and backend development
- Auto-generate SDKs, documentation, and mock servers
- Validate requests and responses against a contract
- Create a single source of truth for your API
- Improve developer experience with interactive docs
Specway Makes Design-First Easy
Import your OpenAPI spec and instantly generate beautiful, interactive documentation. Make changes to your spec and watch your docs update automatically. No manual synchronization needed.
8 Core API Design Principles
Follow these principles to build APIs that are consistent, predictable, and a joy to integrate with.
Design-First Approach
Start with your API specification before writing any code. Define your endpoints, data models, and error responses upfront. This catches design issues early, enables parallel frontend and backend development, and produces better documentation from day one.
# Design-first: Write your OpenAPI spec first
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
paths:
/users:
get:
summary: List all users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100Use Nouns, Not Verbs
REST endpoints should represent resources (nouns), not actions (verbs). The HTTP method conveys the action. Use plural nouns for collections and keep URLs predictable and hierarchical.
# GOOD - Resource-oriented URLs
GET /users # List users
POST /users # Create a user
GET /users/123 # Get a specific user
PUT /users/123 # Update a user
DELETE /users/123 # Delete a user
# BAD - Verb-oriented URLs
GET /getUsers
POST /createUser
POST /deleteUser/123Consistent Naming Conventions
Pick a naming convention and apply it everywhere. Most REST APIs use snake_case for JSON fields, kebab-case for URL paths, and camelCase for query parameters. The key is consistency across your entire API surface.
// Consistent snake_case in response bodies
{
"user_id": "usr_abc123",
"first_name": "Jane",
"last_name": "Smith",
"created_at": "2025-01-15T10:30:00Z",
"is_active": true,
"email_verified": true
}
// Consistent kebab-case in URL paths
GET /api/v1/user-profiles/usr_abc123/access-tokensVersioning Strategy
Version your API from the start. URL-based versioning (/v1/, /v2/) is the most common and easiest to understand. Header-based versioning is more RESTful but harder to test. Whichever you choose, plan for backwards compatibility.
# URL-based versioning (most common)
GET /api/v1/users
GET /api/v2/users
# Header-based versioning
GET /api/users
Accept: application/vnd.myapi.v2+json
# Query parameter versioning
GET /api/users?version=2Meaningful Error Responses
Return structured error responses with an error code, a human-readable message, and a link to documentation. Use appropriate HTTP status codes: 400 for bad input, 401 for unauthenticated, 403 for forbidden, 404 for not found, and 429 for rate limiting.
// Structured error response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body contains invalid fields.",
"details": [
{
"field": "email",
"message": "Must be a valid email address."
},
{
"field": "age",
"message": "Must be a positive integer."
}
],
"doc_url": "https://api.example.com/docs/errors#validation"
}
}Pagination, Filtering & Sorting
Every list endpoint needs pagination. Cursor-based pagination is more reliable than offset-based for large datasets. Support filtering with query parameters and sorting with a sort parameter that accepts field names with a direction prefix.
# Cursor-based pagination
GET /api/v1/users?limit=20&cursor=eyJpZCI6MTAwfQ
# Response with pagination metadata
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIwfQ",
"has_more": true,
"total_count": 1583
}
}
# Filtering and sorting
GET /api/v1/users?status=active&role=admin&sort=-created_atRate Limiting
Protect your API with rate limits and communicate them clearly through response headers. Return 429 Too Many Requests when limits are exceeded, with a Retry-After header indicating when the client can try again.
# Rate limit headers in every response
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 994
X-RateLimit-Reset: 1704067200
# When rate limited
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Try again in 30 seconds."
}
}Authentication Patterns
Choose the right auth mechanism for your use case. API keys work for server-to-server calls. OAuth 2.0 is best for user-delegated access. JWT tokens enable stateless authentication. Always transmit credentials over HTTPS.
# API Key (header)
GET /api/v1/users
Authorization: Bearer sk_live_abc123def456
# OAuth 2.0 (Bearer token)
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
# API Key (query parameter - less secure)
GET /api/v1/users?api_key=sk_live_abc123def456Frequently Asked Questions
Common questions about API design.
Design APIs the Right Way
Import your OpenAPI spec into Specway and publish beautiful, interactive API documentation in minutes.