Skip to main content

RESTful API Design Skill

RESTful API Design Skill

How to Use This Skill

  1. Review the patterns and examples below
  2. Apply the relevant patterns to your implementation
  3. Follow the best practices outlined in this skill

Production-ready RESTful API design skill implementing industry best practices for resource-oriented architecture, comprehensive documentation with OpenAPI 3.1, and consistent error handling patterns.

When to Use This Skill

Use restful-api-design when:

  • Designing new REST APIs from scratch
  • Creating OpenAPI/Swagger documentation
  • Reviewing existing API designs for consistency
  • Implementing pagination, filtering, or sorting
  • Defining error response standards
  • Planning API versioning strategies

Don't use restful-api-design when:

  • Building GraphQL APIs (use graphql-design skill)
  • Implementing gRPC or WebSocket protocols
  • Only need API versioning guidance (use api-versioning skill)
  • Working on API security (use security-audit skill)

REST Design Principles

Resource-Oriented Architecture

PrincipleDescriptionExample
ResourcesUse nouns, not verbs/users not /getUsers
CollectionsPlural naming/users, /orders, /products
HierarchyNested resources/users/{id}/orders
ConsistencyUniform interfaceSame patterns across all endpoints
StatelessNo server-side sessionsUse tokens/API keys

HTTP Methods

MethodPurposeIdempotentSafeRequest Body
GETRetrieve resource(s)YesYesNo
POSTCreate resourceNoNoYes
PUTReplace resourceYesNoYes
PATCHPartial updateNoNoYes
DELETERemove resourceYesNoOptional
OPTIONSGet allowed methodsYesYesNo
HEADGet headers onlyYesYesNo

HTTP Status Codes

2xx Success
├── 200 OK - Successful GET, PUT, PATCH, DELETE
├── 201 Created - Successful POST (include Location header)
├── 202 Accepted - Async operation accepted
└── 204 No Content - Successful DELETE (no body)

3xx Redirection
├── 301 Moved Permanently - Resource relocated
├── 304 Not Modified - Cache hit (ETag/If-Modified-Since)
└── 307 Temporary Redirect - Use for redirects

4xx Client Error
├── 400 Bad Request - Malformed request, validation error
├── 401 Unauthorized - Missing/invalid authentication
├── 403 Forbidden - Valid auth, insufficient permissions
├── 404 Not Found - Resource doesn't exist
├── 405 Method Not Allowed - Wrong HTTP method
├── 409 Conflict - State conflict (duplicate, version mismatch)
├── 422 Unprocessable Entity - Semantic validation error
└── 429 Too Many Requests - Rate limit exceeded

5xx Server Error
├── 500 Internal Server Error - Unexpected server failure
├── 502 Bad Gateway - Upstream service failure
├── 503 Service Unavailable - Maintenance/overload
└── 504 Gateway Timeout - Upstream timeout

Instructions

Phase 1: Resource Design

Objective: Define API resources and relationships.

  1. Identify resources:

    Domain: E-commerce

    Primary Resources:
    - Users (/users)
    - Products (/products)
    - Orders (/orders)
    - Categories (/categories)

    Nested Resources:
    - User's orders (/users/{id}/orders)
    - Product reviews (/products/{id}/reviews)
    - Order items (/orders/{id}/items)
  2. Define resource relationships:

    # Resource relationships
    User:
    has_many: [orders, addresses, reviews]
    belongs_to: []

    Order:
    has_many: [order_items, payments]
    belongs_to: [user]

    Product:
    has_many: [reviews, variants]
    belongs_to: [category]
  3. Design URL structure:

    # Collection endpoints
    GET /api/v1/users # List users
    POST /api/v1/users # Create user

    # Instance endpoints
    GET /api/v1/users/{id} # Get user
    PUT /api/v1/users/{id} # Replace user
    PATCH /api/v1/users/{id} # Update user
    DELETE /api/v1/users/{id} # Delete user

    # Nested resources
    GET /api/v1/users/{id}/orders # User's orders
    POST /api/v1/users/{id}/orders # Create order for user
    GET /api/v1/users/{id}/orders/{orderId} # Specific order

Phase 2: OpenAPI Specification

