skills/api-design/SKILL.md
REST API design patterns — resource naming, HTTP status codes, pagination, filtering, error response format, versioning, rate limiting. Use when designing or reviewing any API endpoint.
npx skillsauth add a2mus/ecc-antigravity api-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
# Resources are nouns, plural, lowercase, kebab-case
GET /api/v1/users
GET /api/v1/users/:id
POST /api/v1/users
PUT /api/v1/users/:id
PATCH /api/v1/users/:id
DELETE /api/v1/users/:id
# Sub-resources for relationships
GET /api/v1/users/:id/orders
POST /api/v1/users/:id/orders
# Actions that don't map to CRUD — verbs sparingly
POST /api/v1/orders/:id/cancel
POST /api/v1/auth/login
POST /api/v1/auth/refresh
# GOOD
/api/v1/team-members # kebab-case for multi-word
/api/v1/orders?status=active # query params for filtering
# BAD
/api/v1/getUsers # verb in URL
/api/v1/user # singular (use plural)
/api/v1/team_members # snake_case in URLs
# Success
200 OK — GET, PUT, PATCH (with response body)
201 Created — POST (send Location header)
204 No Content — DELETE, PUT (no body needed)
# Client Errors
400 Bad Request — malformed JSON, type mismatch
401 Unauthorized — missing or invalid auth token
403 Forbidden — authenticated but not allowed
404 Not Found — resource doesn't exist
409 Conflict — duplicate entry, state conflict
422 Unprocessable Entity — valid JSON but semantically wrong
429 Too Many Requests — rate limit exceeded
# Server Errors
500 Internal Server Error — unexpected failure (never leak details)
503 Service Unavailable — temporary overload, include Retry-After
{ "data": { "id": "abc-123", "email": "[email protected]" } }
{
"data": [{ "id": "abc-123" }, { "id": "def-456" }],
"meta": { "total": 142, "page": 1, "per_page": 20, "total_pages": 8 },
"links": {
"self": "/api/v1/users?page=1",
"next": "/api/v1/users?page=2",
"last": "/api/v1/users?page=8"
}
}
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Must be a valid email", "code": "invalid_format" }
]
}
}
| Strategy | When to Use |
|---|---|
| Offset (?page=2&per_page=20) | Admin dashboards, small datasets < 10K, search results |
| Cursor (?cursor=...&limit=20) | Feeds, infinite scroll, large/high-write datasets |
GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20
Response:
{ "data": [...], "meta": { "has_next": true, "next_cursor": "eyJpZCI6MTQzfQ" } }
# Equality
GET /api/v1/orders?status=active&customer_id=abc-123
# Comparison (bracket notation)
GET /api/v1/products?price[gte]=10&price[lte]=100
# Multiple values
GET /api/v1/products?category=electronics,clothing
# Sorting (prefix - for descending)
GET /api/v1/products?sort=-created_at,price
Response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000
429 Too Many Requests:
{ "error": { "code": "rate_limit_exceeded", "message": "Try again in 60 seconds." } }
Retry-After: 60
| Tier | Limit | Per | |---|---|---| | Anonymous | 30/min | IP | | Authenticated | 100/min | user | | Premium | 1000/min | API key |
# URL path versioning (recommended)
/api/v1/users
/api/v2/users
# Rules:
# - Start with /api/v1/ — don't version until you need to
# - Maintain at most 2 active versions (current + previous)
# - Breaking changes require a new version
# - Adding fields / optional params does NOT require a new version
# - Deprecation: 6 months notice, Sunset header, then 410 Gone
import { z } from 'zod'
import { NextRequest, NextResponse } from 'next/server'
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
})
export async function POST(req: NextRequest) {
const body = await req.json()
const parsed = createUserSchema.safeParse(body)
if (!parsed.success) {
return NextResponse.json({
error: {
code: 'validation_error',
message: 'Request validation failed',
details: parsed.error.issues.map(i => ({
field: i.path.join('.'),
message: i.message,
code: i.code,
})),
},
}, { status: 422 })
}
const user = await createUser(parsed.data)
return NextResponse.json({ data: user }, {
status: 201,
headers: { Location: `/api/v1/users/${user.id}` },
})
}
development
Test-Driven Development workflow. Enforces RED → GREEN → REFACTOR cycle with 80% coverage gate. Use for all new features and bug fixes.
testing
Security audit checklist and workflow. Run before commits, PRs, or deploying. Covers secrets detection, input validation, OWASP Top 10, and dependency scanning.
tools
Research-before-coding workflow. Search for existing tools, libraries, and patterns before writing custom code. Use whenever adding new functionality.
development
Comprehensive Python idioms, best practices, and patterns. Covers dataclasses, type hints, async, error handling, testing, and QGIS-specific patterns.