---
name: api-forge
description: Design REST/GraphQL APIs with OpenAPI 3.1, error handling, pagination, rate limiting, webhooks, and idempotency. Use when user asks to design an API, create endpoints, define REST/GraphQL schema, or generate OpenAPI spec. Do NOT use for database schema design, frontend API integration, or non-HTTP protocols (gRPC, WebSocket, MQTT).
triggers:
- "design an API"
- "create endpoints"
- "REST API"
- "GraphQL schema"
- "OpenAPI spec"
- "rate limiting"
- "webhooks"
- "idempotency"
- "API pagination"
- "API versioning"
- "API security"
negatives:
- "database schema"
- "frontend integration"
- "gRPC"
- "WebSocket"
- "MQTT"
- "database design"
license: MIT
compatibility: opencode
metadata:
workflow: backend
audience: developers
version: "4.0.0"
author: shokunin
API Forge
Design APIs that developers love. Based on patterns from Stripe, GitHub, Twilio, Slack, and the OpenAPI 3.1 specification.
Sub-Commands
Command
Category
Description
design
Build
Design an API from user requirements. Generate OpenAPI 3.1 spec.
endpoint
Build
Design a single endpoint with all methods, parameters, responses, and error codes.
audit
Evaluate
Audit an existing API against design rules, security checklist, and anti-patterns.
document
Document
Generate API documentation from OpenAPI spec or route handlers.
extract
Document
Extract OpenAPI spec from existing route handlers.
webhook
Build
Design webhook delivery, retry, and signature verification.
Workflow
Step 1: Determine API type
Type
Use Case
Spec
REST
CRUD, resource-oriented
OpenAPI 3.1
GraphQL
Complex queries, multiple resources
Schema Definition Language
Webhook
Event-driven, async notifications
Standard webhooks (Stripe pattern)
Step 2: Define resources and naming
Pattern
Example
Notes
Nouns, plural
/users, /orders
Never verbs
Nested (max 2 levels)
/users/{id}/orders
Flat preferred over deep nesting
Actions as sub-resources
/orders/{id}/cancel
Only for non-CRUD operations
Query for filters
/users?role=admin
Not /users/admins
kebab-case for paths
/order-items
Not /orderItems
snake_case for fields
first_name
Not firstName in JSON:API
Step 3: Map HTTP methods
Method
Purpose
Idempotent
Safe
Body
GET
Read resource
Yes
Yes
No
POST
Create resource
No
No
Yes
PUT
Full replace
Yes
No
Yes
PATCH
Partial update
No
No
Yes
DELETE
Remove resource
Yes
No
Optional
Step 4: Design response format
Stripe-style standard envelope:
{
"data": {},
"meta": {
"page": 1,
"per_page": 25,
"total": 100
},
"error": null,
"request_id": "req_abc123"
}
If using JSON:API or GraphQL, use their standard envelopes instead.
Step 5: Implement pagination
Cursor-based for production. Page-based only for admin/internal tools.
GET /items?cursor=abc123&limit=25
{
"data": [...],
"meta": {
"next_cursor": "def456",
"has_more": true
}
}
Cursor must be opaque (base64-encoded compound key). Never expose internal IDs. Maximum limit: 100. Default: 25.
Error Handling
Every error response includes:
code: machine-readable error code (VALIDATION_ERROR, NOT_FOUND, RATE_LIMITED)
message: human-readable summary, max 150 chars
details: array of field-level errors for validation
request_id: UUIDv4 for debugging correlation
docs_url: link to error documentation (optional, strongly recommended)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Email is required",
"details": [
{ "field": "email", "code": "required", "message": "Email is required" }
],
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "https://docs.example.com/errors/validation"
}
}
Status codes — exact mapping
Code
When
What to return
200
Success (GET, PUT, PATCH)
Resource + meta
201
Created (POST)
Created resource + Location header
204
No content (DELETE)
Empty body
400
Validation error
Error details + fields
401
Missing/invalid auth
Generic message. Never reveal which part of auth failed.
403
Insufficient permissions
Generic message
404
Resource not found
Minimal. Don't reveal if the resource ever existed.
409
Conflict (duplicate, stale version)
Details of conflicting field
422
Unprocessable entity
Validation details
429
Rate limited
Retry-After header (seconds)
500
Internal error
Generic message. No stack traces. No internal state.
502
Downstream failure
"Service temporarily unavailable"
503
Maintenance / overload
Retry-After header
Rate Limiting
Algorithm decision
Algorithm
Best for
Behavior
Token Bucket
General purpose, bursts allowed
Tokens refill at configurable rate. Allows bursts up to bucket size.
Sliding Window
Strict fairness, multi-tenant
Counts requests in rolling time window. No burst edge at boundaries.
Fixed Window
Simple, non-critical
Resets at interval. Budget edge problem at boundaries.
Default: Token Bucket.
Headers (every response)
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1700000000
Return 429 Too Many Requests with Retry-After header when exceeded.
Rate limit tiers (exact values)
Tier
Requests
Window
Per
Anonymous
60
60s
IP
Authenticated
1000
60s
User ID + endpoint group
Critical endpoints
5
15min
IP + identifier
Critical: login (5/15min per IP+username), password reset (3/60min per email), MFA (3/15min per user).
Versioning
Strategy
Example
When
Risk
URL path
/v1/users
Default for REST APIs
URL pollution
Header
Accept: application/vnd.api+json;version=2
Clean URLs needed
Harder to discover
Query param
/users?version=2
Simple, transitional
Cache poisoning risk
Prefer URL path for public APIs. Deprecate with sunset headers. 6-month migration window minimum.
Deprecation: true
Sunset: Sat, 12 May 2027 00:00:00 GMT
Webhooks
Delivery format (Stripe pattern)
{
"id": "wh_abc123",
"type": "order.created",
"created": 1700000000,
"data": {
"id": "order_456",
"status": "paid",
"total": 2999
}
}
Delivery protocol
Retry: exponential backoff (1s, 2s, 4s, 8s, 16s, 32s…)
Max retries: 3. Max TTL: 24 hours.
Expect 200 response within 5 seconds.
Signature: HMAC-SHA256.
Signature verification (exact implementation)
X-Webhook-Signature: t=1700000000,v1=abc123def456...
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const [timestampStr, signatures] = signature.split(',').map(s => s.trim())
const timestamp = timestampStr.split('=')[1]
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatures.split('=')[1])
)
}
Idempotency
Header
Value
TTL
Idempotency-Key
UUIDv4
24 hours
Return cached response (same status, same body) if same key seen within TTL. Return 409 Conflict if different request body arrives with same key.
API Security Checklist
HTTPS enforced (HTTP → 301 redirect)
TLS 1.2+ only (no TLS 1.0/1.1)
CORS whitelist per environment, never * with credentials
Input validation at boundary (Zod, Joi, Pydantic)
Parameterized queries (no SQL injection). Never string interpolation.
No secrets in responses, logs, or error messages
Rate limiting on auth + password endpoints
Request size limit: 1MB default, configurable per endpoint
Body parsing limits (depth, field count, string length)
Security headers: X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Content-Security-Policy
OpenAPI 3.1 Generation
Every endpoint needs:
summary: one sentence. Verb + resource.
parameters: name, in, required, schema, description, example
responses: every possible status code
requestBody (for POST/PUT/PATCH): content, schema, required
GraphQL
Schema design
Queries for read, Mutations for write, Subscriptions for real-time
Max 3 nesting levels per query
@deprecated(reason: "Use fieldX instead") for removals
DataLoader for N+1 prevention
Complexity limits: max depth 5, max cost 1000
Error handling
{
"errors": [
{
"message": "Validation error",
"extensions": {
"code": "VALIDATION_ERROR",
"field": "email",
"request_id": "req_abc123"
}
}
]
}
Anti-Patterns
Anti-pattern
Fix
Verbs in URL (/getUsers)
Use HTTP methods on noun resources
Page-based pagination for real-time data
Cursor-based with opaque cursors
No rate limit headers
Include X-RateLimit-* on every response
Returning 500 with stack trace
Log internally, return generic message
Breaking changes without migration
Version via URL, deprecation + sunset headers
No idempotency on POST creates
Add Idempotency-Key header support
Inconsistent error format across endpoints
Standard envelope for all errors
GraphQL without complexity limits
Implement query depth + cost analysis
Nested resources > 2 levels
Restructure. Deep nesting = tight coupling.
POST for everything
Use correct HTTP methods. GET=read, PUT=replace, PATCH=partial.
Production Checklist
Sources
Stripe API Reference — idempotency, pagination, webhooks, error format
GitHub REST API — resource naming, versioning
Twilio API — webhook signature verification
OpenAPI 3.1 Specification (openapis.org)
JSON:API Specification (jsonapi.org)
GraphQL Relay Connection Specification
IETF RFC 7231 — HTTP semantics
IETF RFC 6585 — Additional HTTP status codes
Slack API — rate limiting headers
Checklist
1 --- 2 name: api-forge 3 description: --- 4 --- 5 --- 6 name: api-forge 7 description: Design REST/GraphQL APIs with OpenAPI 3.1, error handling, pagination, rate limiting, webhooks, and idempotency. Use when user asks to design an API, create endpoints, define REST/GraphQL schema, or generate OpenAPI spec. Do NOT use for database schema design, frontend API integration, or non-HTTP protocols (gRPC, WebSocket, MQTT). 8 triggers: 9 - "design an API" 10 - "create endpoints" 11 - "REST API" 12 - "GraphQL schema" 13 - "OpenAPI spec" 14 - "rate limiting" 15 - "webhooks" 16 - "idempotency" 17 - "API pagination" 18 - "API versioning" 19 - "API security" 20 negatives: 21 - "database schema" 22 - "frontend integration" 23 - "gRPC" 24 - "WebSocket" 25 - "MQTT" 26 - "database design" 27 license: MIT 28 compatibility: opencode 29 metadata: 30 workflow: backend 31 audience: developers 32 version: "4.0.0" 33 author: shokunin 34 --- 35 36 37 # API Forge 38 39 Design APIs that developers love. Based on patterns from Stripe, GitHub, Twilio, Slack, and the OpenAPI 3.1 specification. 40 41 ## Sub-Commands 42 43 | Command | Category | Description | 44 |---------|----------|-------------| 45 | `design` | Build | Design an API from user requirements. Generate OpenAPI 3.1 spec. | 46 | `endpoint` | Build | Design a single endpoint with all methods, parameters, responses, and error codes. | 47 | `audit` | Evaluate | Audit an existing API against design rules, security checklist, and anti-patterns. | 48 | `document` | Document | Generate API documentation from OpenAPI spec or route handlers. | 49 | `extract` | Document | Extract OpenAPI spec from existing route handlers. | 50 | `webhook` | Build | Design webhook delivery, retry, and signature verification. | 51 52 ## Workflow 53 54 ### Step 1: Determine API type 55 56 | Type | Use Case | Spec | 57 |------|----------|------| 58 | REST | CRUD, resource-oriented | OpenAPI 3.1 | 59 | GraphQL | Complex queries, multiple resources | Schema Definition Language | 60 | Webhook | Event-driven, async notifications | Standard webhooks (Stripe pattern) | 61 62 ### Step 2: Define resources and naming 63 64 | Pattern | Example | Notes | 65 |---------|---------|-------| 66 | Nouns, plural | `/users`, `/orders` | Never verbs | 67 | Nested (max 2 levels) | `/users/{id}/orders` | Flat preferred over deep nesting | 68 | Actions as sub-resources | `/orders/{id}/cancel` | Only for non-CRUD operations | 69 | Query for filters | `/users?role=admin` | Not `/users/admins` | 70 | kebab-case for paths | `/order-items` | Not `/orderItems` | 71 | snake_case for fields | `first_name` | Not `firstName` in JSON:API | 72 73 ### Step 3: Map HTTP methods 74 75 | Method | Purpose | Idempotent | Safe | Body | 76 |--------|---------|:--:|:--:|:--:| 77 | GET | Read resource | Yes | Yes | No | 78 | POST | Create resource | No | No | Yes | 79 | PUT | Full replace | Yes | No | Yes | 80 | PATCH | Partial update | No | No | Yes | 81 | DELETE | Remove resource | Yes | No | Optional | 82 83 ### Step 4: Design response format 84 85 Stripe-style standard envelope: 86 87 ```json 88 { 89 "data": {}, 90 "meta": { 91 "page": 1, 92 "per_page": 25, 93 "total": 100 94 }, 95 "error": null, 96 "request_id": "req_abc123" 97 } 98 ``` 99 100 If using JSON:API or GraphQL, use their standard envelopes instead. 101 102 ### Step 5: Implement pagination 103 104 **Cursor-based** for production. Page-based only for admin/internal tools. 105 106 ```json 107 GET /items?cursor=abc123&limit=25 108 { 109 "data": [...], 110 "meta": { 111 "next_cursor": "def456", 112 "has_more": true 113 } 114 } 115 ``` 116 117 Cursor must be opaque (base64-encoded compound key). Never expose internal IDs. Maximum limit: 100. Default: 25. 118 119 ## Error Handling 120 121 Every error response includes: 122 - `code`: machine-readable error code (`VALIDATION_ERROR`, `NOT_FOUND`, `RATE_LIMITED`) 123 - `message`: human-readable summary, max 150 chars 124 - `details`: array of field-level errors for validation 125 - `request_id`: UUIDv4 for debugging correlation 126 - `docs_url`: link to error documentation (optional, strongly recommended) 127 128 ```json 129 { 130 "error": { 131 "code": "VALIDATION_ERROR", 132 "message": "Email is required", 133 "details": [ 134 { "field": "email", "code": "required", "message": "Email is required" } 135 ], 136 "request_id": "req_a1b2c3d4e5f6", 137 "docs_url": "https://docs.example.com/errors/validation" 138 } 139 } 140 ``` 141 142 ### Status codes — exact mapping 143 144 | Code | When | What to return | 145 |------|------|---------------| 146 | 200 | Success (GET, PUT, PATCH) | Resource + meta | 147 | 201 | Created (POST) | Created resource + `Location` header | 148 | 204 | No content (DELETE) | Empty body | 149 | 400 | Validation error | Error details + fields | 150 | 401 | Missing/invalid auth | Generic message. Never reveal which part of auth failed. | 151 | 403 | Insufficient permissions | Generic message | 152 | 404 | Resource not found | Minimal. Don't reveal if the resource ever existed. | 153 | 409 | Conflict (duplicate, stale version) | Details of conflicting field | 154 | 422 | Unprocessable entity | Validation details | 155 | 429 | Rate limited | `Retry-After` header (seconds) | 156 | 500 | Internal error | Generic message. No stack traces. No internal state. | 157 | 502 | Downstream failure | "Service temporarily unavailable" | 158 | 503 | Maintenance / overload | `Retry-After` header | 159 160 ## Rate Limiting 161 162 ### Algorithm decision 163 164 | Algorithm | Best for | Behavior | 165 |-----------|----------|----------| 166 | Token Bucket | General purpose, bursts allowed | Tokens refill at configurable rate. Allows bursts up to bucket size. | 167 | Sliding Window | Strict fairness, multi-tenant | Counts requests in rolling time window. No burst edge at boundaries. | 168 | Fixed Window | Simple, non-critical | Resets at interval. Budget edge problem at boundaries. | 169 170 **Default: Token Bucket.** 171 172 ### Headers (every response) 173 174 ``` 175 X-RateLimit-Limit: 100 176 X-RateLimit-Remaining: 42 177 X-RateLimit-Reset: 1700000000 178 ``` 179 180 Return `429 Too Many Requests` with `Retry-After` header when exceeded. 181 182 ### Rate limit tiers (exact values) 183 184 | Tier | Requests | Window | Per | 185 |------|----------|--------|-----| 186 | Anonymous | 60 | 60s | IP | 187 | Authenticated | 1000 | 60s | User ID + endpoint group | 188 | Critical endpoints | 5 | 15min | IP + identifier | 189 190 Critical: login (5/15min per IP+username), password reset (3/60min per email), MFA (3/15min per user). 191 192 ## Versioning 193 194 | Strategy | Example | When | Risk | 195 |----------|---------|------|------| 196 | URL path | `/v1/users` | Default for REST APIs | URL pollution | 197 | Header | `Accept: application/vnd.api+json;version=2` | Clean URLs needed | Harder to discover | 198 | Query param | `/users?version=2` | Simple, transitional | Cache poisoning risk | 199 200 Prefer URL path for public APIs. Deprecate with sunset headers. 6-month migration window minimum. 201 202 ``` 203 Deprecation: true 204 Sunset: Sat, 12 May 2027 00:00:00 GMT 205 ``` 206 207 ## Webhooks 208 209 ### Delivery format (Stripe pattern) 210 211 ```json 212 { 213 "id": "wh_abc123", 214 "type": "order.created", 215 "created": 1700000000, 216 "data": { 217 "id": "order_456", 218 "status": "paid", 219 "total": 2999 220 } 221 } 222 ``` 223 224 ### Delivery protocol 225 226 - Retry: exponential backoff (1s, 2s, 4s, 8s, 16s, 32s…) 227 - Max retries: 3. Max TTL: 24 hours. 228 - Expect 200 response within 5 seconds. 229 - Signature: HMAC-SHA256. 230 231 ### Signature verification (exact implementation) 232 233 ``` 234 X-Webhook-Signature: t=1700000000,v1=abc123def456... 235 ``` 236 237 ```typescript 238 function verifyWebhook(payload: string, signature: string, secret: string): boolean { 239 const [timestampStr, signatures] = signature.split(',').map(s => s.trim()) 240 const timestamp = timestampStr.split('=')[1] 241 242 const expected = crypto 243 .createHmac('sha256', secret) 244 .update(`${timestamp}.${payload}`) 245 .digest('hex') 246 247 return crypto.timingSafeEqual( 248 Buffer.from(expected), 249 Buffer.from(signatures.split('=')[1]) 250 ) 251 } 252 ``` 253 254 ## Idempotency 255 256 | Header | Value | TTL | 257 |--------|-------|-----| 258 | `Idempotency-Key` | UUIDv4 | 24 hours | 259 260 Return cached response (same status, same body) if same key seen within TTL. Return `409 Conflict` if different request body arrives with same key. 261 262 ## API Security Checklist 263 264 - [ ] HTTPS enforced (HTTP → 301 redirect) 265 - [ ] TLS 1.2+ only (no TLS 1.0/1.1) 266 - [ ] CORS whitelist per environment, never `*` with credentials 267 - [ ] Input validation at boundary (Zod, Joi, Pydantic) 268 - [ ] Parameterized queries (no SQL injection). Never string interpolation. 269 - [ ] No secrets in responses, logs, or error messages 270 - [ ] Rate limiting on auth + password endpoints 271 - [ ] Request size limit: 1MB default, configurable per endpoint 272 - [ ] Body parsing limits (depth, field count, string length) 273 - [ ] Security headers: `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Content-Security-Policy` 274 275 ## OpenAPI 3.1 Generation 276 277 Every endpoint needs: 278 - `summary`: one sentence. Verb + resource. 279 - `parameters`: name, in, required, schema, description, example 280 - `responses`: every possible status code 281 - `requestBody` (for POST/PUT/PATCH): `content`, `schema`, `required` 282 283 ## GraphQL 284 285 ### Schema design 286 287 - Queries for read, Mutations for write, Subscriptions for real-time 288 - Max 3 nesting levels per query 289 - `@deprecated(reason: "Use fieldX instead")` for removals 290 - DataLoader for N+1 prevention 291 - Complexity limits: max depth 5, max cost 1000 292 293 ### Error handling 294 295 ```json 296 { 297 "errors": [ 298 { 299 "message": "Validation error", 300 "extensions": { 301 "code": "VALIDATION_ERROR", 302 "field": "email", 303 "request_id": "req_abc123" 304 } 305 } 306 ] 307 } 308 ``` 309 310 ## Anti-Patterns 311 312 | Anti-pattern | Fix | 313 |-------------|-----| 314 | Verbs in URL (`/getUsers`) | Use HTTP methods on noun resources | 315 | Page-based pagination for real-time data | Cursor-based with opaque cursors | 316 | No rate limit headers | Include `X-RateLimit-*` on every response | 317 | Returning 500 with stack trace | Log internally, return generic message | 318 | Breaking changes without migration | Version via URL, deprecation + sunset headers | 319 | No idempotency on POST creates | Add `Idempotency-Key` header support | 320 | Inconsistent error format across endpoints | Standard envelope for all errors | 321 | GraphQL without complexity limits | Implement query depth + cost analysis | 322 | Nested resources > 2 levels | Restructure. Deep nesting = tight coupling. | 323 | POST for everything | Use correct HTTP methods. GET=read, PUT=replace, PATCH=partial. | 324 325 ## Production Checklist 326 327 - [ ] All endpoints documented with OpenAPI 3.1 328 - [ ] Envelope: `{ data, meta, error, request_id }` on every response 329 - [ ] Cursor-based pagination for public endpoints 330 - [ ] Rate limit headers on every response 331 - [ ] Rate limiting on auth endpoints (5/15min login, 3/60min reset) 332 - [ ] Idempotency-Key support on POST/PATCH 333 - [ ] Webhook signature verification (HMAC-SHA256) 334 - [ ] Security headers on every response 335 - [ ] CORS whitelist (never `*` with credentials) 336 - [ ] Input validation at boundary 337 - [ ] Parameterized queries everywhere 338 - [ ] Error responses include `request_id` and `code` 339 - [ ] Versioning strategy defined (URL path preferred) 340 - [ ] Deprecation + Sunset headers on deprecated endpoints 341 342 ## Sources 343 344 - Stripe API Reference — idempotency, pagination, webhooks, error format 345 - GitHub REST API — resource naming, versioning 346 - Twilio API — webhook signature verification 347 - OpenAPI 3.1 Specification (openapis.org) 348 - JSON:API Specification (jsonapi.org) 349 - GraphQL Relay Connection Specification 350 - IETF RFC 7231 — HTTP semantics 351 - IETF RFC 6585 — Additional HTTP status codes 352 - Slack API — rate limiting headers 353 354 ## Checklist 355 356 - [ ] Skill loads without errors in the AI agent 357 - [ ] YAML frontmatter is valid (description, compatibility, audience) 358 - [ ] Workflow section provides clear step-by-step instructions 359 - [ ] Error handling section covers common failure modes 360 - [ ] All referenced files (references/, scripts/, assets/) exist 361 - [ ] Skill triggers correctly for intended use cases 362 - [ ] No broken links or missing resources