Objective: Create comprehensive API documentation.

  1. Define OpenAPI structure:

    openapi: 3.1.0
    info:
    title: E-commerce API
    version: 1.0.0
    description: |
    RESTful API for e-commerce platform.

    ## Authentication
    All endpoints require Bearer token authentication.

    ## Rate Limiting
    - Standard: 100 requests/minute
    - Premium: 1000 requests/minute
    contact:
    name: API Support
    email: api@example.com
    license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0

    servers:
    - url: https://api.example.com/v1
    description: Production
    - url: https://staging-api.example.com/v1
    description: Staging

    tags:
    - name: Users
    description: User management operations
    - name: Products
    description: Product catalog operations
    - name: Orders
    description: Order management operations
  2. Define schemas:

    components:
    schemas:
    User:
    type: object
    required:
    - email
    - name
    properties:
    id:
    type: string
    format: uuid
    readOnly: true
    example: "550e8400-e29b-41d4-a716-446655440000"
    email:
    type: string
    format: email
    example: "user@example.com"
    name:
    type: string
    minLength: 1
    maxLength: 100
    example: "John Doe"
    created_at:
    type: string
    format: date-time
    readOnly: true
    updated_at:
    type: string
    format: date-time
    readOnly: true

    UserCreate:
    type: object
    required:
    - email
    - name
    - password
    properties:
    email:
    type: string
    format: email
    name:
    type: string
    password:
    type: string
    format: password
    minLength: 8

    Error:
    type: object
    required:
    - code
    - message
    properties:
    code:
    type: string
    example: "VALIDATION_ERROR"
    message:
    type: string
    example: "Invalid input data"
    details:
    type: array
    items:
    $ref: '#/components/schemas/ErrorDetail'

    ErrorDetail:
    type: object
    properties:
    field:
    type: string
    message:
    type: string
  3. Define paths:

    paths:
    /users:
    get:
    summary: List users
    operationId: listUsers
    tags: [Users]
    parameters:
    - $ref: '#/components/parameters/PageParam'
    - $ref: '#/components/parameters/LimitParam'
    - $ref: '#/components/parameters/SortParam'
    responses:
    '200':
    description: Successful response
    content:
    application/json:
    schema:
    $ref: '#/components/schemas/UserListResponse'
    '401':
    $ref: '#/components/responses/Unauthorized'

    post:
    summary: Create user
    operationId: createUser
    tags: [Users]
    requestBody:
    required: true
    content:
    application/json:
    schema:
    $ref: '#/components/schemas/UserCreate'
    responses:
    '201':
    description: User created
    headers:
    Location:
    description: URL of created user
    schema:
    type: string
    content:
    application/json:
    schema:
    $ref: '#/components/schemas/User'
    '400':
    $ref: '#/components/responses/BadRequest'
    '409':
    $ref: '#/components/responses/Conflict'

    /users/{id}:
    parameters:
    - name: id
    in: path
    required: true
    schema:
    type: string
    format: uuid

    get:
    summary: Get user by ID
    operationId: getUser
    tags: [Users]
    responses:
    '200':
    description: Successful response
    content:
    application/json:
    schema:
    $ref: '#/components/schemas/User'
    '404':
    $ref: '#/components/responses/NotFound'

Phase 3: Pagination & Filtering

Objective: Implement consistent pagination and filtering.

  1. Cursor-based pagination (recommended):

    # Request
    GET /api/v1/users?limit=20&cursor=eyJpZCI6MTAwfQ==

    # Response
    {
    "data": [...],
    "pagination": {
    "next_cursor": "eyJpZCI6MTIwfQ==",
    "prev_cursor": "eyJpZCI6ODB9",
    "has_more": true
    }
    }
  2. Offset-based pagination:

    # Request
    GET /api/v1/users?page=2&limit=20

    # Response
    {
    "data": [...],
    "pagination": {
    "page": 2,
    "limit": 20,
    "total": 150,
    "total_pages": 8
    }
    }
  3. Filtering patterns:

    # Simple equality
    GET /api/v1/products?category=electronics

    # Multiple values
    GET /api/v1/products?status=active,pending

    # Range queries
    GET /api/v1/products?price_min=10&price_max=100
    GET /api/v1/orders?created_after=2024-01-01&created_before=2024-12-31

    # Text search
    GET /api/v1/products?q=laptop

    # Nested field
    GET /api/v1/orders?user.email=john@example.com
  4. Sorting:

    # Single field
    GET /api/v1/products?sort=price # Ascending
    GET /api/v1/products?sort=-price # Descending

    # Multiple fields
    GET /api/v1/products?sort=-created_at,name

Phase 4: Error Handling

