Search

PythonReactSEOPDF
GitHub Skill

arcanea-api-design

Design APIs that developers love. RESTful principles, GraphQL patterns, versioning strategies, and the art of creating interfaces that are intuitive, consistent, and future-proof.

frankxai☆ 1 Repository stars0 Repository forks

Installation

Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.

View source ↗Guides

Skill instructions

The API Design Codex

"An API is a user interface for developers. Design it with the same care you'd design a UI for users."


The API Design Philosophy

First Principles
GOOD APIs ARE:
• Predictable   - Behavior matches expectations
• Consistent    - Same patterns everywhere
• Simple        - Easy to use, hard to misuse
• Evolvable     - Can change without breaking
• Documented    - Self-describing where possible

GOOD APIs DO NOT:
• Surprise developers
• Require reading implementation
• Change behavior silently
• Expose internal details
• Force awkward workarounds

RESTful API Design

The REST Maturity Model
╔═══════════════════════════════════════════════════════════════════╗
║                    RICHARDSON MATURITY MODEL                       ║
╠═══════════════════════════════════════════════════════════════════╣
║                                                                    ║
║   LEVEL 0: The Swamp of POX                                       ║
║   Single endpoint, RPC-style                                       ║
║   POST /api → {action: "getUser", id: 1}                          ║
║                                                                    ║
║   LEVEL 1: Resources                                               ║
║   Multiple endpoints, still mostly POST                            ║
║   POST /users/1 → {action: "get"}                                 ║
║                                                                    ║
║   LEVEL 2: HTTP Verbs                                              ║
║   Proper use of GET, POST, PUT, DELETE                             ║
║   GET /users/1                                                     ║
║                                                                    ║
║   LEVEL 3: Hypermedia (HATEOAS)                                    ║
║   Responses include links to related actions                       ║
║   GET /users/1 → {..., links: [{rel: "orders", href: "/..."}]}    ║
║                                                                    ║
╚═══════════════════════════════════════════════════════════════════╝
Resource Naming
NOUNS, NOT VERBS:
✓ GET /users          ✗ GET /getUsers
✓ POST /orders        ✗ POST /createOrder
✓ DELETE /items/1     ✗ POST /deleteItem

PLURAL FOR COLLECTIONS:
✓ /users              ✗ /user
✓ /orders             ✗ /order

HIERARCHY FOR RELATIONSHIPS:
✓ /users/1/orders     ✗ /getUserOrders?userId=1
✓ /orders/1/items     ✗ /orderItems?orderId=1

KEBAB-CASE FOR MULTI-WORD:
✓ /user-profiles      ✗ /userProfiles
✓ /order-items        ✗ /order_items
HTTP Methods
┌────────┬────────────────┬──────────────┬──────────────┐
│ Method │ Purpose        │ Idempotent   │ Safe         │
├────────┼────────────────┼──────────────┼──────────────┤
│ GET    │ Read resource  │ Yes          │ Yes          │
│ POST   │ Create new     │ No           │ No           │
│ PUT    │ Replace all    │ Yes          │ No           │
│ PATCH  │ Partial update │ No*          │ No           │
│ DELETE │ Remove         │ Yes          │ No           │
└────────┴────────────────┴──────────────┴──────────────┘

*PATCH can be idempotent if designed carefully
Status Codes
2XX SUCCESS:
200 OK           - General success
201 Created      - Resource created (include Location header)
202 Accepted     - Processing started (async operations)
204 No Content   - Success with no body (DELETE, PUT)

4XX CLIENT ERRORS:
400 Bad Request  - Malformed request
401 Unauthorized - Authentication required
403 Forbidden    - Authenticated but not permitted
404 Not Found    - Resource doesn't exist
409 Conflict     - State conflict (e.g., duplicate)
422 Unprocessable - Valid syntax, invalid semantics

5XX SERVER ERRORS:
500 Internal     - Unexpected error
502 Bad Gateway  - Upstream service failed
503 Unavailable  - Temporarily overloaded
504 Gateway Timeout - Upstream timeout
Request/Response Design
// GOOD REQUEST
POST /api/v1/users
{
  "email": "user@example.com",
  "name": "John Doe",
  "role": "member"
}

