{"id":"2b4b3cad-2e56-4a12-87ba-dab2419839bc","shortId":"sqn88N","kind":"skill","title":"api-design","tagline":"REST/GraphQL/gRPC API design best practices. Use when designing APIs, defining contracts, handling versioning. Covers OpenAPI 3.2, GraphQL Federation, gRPC streaming.","description":"# API Design\n\n## Core Principles\n\n- **Contract-First** — Define API spec before implementation\n- **OpenAPI 3.2** — Use OpenAPI for REST API documentation\n- **URL Versioning** — Version in path `/v1/`, with Sunset headers\n- **Idempotency** — PUT/DELETE must be idempotent, POST uses Idempotency-Key\n- **Cursor Pagination** — Avoid offset-based pagination\n- **RFC 7807 Errors** — Standard Problem Details format\n- **No backwards compatibility** — Delete, don't deprecate\n\n---\n\n## Quick Reference\n\n### When to Use What\n\n| Scenario | Choice | Reason |\n|----------|--------|--------|\n| Public API / MVP | REST | Simple, universal, easy debugging |\n| Frontend-driven / Mobile | GraphQL | Fetch exactly what you need |\n| Microservices internal | gRPC | High performance, strong typing |\n| Real-time data | gRPC / GraphQL Subscriptions | Bidirectional streaming |\n\n---\n\n## REST API Design\n\n### Resource Naming\n\n```\n# Good\nGET  /users              # List users\nGET  /users/123          # Get user\nPOST /users              # Create user\nPUT  /users/123          # Replace user\nPATCH /users/123         # Update user\nDELETE /users/123        # Delete user\n\n# Nested resources\nGET /users/123/orders    # User's orders\n\n# Actions (when CRUD doesn't fit)\nPOST /users/123/activate # Action on resource\n\n# Query parameters for filtering\nGET /users?status=active&role=admin&limit=20\n```\n\n### HTTP Methods\n\n| Method | Purpose | Idempotent | Safe |\n|--------|---------|------------|------|\n| GET | Read | Yes | Yes |\n| POST | Create | No | No |\n| PUT | Replace | Yes | No |\n| PATCH | Update | No | No |\n| DELETE | Remove | Yes | No |\n\n### Status Codes\n\n```\n# Success\n200 OK              - Successful GET/PUT/PATCH\n201 Created         - Successful POST (include Location header)\n204 No Content      - Successful DELETE\n\n# Client Errors\n400 Bad Request     - Malformed request syntax\n401 Unauthorized    - Missing/invalid authentication\n403 Forbidden       - Authenticated but not authorized\n404 Not Found       - Resource doesn't exist\n409 Conflict        - Duplicate/conflict (e.g., unique constraint)\n422 Unprocessable   - Validation failed\n429 Too Many        - Rate limited\n\n# Server Errors\n500 Internal Error  - Unexpected server error\n503 Unavailable     - Service temporarily down\n```\n\n### Error Response (RFC 7807)\n\n```json\n{\n  \"type\": \"https://api.example.com/errors/validation\",\n  \"title\": \"Validation Error\",\n  \"status\": 422,\n  \"detail\": \"The request contains invalid parameters\",\n  \"instance\": \"/users/123\",\n  \"errors\": [\n    { \"field\": \"email\", \"message\": \"Invalid email format\" },\n    { \"field\": \"age\", \"message\": \"Must be positive integer\" }\n  ]\n}\n```\n\n### Pagination (Cursor-Based)\n\n```json\n// Request\nGET /users?limit=20&cursor=eyJpZCI6MTAwfQ\n\n// Response\n{\n  \"data\": [...],\n  \"pagination\": {\n    \"next_cursor\": \"eyJpZCI6MTIwfQ\",\n    \"prev_cursor\": \"eyJpZCI6ODB9\",\n    \"has_next\": true,\n    \"has_prev\": true,\n    \"limit\": 20\n  }\n}\n```\n\n### Versioning\n\n```\n# URL versioning (recommended)\nGET /v1/users\nGET /v2/users\n\n# Deprecation headers\nSunset: Sat, 31 Dec 2025 23:59:59 GMT\nDeprecation: true\nLink: </v2/users>; rel=\"successor-version\"\n```\n\n### Idempotency\n\n```\n# For non-idempotent operations (POST)\nPOST /orders\nIdempotency-Key: 550e8400-e29b-41d4-a716-446655440000\n\n# Server stores result and returns same response for duplicate key\n```\n\n---\n\n## GraphQL Design\n\n### Schema Principles\n\n- **Domain-driven** — Schema reflects business domain, not database\n- **Descriptive names** — Clear field/type names for monitoring\n- **Limit nesting** — Deep nesting hurts performance\n- **Use @key** — Mark entity identifiers for Federation\n\n### Type Definitions\n\n```graphql\ntype Query {\n  user(id: ID!): User\n  users(first: Int, after: String, filter: UserFilter): UserConnection!\n}\n\ntype Mutation {\n  createUser(input: CreateUserInput!): CreateUserPayload!\n  updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!\n}\n\ntype User @key(fields: \"id\") {\n  id: ID!\n  email: String!\n  name: String!\n  orders(first: Int, after: String): OrderConnection!\n  createdAt: DateTime!\n}\n\n# Relay-style pagination\ntype UserConnection {\n  edges: [UserEdge!]!\n  pageInfo: PageInfo!\n  totalCount: Int!\n}\n\ntype UserEdge {\n  node: User!\n  cursor: String!\n}\n\ntype PageInfo {\n  hasNextPage: Boolean!\n  hasPreviousPage: Boolean!\n  startCursor: String\n  endCursor: String\n}\n```\n\n### Error Handling\n\n```graphql\ntype Mutation {\n  createUser(input: CreateUserInput!): CreateUserPayload!\n}\n\n# Union for typed errors\nunion CreateUserPayload = User | ValidationError | ConflictError\n\ntype ValidationError {\n  message: String!\n  field: String\n  code: String!\n}\n\ntype ConflictError {\n  message: String!\n  existingId: ID!\n}\n```\n\n### N+1 Prevention\n\n```typescript\n// Use DataLoader for batching\nconst userLoader = new DataLoader(async (ids: string[]) => {\n  const users = await db.user.findMany({\n    where: { id: { in: ids } }\n  });\n  return ids.map(id => users.find(u => u.id === id));\n});\n\n// Resolver\nconst resolvers = {\n  Order: {\n    user: (order) => userLoader.load(order.userId),\n  },\n};\n```\n\n---\n\n## gRPC Design\n\n### Proto Definition\n\n```protobuf\nsyntax = \"proto3\";\n\npackage api.v1;\n\nimport \"google/protobuf/timestamp.proto\";\nimport \"google/protobuf/empty.proto\";\n\nservice UserService {\n  // Unary\n  rpc GetUser(GetUserRequest) returns (User);\n  rpc CreateUser(CreateUserRequest) returns (User);\n\n  // Server streaming\n  rpc ListUsers(ListUsersRequest) returns (stream User);\n\n  // Client streaming\n  rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);\n\n  // Bidirectional streaming\n  rpc SyncUsers(stream UserUpdate) returns (stream UserUpdate);\n}\n\nmessage User {\n  string id = 1;\n  string email = 2;\n  string name = 3;\n  google.protobuf.Timestamp created_at = 4;\n}\n\nmessage GetUserRequest {\n  string id = 1;\n}\n\nmessage ListUsersRequest {\n  int32 page_size = 1;\n  string page_token = 2;\n  UserFilter filter = 3;\n}\n\nmessage UserFilter {\n  optional string status = 1;\n  optional string role = 2;\n}\n```\n\n### Error Handling\n\n```protobuf\n// Use Google's richer error model\nimport \"google/rpc/status.proto\";\nimport \"google/rpc/error_details.proto\";\n\n// For streaming: embed errors in response\nmessage StreamResponse {\n  oneof result {\n    User user = 1;\n    StreamError error = 2;\n  }\n}\n\nmessage StreamError {\n  string code = 1;\n  string message = 2;\n  map<string, string> details = 3;\n}\n```\n\n### Deadlines & Retries\n\n```typescript\n// Always set deadlines\nconst deadline = new Date();\ndeadline.setSeconds(deadline.getSeconds() + 5);\n\nconst user = await client.getUser(\n  { id: '123' },\n  { deadline }\n);\n\n// Configure retry policy\nconst retryPolicy = {\n  maxAttempts: 3,\n  initialBackoff: '0.1s',\n  maxBackoff: '1s',\n  backoffMultiplier: 2,\n  retryableStatusCodes: ['UNAVAILABLE', 'DEADLINE_EXCEEDED'],\n};\n```\n\n---\n\n## Rate Limiting\n\n### Headers\n\n```\nX-RateLimit-Limit: 100\nX-RateLimit-Remaining: 95\nX-RateLimit-Reset: 1640995200\nRetry-After: 60\n```\n\n### Response (429)\n\n```json\n{\n  \"type\": \"https://api.example.com/errors/rate-limited\",\n  \"title\": \"Rate Limit Exceeded\",\n  \"status\": 429,\n  \"detail\": \"You have exceeded the rate limit of 100 requests per minute\",\n  \"retryAfter\": 60\n}\n```\n\n---\n\n## Checklist\n\n```markdown\n## Design\n- [ ] API spec defined before implementation\n- [ ] Resources use plural nouns\n- [ ] Correct HTTP methods/status codes\n- [ ] RFC 7807 error format\n\n## Features\n- [ ] Cursor-based pagination\n- [ ] Rate limiting with headers\n- [ ] Idempotency keys for POST\n- [ ] API versioning strategy\n\n## Documentation\n- [ ] OpenAPI/GraphQL schema published\n- [ ] Examples for all endpoints\n- [ ] Error codes documented\n\n## Operations\n- [ ] Request/response logging\n- [ ] Latency and error rate metrics\n- [ ] Deprecation notices for old versions\n```\n\n---\n\n## See Also\n\n- [reference/rest.md](reference/rest.md) — REST deep dive\n- [reference/graphql.md](reference/graphql.md) — GraphQL patterns\n- [reference/grpc.md](reference/grpc.md) — gRPC patterns\n- [reference/comparison.md](reference/comparison.md) — Selection guide","tags":["api","design","claude","arsenal","majiayu000","agent-skills","ai-agents","ai-coding-assistant","automation","claude-code","code-review","developer-tools"],"capabilities":["skill","source-majiayu000","skill-api-design","topic-agent-skills","topic-ai-agents","topic-ai-coding-assistant","topic-automation","topic-claude","topic-claude-code","topic-code-review","topic-developer-tools","topic-devops","topic-productivity","topic-prompt-engineering","topic-python"],"categories":["claude-arsenal"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/majiayu000/claude-arsenal/api-design","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add majiayu000/claude-arsenal","source_repo":"https://github.com/majiayu000/claude-arsenal","install_from":"skills.sh"}},"qualityScore":"0.464","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 29 github stars · SKILL.md body (8,025 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-01T07:01:11.987Z","embedding":null,"createdAt":"2026-04-18T22:23:56.770Z","updatedAt":"2026-05-01T07:01:11.987Z","lastSeenAt":"2026-05-01T07:01:11.987Z","tsv":"'+1':547 '/errors/rate-limited':786 '/errors/validation':293 '/orders':385 '/users':134,142,180,328 '/users/123':138,146,150,154,306 '/users/123/activate':171 '/users/123/orders':160 '/v1':49 '/v1/users':355 '/v2/users':357,372 '0.1':748 '1':639,654,660,673,703,711 '100':765,801 '123':738 '1640995200':775 '1s':751 '2':642,664,677,706,714,753 '20':186,330,349 '200':216 '201':220 '2025':364 '204':227 '23':365 '3':645,667,719,746 '3.2':19,37 '31':362 '4':649 '400':234 '401':240 '403':244 '404':250 '409':257 '41d4':392 '422':263,298 '429':267,781,792 '446655440000':394 '5':732 '500':274 '503':280 '550e8400':389 '59':366,367 '60':779,806 '7807':71,288,824 '95':770 'a716':393 'action':164,172 'activ':182 'admin':184 'age':315 'also':868 'alway':723 'api':2,5,12,24,32,42,94,128,810,840 'api-design':1 'api.example.com':292,785 'api.example.com/errors/rate-limited':784 'api.example.com/errors/validation':291 'api.v1':592 'async':558 'authent':243,246 'author':249 'avoid':65 'await':563,735 'backoffmultipli':752 'backward':78 'bad':235 'base':68,324,830 'batch':553 'batchcreaterespons':625 'batchcreateus':621 'best':7 'bidirect':125,626 'boolean':507,509 'busi':414 'checklist':807 'choic':91 'clear':420 'client':232,618 'client.getuser':736 'code':214,538,710,822,852 'compat':79 'configur':740 'conflict':258 'conflicterror':531,541 'const':554,561,577,726,733,743 'constraint':262 'contain':302 'content':229 'contract':14,29 'contract-first':28 'core':26 'correct':819 'cover':17 'creat':143,198,221,647 'createdat':484 'createus':457,519,606 'createuserinput':459,521 'createuserpayload':460,522,528 'createuserrequest':607,623 'crud':166 'cursor':63,323,331,337,340,502,829 'cursor-bas':322,828 'data':121,334 'databas':417 'dataload':551,557 'date':729 'datetim':485 'db.user.findmany':564 'deadlin':720,725,727,739,756 'deadline.getseconds':731 'deadline.setseconds':730 'debug':100 'dec':363 'deep':427,872 'defin':13,31,812 'definit':439,587 'delet':80,153,155,209,231 'deprec':83,358,369,862 'descript':418 'design':3,6,11,25,129,406,585,809 'detail':75,299,718,793 'dive':873 'document':43,843,853 'doesn':167,254 'domain':410,415 'domain-driven':409 'driven':103,411 'duplic':403 'duplicate/conflict':259 'e.g':260 'e29b':391 'e29b-41d4-a716':390 'easi':99 'edg':492 'email':309,312,474,641 'emb':693 'endcursor':512 'endpoint':850 'entiti':434 'error':72,233,273,276,279,285,296,307,514,526,678,685,694,705,825,851,859 'exact':107 'exampl':847 'exceed':757,790,796 'exist':256 'existingid':544 'eyjpzci6mtawfq':332 'eyjpzci6mtiwfq':338 'eyjpzci6odb9':341 'fail':266 'featur':827 'feder':21,437 'fetch':106 'field':308,314,470,536 'field/type':421 'filter':178,452,666 'first':30,448,479 'fit':169 'forbidden':245 'format':76,313,826 'found':252 'frontend':102 'frontend-driven':101 'get':133,137,139,159,179,193,327,354,356 'get/put/patch':219 'getus':601 'getuserrequest':602,651 'gmt':368 'good':132 'googl':682 'google.protobuf.timestamp':646 'google/protobuf/empty.proto':596 'google/protobuf/timestamp.proto':594 'google/rpc/error_details.proto':690 'google/rpc/status.proto':688 'graphql':20,105,123,405,440,516,876 'grpc':22,113,122,584,880 'guid':885 'handl':15,515,679 'hasnextpag':506 'haspreviouspag':508 'header':52,226,359,760,835 'high':114 'http':187,820 'hurt':429 'id':444,445,462,463,471,472,473,545,559,566,568,571,575,638,653,737 'idempot':53,57,61,191,377,381,387,836 'idempotency-key':60,386 'identifi':435 'ids.map':570 'implement':35,814 'import':593,595,687,689 'includ':224 'initialbackoff':747 'input':458,464,520 'instanc':305 'int':449,480,497 'int32':657 'integ':320 'intern':112,275 'invalid':303,311 'json':289,325,782 'key':62,388,404,432,469,837 'latenc':857 'limit':185,271,329,348,425,759,764,789,799,833 'link':371 'list':135 'listus':613 'listusersrequest':614,656 'locat':225 'log':856 'malform':237 'mani':269 'map':715 'mark':433 'markdown':808 'maxattempt':745 'maxbackoff':750 'messag':310,316,534,542,635,650,655,668,697,707,713 'method':188,189 'methods/status':821 'metric':861 'microservic':111 'minut':804 'missing/invalid':242 'mobil':104 'model':686 'monitor':424 'must':55,317 'mutat':456,518 'mvp':95 'n':546 'name':131,419,422,476,644 'need':110 'nest':157,426,428 'new':556,728 'next':336,343 'node':500 'non':380 'non-idempot':379 'notic':863 'noun':818 'offset':67 'offset-bas':66 'ok':217 'old':865 'oneof':699 'openapi':18,36,39 'openapi/graphql':844 'oper':382,854 'option':670,674 'order':163,478,579,581 'order.userid':583 'orderconnect':483 'packag':591 'page':658,662 'pageinfo':494,495,505 'pagin':64,69,321,335,489,831 'paramet':176,304 'patch':149,205 'path':48 'pattern':877,881 'per':803 'perform':115,430 'plural':817 'polici':742 'posit':319 'post':58,141,170,197,223,383,384,839 'practic':8 'prev':339,346 'prevent':548 'principl':27,408 'problem':74 'proto':586 'proto3':590 'protobuf':588,680 'public':93 'publish':846 'purpos':190 'put':145,201 'put/delete':54 'queri':175,442 'quick':84 'rate':270,758,788,798,832,860 'ratelimit':763,768,773 'read':194 'real':119 'real-tim':118 'reason':92 'recommend':353 'refer':85 'reference/comparison.md':882,883 'reference/graphql.md':874,875 'reference/grpc.md':878,879 'reference/rest.md':869,870 'reflect':413 'rel':373 'relay':487 'relay-styl':486 'remain':769 'remov':210 'replac':147,202 'request':236,238,301,326,802 'request/response':855 'reset':774 'resolv':576,578 'resourc':130,158,174,253,815 'respons':286,333,401,696,780 'rest':41,96,127,871 'rest/graphql/grpc':4 'result':397,700 'retri':721,741,777 'retry-aft':776 'retryablestatuscod':754 'retryaft':805 'retrypolici':744 'return':399,569,603,608,615,624,632 'rfc':70,287,823 'richer':684 'role':183,676 'rpc':600,605,612,620,628 'safe':192 'sat':361 'scenario':90 'schema':407,412,845 'see':867 'select':884 'server':272,278,395,610 'servic':282,597 'set':724 'simpl':97 'size':659 'skill' 'skill-api-design' 'source-majiayu000' 'spec':33,811 'standard':73 'startcursor':510 'status':181,213,297,672,791 'store':396 'strategi':842 'stream':23,126,611,616,619,622,627,630,633,692 'streamerror':704,708 'streamrespons':698 'string':451,475,477,482,503,511,513,535,537,539,543,560,637,640,643,652,661,671,675,709,712,716,717 'strong':116 'style':488 'subscript':124 'success':215,218,222,230 'successor':375 'successor-vers':374 'sunset':51,360 'syncus':629 'syntax':239,589 'temporarili':283 'time':120 'titl':294,787 'token':663 'topic-agent-skills' 'topic-ai-agents' 'topic-ai-coding-assistant' 'topic-automation' 'topic-claude' 'topic-claude-code' 'topic-code-review' 'topic-developer-tools' 'topic-devops' 'topic-productivity' 'topic-prompt-engineering' 'topic-python' 'totalcount':496 'true':344,347,370 'type':117,290,438,441,455,467,490,498,504,517,525,532,540,783 'typescript':549,722 'u':573 'u.id':574 'unari':599 'unauthor':241 'unavail':281,755 'unexpect':277 'union':523,527 'uniqu':261 'univers':98 'unprocess':264 'updat':151,206 'updateus':461 'updateuserinput':465 'updateuserpayload':466 'url':44,351 'use':9,38,59,88,431,550,681,816 'user':136,140,144,148,152,156,161,443,446,447,468,501,529,562,580,604,609,617,636,701,702,734 'userconnect':454,491 'useredg':493,499 'userfilt':453,665,669 'userload':555 'userloader.load':582 'users.find':572 'userservic':598 'userupd':631,634 'valid':265,295 'validationerror':530,533 'version':16,45,46,350,352,376,841,866 'x':762,767,772 'x-ratelimit-limit':761 'x-ratelimit-remain':766 'x-ratelimit-reset':771 'yes':195,196,203,211","prices":[{"id":"5c61cce9-484c-48d6-b6c7-7b1e486fe848","listingId":"2b4b3cad-2e56-4a12-87ba-dab2419839bc","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"majiayu000","category":"claude-arsenal","install_from":"skills.sh"},"createdAt":"2026-04-18T22:23:56.770Z"}],"sources":[{"listingId":"2b4b3cad-2e56-4a12-87ba-dab2419839bc","source":"github","sourceId":"majiayu000/claude-arsenal/api-design","sourceUrl":"https://github.com/majiayu000/claude-arsenal/tree/main/skills/api-design","isPrimary":false,"firstSeenAt":"2026-04-18T22:23:56.770Z","lastSeenAt":"2026-05-01T07:01:11.987Z"}],"details":{"listingId":"2b4b3cad-2e56-4a12-87ba-dab2419839bc","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"majiayu000","slug":"api-design","github":{"repo":"majiayu000/claude-arsenal","stars":29,"topics":["agent-skills","ai-agents","ai-coding-assistant","automation","claude","claude-code","code-review","developer-tools","devops","productivity","prompt-engineering","python","software-development","typescript","workflows"],"license":"mit","html_url":"https://github.com/majiayu000/claude-arsenal","pushed_at":"2026-04-29T04:12:22Z","description":"52 production-ready Claude Code skills and 7 specialized agents for software development, DevOps, product workflows, and automation.","skill_md_sha":"09de194a79795e9cf077d5bdd09b313305fc15ef","skill_md_path":"skills/api-design/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/majiayu000/claude-arsenal/tree/main/skills/api-design"},"layout":"multi","source":"github","category":"claude-arsenal","frontmatter":{"name":"api-design","description":"REST/GraphQL/gRPC API design best practices. Use when designing APIs, defining contracts, handling versioning. Covers OpenAPI 3.2, GraphQL Federation, gRPC streaming."},"skills_sh_url":"https://skills.sh/majiayu000/claude-arsenal/api-design"},"updatedAt":"2026-05-01T07:01:11.987Z"}}