- Published on
REST API Design Best Practices 2025: Naming, Versioning, Error Handling, Pagination, Security
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- Introduction
- 1. REST Principles
- 2. Resource Naming
- 3. Correct HTTP Method Usage
- 4. Status Codes
- 5. Error Response Design
- 6. Pagination
- 7. Filtering, Sorting, Field Selection
- 8. Versioning
- 9. Authentication and Security
- 10. Rate Limiting
- 11. HATEOAS
- 12. Bulk Operations and Async Processing
- 13. OpenAPI 3.1 Specification
- 14. API Design Anti-Patterns
- 15. GraphQL vs REST vs gRPC Comparison
- 16. Quiz
- References
Introduction
REST APIs are the foundation of modern web services. Almost every piece of software -- mobile apps, SPAs, microservices, IoT devices -- communicates through REST APIs. Yet a surprising number of APIs claiming to be "RESTful" actually violate REST principles.
Well-designed APIs improve developer experience (DX), simplify maintenance, and ensure scalability and security. Poorly designed APIs increase communication costs between teams, create security vulnerabilities, and accumulate technical debt.
This guide covers all REST API design best practices, from resource naming to versioning, error handling, authentication, and OpenAPI.
1. REST Principles
1.1 The 6 REST Constraints
1. Client-Server (Separation of Concerns)
- Separate UI and data storage concerns
- Enable independent evolution
2. Stateless
- Each request contains complete information
- Server does not store client state
- Improves scalability
3. Cacheable
- Responses must declare cacheability
- Use Cache-Control, ETag, Last-Modified headers
4. Uniform Interface
- Resource identification (URI)
- Resource manipulation through representations
- Self-descriptive messages
- HATEOAS
5. Layered System
- Client cannot tell if communicating directly with server or intermediary
- Load balancers, caches, gateways can be added
6. Code-on-Demand (Optional)
- Server can send executable code to client
- The only optional constraint
1.2 Richardson Maturity Model
Level 3: Hypermedia Controls (HATEOAS)
--> Responses include links to related resources
Level 2: HTTP Methods
--> Correct use of GET, POST, PUT, DELETE
Level 1: Resources
--> Individual URIs for resources
Level 0: The Swamp of POX
--> Single endpoint, all operations via POST
Most APIs target Level 2.
Level 3 (HATEOAS) is ideal but optional in practice.
2. Resource Naming
2.1 Basic Rules
Good:
GET /users # List users
GET /users/123 # Get specific user
POST /users # Create user
PUT /users/123 # Full update user
PATCH /users/123 # Partial update user
DELETE /users/123 # Delete user
Bad:
GET /getUsers # No verbs
GET /user/123 # Use plural
POST /createUser # No verbs
POST /user/123/delete # Use HTTP methods
2.2 Hierarchical Relationships
# User's orders list
GET /users/123/orders
# User's specific order
GET /users/123/orders/456
# Order's items list
GET /users/123/orders/456/items
# Warning: Avoid nesting beyond 3 levels
# Bad:
GET /users/123/orders/456/items/789/reviews
# Good (alternatives):
GET /items/789/reviews
GET /reviews?item_id=789
2.3 Naming Conventions
# Use lowercase + hyphens (kebab-case)
Good: /user-profiles
Bad: /userProfiles, /user_profiles, /UserProfiles
# No file extensions
Good: Accept: application/json
Bad: /users/123.json
# No trailing slashes
Good: /users
Bad: /users/
# Use query parameters for filtering
GET /users?status=active&role=admin
GET /products?category=electronics&min_price=100&max_price=500
# Sorting
GET /users?sort=created_at&order=desc
GET /users?sort=-created_at,+name # - descending, + ascending
# Field Selection (Sparse Fieldsets)
GET /users/123?fields=name,email,avatar
2.4 Handling Non-Resource Operations
# Actions: express as sub-resources
POST /users/123/activate # Activate user
POST /users/123/deactivate # Deactivate user
POST /orders/456/cancel # Cancel order
POST /payments/789/refund # Refund payment
# Search (verb-like action, not a resource)
GET /search?q=keyword&type=users
# Batch operations
POST /users/batch
Body: { "ids": [1, 2, 3], "action": "deactivate" }
# Aggregations
GET /orders/statistics
GET /dashboard/metrics
3. Correct HTTP Method Usage
3.1 Method Characteristics
| Method | Purpose | Idempotent | Safe | Request Body | Response Body |
|---|---|---|---|---|---|
| GET | Read | Yes | Yes | No | Yes |
| POST | Create | No | No | Yes | Yes |
| PUT | Full replace | Yes | No | Yes | Optional |
| PATCH | Partial update | No* | No | Yes | Yes |
| DELETE | Delete | Yes | No | Optional | Optional |
| HEAD | Headers only | Yes | Yes | No | No |
| OPTIONS | Available methods | Yes | Yes | No | Yes |
*PATCH can be idempotent depending on implementation
3.2 Idempotency in Detail
# PUT - Idempotent (same request multiple times = same result)
PUT /users/123
Body: { "name": "Alice", "email": "alice@example.com" }
# Sending once or 10 times yields same result
# DELETE - Idempotent
DELETE /users/123
# First time: 200 OK (deleted)
# Second time: 404 Not Found (already deleted) - no side effects
# POST - NOT idempotent (each request creates new resource)
POST /orders
Body: { "product_id": 1, "quantity": 2 }
# Sending twice creates 2 orders!
Making POST safe with idempotency keys:
POST /payments
Headers:
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Body: { "amount": 10000, "currency": "USD" }
# Resending with same Idempotency-Key prevents duplicate payments
# Server stores the key and returns previous response for same key
3.3 PUT vs PATCH
# Original data
{
"id": 123,
"name": "Alice",
"email": "alice@example.com",
"role": "user",
"avatar": "default.png"
}
# PUT: Full replacement (missing fields reset)
PUT /users/123
Body: { "name": "Alice Updated", "email": "alice@new.com" }
# Result: role and avatar become null/default!
# PATCH: Partial update (only sent fields change)
PATCH /users/123
Body: { "name": "Alice Updated" }
# Result: only name changes, other fields preserved
4. Status Codes
4.1 Key Status Codes
2xx Success:
200 OK - General success (GET, PUT, PATCH, DELETE)
201 Created - Resource created (POST) + Location header
202 Accepted - Async processing accepted
204 No Content - Success with no response body (DELETE)
3xx Redirection:
301 Moved Permanently - Permanent redirect
302 Found - Temporary redirect
304 Not Modified - Use cached version
4xx Client Errors:
400 Bad Request - Invalid request (validation failure)
401 Unauthorized - Authentication required (not logged in)
403 Forbidden - Authorization failure (no permission)
404 Not Found - Resource does not exist
405 Method Not Allowed - Unsupported HTTP method
409 Conflict - Resource conflict (duplicate creation)
422 Unprocessable Entity - Syntax ok but semantics wrong
429 Too Many Requests - Rate limit exceeded
5xx Server Errors:
500 Internal Server Error - Server error
502 Bad Gateway - Upstream server error
503 Service Unavailable - Service temporarily down
504 Gateway Timeout - Upstream server timeout
4.2 Status Code Selection Guide
POST resource creation success -> 201 Created
Response Headers:
Location: /users/124
Response Body:
{ "id": 124, "name": "Bob", ... }
DELETE success:
Option 1: 204 No Content (no body)
Option 2: 200 OK + deleted resource info
Authentication vs Authorization:
Not logged in -> 401 Unauthorized
Logged in but no permission -> 403 Forbidden
Validation failures:
JSON parse failure -> 400 Bad Request
Field value invalid -> 422 Unprocessable Entity
Resource conflicts:
Duplicate email -> 409 Conflict
Optimistic lock failure -> 409 Conflict
5. Error Response Design
5.1 RFC 7807 Problem Details
{
"type": "https://api.example.com/errors/validation-error",
"title": "Validation Error",
"status": 422,
"detail": "One or more fields failed validation.",
"instance": "/users/123",
"errors": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Must be a valid email address"
},
{
"field": "age",
"code": "OUT_OF_RANGE",
"message": "Must be between 0 and 150"
}
],
"timestamp": "2025-03-25T10:30:00Z",
"trace_id": "abc-123-def-456"
}
5.2 Error Code System
Error code naming convention:
AUTH_001 - Authentication token expired
AUTH_002 - Invalid credentials
AUTH_003 - Account locked
USER_001 - User not found
USER_002 - Email already exists
USER_003 - Password policy violation
ORDER_001 - Insufficient stock
ORDER_002 - Payment failed
ORDER_003 - Order already cancelled
RATE_001 - API call limit exceeded
RATE_002 - Daily limit exceeded
5.3 Error Response Implementation
from flask import Flask, jsonify
from werkzeug.exceptions import HTTPException
app = Flask(__name__)
class APIError(Exception):
def __init__(self, status, error_type, title, detail, errors=None):
self.status = status
self.error_type = error_type
self.title = title
self.detail = detail
self.errors = errors or []
@app.errorhandler(APIError)
def handle_api_error(error):
response = {
"type": f"https://api.example.com/errors/{error.error_type}",
"title": error.title,
"status": error.status,
"detail": error.detail,
}
if error.errors:
response["errors"] = error.errors
return jsonify(response), error.status
@app.errorhandler(404)
def not_found(e):
return jsonify({
"type": "https://api.example.com/errors/not-found",
"title": "Resource Not Found",
"status": 404,
"detail": "The requested resource does not exist."
}), 404
6. Pagination
6.1 Offset-based
GET /users?page=3&per_page=20
Response:
{
"data": [...],
"pagination": {
"page": 3,
"per_page": 20,
"total_items": 1543,
"total_pages": 78,
"has_next": true,
"has_prev": true
}
}
Pros: Simple implementation, direct page jump possible
Cons: Slow on large datasets (OFFSET 10000), duplicates/misses on data changes
The Offset Problem:
-- When page=500, per_page=20
SELECT * FROM users ORDER BY id LIMIT 20 OFFSET 9980;
-- DB reads 9980 rows and discards them, returns 20 -> very inefficient!
6.2 Cursor-based (Recommended)
GET /users?limit=20&cursor=eyJpZCI6MTIzfQ==
Response:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTQzfQ==",
"prev_cursor": "eyJpZCI6MTI0fQ==",
"has_next": true,
"has_prev": true,
"limit": 20
}
}
Cursor Implementation:
import base64
import json
def encode_cursor(last_item):
"""Encode sort key of last item as cursor"""
cursor_data = {"id": last_item["id"], "created_at": last_item["created_at"]}
return base64.urlsafe_b64encode(
json.dumps(cursor_data).encode()
).decode()
def decode_cursor(cursor_str):
"""Decode cursor to extract sort key"""
return json.loads(
base64.urlsafe_b64decode(cursor_str.encode()).decode()
)
def get_users(cursor=None, limit=20):
query = "SELECT * FROM users"
if cursor:
decoded = decode_cursor(cursor)
query += f" WHERE id > {decoded['id']}"
query += f" ORDER BY id ASC LIMIT {limit + 1}"
results = db.execute(query)
has_next = len(results) > limit
items = results[:limit]
return {
"data": items,
"pagination": {
"next_cursor": encode_cursor(items[-1]) if has_next else None,
"has_next": has_next,
"limit": limit
}
}
6.3 Keyset-based
-- Keyset Pagination is the core of Cursor
-- Composite sort key example
-- First page
SELECT * FROM orders
ORDER BY created_at DESC, id DESC
LIMIT 20;
-- Next page (last item: created_at='2025-03-20', id=456)
SELECT * FROM orders
WHERE (created_at, id) < ('2025-03-20', 456)
ORDER BY created_at DESC, id DESC
LIMIT 20;
-- Very fast with proper index!
CREATE INDEX idx_orders_cursor ON orders(created_at DESC, id DESC);
6.4 Pagination Comparison
| Method | Performance | Consistency | Direct Page Jump | Complexity |
|---|---|---|---|---|
| Offset | Slow on large data | Issues on data changes | Yes | Low |
| Cursor | Consistent performance | Maintains consistency | No | Medium |
| Keyset | Very fast | Maintains consistency | No | Medium |
7. Filtering, Sorting, Field Selection
7.1 Filtering
# Basic filtering
GET /products?category=electronics&status=available
# Range filters
GET /products?min_price=100&max_price=500
GET /orders?created_after=2025-01-01&created_before=2025-03-25
# Multiple value filter
GET /products?tags=phone,tablet,laptop
# Search
GET /products?q=wireless+keyboard
# Complex filters (advanced)
GET /products?filter[category]=electronics&filter[price][gte]=100&filter[price][lte]=500
7.2 Sorting
# Single field sorting
GET /users?sort=name # ascending
GET /users?sort=-name # descending
# Multiple field sorting
GET /users?sort=-created_at,name
# JSON:API style
GET /articles?sort=-published_at,title
7.3 Field Selection (Sparse Fieldsets)
# Request only needed fields (save bandwidth)
GET /users/123?fields=id,name,email
# Include related resources (embedding)
GET /users/123?include=orders,profile
GET /articles/456?include=author,comments&fields[articles]=title,body&fields[author]=name
8. Versioning
8.1 URL Path Versioning (Most Common)
GET /v1/users/123
GET /v2/users/123
Pros:
- Clear and intuitive
- Easy to test in browser
- Easy routing separation
Cons:
- Violates REST principle that URL should identify resource location
- All URLs change on version change
8.2 Header Versioning
# Accept header (Content Negotiation)
GET /users/123
Accept: application/vnd.myapi.v2+json
# Custom header
GET /users/123
X-API-Version: 2
Pros: Clean URLs, follows REST principles
Cons: Testing inconvenient, harder to debug
8.3 Version Management Strategy
1. Compatibility principles:
- Adding fields is non-breaking (no impact on existing clients)
- Removing/renaming fields is breaking -> new version needed
- Changing field types is breaking -> new version needed
2. Deprecation process:
Phase 1: Release new version + add Sunset header to old
Phase 2: Add warning responses to old version
Phase 3: End old version (minimum 6-month grace period)
HTTP Headers:
Sunset: Sat, 01 Jan 2026 00:00:00 GMT
Deprecation: true
Link: <https://api.example.com/v2>; rel="successor-version"
3. API changelog:
- Document all changes
- Provide migration guides
- Pre-notify clients
9. Authentication and Security
9.1 Authentication Method Comparison
| Method | Use Case | Pros | Cons |
|---|---|---|---|
| API Key | Server-to-server, simple API | Simple implementation | Hard to scope permissions |
| OAuth 2.0 | Acting on behalf of user | Standardized, scope control | Complex implementation |
| JWT | Stateless authentication | Easy server scaling | Hard to revoke tokens |
| Session | Traditional web apps | Easy server control | Scalability limited |
9.2 JWT (JSON Web Token)
JWT Structure:
Header.Payload.Signature
Header:
{
"alg": "RS256",
"typ": "JWT"
}
Payload:
{
"sub": "user-123",
"email": "alice@example.com",
"roles": ["admin", "user"],
"iat": 1711353000,
"exp": 1711356600
}
Signature:
RS256(base64(header) + "." + base64(payload), privateKey)
JWT Usage Pattern:
# Access Token + Refresh Token
Access Token: 15-minute validity
Refresh Token: 7-day validity
1. Login -> Issue Access + Refresh tokens
2. API call -> Authorization: Bearer access_token_value
3. Access Token expired -> Refresh using Refresh Token
4. Refresh Token expired -> Re-login
# Header example
GET /users/me
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
9.3 OAuth 2.0 Flows
Authorization Code Flow (Web apps):
1. User -> redirect to authorization server
2. User login + consent
3. Authorization code returned to callback URL
4. Exchange authorization code for Access Token
5. Call API with Access Token
Client Credentials Flow (Server-to-server):
1. Request token directly with Client ID + Secret
2. Access Token issued
3. Call API
9.4 API Security Checklist
1. HTTPS mandatory (never HTTP)
2. Auth tokens in headers only (never URL parameters)
3. Input validation (SQL Injection, XSS prevention)
4. CORS configuration (allowed domains only)
5. Rate Limiting applied
6. Never log sensitive data
7. Never expose internal info in error messages
8. Manage API keys via environment variables
9. Regular security audits
10. Dependency vulnerability scanning
10. Rate Limiting
10.1 Response Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000 # Max requests per hour
X-RateLimit-Remaining: 742 # Remaining requests
X-RateLimit-Reset: 1711360000 # Reset time (Unix timestamp)
# When limit exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 60 # Retry after 60 seconds
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711360000
{
"type": "https://api.example.com/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "You have exceeded the rate limit of 1000 requests per hour.",
"retry_after": 60
}
10.2 Rate Limiting Strategies
1. Per-user limits:
- Authenticated users: 1000 req/hour
- Unauthenticated users: 100 req/hour
2. Per-endpoint limits:
- GET /users: 100 req/min
- POST /users: 10 req/min
- POST /payments: 5 req/min
3. Tier-based limits:
- Free: 100 req/day
- Basic: 10,000 req/day
- Pro: 100,000 req/day
- Enterprise: Custom
4. Implementation (Redis):
key = "rate:user:123:2025-03-25-10" # user + time window
INCR key
EXPIRE key 3600
11. HATEOAS
11.1 What is HATEOAS?
{
"id": 123,
"name": "Alice",
"email": "alice@example.com",
"status": "active",
"_links": {
"self": {
"href": "/users/123"
},
"orders": {
"href": "/users/123/orders"
},
"deactivate": {
"href": "/users/123/deactivate",
"method": "POST"
},
"update": {
"href": "/users/123",
"method": "PUT"
}
}
}
11.2 HATEOAS Pros and Cons
Pros:
- API is explorable (self-documenting)
- Client does not need to hardcode URLs
- Server can change URL structure
- Discover available actions from responses
Cons:
- Increased response size
- Implementation complexity
- Most clients do not utilize it
- Not mandatory in practice
Conclusion: Recommended for public APIs, optional for internal APIs
12. Bulk Operations and Async Processing
12.1 Batch Endpoints
// POST /users/batch
{
"operations": [
{ "method": "POST", "body": { "name": "Alice", "email": "a@ex.com" } },
{ "method": "POST", "body": { "name": "Bob", "email": "b@ex.com" } },
{ "method": "POST", "body": { "name": "Carol", "email": "c@ex.com" } }
]
}
// Response: 207 Multi-Status
{
"results": [
{ "status": 201, "data": { "id": 1, "name": "Alice" } },
{ "status": 201, "data": { "id": 2, "name": "Bob" } },
{ "status": 409, "error": { "code": "USER_002", "message": "Email already exists" } }
]
}
12.2 Async Processing Pattern
# Long-running tasks (report generation, bulk data processing)
POST /reports
Body: { "type": "annual", "year": 2025 }
Response: 202 Accepted
{
"task_id": "task-abc-123",
"status": "processing",
"estimated_time": 120,
"_links": {
"status": { "href": "/tasks/task-abc-123" },
"cancel": { "href": "/tasks/task-abc-123/cancel", "method": "POST" }
}
}
# Check status
GET /tasks/task-abc-123
{
"task_id": "task-abc-123",
"status": "completed",
"result": {
"download_url": "/reports/2025-annual.pdf"
},
"completed_at": "2025-03-25T10:35:00Z"
}
13. OpenAPI 3.1 Specification
13.1 OpenAPI Basic Structure
openapi: "3.1.0"
info:
title: User Management API
description: API for managing users
version: "1.0.0"
contact:
email: api@example.com
servers:
- url: https://api.example.com/v1
description: Production
- url: https://staging-api.example.com/v1
description: Staging
paths:
/users:
get:
summary: List users
operationId: listUsers
tags:
- Users
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
- name: cursor
in: query
schema:
type: string
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/User"
pagination:
$ref: "#/components/schemas/Pagination"
post:
summary: Create user
operationId: createUser
tags:
- Users
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUserRequest"
responses:
"201":
description: User created
headers:
Location:
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"422":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ProblemDetails"
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string
format: email
required:
- id
- name
- email
CreateUserRequest:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 100
email:
type: string
format: email
required:
- name
- email
13.2 OpenAPI Code Generation
# Auto-generate code from OpenAPI
1. Client SDK generation:
npx openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-fetch \
-o ./generated-client
2. Server stub generation:
npx openapi-generator-cli generate \
-i openapi.yaml \
-g python-flask \
-o ./generated-server
3. Mock server:
npx prism mock openapi.yaml
4. API documentation:
npx redocly build-docs openapi.yaml
14. API Design Anti-Patterns
14.1 10 Common Mistakes
1. Using verbs in URLs
Bad: POST /createUser
Good: POST /users
2. Inconsistent naming
Bad: /users, /getOrders, /product-list
Good: /users, /orders, /products
3. Singular resource names
Bad: /user/123
Good: /users/123
4. Ignoring HTTP methods
Bad: POST /users/123/delete
Good: DELETE /users/123
5. Always returning 200
Bad: 200 OK { "error": true, "message": "Not found" }
Good: 404 Not Found { "title": "Not Found", ... }
6. Excessive nesting
Bad: /countries/us/cities/nyc/districts/manhattan/restaurants
Good: /restaurants?city=nyc&district=manhattan
7. Unversioned API
Bad: /users (can change anytime)
Good: /v1/users
8. No error response standard
Bad: { "error": "something went wrong" }
Good: RFC 7807 Problem Details format
9. No pagination
Bad: GET /logs (returns 1 million records)
Good: GET /logs?limit=50&cursor=abc
10. Missing security headers
Bad: Allow HTTP, CORS wide open
Good: HTTPS required, minimal CORS, proper headers
15. GraphQL vs REST vs gRPC Comparison
+--------------+-------------------+-------------------+-------------------+
| Feature | REST | GraphQL | gRPC |
+--------------+-------------------+-------------------+-------------------+
| Protocol | HTTP/1.1, 2 | HTTP/1.1, 2 | HTTP/2 |
| Data Format | JSON/XML | JSON | Protocol Buffers |
| Schema | OpenAPI (optional)| SDL (required) | .proto (required) |
| Over/Under | Over-fetching | Client decides | Exact schema |
| fetching | possible | | |
| Real-time | WebSocket/SSE | Subscriptions | Bidirectional |
| | | | Streaming |
| Use Cases | Public APIs, | Complex data | Microservices |
| | simple CRUD | relations, mobile | internal comms |
| Learning | Low | Medium | High |
| Caching | HTTP caching easy | Difficult | None |
| Error | HTTP status codes | errors array | Status codes |
+--------------+-------------------+-------------------+-------------------+
Selection Guide:
Choose REST:
- Public APIs (for external developers)
- Simple CRUD operations
- HTTP caching needed
- Direct browser calls
Choose GraphQL:
- Multiple clients (web, mobile, IoT)
- Complex data relationships
- Need to solve over/under-fetching
- Rapid frontend development
Choose gRPC:
- Internal microservice communication
- Low latency requirements
- Bidirectional streaming needed
- Strong typed contracts needed
16. Quiz
Q1. REST Principles
Explain why the "Stateless" constraint contributes to scalability.
Answer: The Stateless constraint means the server does not store client state, so each request contains all information needed for processing. This enables:
- Simple load balancing: Any server can handle any request (no session stickiness needed)
- Easy horizontal scaling: Servers can be added/removed freely
- Easy fault recovery: On server failure, another server immediately takes over
- Improved caching efficiency: Requests are independent, making cache key generation straightforward
Q2. Pagination
Describe 2 situations where you should choose Cursor pagination over Offset pagination.
Answer:
-
Large datasets: With millions of records, Offset requires
OFFSET 100000which skips many rows and is very slow. Cursor uses indexes with a WHERE clause to jump directly, maintaining consistent performance regardless of data size. -
Real-time data: When data is frequently added or deleted, Offset causes duplicates or misses when re-requesting the same page. Cursor uses the unique identifier of the last seen item as reference point, maintaining consistency.
Q3. Error Handling
Explain the difference between 401 Unauthorized and 403 Forbidden with specific examples.
Answer:
-
401 Unauthorized (Authentication failure): The client has not proven who they are. For example, calling an API without an Authorization header, or using an expired JWT token. "I don't know who you are. Please log in."
-
403 Forbidden (Authorization failure): The client is authenticated but lacks permission for the resource. For example, a regular user calling an admin-only API. "I know who you are, but you don't have permission to access this resource."
Q4. Versioning
Compare the pros and cons of URL path versioning (/v1/users) vs Accept header versioning.
Answer: URL Path Versioning (/v1/users):
- Pros: Intuitive, easy browser testing, easy routing separation, easy caching
- Cons: Violates REST principles (URL should identify resource location), all URLs change
Accept Header Versioning (Accept: application/vnd.myapi.v2+json):
- Pros: Clean URLs, follows REST principles, one URL for multiple versions
- Cons: Testing inconvenient (need curl/Postman), harder debugging, complex caching
In practice, URL path versioning is overwhelmingly more common (GitHub, Stripe, Google, etc.).
Q5. Security
Explain JWT pros/cons and how to respond to token theft.
Answer: Pros: No server session storage needed (Stateless), easy horizontal scaling, easy propagation between microservices, user info in claims
Cons: Hard to revoke tokens (valid until expiration), larger than session IDs, payload not encrypted (only encoded)
Token theft response:
- Short Access Token validity (15 minutes)
- Refresh Token rotation (issue new token on use, invalidate old)
- Token blacklist (store revoked tokens in Redis)
- IP/User-Agent binding
- Invalidate all Refresh Tokens on suspicious activity detection
References
- RFC 7231 - HTTP/1.1 Semantics and Content - https://tools.ietf.org/html/rfc7231
- RFC 7807 - Problem Details for HTTP APIs - https://tools.ietf.org/html/rfc7807
- OpenAPI Specification 3.1 - https://spec.openapis.org/oas/v3.1.0
- JSON:API Specification - https://jsonapi.org/
- Google API Design Guide - https://cloud.google.com/apis/design
- Microsoft REST API Guidelines - https://github.com/microsoft/api-guidelines
- Stripe API Reference - https://stripe.com/docs/api
- GitHub REST API - https://docs.github.com/en/rest
- Zalando RESTful API Guidelines - https://opensource.zalando.com/restful-api-guidelines/
- REST API Design Rulebook - Mark Masse (O'Reilly)
- OAuth 2.0 RFC 6749 - https://tools.ietf.org/html/rfc6749
- JWT RFC 7519 - https://tools.ietf.org/html/rfc7519
- Roy Fielding's Dissertation - https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
- Richardson Maturity Model - https://martinfowler.com/articles/richardsonMaturityModel.html