start-vibing-stacks 2.7.3 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/setup.js +2 -2
- package/package.json +1 -1
- package/stacks/_shared/skills/hook-development/SKILL.md +2 -2
- package/stacks/_shared/skills/openapi-design/SKILL.md +409 -0
- package/stacks/_shared/skills/postgres-patterns/SKILL.md +521 -0
- package/stacks/nodejs/skills/fastify-api/SKILL.md +569 -0
- package/stacks/nodejs/stack.json +5 -4
- package/stacks/php/stack.json +3 -2
- package/stacks/python/stack.json +4 -4
package/dist/setup.js
CHANGED
|
@@ -217,7 +217,7 @@ export async function setupProject(projectDir, config, options = {}) {
|
|
|
217
217
|
hooks: [
|
|
218
218
|
{
|
|
219
219
|
type: 'command',
|
|
220
|
-
command: 'npx tsx
|
|
220
|
+
command: 'npx tsx "$CLAUDE_PROJECT_DIR/.claude/hooks/user-prompt-submit.ts"',
|
|
221
221
|
timeout: 10,
|
|
222
222
|
},
|
|
223
223
|
],
|
|
@@ -228,7 +228,7 @@ export async function setupProject(projectDir, config, options = {}) {
|
|
|
228
228
|
hooks: [
|
|
229
229
|
{
|
|
230
230
|
type: 'command',
|
|
231
|
-
command: 'npx tsx
|
|
231
|
+
command: 'npx tsx "$CLAUDE_PROJECT_DIR/.claude/hooks/stop-validator.ts"',
|
|
232
232
|
timeout: 30,
|
|
233
233
|
},
|
|
234
234
|
],
|
package/package.json
CHANGED
|
@@ -77,8 +77,8 @@ console.log(JSON.stringify(result));
|
|
|
77
77
|
```json
|
|
78
78
|
{
|
|
79
79
|
"hooks": {
|
|
80
|
-
"Stop": [{ "hooks": [{ "type": "command", "command": "bash
|
|
81
|
-
"UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": "bash
|
|
80
|
+
"Stop": [{ "hooks": [{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" stop-validator", "timeout": 30 }] }],
|
|
81
|
+
"UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/run-hook.sh\" user-prompt-submit", "timeout": 10 }] }]
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
```
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: openapi-design
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: OpenAPI 3.1 spec design — resource naming, status codes, problem+json (RFC 9457) errors, pagination, versioning, security schemes, idempotency, deprecation. Stack-agnostic. Invoke when designing or documenting any HTTP/REST API that publishes an OpenAPI spec, regardless of framework (Fastify, Hono, FastAPI, Laravel, Express, etc.).
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# OpenAPI Design — Universal API Contract
|
|
8
|
+
|
|
9
|
+
**Invoke before adding a new endpoint, before publishing a public API, and during code review of any route handler.**
|
|
10
|
+
|
|
11
|
+
> The OpenAPI document is the contract. Code that drifts from it lies to clients. Generate the spec from code (Zod / Pydantic / DTO classes) — never write it by hand for a real service.
|
|
12
|
+
|
|
13
|
+
This skill covers **what the spec should look like**. For **how to generate it** in your stack, see the per-framework skill (`fastify-api`, `hono-api`, `fastapi-patterns`, `laravel-patterns`, etc.).
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Resource Naming
|
|
18
|
+
|
|
19
|
+
| Rule | Good | Bad |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Plural nouns | `/users`, `/orders` | `/user`, `/getUser` |
|
|
22
|
+
| No verbs in path | `POST /users` | `/createUser` |
|
|
23
|
+
| Kebab-case multi-word | `/payment-methods` | `/paymentMethods`, `/payment_methods` |
|
|
24
|
+
| Hierarchy via path | `/users/{id}/orders` | `/orders?userId=...` (only for filters) |
|
|
25
|
+
| Verbs only for actions | `POST /orders/{id}/cancel` | `PATCH /orders/{id}` with `action=cancel` |
|
|
26
|
+
|
|
27
|
+
The action sub-resource pattern (`/orders/{id}/cancel`) is the escape hatch when an operation is not a clean CRUD on a resource. Use sparingly.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 2. HTTP Status Codes
|
|
32
|
+
|
|
33
|
+
| Code | Use For | Body |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `200 OK` | Successful GET / PUT / PATCH | Resource representation |
|
|
36
|
+
| `201 Created` | Successful POST that creates | New resource + `Location` header |
|
|
37
|
+
| `202 Accepted` | Async work queued | Job ID + status URL |
|
|
38
|
+
| `204 No Content` | DELETE, or PUT/PATCH with no body | Empty |
|
|
39
|
+
| `400 Bad Request` | Malformed request | problem+json |
|
|
40
|
+
| `401 Unauthorized` | Missing / invalid credentials | problem+json + `WWW-Authenticate` header |
|
|
41
|
+
| `403 Forbidden` | Authenticated but not allowed | problem+json |
|
|
42
|
+
| `404 Not Found` | Resource not found OR not visible to caller | problem+json |
|
|
43
|
+
| `409 Conflict` | Version conflict, duplicate key, state mismatch | problem+json |
|
|
44
|
+
| `410 Gone` | Resource permanently removed | problem+json |
|
|
45
|
+
| `422 Unprocessable Entity` | Body parses but fails business validation | problem+json with field errors |
|
|
46
|
+
| `429 Too Many Requests` | Rate limited | problem+json + `Retry-After` |
|
|
47
|
+
| `5xx` | Server fault | problem+json (no internals) |
|
|
48
|
+
|
|
49
|
+
**Never** return `200` with `{ "success": false, "error": ... }`. That breaks every reverse proxy, CDN cache, and HTTP client retry policy. Use the right code.
|
|
50
|
+
|
|
51
|
+
`401` vs `403`: 401 = "I don't know who you are". 403 = "I know who you are and you can't do this". Resources you don't want to leak the existence of: return `404` even if authenticated (with a security review note in the spec).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 3. Error Envelope — `application/problem+json` (RFC 9457)
|
|
56
|
+
|
|
57
|
+
One shape for every error response in the entire API. Codegen clients can match a single discriminator.
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"type": "https://api.example.com/problems/validation-error",
|
|
62
|
+
"title": "Validation failed",
|
|
63
|
+
"status": 422,
|
|
64
|
+
"detail": "The request body did not pass schema validation.",
|
|
65
|
+
"instance": "/orders/01HZ.../validate",
|
|
66
|
+
"errors": [
|
|
67
|
+
{ "field": "email", "code": "invalid_format", "message": "Must be a valid email." },
|
|
68
|
+
{ "field": "items[0].quantity", "code": "out_of_range", "message": "Must be ≥ 1." }
|
|
69
|
+
],
|
|
70
|
+
"trace_id": "0af7651916cd43dd8448eb211c80319c"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
| Field | Source | Notes |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `type` | RFC | URI identifying the error class. Stable across versions; humans can read at the URL |
|
|
77
|
+
| `title` | RFC | Short, human, generic per `type` |
|
|
78
|
+
| `status` | RFC | Mirrors HTTP status — useful when only the body is logged |
|
|
79
|
+
| `detail` | RFC | Specific to **this occurrence**; safe to show to the user |
|
|
80
|
+
| `instance` | RFC | URI/path of the failing operation |
|
|
81
|
+
| `errors[]` | extension | Field-level validation errors (422 only) |
|
|
82
|
+
| `trace_id` | extension | Correlate with logs / observability |
|
|
83
|
+
|
|
84
|
+
**Never** put stack traces, raw SQL, file paths, or PII (email, name) in `detail`. Log those server-side, hand the user the `trace_id`.
|
|
85
|
+
|
|
86
|
+
OpenAPI declaration of the schema (reuse via `$ref` everywhere):
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
components:
|
|
90
|
+
schemas:
|
|
91
|
+
Problem:
|
|
92
|
+
type: object
|
|
93
|
+
required: [type, title, status]
|
|
94
|
+
properties:
|
|
95
|
+
type: { type: string, format: uri }
|
|
96
|
+
title: { type: string }
|
|
97
|
+
status: { type: integer, minimum: 100, maximum: 599 }
|
|
98
|
+
detail: { type: string }
|
|
99
|
+
instance: { type: string, format: uri-reference }
|
|
100
|
+
trace_id: { type: string }
|
|
101
|
+
ValidationProblem:
|
|
102
|
+
allOf:
|
|
103
|
+
- $ref: '#/components/schemas/Problem'
|
|
104
|
+
- type: object
|
|
105
|
+
properties:
|
|
106
|
+
errors:
|
|
107
|
+
type: array
|
|
108
|
+
items:
|
|
109
|
+
type: object
|
|
110
|
+
required: [field, code, message]
|
|
111
|
+
properties:
|
|
112
|
+
field: { type: string }
|
|
113
|
+
code: { type: string }
|
|
114
|
+
message: { type: string }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 4. Pagination
|
|
120
|
+
|
|
121
|
+
Pick **one** strategy per endpoint and document it. Mixing breaks clients.
|
|
122
|
+
|
|
123
|
+
### Cursor-based (preferred for large/realtime sets)
|
|
124
|
+
|
|
125
|
+
```http
|
|
126
|
+
GET /events?limit=50&cursor=eyJpZCI6IjAxSFoifQ
|
|
127
|
+
```
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"data": [ ... ],
|
|
131
|
+
"next_cursor": "eyJpZCI6IjAxSFAifQ",
|
|
132
|
+
"has_more": true
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Pros: stable under writes (no skipped/duplicated rows when items are inserted), unbounded depth. Cons: no random access, no total count.
|
|
137
|
+
|
|
138
|
+
### Offset-based (only for small, finite sets — admin, archives)
|
|
139
|
+
|
|
140
|
+
```http
|
|
141
|
+
GET /users?page=2&page_size=50
|
|
142
|
+
```
|
|
143
|
+
```json
|
|
144
|
+
{ "data": [ ... ], "page": 2, "page_size": 50, "total": 1247 }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Cap `page_size` at 100. Cap `page * page_size` (deep pagination is expensive on every database).
|
|
148
|
+
|
|
149
|
+
### Link header alternative (RFC 8288)
|
|
150
|
+
|
|
151
|
+
```http
|
|
152
|
+
Link: </users?cursor=eyJ...>; rel="next", </users?cursor=eyI...>; rel="prev"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Useful when the body is a bare array. Most teams prefer the JSON envelope above.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 5. Versioning
|
|
160
|
+
|
|
161
|
+
Pick one strategy at the API level. Don't mix.
|
|
162
|
+
|
|
163
|
+
| Strategy | Example | When |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| URI segment | `/v1/users` | Public APIs, mobile clients (most common, simplest) |
|
|
166
|
+
| Header | `Accept: application/vnd.example.v1+json` | Hypermedia APIs, content negotiation purists |
|
|
167
|
+
| Query param | `/users?api_version=2024-10-01` | Date-based versioning (Stripe-style) |
|
|
168
|
+
|
|
169
|
+
Date-based (`api_version=2024-10-01`) lets you ship breaking changes more gracefully than `/v2`: clients pin a date, you accumulate changes, deprecate per change. Higher cognitive load to operate.
|
|
170
|
+
|
|
171
|
+
**Never** version internal microservice contracts the same way as public ones. Internal: SemVer the package, deploy in lockstep. Public: dated or `/vN`, kept stable for years.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 6. Deprecation Lifecycle (RFC 8594, RFC 9745)
|
|
176
|
+
|
|
177
|
+
```http
|
|
178
|
+
HTTP/1.1 200 OK
|
|
179
|
+
Deprecation: @1735689600
|
|
180
|
+
Sunset: Wed, 01 Jan 2026 00:00:00 GMT
|
|
181
|
+
Link: <https://docs.example.com/migration/v2>; rel="deprecation"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Stage | Action | Communicate via |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| Announce | Mark `deprecated: true` in OpenAPI; document replacement | spec, docs, CHANGELOG |
|
|
187
|
+
| Headers | Add `Deprecation` + `Sunset` headers on every response | runtime |
|
|
188
|
+
| Monitor | Track usage by client (User-Agent, API key) | observability |
|
|
189
|
+
| Sunset | Return `410 Gone` after the published date | runtime |
|
|
190
|
+
|
|
191
|
+
Rule of thumb: announce at least 6 months for paid B2B, 12 months for free public APIs.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 7. Idempotency
|
|
196
|
+
|
|
197
|
+
Required for any unsafe retry-able operation: payments, signups, write webhooks.
|
|
198
|
+
|
|
199
|
+
```http
|
|
200
|
+
POST /orders
|
|
201
|
+
Idempotency-Key: 7f9c2bd6-1f3a-4b9e-8a4d-1e2d3a4b5c6d
|
|
202
|
+
Content-Type: application/json
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Server contract:
|
|
206
|
+
1. Hash the request (method + path + body) and store it under the key for ≥ 24h
|
|
207
|
+
2. Same key + same hash → return the original response
|
|
208
|
+
3. Same key + **different** hash → `409 Conflict` (client bug)
|
|
209
|
+
4. Reject keys longer than 255 chars; require client-generated UUIDs
|
|
210
|
+
|
|
211
|
+
Document in the spec which endpoints require it (the IETF `Idempotency-Key` draft is widely adopted: Stripe, Square, PayPal).
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## 8. Security Schemes
|
|
216
|
+
|
|
217
|
+
Declare every auth method. Multiple are fine — clients pick.
|
|
218
|
+
|
|
219
|
+
```yaml
|
|
220
|
+
components:
|
|
221
|
+
securitySchemes:
|
|
222
|
+
bearerAuth:
|
|
223
|
+
type: http
|
|
224
|
+
scheme: bearer
|
|
225
|
+
bearerFormat: JWT
|
|
226
|
+
apiKey:
|
|
227
|
+
type: apiKey
|
|
228
|
+
in: header
|
|
229
|
+
name: X-API-Key
|
|
230
|
+
oauth2:
|
|
231
|
+
type: oauth2
|
|
232
|
+
flows:
|
|
233
|
+
authorizationCode:
|
|
234
|
+
authorizationUrl: https://auth.example.com/authorize
|
|
235
|
+
tokenUrl: https://auth.example.com/token
|
|
236
|
+
scopes:
|
|
237
|
+
read:orders: Read user's orders
|
|
238
|
+
write:orders: Create and update orders
|
|
239
|
+
cookieSession:
|
|
240
|
+
type: apiKey
|
|
241
|
+
in: cookie
|
|
242
|
+
name: session
|
|
243
|
+
|
|
244
|
+
security:
|
|
245
|
+
- bearerAuth: []
|
|
246
|
+
# OR per-operation:
|
|
247
|
+
# security:
|
|
248
|
+
# - oauth2: [read:orders]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
| Scheme | Use For | Caveats |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| `bearer` JWT | Service-to-service, mobile/SPA | Short expiry (≤15min), refresh flow |
|
|
254
|
+
| `apiKey` (header) | Server-to-server with rotation | Never in query string (logs leak) |
|
|
255
|
+
| `oauth2` | Third-party access on user's behalf | PKCE for public clients |
|
|
256
|
+
| `cookieSession` | First-party browser session | `HttpOnly`, `Secure`, `SameSite` |
|
|
257
|
+
|
|
258
|
+
**Never** declare `security: []` globally and forget to re-add per operation. Easier to declare globally and override `security: []` only on truly public endpoints (health, login, public docs).
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 9. Document Hygiene
|
|
263
|
+
|
|
264
|
+
### `operationId` matters
|
|
265
|
+
|
|
266
|
+
Codegen uses it as the function name. Make it stable, snake_case or camelCase, descriptive:
|
|
267
|
+
|
|
268
|
+
```yaml
|
|
269
|
+
paths:
|
|
270
|
+
/users/{id}/orders:
|
|
271
|
+
get:
|
|
272
|
+
operationId: listUserOrders # → client.listUserOrders(...)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Renaming an `operationId` is a breaking change for SDK users. Treat it like a public symbol.
|
|
276
|
+
|
|
277
|
+
### Reuse via `$ref`
|
|
278
|
+
|
|
279
|
+
Define schemas once, reference everywhere. Avoid repeating field shapes inline — codegen produces ugly anonymous types.
|
|
280
|
+
|
|
281
|
+
```yaml
|
|
282
|
+
components:
|
|
283
|
+
schemas:
|
|
284
|
+
User: { ... }
|
|
285
|
+
UserList:
|
|
286
|
+
type: object
|
|
287
|
+
properties:
|
|
288
|
+
data: { type: array, items: { $ref: '#/components/schemas/User' } }
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Examples — always
|
|
292
|
+
|
|
293
|
+
Every schema and parameter should have at least one realistic `example`. Tools like Swagger UI, Redoc, Stoplight render them; mock servers serve them. Empty examples → empty SDK fixtures → no testing.
|
|
294
|
+
|
|
295
|
+
Use **placeholder data only**. Never copy production payloads with real emails, names, IDs.
|
|
296
|
+
|
|
297
|
+
### Tags
|
|
298
|
+
|
|
299
|
+
Group by resource, not by handler file. ≤ 10 tags total — more becomes navigational soup.
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
tags:
|
|
303
|
+
- { name: Users, description: User accounts and profiles }
|
|
304
|
+
- { name: Orders, description: Order lifecycle }
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Servers
|
|
308
|
+
|
|
309
|
+
List every environment so the spec is usable as-is in tooling:
|
|
310
|
+
|
|
311
|
+
```yaml
|
|
312
|
+
servers:
|
|
313
|
+
- url: https://api.example.com/v1
|
|
314
|
+
description: Production
|
|
315
|
+
- url: https://api.staging.example.com/v1
|
|
316
|
+
description: Staging
|
|
317
|
+
- url: http://localhost:3000/v1
|
|
318
|
+
description: Local dev
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 10. Content Types & Negotiation
|
|
324
|
+
|
|
325
|
+
| Content | Type |
|
|
326
|
+
|---|---|
|
|
327
|
+
| Standard JSON request/response | `application/json` |
|
|
328
|
+
| Errors | `application/problem+json` |
|
|
329
|
+
| File upload | `multipart/form-data` |
|
|
330
|
+
| Streaming events | `text/event-stream` (SSE) or `application/x-ndjson` |
|
|
331
|
+
| Large file download | `application/octet-stream` with `Content-Disposition` |
|
|
332
|
+
| Patch | `application/merge-patch+json` (RFC 7396) or `application/json-patch+json` (RFC 6902) |
|
|
333
|
+
|
|
334
|
+
Don't invent new content types unless you genuinely need to. `application/json` covers 95% of APIs.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## 11. CORS
|
|
339
|
+
|
|
340
|
+
Document expected `Access-Control-Allow-Origin`/`Methods`/`Headers` in the OpenAPI description **and** enforce them in middleware. Mismatch between spec and reality is a top integration bug.
|
|
341
|
+
|
|
342
|
+
For browser clients: every endpoint must have CORS configured. Don't rely on a wildcard reverse-proxy default — origin allowlists must be explicit.
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## 12. What NOT to Put in OpenAPI
|
|
347
|
+
|
|
348
|
+
| Don't | Why | Where instead |
|
|
349
|
+
|---|---|---|
|
|
350
|
+
| Internal/admin endpoints | Spec is shipped to SDK consumers | Separate `internal-openapi.yaml`, not deployed |
|
|
351
|
+
| Experimental endpoints | Tagged `x-internal` is brittle; consumers see them anyway | Feature flag + private spec |
|
|
352
|
+
| Secret schemas | Auth payloads / signed tokens internals | Document only the contract, not the implementation |
|
|
353
|
+
| Real production data in examples | PII leak | Synthetic fixtures |
|
|
354
|
+
| Implementation details in `description` | Rot when you refactor | API behavior only — not the database schema |
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 13. Spec-First or Code-First?
|
|
359
|
+
|
|
360
|
+
Both are valid; pick one and be consistent.
|
|
361
|
+
|
|
362
|
+
| Approach | Pro | Con | Tooling |
|
|
363
|
+
|---|---|---|---|
|
|
364
|
+
| **Code-first** (generate spec from Zod / Pydantic / DTO) | Spec never drifts; types are the source of truth | Coupled to framework; spec PRs noisy in diff | `fastify-type-provider-zod`, `@hono/zod-openapi`, FastAPI built-in, drf-spectacular |
|
|
365
|
+
| **Spec-first** (write YAML, generate stubs) | Design conversation in PR; multiple impls | Discipline required; easy for code to drift | OpenAPI Generator, Stoplight, Redocly |
|
|
366
|
+
|
|
367
|
+
For most product APIs in 2025, code-first with Zod/Pydantic wins on velocity. Spec-first is better for: cross-team contracts, public APIs with multiple SDK targets, regulated environments.
|
|
368
|
+
|
|
369
|
+
**Mandatory for code-first:** lock the generated spec into version control and CI-diff it. A breaking change to a route should produce a spec diff in the PR — that's your review checkpoint.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## 14. Pre-Merge Checklist
|
|
374
|
+
|
|
375
|
+
- [ ] Every operation has `operationId`, `summary`, response schema, error responses (at least 400, 401/403 if auth, 404 if path param)
|
|
376
|
+
- [ ] All 4xx responses use `application/problem+json` schema
|
|
377
|
+
- [ ] Schemas defined in `components/schemas` and `$ref`'d
|
|
378
|
+
- [ ] Examples present and synthetic (no real PII)
|
|
379
|
+
- [ ] Pagination strategy documented
|
|
380
|
+
- [ ] Security scheme set (global or per-op)
|
|
381
|
+
- [ ] Spec checked into git; CI fails on uncommitted regeneration
|
|
382
|
+
- [ ] Linted with Spectral or Redocly (`oas-3.1-*` rules)
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## FORBIDDEN
|
|
387
|
+
|
|
388
|
+
| Pattern | Reason |
|
|
389
|
+
|---|---|
|
|
390
|
+
| `200 OK` with `{ "error": ... }` | Breaks every HTTP-aware tool in the chain |
|
|
391
|
+
| Plain-text error bodies on 4xx/5xx | Codegen can't model it; client retries break |
|
|
392
|
+
| Hand-written OpenAPI for a real service | Drifts within one sprint |
|
|
393
|
+
| Renaming `operationId` without major version bump | Breaks every generated SDK |
|
|
394
|
+
| Mixing pagination styles in one API | Client code has to special-case |
|
|
395
|
+
| Real PII in examples | GDPR violation, PR review surface area |
|
|
396
|
+
| Stack traces / SQL / file paths in `detail` | Information disclosure |
|
|
397
|
+
| `security: []` global + forgotten per-op | Endpoints ship publicly unauthenticated |
|
|
398
|
+
| Versioning by both URI and header | Cache fragmentation, doc confusion |
|
|
399
|
+
| Deprecating without `Sunset` header | Clients can't plan migration |
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## See Also
|
|
404
|
+
|
|
405
|
+
- `security-baseline` — auth patterns, problem+json complements `A03 Injection` defense
|
|
406
|
+
- `fastify-api` (Node) — generate this spec via `@fastify/swagger` + `fastify-type-provider-zod`
|
|
407
|
+
- `fastapi-patterns` (Python) — FastAPI generates 3.1 spec automatically; this skill says **what shape** it should have
|
|
408
|
+
- `laravel-patterns` / `api-design` (PHP) — Laravel + L5-Swagger / Scribe
|
|
409
|
+
- `database-migrations` — coordinate spec deprecation with backwards-compat schema migrations
|