// GOOD RESPONSE
{
  "data": {
    "id": "usr_123abc",
    "email": "user@example.com",
    "name": "John Doe",
    "role": "member",
    "createdAt": "2024-01-15T10:30:00Z"
  },
  "links": {
    "self": "/api/v1/users/usr_123abc",
    "orders": "/api/v1/users/usr_123abc/orders"
  }
}

// GOOD ERROR
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request data",
    "details": [
      {
        "field": "email",
        "message": "Email format is invalid"
      }
    ]
  }
}

Pagination, Filtering, Sorting

Pagination Patterns
OFFSET-BASED (Simple, but has issues at scale):
GET /users?offset=20&limit=10

CURSOR-BASED (Better for large datasets):
GET /users?cursor=eyJpZCI6MTAwfQ&limit=10

Response:
{
  "data": [...],
  "pagination": {
    "total": 1000,
    "limit": 10,
    "nextCursor": "eyJpZCI6MTEwfQ",
    "prevCursor": "eyJpZCI6OTB9"
  }
}
Filtering
SIMPLE EQUALITY:
GET /users?status=active&role=admin

COMPARISON OPERATORS:
GET /orders?total[gte]=100&total[lte]=500
GET /users?createdAt[gt]=2024-01-01

ARRAY VALUES:
GET /users?status[]=active&status[]=pending

SEARCH:
GET /users?q=john
GET /products?search=widget
Sorting
SINGLE FIELD:
GET /users?sort=createdAt
GET /users?sort=-createdAt  (descending)

MULTIPLE FIELDS:
GET /users?sort=-createdAt,name
GET /users?orderBy=createdAt:desc,name:asc

API Versioning

Versioning Strategies
URL PATH (Most common, explicit):
GET /api/v1/users
GET /api/v2/users

QUERY PARAMETER:
GET /api/users?version=1
GET /api/users?v=2

HEADER (Clean URLs, hidden version):
GET /api/users
Accept: application/vnd.api+json;version=1

CONTENT NEGOTIATION:
GET /api/users
Accept: application/vnd.company.v2+json
Version Lifecycle
┌─────────┐     ┌─────────┐     ┌─────────┐     ┌─────────┐
│  Alpha  │────▶│  Beta   │────▶│ Stable  │────▶│ Sunset  │
└─────────┘     └─────────┘     └─────────┘     └─────────┘
    ↓               ↓               ↓               ↓
 Breaking        Breaking        Backward        Deprecation
 changes OK      with notice     compatible      warnings
Breaking vs Non-Breaking Changes
NON-BREAKING (Safe to add):
✓ New optional fields
✓ New endpoints
✓ New query parameters
✓ New response fields

BREAKING (Requires new version):
✗ Removing fields
✗ Renaming fields
✗ Changing field types
✗ Changing URL structure
✗ Changing validation rules

GraphQL Design

Schema Design
# Type definitions
type User {
  id: ID!
  email: String!
  name: String!
  orders(first: Int, after: String): OrderConnection!
  createdAt: DateTime!
}

type Order {
  id: ID!
  user: User!
  items: [OrderItem!]!
  total: Money!
  status: OrderStatus!
}

enum OrderStatus {
  PENDING
  CONFIRMED
  SHIPPED
  DELIVERED
  CANCELLED
}

# Connections for pagination
type OrderConnection {
  edges: [OrderEdge!]!
  pageInfo: PageInfo!
}

type OrderEdge {
  node: Order!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}
Query Design
type Query {
  # Single resource
  user(id: ID!): User

  # Collection with filtering
  users(
    filter: UserFilter
    orderBy: UserOrderBy
    first: Int
    after: String
  ): UserConnection!

  # Viewer pattern for current user
  viewer: User
}

input UserFilter {
  status: UserStatus
  role: UserRole
  search: String
}

input UserOrderBy {
  field: UserOrderField!
  direction: OrderDirection!
}
Mutation Design
type Mutation {
  # Create with input type
  createUser(input: CreateUserInput!): CreateUserPayload!

  # Update with partial input
  updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!

  # Delete returns deleted item or boolean
  deleteUser(id: ID!): DeleteUserPayload!
}

