api-design
Skill0 starsChanged 2 months agoMITView on GitHub
Design clear REST APIs with good names, status codes, page rules, filters, errors, versions, access checks, and rate limits.
192 other projects use a version of this, 52 of them edited.
Adopted by
Most recently taken up by
- lengrufeng2008/dsh-skill-librarytheir own version, first seen Nov 15, 2026
- dovuthanhhien1609/claude-skillstheir own version, first seen Nov 15, 2026
- tayyebi/skillstheir own version, first seen Nov 15, 2026
- swap-cameroon/kuro-antigravity-configtheir own version, first seen Nov 15, 2026
- RostislavDugin/postgresustheir own version, first seen Nov 8, 2026
- Markssuave/Markssuave-Agent-skillstheir own version, first seen Nov 8, 2026
Used with
Projects with it also use db-design, everything-claude-code and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
yuexueyuSkilldb-design
数据库工程师技能 - Schema设计、索引优化、并发安全、数据库审计。当你涉及数据库表/字段/索引/SQL/迁移/MCP数据库操作、建表改表、写查询时必须使用此技能。即使用户只是说"加个字段"或"查下数据",也应触发。
Also in 4 projects that use this
InfopibeSkilleverything-claude-code
Also in 9 projects that use this
mryeastcodeSkilleverything-claude-code-conventions
Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
Also in 8 projects that use this
tpavanipradeepSkillnextjs-turbopack
Next.js 16+ and Turbopack — incremental bundling, FS caching, dev speed, and when to use Turbopack vs webpack.
Also in 15 projects that use this
Indium-AI-LabsSkilldata-pipeline-design
Design batch or incremental ETL and ELT pipelines and warehouse models: grain, partitioning, idempotent loads, late-arriving data, data quality tests, PII controls, and cost monitoring, before building them. Use when adding or changing a dbt model, warehouse table, scheduled transformation, or ingestion job. Not for transactional schema design (use the database-design skill) or event-sourced stores (use the event-sourcing-architecture skill).
Also in 3 projects that use this
thejordanleopoldSkillthird-party-integration
Use when integrating with external APIs, payment processors (Stripe), webhooks, or third-party services. Use when implementing API clients with authentication and rate limiting, handling webhooks with signature verification and idempotency, writing contract tests for external services, or isolating third-party dependencies behind adapters. Triggers: \"Stripe\", \"payment integration\", \"webhook\", \"third-party API\", \"external service\", \"API client\", \"integrate with\", \"signature verification\", \"idempotency key\".
Also in 3 projects that use this
Other versions192 projects use a version of this.
Word for word · 0
None found.
Edited · 52
- affaan-m/ECC★ 246k19% same
- xu-xiang/everything-claude-code-zh★ 1.9k23% same
- loulanyue/awesome-claude-notes★ 27219% same
- kid-sid/claude-spellbook★ 19017% same
- tpavanipradeep/everything-claude-code★ 10419% same
- mturac/everything-openai-codex★ 8919% same
- mit-network/everything-claude-code★ 7619% same
- Fmarzochi/EGC★ 6119% same
- ysyecust/everything-claude-code★ 4419% same
- skateddu/claude-code-python-setup★ 2819% same
42 more not listed
Wrote their own version · 140
- databasus/databasus★ 9.3ksame name
- RostislavDugin/postgresus★ 9.3ksame name
- authgear/authgear-server★ 2.1ksame name
- Aaronontheweb/dotnet-skills★ 1.2ksame name
- epam/ai-dial-chat★ 501same name
- KbWen/agentic-os★ 229same name
- DevelopersGlobal/ai-agent-skills★ 216same name
- margelo/react-native-skills★ 172same name
- proyecto26/system-design-skills★ 118same name
- microsoft/haste★ 110same name
130 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
The file
Show its rules and text
We split this skill into the rules it gives an agent
47 rules. 0 are common, 36 are distinctive, and 11 are very specific to this project.
0
Common
Most of the 166 edited versions we read keep these.
36
Distinctive
This author's own choices. Worth a look.
11
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 11
Use this guide to make REST APIs that are clear, safe, and easy to use.
- Distinctive: line 15
Use this skill when you:
- Distinctive: line 17
Add a new API route.
- Distinctive: line 19
Add pages, filters, search, or sort.
- Distinctive: line 26
Before you write code, list:
- Distinctive: line 37
Keep names and response shapes the same across all routes.
- Distinctive: line 43
Use plural nouns. Use lowercase words.
- Distinctive: line 54
Use a nested URL when one resource belongs to another:
- Distinctive: line 61
Keep nesting short. Do not go past two resource levels.
- Distinctive: line 67
Use an action route only when normal create, read, update, and delete rules do not fit:
- Very specific: line 90
Use one field style in JSON.
- Very specific: line 142
Do not send a body with 204 No Content.
- Very specific: line 159
Use either 400 or 422 for field checks.
- Very specific: line 172
Add Retry-After to 429 and short-term 503 replies when you know when to retry.
- Distinctive: line 174
Do not show stack traces, SQL, file paths, keys, tokens, or private data.
- Distinctive: line 191
Use ISO 8601 dates in UTC:
- Distinctive: line 197
Use strings for IDs. This avoids number size bugs in some tools.
- Very specific: line 244
Keep code short and stable.
- Very specific: line 246
Put field errors in details.
- Distinctive: line 247
Add a request ID for support.
- Distinctive: line 248
Never put secrets in an error.
- Distinctive: line 249
Do not make clients read the message to learn the error type.
- Very specific: line 272
Always use a fixed sort. Add a unique last sort field, such as id.
- Distinctive: line 296
Use this for small lists and screens that need page numbers.
- Distinctive: line 304
Read one extra row to learn if another page exists:
- Distinctive: line 328
Use cursor pages for feeds, large lists, and public APIs.
- Distinctive: line 332
Only allow known fields and actions.
- Distinctive: line 379
Check these before work starts:
- Very specific: line 392
Do not trust client fields such as user_id, role, price, or is_admin.
- Distinctive: line 398
Use TLS for every API call.
- Distinctive: line 412
Do not place keys or tokens in URLs.
- Distinctive: line 414
Check both login and access:
- Distinctive: line 436
Check access on every object.
- Distinctive: line 468
When the limit is passed:
- Distinctive: line 498
Use cache rules only when they are safe.
- Very specific: line 505
Use Cache-Control: no-store for secrets and other private data that must not be saved.
- Distinctive: line 507
Add all fields that change the reply to the cache key.
- Distinctive: line 509
Never let one user receive another user's cached data.
- Very specific: line 520
Start with /api/v1. Make a new major version only for a change that can break a client.
- Distinctive: line 524
Add a new route.
- Distinctive: line 525
Add an optional request field.
- Distinctive: line 526
Add a response field, if clients are told to ignore unknown fields.
- Distinctive: line 527
Add an optional query field.
- Distinctive: line 531
Remove or rename a field.
- Distinctive: line 539
Keep no more than two active major versions when you can.
- Very specific: line 549
After the end date, return 410 Gone only if the route is truly removed.
- Distinctive: line 709
Before you ship a route, check:
“Common” is measured: kept by at least 6 in 10 of the 166 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
REST API Design
Original work by ECC. Credit to ECC for the ideas and base guide.
Use this guide to make REST APIs that are clear, safe, and easy to use.
When to Use This Skill
Use this skill when you:
- Add a new API route.
- Review an API contract.
- Add pages, filters, search, or sort.
- Add error handling.
- Plan API versions.
- Build a public or partner API.
Start With the Contract
Before you write code, list:
- The resource and URL.
- The request fields.
- The response fields.
- The status codes.
- The access rules.
- The page and sort rules.
- The rate limit.
- The errors a client may get.
Keep names and response shapes the same across all routes.
Resource Design
URL Rules
Use plural nouns. Use lowercase words. Join words with hyphens.
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/:idUse a nested URL when one resource belongs to another:
GET /api/v1/users/:id/orders
POST /api/v1/users/:id/ordersKeep nesting short. Do not go past two resource levels. Use a top-level route if the child has its own ID:
GET /api/v1/orders/:idUse an action route only when normal create, read, update, and delete rules do not fit:
POST /api/v1/orders/:id/cancel
POST /api/v1/auth/login
POST /api/v1/auth/refreshGood and Bad Names
# Good
/api/v1/team-members
/api/v1/orders?status=active
/api/v1/users/123/orders
# Bad
/api/v1/getUsers
/api/v1/user
/api/v1/team_members
/api/v1/users/123/getOrdersUse one field style in JSON. This guide uses snake_case.
HTTP Methods
| Method | Safe to retry | Changes data | Use |
| GET | Yes | No | Read data |
| POST | Usually no | Yes | Create data or run an action |
| PUT | Yes | Yes | Replace all fields |
| PATCH | It depends | Yes | Change some fields |
| DELETE | Yes | Yes | Delete data |
A retry is safe only when the same call has the same final result. A PATCH that adds 1 each time is not safe. A PATCH that sets a name is safe.
For POST calls that may be retried, support an idempotency key:
Idempotency-Key: 7bb1c5c0-52a1-4f18-8af8-55ebf85e13fdStore the key with the first result for a set time. Return that result for a repeat call.
Status Codes
Success
200 OK GET, PUT, or PATCH with a body
201 Created A new resource was made
202 Accepted Work will finish later
204 No Content Success with no bodyFor 201 Created, add a Location header:
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123For 202 Accepted, return a way to check the work:
{
"data": {
"job_id": "job-123",
"status": "pending",
"status_url": "/api/v1/jobs/job-123"
}
}Do not send a body with 204 No Content.
Client Errors
400 Bad Request Bad JSON or bad request form
401 Unauthorized Login data is missing or not valid
403 Forbidden The caller is known but lacks access
404 Not Found The resource does not exist
405 Method Not Allowed The HTTP method is not allowed
409 Conflict A duplicate or state clash happened
412 Precondition Failed A write used an old version
415 Unsupported Media Type The body type is not allowed
422 Unprocessable Content The JSON is valid but its values are not
429 Too Many Requests The rate limit was passedUse either 400 or 422 for field checks. Pick one rule and use it on every route.
A private API may return 404 instead of 403 when this helps hide whether a resource exists.
Server Errors
500 Internal Server Error An unknown server fault
502 Bad Gateway A service used by this API failed
503 Service Unavailable A short outage or heavy load
504 Gateway Timeout A service used by this API took too longAdd Retry-After to 429 and short-term 503 replies when you know when to retry.
Do not show stack traces, SQL, file paths, keys, tokens, or private data.
Response Shapes
One Resource
{
"data": {
"id": "abc-123",
"email": "alice@example.com",
"name": "Alice",
"created_at": "2025-01-15T10:30:00Z"
}
}Use ISO 8601 dates in UTC:
2025-01-15T10:30:00ZUse strings for IDs. This avoids number size bugs in some tools.
A List
{
"data": [
{ "id": "abc-123", "name": "Alice" },
{ "id": "def-456", "name": "Bob" }
],
"meta": {
"total": 142,
"page": 1,
"per_page": 20,
"total_pages": 8
},
"links": {
"self": "/api/v1/users?page=1&per_page=20",
"next": "/api/v1/users?page=2&per_page=20",
"last": "/api/v1/users?page=8&per_page=20"
}
}Return an empty list as []. Do not use null.
An Error
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"request_id": "req-789",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"code": "invalid_format"
}
]
}
}Rules for errors:
- Keep
codeshort and stable. - Make
messagesafe for users. - Put field errors in
details. - Add a request ID for support.
- Never put secrets in an error.
- Do not make clients read the message to learn the error type.
Choose one response style for the whole API:
interface ApiResponse<T> {
data: T;
meta?: PaginationMeta;
links?: PaginationLinks;
}
interface ApiError {
error: {
code: string;
message: string;
request_id?: string;
details?: FieldError[];
};
}Page Rules
Always use a fixed sort. Add a unique last sort field, such as id. Without this, rows may move or repeat.
Set a default and a hard limit:
Default limit: 20
Max limit: 100Reject bad values, such as a negative page or a limit over the max.
Page and Offset
GET /api/v1/users?page=2&per_page=20SELECT *
FROM users
ORDER BY created_at DESC, id DESC
LIMIT 20 OFFSET 20;Use this for small lists and screens that need page numbers. Large offsets can be slow. New rows can also cause repeats or missed rows.
Cursor
GET /api/v1/users?cursor=opaque-value&limit=20Read one extra row to learn if another page exists:
SELECT *
FROM users
WHERE (created_at, id) < (:cursor_created_at, :cursor_id)
ORDER BY created_at DESC, id DESC
LIMIT 21;{
"data": [],
"meta": {
"has_next": true,
"next_cursor": "opaque-value"
}
}The cursor must hold all sort fields. Sign or lock it so clients cannot change it. Treat it as a secret value, but do not place private data inside it.
Return 400 Bad Request for a bad or old cursor.
Use cursor pages for feeds, large lists, and public APIs. Cursor pages do not support a direct jump to page 50.
Filters, Sort, and Search
Only allow known fields and actions. Never place raw client input into SQL.
Filters
GET /api/v1/orders?status=active&customer_id=abc-123
GET /api/v1/products?price[gte]=10&price[lte]=100
GET /api/v1/orders?created_at[after]=2025-01-01
GET /api/v1/products?category=electronics,clothing
GET /api/v1/orders?customer.country=USState how commas, spaces, empty values, and letter case work. URL encode all special signs.
Sort
GET /api/v1/products?sort=-created_at
GET /api/v1/products?sort=-featured,price,-created_atA leading - means high to low. Add id as the last sort field when the client leaves it out.
Reject unknown sort fields.
Search
GET /api/v1/products?q=wireless%20headphones
GET /api/v1/users?email=aliceSet a max search length. State if search ignores letter case. Escape search text before use.
Pick Fields
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status&include=customer.nameAllow only safe fields. Do not let fields expose passwords, tokens, private notes, or hidden admin data.
Set a limit on linked data in include. This stops very large replies and slow calls.
Request Checks
Check these before work starts:
- The body is valid JSON.
Content-Typeis allowed.- Required fields exist.
- Text and list sizes are within limits.
- Values have the right type and range.
- IDs have the right form.
- Unknown fields follow one clear rule.
- The caller has access to each resource.
Reject very large bodies before parsing them.
Do not trust client fields such as user_id, role, price, or is_admin. Get trusted values from the login data or server records.
For PATCH, state how null works. It may clear a field, or it may be banned. Do not leave this unclear.
Login and Access
Use TLS for every API call.
GET /api/v1/users
Authorization: Bearer token-valueFor service calls:
GET /api/v1/data
X-API-Key: key-valueDo not place keys or tokens in URLs. URLs may be saved in logs and browser history.
Check both login and access:
app.get("/api/v1/orders/:id", async (req, res) => {
const order = await Order.findById(req.params.id);
if (!order) {
return res.status(404).json({
error: { code: "not_found", message: "Order not found" }
});
}
if (order.userId !== req.user.id) {
return res.status(403).json({
error: { code: "forbidden", message: "Access denied" }
});
}
return res.json({ data: order });
});Check access on every object. Do not rely only on a user ID from the URL.
Safe Updates
Two users may edit the same item at once. Use a version value or ETag to stop lost updates:
GET /api/v1/users/abc-123
ETag: "v7"PATCH /api/v1/users/abc-123
If-Match: "v7"
Content-Type: application/jsonReturn 412 Precondition Failed if the item changed after the client read it.
For delete calls, decide if the item is fully removed or only marked as deleted. State this in the API guide.
Rate Limits
Rate limits must use a clear key, such as IP, user, API key, or service.
HTTP/1.1 200 OK
RateLimit-Limit: 100
RateLimit-Remaining: 95
RateLimit-Reset: 60When the limit is passed:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests. Try again in 60 seconds."
}
}These are sample limits. Pick limits from real load tests and user needs:
| Group | Sample limit | Count by |
| Guest | 30 each minute | IP |
| Signed in | 100 each minute | User |
| Paid | 1,000 each minute | API key |
| Internal | 10,000 each minute | Service |
Set lower limits on costly routes, login tries, password reset, search, and file upload.
Caching
Use cache rules only when they are safe.
Cache-Control: private, max-age=60
ETag: "user-abc-123-v7"Use Cache-Control: no-store for secrets and other private data that must not be saved.
Add all fields that change the reply to the cache key. This may include the user, query, language, and API version.
Never let one user receive another user's cached data.
API Versions
Path versions are clear and easy to test:
/api/v1/users
/api/v2/usersStart with /api/v1. Make a new major version only for a change that can break a client.
Changes that often do not need a new version:
- Add a new route.
- Add an optional request field.
- Add a response field, if clients are told to ignore unknown fields.
- Add an optional query field.
Changes that need a new version:
- Remove or rename a field.
- Change a field type.
- Change a field from optional to required.
- Change the meaning of a field.
- Change a URL.
- Change login rules.
- Change the default sort in a way that breaks page use.
Keep no more than two active major versions when you can. Give public API users at least six months of notice when possible.
Send clear end dates:
Deprecation: true
Sunset: Thu, 01 Jan 2026 00:00:00 GMT
Link: <https://api.example.com/migrate-v2>; rel="deprecation"After the end date, return 410 Gone only if the route is truly removed.
Concrete Example
Task: Add an endpoint that creates a user.
Contract:
POST /api/v1/users
Content-Type: application/json
Idempotency-Key: 7bb1c5c0-52a1-4f18-8af8-55ebf85e13fdRequest:
{
"email": "alice@example.com",
"name": "Alice"
}Success:
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123
Content-Type: application/json{
"data": {
"id": "abc-123",
"email": "alice@example.com",
"name": "Alice",
"created_at": "2025-01-15T10:30:00Z"
}
}Bad email:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"code": "invalid_format"
}
]
}
}Email already used:
HTTP/1.1 409 Conflict
Content-Type: application/json{
"error": {
"code": "email_in_use",
"message": "That email address is already in use"
}
}TypeScript route:
import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";
const createUserSchema = z.object({
email: z.string().email().max(254),
name: z.string().trim().min(1).max(100),
}).strict();
export async function POST(req: NextRequest) {
let body: unknown;
try {
body = await req.json();
} catch {
return NextResponse.json(
{
error: {
code: "bad_json",
message: "The request body must be valid JSON",
},
},
{ status: 400 }
);
}
const parsed = createUserSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{
error: {
code: "validation_error",
message: "Request validation failed",
details: parsed.error.issues.map((issue) => ({
field: issue.path.join("."),
message: issue.message,
code: issue.code,
})),
},
},
{ status: 422 }
);
}
const found = await findUserByEmail(parsed.data.email);
if (found) {
return NextResponse.json(
{
error: {
code: "email_in_use",
message: "That email address is already in use",
},
},
{ status: 409 }
);
}
const user = await createUser(parsed.data);
return NextResponse.json(
{ data: user },
{
status: 201,
headers: {
Location: `/api/v1/users/${user.id}`,
},
}
);
}In real code, also add login checks, rate limits, body size limits, request IDs, and idempotency storage.
Review List
Before you ship a route, check:
- The URL uses plural nouns.
- The HTTP method fits the work.
- Each result has the right status code.
- Request fields have size and type checks.
- Login and access checks are present.
- Errors use one stable shape.
- Secrets never appear in URLs or errors.
- Lists have limits and a fixed sort.
- Filters and sort fields use allowlists.
- Writes handle retries when needed.
- Updates guard against lost changes.
- Rate limits are set.
- Cache rules do not leak private data.
- Breaking changes use a new version.
- The API contract and examples are up to date.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