Objective: Implement consistent error responses.

  1. Standard error format:

    {
    "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "request_id": "req_abc123xyz",
    "timestamp": "2024-01-15T10:30:00Z",
    "details": [
    {
    "field": "email",
    "message": "must be a valid email address",
    "code": "INVALID_FORMAT"
    },
    {
    "field": "password",
    "message": "must be at least 8 characters",
    "code": "MIN_LENGTH"
    }
    ]
    }
    }
  2. Error code enum:

    ErrorCode:
    - VALIDATION_ERROR # 400 - Input validation failed
    - INVALID_FORMAT # 400 - Wrong data format
    - MISSING_FIELD # 400 - Required field missing
    - UNAUTHORIZED # 401 - Missing authentication
    - INVALID_TOKEN # 401 - Invalid/expired token
    - FORBIDDEN # 403 - Insufficient permissions
    - NOT_FOUND # 404 - Resource not found
    - CONFLICT # 409 - Resource conflict
    - RATE_LIMITED # 429 - Too many requests
    - INTERNAL_ERROR # 500 - Server error
    - SERVICE_UNAVAILABLE # 503 - Service down

Examples

Example 1: User CRUD API

OpenAPI Specification:

openapi: 3.1.0
info:
title: User 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: 100
responses:
'200':
description: Success
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'

post:
summary: Create new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserCreate'
responses:
'201':
description: Created
headers:
Location:
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/User'

/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'

Example 2: FastAPI Implementation

from fastapi import FastAPI, HTTPException, Query, Path
from pydantic import BaseModel, EmailStr
from typing import Optional
from uuid import UUID
from datetime import datetime

app = FastAPI(title="User API", version="1.0.0")

class UserCreate(BaseModel):
email: EmailStr
name: str
password: str

class User(BaseModel):
id: UUID
email: EmailStr
name: str
created_at: datetime
updated_at: datetime

class Pagination(BaseModel):
page: int
limit: int
total: int
total_pages: int

class UserListResponse(BaseModel):
data: list[User]
pagination: Pagination

@app.get("/users", response_model=UserListResponse)
async def list_users(
page: int = Query(1, ge=1),
limit: int = Query(20, ge=1, le=100),
sort: Optional[str] = Query(None, regex="^-?(name|email|created_at)$")
):
"""List all users with pagination."""
# Implementation here
pass

@app.post("/users", status_code=201)
async def create_user(user: UserCreate):
"""Create a new user."""
# Implementation here
pass

@app.get("/users/{user_id}", response_model=User)
async def get_user(user_id: UUID = Path(...)):
"""Get user by ID."""
# Implementation here
pass

Example 3: Express.js Implementation

import express, { Request, Response, NextFunction } from 'express';
import { z } from 'zod';

const app = express();

// Schemas
const UserCreateSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
password: z.string().min(8),
});

const PaginationSchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.string().regex(/^-?(name|email|created_at)$/).optional(),
});

// Middleware
const validateQuery = (schema: z.ZodSchema) => {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.query);
if (!result.success) {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid query parameters',
details: result.error.errors,
},
});
}
req.query = result.data;
next();
};
};

// Routes
app.get('/users', validateQuery(PaginationSchema), async (req, res) => {
const { page, limit, sort } = req.query;
// Implementation here
res.json({ data: [], pagination: { page, limit, total: 0, total_pages: 0 } });
});

app.post('/users', async (req, res) => {
const result = UserCreateSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: 'Invalid input',
details: result.error.errors,
},
});
}
// Implementation here
res.status(201).json({ id: 'uuid', ...result.data });
});

Integration

  • Skill: graphql-design - Alternative API design approach
  • Skill: api-versioning - Versioning strategy guidance
  • Skill: security-audit - API security review
  • Agent: senior-architect - API architecture planning
  • Command: /describe-pr - Document API changes

OpenAPI Tools

# Validate OpenAPI spec
npx @redocly/cli lint openapi.yaml

# Generate client SDKs
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-fetch \
-o ./client

# Generate documentation
npx @redocly/cli build-docs openapi.yaml --output docs.html

Troubleshooting

IssueSolution
Inconsistent status codesUse error handling middleware
N+1 queries on nested resourcesImplement eager loading, use ?include=
Large response sizesImplement pagination, use ?fields=
Breaking changesUse API versioning
Poor API discoverabilityAdd HATEOAS links

Best Practices Checklist

  • Use plural nouns for collections (/users not /user)
  • Use UUID or slug for resource IDs
  • Include Content-Type: application/json header
  • Return 201 Created with Location header for POST
  • Use 204 No Content for successful DELETE
  • Implement consistent error response format
  • Add pagination for all list endpoints
  • Use HTTPS in production
  • Include API versioning from day one
  • Document all endpoints with OpenAPI

