
arcanea-api-design
by frankxai
Open source agents, skills, and lore for AI-powered creative work. Transform your AI assistant into a creative companion.
SKILL.md
name: arcanea-api-design description: Design APIs that developers love. RESTful principles, GraphQL patterns, versioning strategies, and the art of creating interfaces that are intuitive, consistent, and future-proof. version: 2.0.0 author: Arcanea tags: [api, rest, graphql, design, interfaces, development] triggers:
- api design
- rest api
- graphql
- endpoints
- api versioning
- interface design
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."
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon


