Loading...
Loading...
REST API design patterns, OpenAPI specifications, versioning strategies, authentication, error handling, and security best practices. Use when designing APIs, creating endpoints, documenting APIs, or implementing backend services that expose HTTP APIs.
npx skill4agent add webdevtodayjason/titanium-plugins api-best-practicesGET /api/v1/users
POST /api/v1/users
GET /api/v1/users/{id}
PUT /api/v1/users/{id}
DELETE /api/v1/users/{id}GET /api/v1/getUsers
POST /api/v1/createUser
POST /api/v1/updateUser
POST /api/v1/deleteUser200 OK201 Created202 Accepted204 No Content400 Bad Request401 Unauthorized403 Forbidden404 Not Found409 Conflict422 Unprocessable Entity429 Too Many Requests500 Internal Server Error502 Bad Gateway503 Service Unavailable504 Gateway TimeoutGET /api/v1/users
GET /api/v2/usersGET /api/users
Accept: application/vnd.myapi.v1+json{
"email": "user@example.com",
"name": "John Doe",
"preferences": {
"newsletter": true,
"notifications": false
}
}GET /api/v1/users?role=admin&status=active&page=2&limit=20&sort=-created_at{
"data": {
"id": "user_123",
"email": "user@example.com",
"name": "John Doe",
"createdAt": "2025-10-16T10:30:00Z"
}
}{
"error": {
"code": "INVALID_EMAIL",
"message": "Email address is invalid",
"field": "email",
"details": "Email must contain @ symbol"
}
}{
"data": [
{ "id": 1, "name": "User 1" },
{ "id": 2, "name": "User 2" }
],
"pagination": {
"page": 2,
"limit": 20,
"total": 156,
"totalPages": 8,
"hasNext": true,
"hasPrev": true
},
"links": {
"self": "/api/v1/users?page=2",
"next": "/api/v1/users?page=3",
"prev": "/api/v1/users?page=1",
"first": "/api/v1/users?page=1",
"last": "/api/v1/users?page=8"
}
}POST /api/v1/auth/login
{
"email": "user@example.com",
"password": "SecurePassword123"
}
Response (200):
{
"accessToken": "eyJhbGc...",
"refreshToken": "eyJhbGc...",
"expiresIn": 900
}GET /api/v1/users/me
Authorization: Bearer eyJhbGc...POST /api/v1/auth/refresh
{
"refreshToken": "eyJhbGc..."
}
Response (200):
{
"accessToken": "eyJhbGc...",
"expiresIn": 900
}GET /api/v1/data
X-API-Key: sk_live_abc123xyzGET /api/v1/public-data?api_key=sk_live_abc123xyz/oauth/authorize/oauth/tokenPOST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=abc&client_secret=xyz{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"errors": [
{
"field": "email",
"message": "Email is required"
},
{
"field": "age",
"message": "Age must be at least 18"
}
]
}
}{
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "Account balance too low for this transaction",
"details": {
"balance": 50.00,
"required": 100.00,
"currency": "USD"
}
}
}HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1634400000
Retry-After: 3600
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "API rate limit exceeded",
"retryAfter": 3600
}
}GET /api/v1/users?offset=40&limit=20GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20
Response:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6MTQzfQ",
"hasMore": true
}
}GET /api/v1/users?page=3&limit=20X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1634400000from pydantic import BaseModel, EmailStr, constr
class UserCreate(BaseModel):
email: EmailStr
password: constr(min_length=8, max_length=100)
name: constr(min_length=1, max_length=100)import html
safe_output = html.escape(user_input)# ✅ SAFE - Parameterized
cursor.execute("SELECT * FROM users WHERE email = ?", (email,))
# ❌ UNSAFE - String concatenation
cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")# Be specific with origins
CORS(app, origins=["https://myapp.com", "https://app.myapp.com"])
# ❌ NEVER use wildcard in production
# CORS(app, origins=["*"]) # DANGEROUS# 1. Verify JWT token (authentication)
# 2. Check user permissions (authorization)
# 3. Process requestlogger.warning(f"Failed login attempt for {email} from {ip_address}")
logger.critical(f"Privilege escalation attempt by user {user_id}")/auth/login/auth/register/auth/reset-passwordopenapi: 3.0.0
info:
title: My API
version: 1.0.0
description: API for managing users and posts
servers:
- url: https://api.example.com/v1
description: Production server
- url: https://staging-api.example.com/v1
description: Staging server
paths:
/users:
get:
summary: List users
operationId: listUsers
tags:
- Users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
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/UserCreate'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Validation error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
User:
type: object
required:
- id
- email
- name
properties:
id:
type: string
example: user_123
email:
type: string
format: email
example: user@example.com
name:
type: string
example: John Doe
createdAt:
type: string
format: date-time
example: 2025-10-16T10:30:00Z
UserCreate:
type: object
required:
- email
- password
- name
properties:
email:
type: string
format: email
password:
type: string
minLength: 8
maxLength: 100
name:
type: string
minLength: 1
maxLength: 100
Pagination:
type: object
properties:
page:
type: integer
limit:
type: integer
total:
type: integer
totalPages:
type: integer
hasNext:
type: boolean
hasPrev:
type: boolean
Error:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
details:
type: object
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- BearerAuth: []# Collection operations
GET /api/v1/posts # List posts
POST /api/v1/posts # Create post
GET /api/v1/posts/{id} # Get specific post
PUT /api/v1/posts/{id} # Replace post
PATCH /api/v1/posts/{id} # Update post
DELETE /api/v1/posts/{id} # Delete post
# Nested resources
GET /api/v1/posts/{id}/comments # List comments for post
POST /api/v1/posts/{id}/comments # Create comment on post
GET /api/v1/comments/{id} # Get specific comment
DELETE /api/v1/comments/{id} # Delete commentPOST /api/v1/users/{id}/verify-email
POST /api/v1/orders/{id}/cancel
POST /api/v1/posts/{id}/publish
POST /api/v1/invoices/{id}/sendPOST /{resource}/{id}/{action}from pydantic import BaseModel, EmailStr, Field, validator
class UserCreate(BaseModel):
email: EmailStr
password: str = Field(min_length=8, max_length=100)
name: str = Field(min_length=1, max_length=100)
age: int = Field(ge=18, le=120)
@validator('password')
def password_strength(cls, v):
if not any(c.isupper() for c in v):
raise ValueError('Password must contain uppercase letter')
if not any(c.isdigit() for c in v):
raise ValueError('Password must contain digit')
return v{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"errors": [
{
"field": "email",
"message": "Email is required",
"code": "REQUIRED_FIELD"
},
{
"field": "password",
"message": "Password must contain uppercase letter",
"code": "INVALID_FORMAT"
}
]
}
}# Single filter
GET /api/v1/posts?status=published
# Multiple filters (AND)
GET /api/v1/posts?status=published&author=john
# Multiple values (OR)
GET /api/v1/posts?tags=tech,ai,ml
# Range filters
GET /api/v1/posts?created_after=2025-01-01&created_before=2025-12-31# Single field ascending
GET /api/v1/posts?sort=created_at
# Single field descending
GET /api/v1/posts?sort=-created_at
# Multiple fields
GET /api/v1/posts?sort=-priority,created_at# Full-text search
GET /api/v1/posts?q=machine+learning
# Field-specific search
GET /api/v1/posts?title=contains:machine&author=starts_with:johnPOST /api/v1/payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{
"amount": 100.00,
"currency": "USD",
"description": "Payment for order #123"
}POST /api/v1/reports/generate
{
"type": "annual_summary",
"year": 2025
}
Response (202 Accepted):
{
"id": "job_abc123",
"status": "processing",
"statusUrl": "/api/v1/jobs/job_abc123"
}GET /api/v1/jobs/job_abc123
Response:
{
"id": "job_abc123",
"status": "completed",
"result": {
"reportUrl": "/api/v1/reports/annual_summary_2025.pdf"
},
"createdAt": "2025-10-16T10:00:00Z",
"completedAt": "2025-10-16T10:05:00Z"
}queuedprocessingcompletedfailed{
"event": "user.created",
"timestamp": "2025-10-16T10:30:00Z",
"id": "evt_abc123",
"data": {
"id": "user_123",
"email": "user@example.com",
"name": "John Doe"
}
}POST https://customer.com/webhooks
X-Webhook-Signature: sha256=abc123...
# Verify signature
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)GET /api/v1/users/123
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
# Client sends If-None-Match on subsequent requests
GET /api/v1/users/123
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Response: 304 Not Modified (if unchanged)# Get only specific fields
GET /api/v1/users/123?fields=id,email,name
Response:
{
"id": "user_123",
"email": "user@example.com",
"name": "John Doe"
}Accept-Encoding: gzip, deflate# Instead of N individual requests
GET /api/v1/users/1
GET /api/v1/users/2
GET /api/v1/users/3
# Use batch endpoint
GET /api/v1/users?ids=1,2,3{
"data": {
"id": "user_123",
"email": "user@example.com",
"name": "John Doe"
},
"links": {
"self": "/api/v1/users/123",
"posts": "/api/v1/users/123/posts",
"comments": "/api/v1/users/123/comments",
"avatar": "/api/v1/users/123/avatar"
}
}Content-Type: application/json
Accept: application/json# Request JSON
Accept: application/json
# Request XML
Accept: application/xml
# Request CSV
Accept: text/csvGET /api/v1/old-endpoint
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: Tue, 1 Oct 2025 00:00:00 GMT
Link: </api/v2/new-endpoint>; rel="alternate"GET /health
Response (200):
{
"status": "healthy",
"version": "1.2.3",
"timestamp": "2025-10-16T10:30:00Z"
}GET /health/ready
Response (200):
{
"status": "ready",
"checks": {
"database": "ok",
"cache": "ok",
"messageQueue": "ok",
"externalAPI": "ok"
}
}
Response (503) if any dependency fails:
{
"status": "not_ready",
"checks": {
"database": "ok",
"cache": "degraded",
"messageQueue": "failed"
}
}def test_create_user():
response = client.post("/api/v1/users", json={
"email": "test@example.com",
"password": "SecurePass123",
"name": "Test User"
})
assert response.status_code == 201
assert response.json()["email"] == "test@example.com"
assert "password" not in response.json() # Never return passwordsdef test_user_flow():
# Create user
response = client.post("/api/v1/users", json=user_data)
user_id = response.json()["id"]
# Login
response = client.post("/api/v1/auth/login", json={
"email": user_data["email"],
"password": user_data["password"]
})
token = response.json()["accessToken"]
# Access protected resource
response = client.get(
f"/api/v1/users/{user_id}",
headers={"Authorization": f"Bearer {token}"}
)
assert response.status_code == 200