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.
Provides comprehensive REST API design guidance covering resource-oriented patterns, HTTP methods, status codes, authentication (JWT, OAuth, API keys), versioning, pagination, rate limiting, and OpenAPI documentation. Use when designing endpoints, implementing backend services, creating API documentation, or reviewing API code.
/plugin marketplace add webdevtodayjason/titanium-plugins/plugin install titanium-toolkit@titanium-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill provides comprehensive guidance for designing, implementing, and documenting RESTful APIs following industry best practices.
APIs should be designed around resources (nouns), not actions (verbs):
Good:
GET /api/v1/users
POST /api/v1/users
GET /api/v1/users/{id}
PUT /api/v1/users/{id}
DELETE /api/v1/users/{id}
Bad:
GET /api/v1/getUsers
POST /api/v1/createUser
POST /api/v1/updateUser
POST /api/v1/deleteUser
Success (2xx):
200 OK: Successful GET, PUT, PATCH, DELETE201 Created: Successful POST with resource creation202 Accepted: Request accepted for async processing204 No Content: Successful DELETE or update with no response bodyClient Errors (4xx):
400 Bad Request: Malformed request, validation error401 Unauthorized: Authentication required403 Forbidden: Authenticated but not authorized404 Not Found: Resource doesn't exist409 Conflict: Resource conflict (duplicate, version mismatch)422 Unprocessable Entity: Valid syntax but semantic errors429 Too Many Requests: Rate limit exceededServer Errors (5xx):
500 Internal Server Error: Unexpected server error502 Bad Gateway: Upstream service failure503 Service Unavailable: Temporary overload or maintenance504 Gateway Timeout: Upstream timeoutGET /api/v1/users
GET /api/v2/users
Pros: Clear, easy to route, visible in logs Cons: Duplicate code across versions
GET /api/users
Accept: application/vnd.myapi.v1+json
Pros: Clean URLs Cons: Harder to test, less visible
JSON Request Body:
{
"email": "user@example.com",
"name": "John Doe",
"preferences": {
"newsletter": true,
"notifications": false
}
}
Query Parameters (for filtering, pagination, sorting):
GET /api/v1/users?role=admin&status=active&page=2&limit=20&sort=-created_at
Success Response:
{
"data": {
"id": "user_123",
"email": "user@example.com",
"name": "John Doe",
"createdAt": "2025-10-16T10:30:00Z"
}
}
Error Response:
{
"error": {
"code": "INVALID_EMAIL",
"message": "Email address is invalid",
"field": "email",
"details": "Email must contain @ symbol"
}
}
Collection Response with Pagination:
{
"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"
}
}
Login Flow:
POST /api/v1/auth/login
{
"email": "user@example.com",
"password": "SecurePassword123"
}
Response (200):
{
"accessToken": "eyJhbGc...",
"refreshToken": "eyJhbGc...",
"expiresIn": 900
}
Using Access Token:
GET /api/v1/users/me
Authorization: Bearer eyJhbGc...
Token Refresh:
POST /api/v1/auth/refresh
{
"refreshToken": "eyJhbGc..."
}
Response (200):
{
"accessToken": "eyJhbGc...",
"expiresIn": 900
}
Header-based (recommended):
GET /api/v1/data
X-API-Key: sk_live_abc123xyz
Query parameter (less secure, use only for public data):
GET /api/v1/public-data?api_key=sk_live_abc123xyz
Authorization Code Flow (for web apps):
/oauth/authorize/oauth/tokenClient Credentials Flow (for server-to-server):
POST /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=20
Pros: Simple, allows jumping to any page Cons: Performance degrades with large offsets, inconsistent if data changes
GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20
Response:
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6MTQzfQ",
"hasMore": true
}
}
Pros: Consistent results, performant at any scale Cons: Can't jump to specific page
GET /api/v1/users?page=3&limit=20
Pros: User-friendly, easy to understand Cons: Same issues as offset pagination
Headers to include:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1634400000
Tiered Limits:
Token Bucket (recommended):
Fixed Window:
Sliding Window:
Never send sensitive data over HTTP. Enforce HTTPS at the load balancer level.
from 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)
Prevent injection attacks by escaping output:
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 request
logger.warning(f"Failed login attempt for {email} from {ip_address}")
logger.critical(f"Privilege escalation attempt by user {user_id}")
Prevent brute force attacks:
/auth/login: 5 attempts per 15 minutes per IP/auth/register: 3 attempts per hour per IP/auth/reset-password: 3 attempts per hour per emailopenapi: 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 comment
Sometimes you need RPC-style endpoints for actions:
POST /api/v1/users/{id}/verify-email
POST /api/v1/orders/{id}/cancel
POST /api/v1/posts/{id}/publish
POST /api/v1/invoices/{id}/send
Pattern: POST /{resource}/{id}/{action}
Use when:
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:john
POST /api/v1/payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{
"amount": 100.00,
"currency": "USD",
"description": "Payment for order #123"
}
Server stores idempotency key:
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"
}
Status values: queued, processing, completed, failed
{
"event": "user.created",
"timestamp": "2025-10-16T10:30:00Z",
"id": "evt_abc123",
"data": {
"id": "user_123",
"email": "user@example.com",
"name": "John Doe"
}
}
HMAC Signature:
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
Server should compress responses >1KB.
# 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"
}
}
Benefits:
Content-Type: application/json
Accept: application/json
# Request JSON
Accept: application/json
# Request XML
Accept: application/xml
# Request CSV
Accept: text/csv
GET /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"
Provide clear migration path:
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 passwords
def 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
Use this skill when:
Remember: A well-designed API is intuitive, secure, performant, and well-documented. Follow these patterns to create APIs that developers love to use.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.