api-design
Skill1 starsChanged 9 months agoApache-2.0View on GitHub
Designs and reviews REST and GraphQL APIs, covering resource naming, HTTP methods and status codes, cursor vs offset pagination, filtering, RFC 9457 error responses, versioning, OpenAPI specs, API authentication, rate limiting, and GraphQL schema, N+1 and subscription design. Use when designing a new endpoint or schema, reviewing an API for consistency, choosing pagination or error formats, writing an OpenAPI spec, or fixing GraphQL resolver performance.
191 other projects use a version of this.
Adopted by
Most recently taken up by
- RostislavDugin/postgresustheir own version, first seen Nov 8, 2026
- Markssuave/Markssuave-Agent-skillstheir own version, first seen Nov 8, 2026
- Rojios/everything-claude-codetheir own version, first seen Nov 8, 2026
- ishandutta2007/Awesome-Claude-Skillstheir own version, first seen Nov 8, 2026
- databasus/databasustheir own version, first seen Nov 8, 2026
- lhbsaa/apex-unifiedtheir 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 versions191 projects use a version of this.
Word for word · 1
- medy-gribkov/arcana★ 1100% same
Edited · 0
None found.
Wrote their own version · 190
- affaan-m/ECC★ 246ksame name
- databasus/databasus★ 9.3ksame name
- RostislavDugin/postgresus★ 9.3ksame name
- authgear/authgear-server★ 2.1ksame name
- xu-xiang/everything-claude-code-zh★ 1.9ksame name
- Aaronontheweb/dotnet-skills★ 1.2ksame name
- epam/ai-dial-chat★ 501same name
- loulanyue/awesome-claude-notes★ 272same name
- KbWen/agentic-os★ 229same name
- DevelopersGlobal/ai-agent-skills★ 216same name
180 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.
In this repository21 other files it gives its agents.
- accessibility-wcagSkill
- bugbounty-reportSkill
- ci-cd-pipelinesSkill
- codebase-analysisSkill
- database-designSkill
- design-docSkill
- docker-kubernetesSkill
- git-workflowSkill
- go-devSkill
- i18n-localizationSkill
- llm-integrationSkill
- nextjs-16Skill
- oauth-authSkill
- performance-optimizationSkill
- python-best-practicesSkill
- scraping-automationSkill
- security-reviewSkill
- seo-metaSkill
- tailwind-cssSkill
- testingSkill
- ts-devSkill
The file
We split this skill into the rules it gives an agent
7 rules. 0 are common, 2 are distinctive, and 5 are very specific to this project.
0
Common
Most of the 164 edited versions we read keep these.
2
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 37
Never use verbs in resource paths (/getUser, /createOrder are wrong).
- Very specific: line 38
Use path params for identity (/users/123), query params for filtering (/users?role=admin).
- Very specific: line 40
Use kebab-case for multi-word resources: /order-items, not /orderItems.
- Distinctive: line 188
Use Authorization Code flow with PKCE for SPAs and mobile apps.
- Very specific: line 339
Use Connection pattern (Relay spec) for all lists.
- Distinctive: line 341
Never expose database IDs directly.
- Very specific: line 408
Subscription tips: Use graphql-ws library (not the legacy subscriptions-transport-ws).
“Common” is measured: kept by at least 6 in 10 of the 164 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
You are an API architect specializing in RESTful API design, GraphQL schema design, and production-grade API patterns for scalability and developer experience.
Use this skill when
- Designing REST or GraphQL APIs
- Choosing pagination, filtering, or error response patterns
- Setting up API authentication and authorization
- Writing OpenAPI/Swagger specifications
- Reviewing API design for consistency and best practices
REST Resource Naming
# Resources are nouns, plural, lowercase, hyphen-separated
GET /api/v1/users # list
POST /api/v1/users # create
GET /api/v1/users/{userId} # read
PUT /api/v1/users/{userId} # full replace
PATCH /api/v1/users/{userId} # partial update
DELETE /api/v1/users/{userId} # delete
# Nested resources for strong parent-child relationships
GET /api/v1/users/{userId}/orders
POST /api/v1/users/{userId}/orders
# Actions that don't map to CRUD: use verbs as sub-resources
POST /api/v1/users/{userId}/activate
POST /api/v1/orders/{orderId}/cancel
POST /api/v1/reports/generateRules:
- Never use verbs in resource paths (
/getUser,/createOrderare wrong). - Use path params for identity (
/users/123), query params for filtering (/users?role=admin). - Max nesting depth: 2 levels (
/users/{id}/orders). Beyond that, promote to top-level with a filter. - Use kebab-case for multi-word resources:
/order-items, not/orderItems.
HTTP Methods and Status Codes
| Method | Idempotent | Safe | Use Case |
| GET | Yes | Yes | Read resource(s) |
| POST | No | No | Create resource, trigger action |
| PUT | Yes | No | Full replace (client sends complete resource) |
| PATCH | No (see note) | No | Partial update (send only changed fields) |
| DELETE | Yes | No | Remove resource |
PATCH is idempotent only when the patch sets absolute values (JSON Merge Patch). A patch that appends or increments is not.
Status codes to actually use:
200 OK - GET success, PUT/PATCH success with body
201 Created - POST success (include Location header)
204 No Content - DELETE success, PUT/PATCH success without body
400 Bad Request - Validation error, malformed request
401 Unauthorized - Missing or invalid authentication
403 Forbidden - Authenticated but not authorized
404 Not Found - Resource doesn't exist (also use for authz to prevent enumeration)
409 Conflict - Duplicate resource, version conflict
422 Unprocessable - Semantically invalid (valid JSON, invalid business logic)
429 Too Many Reqs - Rate limited (include Retry-After header)
500 Internal Error - Unhandled server error (never expose stack traces)Don't use: 200 for everything, 403 when 404 is safer, custom 4xx codes, 500 for validation errors.
Error Response (RFC 9457)
{
"type": "https://api.example.com/errors/validation",
"title": "Validation Error",
"status": 422,
"detail": "The request body contains invalid fields.",
"instance": "/api/v1/users",
"errors": [
{
"field": "email",
"message": "must be a valid email address",
"code": "INVALID_FORMAT"
},
{
"field": "age",
"message": "must be between 0 and 150",
"code": "OUT_OF_RANGE"
}
]
}Content-Type: application/problem+json (RFC 9457 obsoletes RFC 7807; the format is the same). Every error response uses the same shape. Include type as a stable URI for documentation. The errors array for field-level validation details is an extension.
Pagination
Cursor-Based (preferred for feeds, real-time data)
GET /api/v1/posts?limit=20&after=eyJpZCI6MTAwfQ
Response:
{
"data": [...],
"pagination": {
"has_next": true,
"next_cursor": "eyJpZCI6MTIwfQ",
"has_previous": true,
"previous_cursor": "eyJpZCI6MTAxfQ"
}
}Cursor is an opaque base64-encoded token (typically the last item's sort key). Advantages: consistent results when data changes, and an index seek instead of skipping rows. Use for: social feeds, activity logs, any dataset that mutates frequently.
Offset-Based (for admin dashboards, search results)
GET /api/v1/products?page=3&per_page=25
Response:
{
"data": [...],
"pagination": {
"page": 3,
"per_page": 25,
"total_count": 1234,
"total_pages": 50
}
}Disadvantages: slow on large offsets (OFFSET 10000 scans 10000 rows), inconsistent with concurrent writes. Use only when users need to jump to arbitrary pages.
Filtering, Sorting, and Field Selection
# Filtering: field[operator]=value
GET /api/v1/products?category=electronics&price[gte]=10&price[lte]=100&status=active
# Sorting: sort=field (ascending), sort=-field (descending), comma-separated
GET /api/v1/products?sort=-created_at,name
# Field selection (sparse fieldsets): reduce payload size
GET /api/v1/users?fields=id,name,email
# Search: use a dedicated query parameter
GET /api/v1/products?q=wireless+keyboardImplementation tip: Validate all filter fields against an allowlist. Never pass user input directly to SQL ORDER BY or WHERE clauses.
Versioning Strategies
| Strategy | Example | Pros | Cons |
| URL path | /api/v1/users | Obvious, cacheable, easy routing | URL pollution |
| Header | Accept: application/vnd.api.v1+json | Clean URLs | Hidden, hard to test in browser |
| Query param | /api/users?version=1 | Easy to switch | Pollutes query string |
Recommended: URL path versioning. It's the most explicit and tooling-friendly. Only bump major version for breaking changes. Keep at most 2 versions alive. Deprecate with the Deprecation header (RFC 9745), the Sunset header (RFC 8594) and a 6-month timeline.
Authentication Patterns
API Keys (server-to-server, simple integrations)
# Send in header, never in URL (URLs get logged)
Authorization: ApiKey sk_live_abc123def456
# Server-side: hash the key, store the hash. Show the key only once at creation.
# Scope keys: read-only vs read-write, per-resource permissions.
# Rotate: support multiple active keys per client for zero-downtime rotation.JWT Bearer (user-facing APIs)
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
# Access token: 15 min, stateless, contains user ID + roles
# Refresh token: 7 days, stored server-side, one-time use (rotate on refresh)
# Token refresh endpoint:
POST /api/v1/auth/refresh
{ "refresh_token": "rt_abc123" }OAuth2 (third-party integrations)
Use Authorization Code flow with PKCE for SPAs and mobile apps. Never use Implicit flow (deprecated). Client Credentials flow for service-to-service.
Rate Limiting
# Response headers (standard)
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1708300800 # Unix timestamp when window resets
Retry-After: 30 # seconds (on 429 response)Implementation patterns:
- Fixed window: Simple but bursty at window boundaries. 100 req/min resets at :00.
- Sliding window: Smoother. Count requests in the last 60 seconds.
- Token bucket: Best for APIs. Allows bursts up to bucket size, refills at steady rate.
Tiered limits: Different limits per plan (free: 100/hr, pro: 10,000/hr). Different limits per endpoint (auth: 5/min, reads: 1000/min, writes: 100/min).
BAD/GOOD rate limiting patterns (the GOOD example is a fixed window counter; use a Lua script or SET key 1 EX 3600 NX so the count and expiry are atomic):
// BAD: Client-side only enforcement (easily bypassed)
if (requestCount > 100) {
alert("Rate limit exceeded");
return;
}
// GOOD: Server-side counter in Redis
const key = `rate:${userId}:${endpoint}`;
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, 3600); // 1 hour window
if (count > 100) {
res.set("Retry-After", "3600");
return res.status(429).json({ error: "Rate limit exceeded" });
}OpenAPI Spec (snippet)
openapi: 3.1.0
info:
title: My API
version: 1.0.0
paths:
/api/v1/users:
get:
summary: List users
operationId: listUsers
tags: [users]
parameters:
- name: page
in: query
schema: { type: integer, minimum: 1, default: 1 }
- name: per_page
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
responses:
"200":
description: Paginated list of users
content:
application/json:
schema:
type: object
properties:
data:
type: array
items: { $ref: "#/components/schemas/User" }
pagination:
$ref: "#/components/schemas/Pagination"
"401":
$ref: "#/components/responses/Unauthorized"
components:
schemas:
User:
type: object
required: [id, name, email]
properties:
id: { type: integer, format: int64 }
name: { type: string, minLength: 1, maxLength: 100 }
email: { type: string, format: email }
created_at: { type: string, format: date-time }
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []Write spec first (design-first), then implement. Generate client SDKs and server stubs from the spec. Validate requests/responses against the spec in tests.
GraphQL: Schema Design
type Query {
user(id: ID!): User
users(first: Int = 20, after: String, filter: UserFilter): UserConnection!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
}
type User {
id: ID!
name: String!
email: String!
orders(first: Int = 10, after: String): OrderConnection!
}
# Relay-style pagination
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
cursor: String!
node: User!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# Input types for mutations
input CreateUserInput {
name: String!
email: String!
}
# Mutation payloads include the result + possible errors
type CreateUserPayload {
user: User
errors: [UserError!]!
}
type UserError {
field: String!
message: String!
}GraphQL design rules:
- Use
Connectionpattern (Relay spec) for all lists. - Mutations take a single
inputargument and return aPayloadtype. - Never expose database IDs directly; use opaque global IDs.
- Make fields non-nullable by default (
String!). Only use nullable for fields that can genuinely be absent.
GraphQL: N+1 Problem
// BAD: each user resolver fetches orders individually
// 1 query for users + N queries for each user's orders = N+1
// GOOD: DataLoader batches and deduplicates
import DataLoader from "dataloader";
const ordersByUserLoader = new DataLoader(async (userIds) => {
// Single query: SELECT * FROM orders WHERE user_id IN (...)
const orders = await db.query(
"SELECT * FROM orders WHERE user_id = ANY($1)",
[userIds]
);
// Map results back to the same order as input IDs
const map = new Map();
for (const order of orders) {
if (!map.has(order.user_id)) map.set(order.user_id, []);
map.get(order.user_id).push(order);
}
return userIds.map((id) => map.get(id) || []);
});
// Resolver
const resolvers = {
User: {
orders: (user, _args, ctx) => ctx.loaders.ordersByUser.load(user.id),
},
};Always use DataLoader (or equivalent) for any field that fetches related data. Create a new DataLoader instance per request to prevent cross-request caching.
GraphQL: Subscriptions
type Subscription {
orderStatusChanged(orderId: ID!): Order!
newMessage(channelId: ID!): Message!
}// Server (graphql-ws protocol, NOT the deprecated subscriptions-transport-ws)
import { WebSocketServer } from "ws";
import { useServer } from "graphql-ws/use/ws"; // graphql-ws v5 and older: "graphql-ws/lib/use/ws"
const wsServer = new WebSocketServer({ server: httpServer, path: "/graphql" });
useServer(
{
schema,
context: async (ctx) => {
// Authenticate on connection_init
const token = ctx.connectionParams?.token;
const user = await verifyToken(token);
if (!user) throw new Error("Unauthorized");
return { user };
},
},
wsServer
);Subscription tips: Use graphql-ws library (not the legacy subscriptions-transport-ws). Authenticate on WebSocket connection_init, not per message. Back subscriptions with Redis Pub/Sub or similar for multi-instance deployments. Always set connection timeouts and heartbeats.
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.

