{"id":"014fe34d-7d32-4fb0-b573-8bd09e98fad3","shortId":"DfqNgt","kind":"skill","title":"typescript-api-design","tagline":"REST API design patterns. Use when designing endpoints, error responses, pagination, versioning, or API structure. Framework-agnostic principles for building consistent, maintainable APIs.","description":"# API Design Patterns\n\nBest practices for designing REST APIs with consistent structure, error handling, and resource patterns.\n\n## Additional References\n\n- [references/error-responses.md](./references/error-responses.md) - Detailed error handling examples\n\n## Resource Naming\n\nUse consistent, predictable URL patterns:\n\n```\n# Collection resources (plural nouns)\nGET    /api/v1/users              # List users\nPOST   /api/v1/users              # Create user\nGET    /api/v1/users/:id          # Get user\nPUT    /api/v1/users/:id          # Update user (full)\nPATCH  /api/v1/users/:id          # Update user (partial)\nDELETE /api/v1/users/:id          # Delete user\n\n# Nested resources\nGET    /api/v1/users/:userId/posts          # List user's posts\nPOST   /api/v1/users/:userId/posts          # Create post for user\nGET    /api/v1/users/:userId/posts/:postId  # Get specific post\n\n# Actions (use verbs sparingly)\nPOST   /api/v1/users/:id/activate          # Activate user\nPOST   /api/v1/posts/:id/publish           # Publish post\nPOST   /api/v1/invoices/:id/send           # Send invoice\n```\n\n### Guidelines\n\n- Use plural nouns for collections (`/users`, not `/user`)\n- Use lowercase with hyphens for multi-word resources (`/ledger-accounts`)\n- Avoid deep nesting (max 2 levels: `/users/:id/posts/:id`)\n- Use query parameters for filtering, sorting, pagination\n- Use verbs only for actions that don't fit CRUD (activate, publish, send)\n\n## API Versioning\n\nVersion APIs in the URL path:\n\n```\n/api/v1/users\n/api/v2/users\n\n# Not in headers (harder to test/debug)\n# Not in query params (breaks caching)\n```\n\n### Version Strategy\n\n```typescript\n// v1/routes.ts\nexport async function v1Routes(app: FastifyInstance) {\n  app.get('/users', getUsersV1)\n  app.post('/users', createUserV1)\n}\n\n// v2/routes.ts\nexport async function v2Routes(app: FastifyInstance) {\n  app.get('/users', getUsersV2)  // Breaking change in response structure\n  app.post('/users', createUserV2)\n}\n\n// server.ts\napp.register(v1Routes, { prefix: '/api/v1' })\napp.register(v2Routes, { prefix: '/api/v2' })\n```\n\n## RFC 7807 Problem Details\n\nStandardized error response format:\n\n```typescript\ninterface ProblemDetail {\n  type: string          // Error type identifier\n  status: number        // HTTP status code\n  title: string         // Short, human-readable summary\n  detail: string        // Specific explanation for this occurrence\n  instance: string      // URI reference to specific occurrence\n  traceId: string       // Request trace ID for debugging\n}\n\n// Example error response\n{\n  \"type\": \"NOT_FOUND\",\n  \"status\": 404,\n  \"title\": \"Not Found\",\n  \"detail\": \"User with ID usr_01h455vb4pex5vsknk084sn02q not found\",\n  \"instance\": \"/api/v1/users/usr_01h455vb4pex5vsknk084sn02q\",\n  \"traceId\": \"req_abc123xyz\"\n}\n```\n\n### Error Types\n\n```typescript\n// Domain error base class\nabstract class AppError extends Error {\n  abstract readonly status: number\n  abstract readonly type: string\n\n  constructor(message: string, public readonly context?: ErrorContext) {\n    super(message)\n  }\n\n  toResponse(instance: string, traceId: string): ProblemDetail {\n    return {\n      type: this.type,\n      status: this.status,\n      title: this.name,\n      detail: this.message,\n      instance,\n      traceId,\n      ...this.context,\n    }\n  }\n}\n\n// Specific error types\nclass NotFoundError extends AppError {\n  readonly status = 404\n  readonly type = 'NOT_FOUND'\n}\n\nclass ConflictError extends AppError {\n  readonly status = 409\n  readonly type = 'CONFLICT'\n\n  constructor(\n    message: string,\n    public readonly retryable: boolean = false,\n    context?: ErrorContext\n  ) {\n    super(message, context)\n  }\n}\n\nclass ServiceUnavailableError extends AppError {\n  readonly status = 503\n  readonly type = 'SERVICE_UNAVAILABLE'\n\n  constructor(\n    message: string,\n    public readonly retryable: boolean = true,\n    context?: ErrorContext\n  ) {\n    super(message, context)\n  }\n}\n```\n\nSee [references/error-responses.md](./references/error-responses.md) for complete examples.\n\n## Pagination (Cursor-Based)\n\nUse cursor-based pagination for large datasets:\n\n```typescript\n// Request\nGET /api/v1/posts?limit=20&cursor=pst_01h455vb4pex5vsknk084sn02q\n\n// Response\n{\n  \"items\": [\n    { \"id\": \"pst_01h455w3x8k5z9y7q1m0n2b3c4\", ... },\n    { \"id\": \"pst_01h455x2y9l6a0z8r2n1o3c5d6\", ... }\n  ],\n  \"nextCursor\": \"pst_01h455z1a0m7b8y9s3o2p4d6e7\",\n  \"hasMore\": true\n}\n```\n\n### Implementation\n\n```typescript\ninterface PaginatedRequest {\n  limit?: number   // Max items to return (default 20, max 100)\n  cursor?: string  // Cursor for next page (opaque to client)\n}\n\ninterface PaginatedResponse<T> {\n  items: T[]\n  nextCursor?: string\n  hasMore: boolean\n}\n\nasync function listPosts(req: PaginatedRequest): Promise<PaginatedResponse<Post>> {\n  const limit = Math.min(req.limit ?? 20, 100)\n  const queryLimit = limit + 1  // Fetch one extra to check hasMore\n\n  const posts = await db.query.posts.findMany({\n    where: req.cursor ? gt(posts.id, req.cursor) : undefined,\n    orderBy: desc(posts.createdAt),\n    limit: queryLimit,\n  })\n\n  const hasMore = posts.length > limit\n  const items = posts.slice(0, limit)\n  const nextCursor = hasMore ? items[items.length - 1].id : undefined\n\n  return { items, nextCursor, hasMore }\n}\n```\n\n### Why Cursor Over Offset\n\n```\n❌ Offset-based (/posts?offset=40&limit=20)\n  - Unstable: Items can shift if new records inserted\n  - Performance: DB must scan all previous rows\n  - Inaccurate: Can miss or duplicate items\n\n✅ Cursor-based (/posts?cursor=pst_xyz&limit=20)\n  - Stable: Cursor points to specific item\n  - Performant: DB uses index seek\n  - Accurate: No gaps or duplicates\n```\n\n## Filtering & Sorting\n\nUse query parameters for filtering and sorting:\n\n```\n# Filtering\nGET /api/v1/users?status=active&role=admin\nGET /api/v1/posts?author=usr_abc&published=true\n\n# Sorting\nGET /api/v1/posts?sort=-createdAt      # Descending (- prefix)\nGET /api/v1/users?sort=name             # Ascending\n\n# Combined\nGET /api/v1/posts?author=usr_abc&status=published&sort=-createdAt&limit=20\n```\n\n### Implementation\n\n```typescript\ninterface ListPostsQuery {\n  author?: string\n  status?: 'draft' | 'published'\n  sort?: 'createdAt' | '-createdAt' | 'title' | '-title'\n  limit?: number\n  cursor?: string\n}\n\nasync function listPosts(query: ListPostsQuery): Promise<PaginatedResponse<Post>> {\n  const conditions = []\n\n  if (query.author) {\n    conditions.push(eq(posts.authorId, query.author))\n  }\n  if (query.status) {\n    conditions.push(eq(posts.status, query.status))\n  }\n\n  const orderByColumn = query.sort?.startsWith('-')\n    ? query.sort.slice(1)\n    : query.sort ?? 'createdAt'\n  const orderByDirection = query.sort?.startsWith('-') ? desc : asc\n\n  return await db.query.posts.findMany({\n    where: conditions.length > 0 ? and(...conditions) : undefined,\n    orderBy: orderByDirection(posts[orderByColumn]),\n    limit: query.limit ?? 20,\n  })\n}\n```\n\n## HTTP Status Codes\n\nUse status codes consistently:\n\n```\n# Success\n200 OK               # Successful GET, PUT, PATCH\n201 Created          # Successful POST (include Location header)\n204 No Content       # Successful DELETE, PUT with no response body\n\n# Client Errors\n400 Bad Request      # Invalid request body/parameters\n401 Unauthorized     # Missing or invalid authentication\n403 Forbidden        # Valid auth, but lacks permission\n404 Not Found        # Resource doesn't exist\n409 Conflict         # Resource already exists, optimistic lock failure\n422 Unprocessable    # Validation error (semantic)\n429 Too Many Requests # Rate limit exceeded\n\n# Server Errors\n500 Internal Server Error  # Unexpected error\n503 Service Unavailable    # Temporary unavailability, retry later\n```\n\n## Response Envelope (When to Use)\n\n**Don't use envelopes for simple CRUD:**\n\n```typescript\n// ❌ Unnecessary wrapping\nGET /api/v1/users/123\n{\n  \"success\": true,\n  \"data\": { \"id\": \"123\", \"name\": \"Alice\" }\n}\n\n// ✅ Return resource directly\nGET /api/v1/users/123\n{\n  \"id\": \"123\",\n  \"name\": \"Alice\"\n}\n```\n\n**Use envelopes for pagination:**\n\n```typescript\n// ✅ Envelope needed for metadata\nGET /api/v1/users?limit=20\n{\n  \"items\": [...],\n  \"nextCursor\": \"usr_xyz\",\n  \"hasMore\": true\n}\n```\n\n## Timestamps\n\nUse ISO 8601 format for all timestamps:\n\n```typescript\n{\n  \"createdAt\": \"2024-01-15T14:30:00.000Z\",  // ISO 8601 UTC\n  \"updatedAt\": \"2024-01-16T09:15:30.123Z\"\n}\n\n// In entities\ntoResponse(): UserResponse {\n  return {\n    ...\n    createdAt: this.createdAt.toISOString(),  // Date → ISO string\n    updatedAt: this.updatedAt.toISOString(),\n  }\n}\n```\n\n## Idempotency\n\nUse idempotency keys for safe retries:\n\n```typescript\n// Request\nPOST /api/v1/transactions\nHeaders:\n  Idempotency-Key: txn_abc123xyz\nBody:\n  { \"amount\": 100, \"from\": \"usr_123\", \"to\": \"usr_456\" }\n\n// Implementation\nasync function createTransaction(rq: CreateTransactionRequest, idempotencyKey: string) {\n  // Check if transaction with this key already exists\n  const existing = await db.query.transactions.findFirst({\n    where: eq(transactions.idempotencyKey, idempotencyKey),\n  })\n\n  if (existing) {\n    return TransactionEntity.fromRecord(existing)  // Return existing\n  }\n\n  // Create new transaction\n  const transaction = TransactionEntity.fromRequest(rq, idempotencyKey)\n  return await transactionRepo.create(transaction)\n}\n```\n\n## Guidelines\n\n1. **Plural nouns** - Collections use plural resource names\n2. **Lowercase with hyphens** - Multi-word resources like `ledger-accounts`\n3. **Version in URL** - `/api/v1/`, `/api/v2/` for breaking changes\n4. **RFC 7807 errors** - Standardized error response format\n5. **Cursor pagination** - For large datasets (more stable than offset)\n6. **Query params** - For filtering, sorting, pagination (not in path)\n7. **HTTP status codes** - Use correct codes (200, 201, 204, 400, 404, 409, 500, 503)\n8. **ISO 8601 timestamps** - Always use `.toISOString()` for dates\n9. **Idempotency keys** - For non-idempotent operations (POST, PATCH)\n10. **No unnecessary envelopes** - Return resources directly unless pagination needed","tags":["typescript","api","design","atelier","martinffx","agent-skills","agentic-coding","anthropic","claude-code","claude-skills","code-review","codex"],"capabilities":["skill","source-martinffx","skill-typescript-api-design","topic-agent-skills","topic-agentic-coding","topic-anthropic","topic-claude-code","topic-claude-skills","topic-code-review","topic-codex","topic-codex-skill","topic-opencode","topic-prompt-engineering","topic-sdd","topic-spec-driven-development"],"categories":["atelier"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/martinffx/atelier/typescript-api-design","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add martinffx/atelier","source_repo":"https://github.com/martinffx/atelier","install_from":"skills.sh"}},"qualityScore":"0.461","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 23 github stars · SKILL.md body (10,007 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-18T19:05:24.708Z","embedding":null,"createdAt":"2026-05-10T07:03:13.236Z","updatedAt":"2026-05-18T19:05:24.708Z","lastSeenAt":"2026-05-18T19:05:24.708Z","tsv":"'-01':892,903 '-15':893 '-16':904 '/api/v1':245,1015 '/api/v1/invoices':133 '/api/v1/posts':128,452,636,644,656 '/api/v1/transactions':931 '/api/v1/users':66,70,74,79,85,91,98,105,112,123,193,630,650,872 '/api/v1/users/123':845,857 '/api/v1/users/usr_01h455vb4pex5vsknk084sn02q':319 '/api/v2':249,1016 '/api/v2/users':194 '/ledger-accounts':155 '/posts':568,597 '/references/error-responses.md':49,433 '/user':145 '/users':143,162,218,221,231,239 '0':547,724 '00.000':896 '01h455vb4pex5vsknk084sn02q':315,457 '01h455w3x8k5z9y7q1m0n2b3c4':462 '01h455x2y9l6a0z8r2n1o3c5d6':465 '01h455z1a0m7b8y9s3o2p4d6e7':468 '1':518,554,710,991 '10':1082 '100':484,514,940 '123':850,859,943 '15':906 '2':160,999 '20':454,482,513,572,602,665,734,874 '200':743,1055 '201':749,1056 '2024':891,902 '204':756,1057 '3':1011 '30':895 '30.123':907 '4':1020 '40':570 '400':768,1058 '401':774 '403':780 '404':306,379,787,1059 '409':390,794,1060 '422':802 '429':807 '456':946 '5':1028 '500':816,1061 '503':413,822,1062 '6':1038 '7':1048 '7807':251,1022 '8':1063 '8601':884,899,1065 '9':1072 'abc':639,659 'abc123xyz':322,937 'abstract':330,335,339 'account':1010 'accur':614 'action':118,176 'activ':125,182,632 'addit':46 'admin':634 'agnost':22 'alic':852,861 'alreadi':797,961 'alway':1067 'amount':939 'api':3,6,18,28,29,37,185,188 'app':215,228 'app.get':217,230 'app.post':220,238 'app.register':242,246 'apperror':332,376,387,410 'asc':718 'ascend':653 'async':212,225,502,684,948 'auth':783 'authent':779 'author':637,657,670 'avoid':156 'await':527,720,965,987 'bad':769 'base':328,440,444,567,596 'best':32 'bodi':765,938 'body/parameters':773 'boolean':400,424,501 'break':205,233,1018 'build':25 'cach':206 'chang':234,1019 'check':523,955 'class':329,331,373,384,407 'client':493,766 'code':270,737,740,1051,1054 'collect':61,142,994 'combin':654 'complet':435 'condit':692,726 'conditions.length':723 'conditions.push':695,701 'conflict':393,795 'conflicterror':385 'consist':26,39,57,741 'const':509,515,525,540,544,549,691,705,713,963,981 'constructor':343,394,418 'content':758 'context':348,402,406,426,430 'correct':1053 'creat':71,107,750,978 'createdat':646,663,676,677,712,890,914 'createtransact':950 'createtransactionrequest':952 'createuserv1':222 'createuserv2':240 'crud':181,840 'cursor':439,443,455,485,487,562,595,598,604,682,1029 'cursor-bas':438,442,594 'data':848 'dataset':448,1033 'date':916,1071 'db':582,610 'db.query.posts.findmany':528,721 'db.query.transactions.findfirst':966 'debug':298 'deep':157 'default':481 'delet':90,93,760 'desc':536,717 'descend':647 'design':4,7,11,30,35 'detail':50,253,278,310,365 'direct':855,1088 'doesn':791 'domain':326 'draft':673 'duplic':592,618 'endpoint':12 'entiti':910 'envelop':830,837,863,867,1085 'eq':696,702,968 'error':13,41,51,255,263,300,323,327,334,371,767,805,815,819,821,1023,1025 'errorcontext':349,403,427 'exampl':53,299,436 'exceed':813 'exist':793,798,962,964,972,975,977 'explan':281 'export':211,224 'extend':333,375,386,409 'extra':521 'failur':801 'fals':401 'fastifyinst':216,229 'fetch':519 'filter':169,619,625,628,1042 'fit':180 'forbidden':781 'format':257,885,1027 'found':304,309,317,383,789 'framework':21 'framework-agnost':20 'full':83 'function':213,226,503,685,949 'gap':616 'get':65,73,76,97,111,115,451,629,635,643,649,655,746,844,856,871 'getusersv1':219 'getusersv2':232 'gt':531 'guidelin':137,990 'handl':42,52 'harder':198 'hasmor':469,500,524,541,551,560,879 'header':197,755,932 'http':268,735,1049 'human':275 'human-read':274 'hyphen':149,1002 'id':75,80,86,92,164,296,313,460,463,555,849,858 'id/activate':124 'id/posts':163 'id/publish':129 'id/send':134 'idempot':921,923,934,1073,1078 'idempotency-key':933 'idempotencykey':953,970,985 'identifi':265 'implement':471,666,947 'inaccur':588 'includ':753 'index':612 'insert':580 'instanc':285,318,353,367 'interfac':259,473,494,668 'intern':817 'invalid':771,778 'invoic':136 'iso':883,898,917,1064 'item':459,478,496,545,552,558,574,593,608,875 'items.length':553 'key':924,935,960,1074 'lack':785 'larg':447,1032 'later':828 'ledger':1009 'ledger-account':1008 'level':161 'like':1007 'limit':453,475,510,517,538,543,548,571,601,664,680,732,812,873 'list':67,100 'listpost':504,686 'listpostsqueri':669,688 'locat':754 'lock':800 'lowercas':147,1000 'maintain':27 'mani':809 'math.min':511 'max':159,477,483 'messag':344,351,395,405,419,429 'metadata':870 'miss':590,776 'multi':152,1004 'multi-word':151,1003 'must':583 'name':55,652,851,860,998 'need':868,1091 'nest':95,158 'new':578,979 'next':489 'nextcursor':466,498,550,559,876 'non':1077 'non-idempot':1076 'notfounderror':374 'noun':64,140,993 'number':267,338,476,681 'occurr':284,291 'offset':564,566,569,1037 'offset-bas':565 'ok':744 'one':520 'opaqu':491 'oper':1079 'optimist':799 'orderbi':535,728 'orderbycolumn':706,731 'orderbydirect':714,729 'page':490 'pagin':15,171,437,445,865,1030,1044,1090 'paginatedrequest':474,506 'paginatedrespons':495,508,690 'param':204,1040 'paramet':167,623 'partial':89 'patch':84,748,1081 'path':192,1047 'pattern':8,31,45,60 'perform':581,609 'permiss':786 'plural':63,139,992,996 'point':605 'post':69,103,104,108,117,122,127,131,132,526,730,752,930,1080 'postid':114 'posts.authorid':697 'posts.createdat':537 'posts.id':532 'posts.length':542 'posts.slice':546 'posts.status':703 'practic':33 'predict':58 'prefix':244,248,648 'previous':586 'principl':23 'problem':252 'problemdetail':260,357 'promis':507,689 'pst':456,461,464,467,599 'public':346,397,421 'publish':130,183,640,661,674 'put':78,747,761 'queri':166,203,622,687,1039 'query.author':694,698 'query.limit':733 'query.sort':707,711,715 'query.sort.slice':709 'query.status':700,704 'querylimit':516,539 'rate':811 'readabl':276 'readon':336,340,347,377,380,388,391,398,411,414,422 'record':579 'refer':47,288 'references/error-responses.md':48,432 'req':321,505 'req.cursor':530,533 'req.limit':512 'request':294,450,770,772,810,929 'resourc':44,54,62,96,154,790,796,854,997,1006,1087 'respons':14,236,256,301,458,764,829,1026 'rest':5,36 'retri':827,927 'retryabl':399,423 'return':358,480,557,719,853,913,973,976,986,1086 'rfc':250,1021 'role':633 'row':587 'rq':951,984 'safe':926 'scan':584 'see':431 'seek':613 'semant':806 'send':135,184 'server':814,818 'server.ts':241 'servic':416,823 'serviceunavailableerror':408 'shift':576 'short':273 'simpl':839 'skill' 'skill-typescript-api-design' 'sort':170,620,627,642,645,651,662,675,1043 'source-martinffx' 'spare':121 'specif':116,280,290,370,607 'stabl':603,1035 'standard':254,1024 'startswith':708,716 'status':266,269,305,337,361,378,389,412,631,660,672,736,739,1050 'strategi':208 'string':262,272,279,286,293,342,345,354,356,396,420,486,499,671,683,918,954 'structur':19,40,237 'success':742,745,751,759,846 'summari':277 'super':350,404,428 't09':905 't14':894 'temporari':825 'test/debug':200 'this.context':369 'this.createdat.toisostring':915 'this.message':366 'this.name':364 'this.status':362 'this.type':360 'this.updatedat.toisostring':920 'timestamp':881,888,1066 'titl':271,307,363,678,679 'toisostr':1069 'topic-agent-skills' 'topic-agentic-coding' 'topic-anthropic' 'topic-claude-code' 'topic-claude-skills' 'topic-code-review' 'topic-codex' 'topic-codex-skill' 'topic-opencode' 'topic-prompt-engineering' 'topic-sdd' 'topic-spec-driven-development' 'torespons':352,911 'trace':295 'traceid':292,320,355,368 'transact':957,980,982,989 'transactionentity.fromrecord':974 'transactionentity.fromrequest':983 'transactionrepo.create':988 'transactions.idempotencykey':969 'true':425,470,641,847,880 'txn':936 'type':261,264,302,324,341,359,372,381,392,415 'typescript':2,209,258,325,449,472,667,841,866,889,928 'typescript-api-design':1 'unauthor':775 'unavail':417,824,826 'undefin':534,556,727 'unexpect':820 'unless':1089 'unnecessari':842,1084 'unprocess':803 'unstabl':573 'updat':81,87 'updatedat':901,919 'uri':287 'url':59,191,1014 'use':9,56,119,138,146,165,172,441,611,621,738,833,836,862,882,922,995,1052,1068 'user':68,72,77,82,88,94,101,110,126,311 'userid/posts':99,106,113 'userrespons':912 'usr':314,638,658,877,942,945 'utc':900 'v1/routes.ts':210 'v1routes':214,243 'v2/routes.ts':223 'v2routes':227,247 'valid':782,804 'verb':120,173 'version':16,186,187,207,1012 'word':153,1005 'wrap':843 'xyz':600,878 'z':897,908","prices":[{"id":"660ce0d7-8118-430e-b85c-c6e68c967b6b","listingId":"014fe34d-7d32-4fb0-b573-8bd09e98fad3","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"martinffx","category":"atelier","install_from":"skills.sh"},"createdAt":"2026-05-10T07:03:13.236Z"}],"sources":[{"listingId":"014fe34d-7d32-4fb0-b573-8bd09e98fad3","source":"github","sourceId":"martinffx/atelier/typescript-api-design","sourceUrl":"https://github.com/martinffx/atelier/tree/main/skills/typescript-api-design","isPrimary":false,"firstSeenAt":"2026-05-10T07:03:13.236Z","lastSeenAt":"2026-05-18T19:05:24.708Z"}],"details":{"listingId":"014fe34d-7d32-4fb0-b573-8bd09e98fad3","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"martinffx","slug":"typescript-api-design","github":{"repo":"martinffx/atelier","stars":23,"topics":["agent-skills","agentic-coding","anthropic","claude-code","claude-skills","code-review","codex","codex-skill","opencode","prompt-engineering","sdd","spec-driven-development"],"license":"mit","html_url":"https://github.com/martinffx/atelier","pushed_at":"2026-05-18T06:56:45Z","description":"An atelier for Opencode, Claude Code, and other coding agents: spec-driven workflows, deep thinking, and code quality.","skill_md_sha":"521c04019b6872182d95b30e9520631a77f24bd6","skill_md_path":"skills/typescript-api-design/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/martinffx/atelier/tree/main/skills/typescript-api-design"},"layout":"multi","source":"github","category":"atelier","frontmatter":{"name":"typescript-api-design","description":"REST API design patterns. Use when designing endpoints, error responses, pagination, versioning, or API structure. Framework-agnostic principles for building consistent, maintainable APIs."},"skills_sh_url":"https://skills.sh/martinffx/atelier/typescript-api-design"},"updatedAt":"2026-05-18T19:05:24.708Z"}}