Success Output

When this skill completes successfully, output:

✅ SKILL COMPLETE: restful-api-design

Completed:
- [x] Resource-oriented architecture designed (nouns, collections, hierarchy)
- [x] OpenAPI 3.1 specification created with complete schemas
- [x] Pagination strategy implemented (cursor or offset-based)
- [x] Error handling standardized with consistent format
- [x] All endpoints documented with examples

Outputs:
- openapi.yaml - OpenAPI 3.1 specification
- api/routes/ - Endpoint implementations
- api/schemas/ - Request/response models
- docs/api/ - API documentation

Validation:
- [ ] OpenAPI spec validates: `npx @redocly/cli lint openapi.yaml`
- [ ] All status codes follow HTTP standards
- [ ] Pagination on all list endpoints
- [ ] Error responses use standard format

Completion Checklist

Before marking this skill as complete, verify:

  • Resources use plural nouns (e.g., /users not /user)
  • HTTP methods correctly applied (GET safe, PUT idempotent, etc.)
  • OpenAPI spec includes all endpoints with request/response schemas
  • Status codes follow HTTP standards (201 + Location for POST, 204 for DELETE)
  • Pagination implemented for all collection endpoints
  • Filtering and sorting parameters documented
  • Error responses follow standard format with codes
  • Authentication/authorization documented
  • OpenAPI spec validates successfully
  • API documentation generated and reviewed

Failure Indicators

This skill has FAILED if:

  • ❌ Resources use verbs instead of nouns (e.g., /getUser)
  • ❌ OpenAPI spec has validation errors or missing schemas
  • ❌ Collection endpoints lack pagination (will fail at scale)
  • ❌ Error responses inconsistent or lack proper codes
  • ❌ HTTP methods misused (POST for retrieval, GET with side effects)
  • ❌ Status codes incorrect (200 for errors, 404 for auth failures)
  • ❌ No authentication/authorization documentation
  • ❌ Nested resources exceed 2-3 levels deep (too complex)

When NOT to Use

Do NOT use this skill when:

  • Building GraphQL APIs (use graphql-design skill instead)
  • Implementing gRPC or WebSocket protocols (use protocol-specific skills)
  • Only need API versioning guidance (use api-versioning skill)
  • Working on API security audits (use security-audit skill instead)
  • Building simple CRUD with existing framework (may be over-engineering)
  • Internal microservice communication (consider simpler RPC)

Use instead:

  • graphql-design for GraphQL APIs
  • grpc-design for gRPC services
  • api-versioning for versioning strategy only
  • security-audit for security reviews
  • Framework defaults for simple CRUD operations

Anti-Patterns (Avoid)

Anti-PatternProblemSolution
Verb-based endpoints/getUser, /createOrder not RESTfulUse /users/{id} with HTTP methods
Ignoring idempotencyPUT/DELETE not idempotent causes issuesDesign for safe retries
No paginationLarge collections crash clients/serversAlways paginate list endpoints
Generic error codesAll errors return 400 or 500Use specific codes (401, 403, 404, 409, 422)
Deep nesting/users/{id}/orders/{id}/items/{id}/reviewsMax 2-3 levels, use query params
Breaking changesAdding required fields breaks clientsUse API versioning, optional fields
No OpenAPI docsManual docs get staleGenerate from OpenAPI spec
Status code confusion404 for auth, 200 for errorsFollow HTTP standards strictly

Principles

This skill embodies CODITECT principles:

  • #1 Recycle → Extend → Re-Use → Create - Uses industry-standard REST patterns and OpenAPI
  • #2 First Principles - Understands WHY REST is resource-oriented, stateless, cacheable
  • #3 Keep It Simple - Simple URL patterns, standard HTTP methods, consistent responses
  • #4 Separation of Concerns - Resources separated by domain, endpoints by responsibility
  • #5 Eliminate Ambiguity - OpenAPI spec eliminates API ambiguity completely
  • #6 Clear, Understandable, Explainable - Self-documenting URLs, standard error codes
  • #8 No Assumptions - Explicit documentation, validation, error handling

Full Standard: CODITECT-STANDARD-AUTOMATION.md

References


Status: Production-ready OpenAPI Version: 3.1.0 Supported Frameworks: FastAPI, Express, Actix-web, Spring Boot Integration: API documentation, SDK generation, security audits Version: 1.0.0 | Updated: 2026-01-04 | Author: CODITECT Team