skills/backend/api-design/SKILL.md
REST API design conventions covering URL structure, HTTP methods, response formats, status codes, pagination, and versioning. Use when designing or reviewing API endpoints.
npx skillsauth add devjarus/coding-agent 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.
/users, not /getUsers/orders, /products/users/{id}/orders is fine; avoid /users/{id}/orders/{id}/items/{id}/blog-posts, /user-profiles| Method | Semantics | |--------|-----------| | GET | Read resource(s), idempotent, no body | | POST | Create a new resource or trigger an action | | PUT | Replace a resource entirely | | PATCH | Partial update of a resource | | DELETE | Remove a resource, idempotent |
Success:
{
"data": { ... },
"meta": { "requestId": "abc123", "timestamp": "2024-01-01T00:00:00Z" }
}
Error:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable description",
"details": [{ "field": "email", "issue": "Invalid format" }]
}
}
| Code | When to use | |------|-------------| | 200 | Successful GET, PUT, PATCH | | 201 | Successful POST that created a resource | | 204 | Successful DELETE or action with no response body | | 400 | Bad request — malformed syntax or invalid parameters | | 401 | Not authenticated | | 403 | Authenticated but not authorized | | 404 | Resource not found | | 409 | Conflict — duplicate, state mismatch | | 422 | Validation failed — well-formed but semantically invalid | | 500 | Unexpected server error |
Cursor-based (for large or frequently updated datasets):
{
"data": [...],
"meta": {
"nextCursor": "eyJpZCI6MTAwfQ==",
"hasMore": true
}
}
Offset-based (for simple, small datasets):
{
"data": [...],
"meta": {
"total": 250,
"page": 2,
"perPage": 25,
"totalPages": 10
}
}
Always return a meta object even when there is only one page.
/v1/users, /v2/usersSunset response headers before removingEvery route handler should be roughly 10 lines: (1) parse input, (2) call a core function, (3) return the serialized result, (4) catch errors and map them to status codes. Business logic lives in the core library or service layer, not in the route.
// GOOD — thin wrapper around a core function
app.get('/api/posts/:slug', async (req, res, next) => {
try {
const post = await posts.findBySlug(req.params.slug);
if (!post) return res.status(404).json({ error: { code: 'NOT_FOUND', message: 'Post not found' } });
return res.status(200).json({ data: post });
} catch (err) {
return next(err);
}
});
// BAD — business logic embedded in the route
app.get('/api/posts/:slug', async (req, res) => {
const db = getDb();
const row = await db.prepare('SELECT * FROM posts WHERE slug = ?').get(req.params.slug);
if (!row) return res.status(404).json(...);
const excerpt = row.content.split('\n').slice(0, 3).join(' ').substring(0, 150); // ← logic
const tags = row.tags?.split(',') ?? []; // ← logic
return res.status(200).json({ data: { ...row, excerpt, tags } });
});
Why it matters: testing the core library is effectively testing every route, because routes add nothing beyond transport glue. If your route handler is >15 lines or contains branching business logic beyond error-to-status mapping, extract it into a pure core function and call that from the route.
Applies to: Next.js Route Handlers, Express middlewares, FastAPI endpoints, Gin handlers, Rails controllers — any framework where you're tempted to put logic "right in the handler because it's convenient."
Exception: trivial projections (e.g., picking 3 fields out of a row for a list view) are fine inline. The rule targets branching, validation, data transformation, and external calls — not one-line field selection.
testing
Multi-source research method — decompose a question, fan out parallel investigators, interleaved-think each result, verify claims adversarially, synthesize a cited answer. Use for breadth-heavy research, stack comparisons, "which approach wins" questions.
testing
Decide when to use unit vs integration vs e2e tests, and when to mock vs use the real thing per dependency. Dependency injection is the enabler — without it you end up monkey-patching imports. Apply when writing tests of any kind.
development
Test-driven development process — write failing test, implement to pass, refactor. Use when implementing any feature or fixing bugs.
development
Patterns for sharing types, API contracts, and validation schemas between frontend and backend. Use when multiple domains consume the same data shapes to prevent contract drift.