input CreateUserInput {
  email: String!
  name: String!
  role: UserRole
}

type CreateUserPayload {
  user: User
  errors: [UserError!]!
}

type UserError {
  field: String
  message: String!
  code: ErrorCode!
}

Security Best Practices

Authentication
TOKEN-BASED:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

API KEY:
X-API-Key: sk_live_abcd1234

OAUTH 2.0 FLOWS:
• Authorization Code - Web apps
• Client Credentials - Server-to-server
• PKCE - Mobile/SPA apps
Rate Limiting
HEADERS:
X-RateLimit-Limit: 1000        # Total allowed
X-RateLimit-Remaining: 999     # Remaining
X-RateLimit-Reset: 1609459200  # Reset timestamp

RESPONSE WHEN LIMITED:
HTTP/1.1 429 Too Many Requests
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded",
    "retryAfter": 60
  }
}
Input Validation
ALWAYS VALIDATE:
□ Type (string, number, boolean)
□ Format (email, URL, UUID)
□ Length (min, max)
□ Range (min, max for numbers)
□ Allowed values (enums)
□ Required fields

SANITIZE:
□ Strip HTML
□ Escape special characters
□ Normalize unicode
□ Limit nested depth

Documentation

OpenAPI/Swagger
openapi: 3.0.0
info:
  title: User API
  version: 1.0.0

paths:
  /users:
    get:
      summary: List all users
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [active, inactive]
      responses:
        200:
          description: User list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserList'
Self-Documenting APIs
// HATEOAS - Include discoverable links
{
  "data": { ... },
  "links": {
    "self": "/api/users/1",
    "edit": "/api/users/1",
    "delete": "/api/users/1",
    "orders": "/api/users/1/orders"
  },
  "actions": [
    {
      "name": "deactivate",
      "method": "POST",
      "href": "/api/users/1/deactivate"
    }
  ]
}

Error Handling

Error Response Structure
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "User with ID 'usr_123' not found",
    "target": "user",
    "details": [
      {
        "code": "INVALID_ID",
        "target": "id",
        "message": "ID format is invalid"
      }
    ],
    "innererror": {
      "trace": "abc123",
      "timestamp": "2024-01-15T10:30:00Z"
    }
  }
}
Error Code Conventions
Use consistent, meaningful codes:

AUTHENTICATION:
• AUTH_REQUIRED
• AUTH_INVALID_TOKEN
• AUTH_TOKEN_EXPIRED

AUTHORIZATION:
• FORBIDDEN
• INSUFFICIENT_PERMISSIONS

VALIDATION:
• VALIDATION_ERROR
• INVALID_FORMAT
• MISSING_FIELD
• FIELD_TOO_LONG

RESOURCE:
• RESOURCE_NOT_FOUND
• RESOURCE_ALREADY_EXISTS
• RESOURCE_CONFLICT

RATE LIMITING:
• RATE_LIMIT_EXCEEDED
• QUOTA_EXCEEDED

Quick Reference

API Design Checklist
□ Resources are nouns, plural
□ HTTP methods used correctly
□ Status codes are semantic
□ Consistent naming conventions
□ Pagination for lists
□ Filtering and sorting
□ Versioning strategy defined
□ Error format standardized
□ Rate limiting implemented
□ Authentication documented
□ OpenAPI spec available
□ Examples for all endpoints
REST vs GraphQL Decision
CHOOSE REST WHEN:
• Simple CRUD operations
• Caching is important
• Team knows REST well
• Multiple simple clients
• Request patterns are predictable

CHOOSE GRAPHQL WHEN:
• Complex, nested data
• Mobile apps with bandwidth concerns
• Frontend needs flexibility
• Multiple related resources
• Rapid frontend iteration

"The best API is invisible. Developers use it without thinking about it because it does what they expect."

Examples

Read the usage examples in the source repository.

View source ↗

Source history

Indexed on
2026-01-20
Index updated
2026-01-26
Branch
main

Index and sync dates describe this directory. View the source repository for code history.

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!