Install any skill in seconds. Free to start, no credit card required.
Get Started Free →REST/GraphQL/gRPC API design best practices. Use when designing APIs, defining contracts, handling versioning. Covers OpenAPI 3.2, GraphQL Federation, gRPC streaming.
.claude/skills/majiayu000-api-design/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 96% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 142% | 0% |
| case-12 | ✓→✓ | = Same ✓ | 109% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 92% | 0% |
/v1/, with Sunset headers| Scenario | Choice | Reason | |----------|--------|--------| | Public API / MVP | REST | Simple, universal, easy debugging | | Frontend-driven / Mobile | GraphQL | Fetch exactly what you need | | Microservices internal | gRPC | High performance, strong typing | | Real-time data | gRPC / GraphQL Subscriptions | Bidirectional streaming |
# Good
GET /users # List users
GET /users/123 # Get user
POST /users # Create user
PUT /users/123 # Replace user
PATCH /users/123 # Update user
DELETE /users/123 # Delete user
# Nested resources
GET /users/123/orders # User's orders
# Actions (when CRUD doesn't fit)
POST /users/123/activate # Action on resource
# Query parameters for filtering
GET /users?status=active&role=admin&limit=20| Method | Purpose | Idempotent | Safe | |--------|---------|------------|------| | GET | Read | Yes | Yes | | POST | Create | No | No | | PUT | Replace | Yes | No | | PATCH | Update | No | No | | DELETE | Remove | Yes | No |
# Success
200 OK - Successful GET/PUT/PATCH
201 Created - Successful POST (include Location header)
204 No Content - Successful DELETE
# Client Errors
400 Bad Request - Malformed request syntax
401 Unauthorized - Missing/invalid authentication
403 Forbidden - Authenticated but not authorized
404 Not Found - Resource doesn't exist
409 Conflict - Duplicate/conflict (e.g., unique constraint)
422 Unprocessable - Validation failed
429 Too Many - Rate limited
# Server Errors
500 Internal Error - Unexpected server error
503 Unavailable - Service temporarily downjson{ "type": "https://api.example.com/errors/validation", "title": "Validation Error", "status": 422, "detail": "The request contains invalid parameters", "instance": "/users/123", "errors": [ { "field": "email", "message": "Invalid email format" }, { "field": "age", "message": "Must be positive integer" } ] }
json// Request GET /users?limit=20&cursor=eyJpZCI6MTAwfQ // Response { "data": [...], "pagination": { "next_cursor": "eyJpZCI6MTIwfQ", "prev_cursor": "eyJpZCI6ODB9", "has_next": true, "has_prev": true, "limit": 20 } }
# URL versioning (recommended)
GET /v1/users
GET /v2/users
# Deprecation headers
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
Link: </v2/users>; rel="successor-version"# For non-idempotent operations (POST)
POST /orders
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
# Server stores result and returns same response for duplicate keygraphqltype Query { user(id: ID!): User users(first: Int, after: String, filter: UserFilter): UserConnection! } type Mutation { createUser(input: CreateUserInput!): CreateUserPayload! updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload! } type User @key(fields: "id") { id: ID! email: String! name: String! orders(first: Int, after: String): OrderConnection! createdAt: DateTime! } # Relay-style pagination type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! } type UserEdge { node: User! cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String }
graphqltype Mutation { createUser(input: CreateUserInput!): CreateUserPayload! } # Union for typed errors union CreateUserPayload = User | ValidationError | ConflictError type ValidationError { message: String! field: String code: String! } type ConflictError { message: String! existingId: ID! }
typescript// Use DataLoader for batching const userLoader = new DataLoader(async (ids: string[]) => { const users = await db.user.findMany({ where: { id: { in: ids } } }); return ids.map(id => users.find(u => u.id === id)); }); // Resolver const resolvers = { Order: { user: (order) => userLoader.load(order.userId), }, };
protobufsyntax = "proto3"; package api.v1; import "google/protobuf/timestamp.proto"; import "google/protobuf/empty.proto"; service UserService { // Unary rpc GetUser(GetUserRequest) returns (User); rpc CreateUser(CreateUserRequest) returns (User); // Server streaming rpc ListUsers(ListUsersRequest) returns (stream User); // Client streaming rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse); // Bidirectional streaming rpc SyncUsers(stream UserUpdate) returns (stream UserUpdate); } message User { string id = 1; string email = 2; string name = 3; google.protobuf.Timestamp created_at = 4; } message GetUserRequest { string id = 1; } message ListUsersRequest { int32 page_size = 1; string page_token = 2; UserFilter filter = 3; } message UserFilter { optional string status = 1; optional string role = 2; }
protobuf// Use Google's richer error model import "google/rpc/status.proto"; import "google/rpc/error_details.proto"; // For streaming: embed errors in response message StreamResponse { oneof result { User user = 1; StreamError error = 2; } } message StreamError { string code = 1; string message = 2; map<string, string> details = 3; }
typescript// Always set deadlines const deadline = new Date(); deadline.setSeconds(deadline.getSeconds() + 5); const user = await client.getUser( { id: '123' }, { deadline } ); // Configure retry policy const retryPolicy = { maxAttempts: 3, initialBackoff: '0.1s', maxBackoff: '1s', backoffMultiplier: 2, retryableStatusCodes: ['UNAVAILABLE', 'DEADLINE_EXCEEDED'], };
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
Retry-After: 60json{ "type": "https://api.example.com/errors/rate-limited", "title": "Rate Limit Exceeded", "status": 429, "detail": "You have exceeded the rate limit of 100 requests per minute", "retryAfter": 60 }
markdown## Design - [ ] API spec defined before implementation - [ ] Resources use plural nouns - [ ] Correct HTTP methods/status codes - [ ] RFC 7807 error format ## Features - [ ] Cursor-based pagination - [ ] Rate limiting with headers - [ ] Idempotency keys for POST - [ ] API versioning strategy ## Documentation - [ ] OpenAPI/GraphQL schema published - [ ] Examples for all endpoints - [ ] Error codes documented ## Operations - [ ] Request/response logging - [ ] Latency and error rate metrics - [ ] Deprecation notices for old versions
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-12 | pass→pass | 12,091 | 12,738 | +5% | 1 | 1 | 0% | 2,404 | 5,028 | +109% | 0 | 0 | — |
case-01 | fail→pass | 18,133 | 25,265 | +39% | 1 | 1 | 0% | 3,395 | 5,574 | +64% | 0 | 0 | — |
case-02 | fail→pass | 11,739 | 7,732 | -34% | 1 | 1 | 0% | 1,926 | 3,766 | +96% | 0 | 0 | — |
case-03 | pass→pass | 16,741 | 15,543 | -7% | 1 | 1 | 0% | 2,597 | 4,994 | +92% | 0 | 0 | — |
case-17 | pass→pass | 6,388 | 3,790 | -41% | 1 | 1 | 0% | 1,220 | 3,171 | +160% | 0 | 0 | — |
case-04 | pass→pass | 15,679 | 12,511 | -20% | 1 | 1 | 0% | 2,623 | 4,593 | +75% | 0 | 0 | — |
case-05 | pass→pass | 13,973 | 12,442 | -11% | 1 | 1 | 0% | 2,206 | 4,292 | +95% | 0 | 0 | — |
case-06 | pass→pass | 10,802 | 8,403 | -22% | 1 | 1 | 0% | 1,925 | 4,120 | +114% | 0 | 0 | — |
case-07 | pass→pass | 24,527 | 9,322 | -62% | 1 | 1 | 0% | 1,618 | 4,210 | +160% | 0 | 0 | — |
case-08 | pass→pass | 14,612 | 13,264 | -9% | 1 | 1 | 0% | 2,573 | 4,904 | +91% | 0 | 0 | — |
case-09 | pass→pass | 16,380 | 16,529 | +1% | 1 | 1 | 0% | 2,901 | 5,409 | +86% | 0 | 0 | — |
case-10 | pass→pass | 15,293 | 12,536 | -18% | 1 | 1 | 0% | 2,419 | 4,725 | +95% | 0 | 0 | — |
case-11 | pass→pass | 15,563 | 13,878 | -11% | 1 | 1 | 0% | 2,641 | 4,783 | +81% | 0 | 0 | — |
case-13 | pass→pass | 11,439 | 7,195 | -37% | 1 | 1 | 0% | 2,125 | 3,909 | +84% | 0 | 0 | — |
case-14 | pass→pass | 4,962 | 2,999 | -40% | 1 | 1 | 0% | 865 | 2,962 | +242% | 0 | 0 | — |
case-15 | pass→pass | 3,335 | 2,666 | -20% | 1 | 1 | 0% | 529 | 2,896 | +447% | 0 | 0 | — |
case-16 | pass→pass | 13,720 | 11,854 | -14% | 1 | 1 | 0% | 2,368 | 4,567 | +93% | 0 | 0 | — |
case-18 | pass→pass | 8,426 | 5,737 | -32% | 1 | 1 | 0% | 1,426 | 3,480 | +144% | 0 | 0 | — |
case-19 | pass→pass | 9,981 | 7,888 | -21% | 1 | 1 | 0% | 1,818 | 3,880 | +113% | 0 | 0 | — |
case-20 | pass→pass | 4,553 | 3,662 | -20% | 1 | 1 | 0% | 713 | 3,125 | +338% | 0 | 0 | — |
case-21 | fail→pass | 10,180 | 11,973 | +18% | 1 | 1 | 0% | 2,045 | 4,948 | +142% | 0 | 0 | — |
case-22 | pass→pass | 13,373 | 16,906 | +26% | 1 | 1 | 0% | 2,645 | 5,873 | +122% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.