clearctx 3.0.0 → 3.1.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/README.md +1 -0
- package/bin/setup.js +33 -1
- package/package.json +3 -2
- package/skills/api-design/SKILL.md +796 -0
- package/skills/devops/SKILL.md +1043 -0
- package/skills/index.json +53 -0
- package/skills/nodejs-backend/SKILL.md +853 -0
- package/skills/postgresql/SKILL.md +315 -0
- package/skills/react-frontend/SKILL.md +683 -0
- package/skills/security/SKILL.md +1000 -0
- package/skills/testing-qa/SKILL.md +842 -0
- package/skills/typescript/SKILL.md +932 -0
- package/src/mcp-server.js +126 -1
- package/src/prompts.js +47 -2
- package/src/skill-registry.js +182 -0
- package/src/stream-session.js +22 -2
- package/STRATEGY.md +0 -485
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-design
|
|
3
|
+
description: Production-grade RESTful API design covering resources, status codes, pagination, auth, versioning, and error handling
|
|
4
|
+
domain: api
|
|
5
|
+
keywords: [api, rest, restful, http, endpoints, status-codes, pagination, authentication, openapi]
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# API Design — Expertise Guide
|
|
10
|
+
|
|
11
|
+
## Worker Context
|
|
12
|
+
|
|
13
|
+
You are an **API design specialist** for REST APIs. Your role: design clean, consistent, production-grade HTTP endpoints that follow REST principles and modern API best practices.
|
|
14
|
+
|
|
15
|
+
### Resource Naming
|
|
16
|
+
|
|
17
|
+
**CRITICAL:** Use plural nouns for resource URLs. NEVER use verbs.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
GOOD:
|
|
21
|
+
GET /users
|
|
22
|
+
POST /users
|
|
23
|
+
GET /users/123
|
|
24
|
+
PUT /users/123
|
|
25
|
+
DELETE /users/123
|
|
26
|
+
|
|
27
|
+
BAD:
|
|
28
|
+
/getUsers ❌ verb in URL
|
|
29
|
+
/user ❌ singular form
|
|
30
|
+
/createNewUser ❌ verb + inconsistent naming
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Nested resources** (max 2 levels deep):
|
|
34
|
+
```
|
|
35
|
+
GET /users/123/orders ✅ user's orders
|
|
36
|
+
GET /users/123/orders/456 ✅ specific order
|
|
37
|
+
GET /orders?user_id=123 ✅ alternative, preferred for filtering
|
|
38
|
+
GET /users/123/orders/456/items ❌ too deep (use /order-items/789)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Multi-word resources:** lowercase with hyphens
|
|
42
|
+
```
|
|
43
|
+
/order-items ✅
|
|
44
|
+
/orderItems ❌
|
|
45
|
+
/order_items ❌
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### HTTP Methods
|
|
49
|
+
|
|
50
|
+
| Method | Purpose | Idempotent | Request Body | Success Code | Use When |
|
|
51
|
+
|--------|---------|------------|--------------|--------------|----------|
|
|
52
|
+
| GET | Read resource(s) | Yes | No | 200 | Fetching data, no side effects |
|
|
53
|
+
| POST | Create new resource | No | Yes | 201 | Creating new records |
|
|
54
|
+
| PUT | Full replacement | Yes | Yes | 200 | Replacing entire resource |
|
|
55
|
+
| PATCH | Partial update | No* | Yes | 200 | Updating specific fields |
|
|
56
|
+
| DELETE | Remove resource | Yes | No | 204 | Deleting records |
|
|
57
|
+
|
|
58
|
+
*PATCH idempotency depends on implementation — prefer making it idempotent.
|
|
59
|
+
|
|
60
|
+
**IMPORTANT:** POST creates, PUT replaces entirely (all fields required), PATCH updates partially (only changed fields).
|
|
61
|
+
|
|
62
|
+
### Status Codes — Exact Usage
|
|
63
|
+
|
|
64
|
+
**CRITICAL:** Use the correct status code for each scenario. Status codes are semantic contracts.
|
|
65
|
+
|
|
66
|
+
| Code | Meaning | When to Use | Headers/Body |
|
|
67
|
+
|------|---------|-------------|--------------|
|
|
68
|
+
| 200 | OK | Successful GET, PUT, PATCH | Response body with data |
|
|
69
|
+
| 201 | Created | Successful POST | `Location: /resource/123` header + created resource in body |
|
|
70
|
+
| 204 | No Content | Successful DELETE | Empty body |
|
|
71
|
+
| 400 | Bad Request | Malformed JSON, invalid syntax | Error details |
|
|
72
|
+
| 401 | Unauthorized | Missing or invalid auth token | `WWW-Authenticate` header |
|
|
73
|
+
| 403 | Forbidden | Valid auth but insufficient permissions | Error details |
|
|
74
|
+
| 404 | Not Found | Resource doesn't exist | Error details |
|
|
75
|
+
| 409 | Conflict | Duplicate resource (unique constraint) | Error details with conflict info |
|
|
76
|
+
| 422 | Unprocessable Entity | Valid syntax but semantic errors (validation) | Field-level error details |
|
|
77
|
+
| 429 | Too Many Requests | Rate limit exceeded | `Retry-After` header (seconds) |
|
|
78
|
+
| 500 | Internal Server Error | Unhandled server error | Generic error, NEVER expose internals |
|
|
79
|
+
|
|
80
|
+
**Decision tree for validation errors:**
|
|
81
|
+
```
|
|
82
|
+
Is the JSON malformed? → 400 Bad Request
|
|
83
|
+
Is a required field missing? → 422 Unprocessable Entity
|
|
84
|
+
Is an email format invalid? → 422 Unprocessable Entity
|
|
85
|
+
Does a username already exist? → 409 Conflict
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Response Envelope
|
|
89
|
+
|
|
90
|
+
**CRITICAL:** Use consistent response structure across ALL endpoints.
|
|
91
|
+
|
|
92
|
+
**Success responses:**
|
|
93
|
+
```json
|
|
94
|
+
// Single resource
|
|
95
|
+
{
|
|
96
|
+
"data": {
|
|
97
|
+
"id": 123,
|
|
98
|
+
"name": "John Doe",
|
|
99
|
+
"email": "john@example.com"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// List (with pagination metadata)
|
|
104
|
+
{
|
|
105
|
+
"data": [
|
|
106
|
+
{ "id": 1, "name": "Item 1" },
|
|
107
|
+
{ "id": 2, "name": "Item 2" }
|
|
108
|
+
],
|
|
109
|
+
"meta": {
|
|
110
|
+
"page": 1,
|
|
111
|
+
"limit": 20,
|
|
112
|
+
"total": 150,
|
|
113
|
+
"hasNext": true,
|
|
114
|
+
"nextCursor": "eyJpZCI6MjB9"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Error responses:**
|
|
120
|
+
```json
|
|
121
|
+
// Simple error
|
|
122
|
+
{
|
|
123
|
+
"error": {
|
|
124
|
+
"code": "RESOURCE_NOT_FOUND",
|
|
125
|
+
"message": "User with ID 123 not found"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Validation errors (422)
|
|
130
|
+
{
|
|
131
|
+
"error": {
|
|
132
|
+
"code": "VALIDATION_ERROR",
|
|
133
|
+
"message": "Request validation failed",
|
|
134
|
+
"details": [
|
|
135
|
+
{
|
|
136
|
+
"field": "email",
|
|
137
|
+
"message": "Invalid email format",
|
|
138
|
+
"value": "not-an-email"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"field": "age",
|
|
142
|
+
"message": "Must be at least 18",
|
|
143
|
+
"value": 15
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Error code naming:** `UPPER_SNAKE_CASE`, machine-readable, consistent across API.
|
|
151
|
+
|
|
152
|
+
**NEVER expose in error responses:**
|
|
153
|
+
- Stack traces
|
|
154
|
+
- SQL queries or database errors
|
|
155
|
+
- Internal file paths
|
|
156
|
+
- Sensitive configuration values
|
|
157
|
+
- Third-party service details
|
|
158
|
+
|
|
159
|
+
### Pagination
|
|
160
|
+
|
|
161
|
+
**Prefer cursor-based pagination** (consistent results, no skip-scan overhead):
|
|
162
|
+
```
|
|
163
|
+
GET /users?cursor=eyJpZCI6MjB9&limit=20
|
|
164
|
+
|
|
165
|
+
Response:
|
|
166
|
+
{
|
|
167
|
+
"data": [...],
|
|
168
|
+
"meta": {
|
|
169
|
+
"limit": 20,
|
|
170
|
+
"hasNext": true,
|
|
171
|
+
"nextCursor": "eyJpZCI6NDB9",
|
|
172
|
+
"prevCursor": "eyJpZCI6MH0"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Offset-based acceptable for small datasets** (< 10,000 records):
|
|
178
|
+
```
|
|
179
|
+
GET /users?page=1&limit=20
|
|
180
|
+
|
|
181
|
+
Response:
|
|
182
|
+
{
|
|
183
|
+
"data": [...],
|
|
184
|
+
"meta": {
|
|
185
|
+
"page": 1,
|
|
186
|
+
"limit": 20,
|
|
187
|
+
"total": 150,
|
|
188
|
+
"totalPages": 8,
|
|
189
|
+
"hasNext": true
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**IMPORTANT:** Always include `hasNext` boolean. NEVER require clients to calculate page counts.
|
|
195
|
+
|
|
196
|
+
**Default limit:** 20 items. **Max limit:** 100 items. Reject requests exceeding max with 400.
|
|
197
|
+
|
|
198
|
+
### Filtering, Sorting, Field Selection
|
|
199
|
+
|
|
200
|
+
**Query parameters for GET requests** (NEVER use request body for GET):
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
// Filtering
|
|
204
|
+
GET /users?status=active&role=admin&created_after=2024-01-01
|
|
205
|
+
|
|
206
|
+
// Sorting (prefix - for descending)
|
|
207
|
+
GET /users?sort=-created_at,name
|
|
208
|
+
// Sorts by created_at DESC, then name ASC
|
|
209
|
+
|
|
210
|
+
// Field selection (reduce payload size)
|
|
211
|
+
GET /users?fields=id,name,email
|
|
212
|
+
// Returns only specified fields
|
|
213
|
+
|
|
214
|
+
// Combined
|
|
215
|
+
GET /users?status=active&sort=-created_at&fields=id,name&limit=50
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Filtering conventions:**
|
|
219
|
+
- Exact match: `?status=active`
|
|
220
|
+
- Range: `?created_after=2024-01-01&created_before=2024-12-31`
|
|
221
|
+
- IN clause: `?status=active,pending` (comma-separated)
|
|
222
|
+
- Search: `?q=search+term` (full-text search)
|
|
223
|
+
|
|
224
|
+
### Versioning
|
|
225
|
+
|
|
226
|
+
**CRITICAL:** Version your API from day one. Breaking changes require a new version.
|
|
227
|
+
|
|
228
|
+
**Preferred: URL path versioning**
|
|
229
|
+
```
|
|
230
|
+
/api/v1/users
|
|
231
|
+
/api/v2/users
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Alternative: Header versioning** (cleaner URLs, harder to debug):
|
|
235
|
+
```
|
|
236
|
+
GET /api/users
|
|
237
|
+
Accept: application/vnd.myapi.v1+json
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Breaking changes** (require new major version):
|
|
241
|
+
- Removing fields from responses
|
|
242
|
+
- Changing field types (string → number)
|
|
243
|
+
- Removing endpoints
|
|
244
|
+
- Changing required fields
|
|
245
|
+
- Renaming fields
|
|
246
|
+
|
|
247
|
+
**Non-breaking changes** (same version):
|
|
248
|
+
- Adding new fields to responses
|
|
249
|
+
- Adding new endpoints
|
|
250
|
+
- Adding optional parameters
|
|
251
|
+
- Adding new enum values (with defaults)
|
|
252
|
+
|
|
253
|
+
**NEVER:** Change behavior of existing endpoints in-place. Always increment version.
|
|
254
|
+
|
|
255
|
+
### Authentication & Authorization
|
|
256
|
+
|
|
257
|
+
**Bearer token auth** (standard for modern APIs):
|
|
258
|
+
```
|
|
259
|
+
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Response codes:**
|
|
263
|
+
- Missing token → 401 Unauthorized + `WWW-Authenticate: Bearer realm="api"`
|
|
264
|
+
- Expired token → 401 Unauthorized
|
|
265
|
+
- Valid token, insufficient permissions → 403 Forbidden
|
|
266
|
+
|
|
267
|
+
**Token refresh flow:**
|
|
268
|
+
```
|
|
269
|
+
POST /api/v1/auth/refresh
|
|
270
|
+
Content-Type: application/json
|
|
271
|
+
|
|
272
|
+
{
|
|
273
|
+
"refresh_token": "abc123..."
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
Response 200:
|
|
277
|
+
{
|
|
278
|
+
"data": {
|
|
279
|
+
"access_token": "new_token...",
|
|
280
|
+
"expires_in": 3600,
|
|
281
|
+
"refresh_token": "new_refresh..."
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**API keys for service-to-service:**
|
|
287
|
+
```
|
|
288
|
+
X-API-Key: sk_live_abc123...
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Rate Limiting
|
|
292
|
+
|
|
293
|
+
**IMPORTANT:** Include rate limit headers on ALL responses (even successful ones):
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
X-RateLimit-Limit: 1000 // Max requests per window
|
|
297
|
+
X-RateLimit-Remaining: 847 // Requests left in current window
|
|
298
|
+
X-RateLimit-Reset: 1640000000 // Unix timestamp when limit resets
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**When limit exceeded (429):**
|
|
302
|
+
```
|
|
303
|
+
HTTP/1.1 429 Too Many Requests
|
|
304
|
+
Retry-After: 60
|
|
305
|
+
X-RateLimit-Limit: 1000
|
|
306
|
+
X-RateLimit-Remaining: 0
|
|
307
|
+
X-RateLimit-Reset: 1640000060
|
|
308
|
+
|
|
309
|
+
{
|
|
310
|
+
"error": {
|
|
311
|
+
"code": "RATE_LIMIT_EXCEEDED",
|
|
312
|
+
"message": "Rate limit exceeded. Retry after 60 seconds."
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### HATEOAS (Hypermedia Links)
|
|
318
|
+
|
|
319
|
+
**Optional but valuable** for API discoverability:
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"data": {
|
|
324
|
+
"id": 123,
|
|
325
|
+
"name": "John Doe",
|
|
326
|
+
"status": "active"
|
|
327
|
+
},
|
|
328
|
+
"_links": {
|
|
329
|
+
"self": "/api/v1/users/123",
|
|
330
|
+
"orders": "/api/v1/users/123/orders",
|
|
331
|
+
"update": {
|
|
332
|
+
"href": "/api/v1/users/123",
|
|
333
|
+
"method": "PUT"
|
|
334
|
+
},
|
|
335
|
+
"deactivate": {
|
|
336
|
+
"href": "/api/v1/users/123/deactivate",
|
|
337
|
+
"method": "POST"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Use `_links` prefix to avoid collision with data fields.
|
|
344
|
+
|
|
345
|
+
### Idempotency
|
|
346
|
+
|
|
347
|
+
**CRITICAL for payment/financial APIs:** Support idempotency keys for POST requests.
|
|
348
|
+
|
|
349
|
+
```
|
|
350
|
+
POST /api/v1/orders
|
|
351
|
+
Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890
|
|
352
|
+
Content-Type: application/json
|
|
353
|
+
|
|
354
|
+
{ "amount": 100, "currency": "USD" }
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Server behavior:**
|
|
358
|
+
1. First request with key → process normally, cache response
|
|
359
|
+
2. Duplicate request with same key → return cached response (201)
|
|
360
|
+
3. Key expires after 24 hours
|
|
361
|
+
|
|
362
|
+
### Request/Response Examples
|
|
363
|
+
|
|
364
|
+
**Complete POST example:**
|
|
365
|
+
```http
|
|
366
|
+
POST /api/v1/users HTTP/1.1
|
|
367
|
+
Host: api.example.com
|
|
368
|
+
Authorization: Bearer eyJhbGc...
|
|
369
|
+
Content-Type: application/json
|
|
370
|
+
|
|
371
|
+
{
|
|
372
|
+
"name": "Jane Doe",
|
|
373
|
+
"email": "jane@example.com",
|
|
374
|
+
"role": "editor"
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
HTTP/1.1 201 Created
|
|
378
|
+
Location: /api/v1/users/456
|
|
379
|
+
Content-Type: application/json
|
|
380
|
+
X-RateLimit-Limit: 1000
|
|
381
|
+
X-RateLimit-Remaining: 999
|
|
382
|
+
|
|
383
|
+
{
|
|
384
|
+
"data": {
|
|
385
|
+
"id": 456,
|
|
386
|
+
"name": "Jane Doe",
|
|
387
|
+
"email": "jane@example.com",
|
|
388
|
+
"role": "editor",
|
|
389
|
+
"created_at": "2024-01-15T10:30:00Z"
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Complete validation error (422):**
|
|
395
|
+
```http
|
|
396
|
+
POST /api/v1/users HTTP/1.1
|
|
397
|
+
Content-Type: application/json
|
|
398
|
+
|
|
399
|
+
{
|
|
400
|
+
"name": "",
|
|
401
|
+
"email": "invalid-email"
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
HTTP/1.1 422 Unprocessable Entity
|
|
405
|
+
Content-Type: application/json
|
|
406
|
+
|
|
407
|
+
{
|
|
408
|
+
"error": {
|
|
409
|
+
"code": "VALIDATION_ERROR",
|
|
410
|
+
"message": "Request validation failed",
|
|
411
|
+
"details": [
|
|
412
|
+
{
|
|
413
|
+
"field": "name",
|
|
414
|
+
"message": "Name is required and cannot be empty"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"field": "email",
|
|
418
|
+
"message": "Email must be a valid email address",
|
|
419
|
+
"value": "invalid-email"
|
|
420
|
+
}
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Conventions
|
|
427
|
+
|
|
428
|
+
**URL structure:**
|
|
429
|
+
- Base: `/api/v{version}/{resource}`
|
|
430
|
+
- Lowercase with hyphens for multi-word: `/order-items`
|
|
431
|
+
- Plural nouns: `/users`, `/products`
|
|
432
|
+
- Max 2 nesting levels: `/users/123/orders`
|
|
433
|
+
|
|
434
|
+
**Response format:**
|
|
435
|
+
- Success: `{ "data": <result> }` or `{ "data": <result>, "meta": {...} }`
|
|
436
|
+
- Error: `{ "error": { "code": "ERROR_CODE", "message": "...", "details": [...] } }`
|
|
437
|
+
|
|
438
|
+
**Status code selection:**
|
|
439
|
+
- Read success → 200
|
|
440
|
+
- Create success → 201 + Location header
|
|
441
|
+
- Delete success → 204
|
|
442
|
+
- Validation error → 422 (NOT 400)
|
|
443
|
+
- Duplicate resource → 409
|
|
444
|
+
- Auth missing/invalid → 401
|
|
445
|
+
- Insufficient permissions → 403
|
|
446
|
+
|
|
447
|
+
**Date/time format:** ISO 8601 with UTC timezone: `2024-01-15T10:30:00Z`
|
|
448
|
+
|
|
449
|
+
**Boolean values:** `true`/`false` (JSON boolean, not strings)
|
|
450
|
+
|
|
451
|
+
**Null handling:** Use `null` for missing optional fields. Omit field entirely if appropriate.
|
|
452
|
+
|
|
453
|
+
**Field naming:** `snake_case` in URLs/query params, match your shared conventions for JSON (typically camelCase for JS, snake_case for Python/Ruby).
|
|
454
|
+
|
|
455
|
+
## Common Patterns
|
|
456
|
+
|
|
457
|
+
### Pattern 1: Bulk Operations
|
|
458
|
+
|
|
459
|
+
**Use case:** Create/update/delete multiple resources in one request.
|
|
460
|
+
|
|
461
|
+
```http
|
|
462
|
+
POST /api/v1/users/bulk
|
|
463
|
+
{
|
|
464
|
+
"operations": [
|
|
465
|
+
{ "action": "create", "data": { "name": "User 1", "email": "u1@example.com" } },
|
|
466
|
+
{ "action": "create", "data": { "name": "User 2", "email": "u2@example.com" } }
|
|
467
|
+
]
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
Response 200:
|
|
471
|
+
{
|
|
472
|
+
"data": {
|
|
473
|
+
"results": [
|
|
474
|
+
{ "index": 0, "status": "success", "id": 789 },
|
|
475
|
+
{ "index": 1, "status": "error", "error": { "code": "DUPLICATE_EMAIL", "message": "..." } }
|
|
476
|
+
],
|
|
477
|
+
"summary": { "total": 2, "succeeded": 1, "failed": 1 }
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**IMPORTANT:** Return partial success. Don't fail entire batch if one item fails.
|
|
483
|
+
|
|
484
|
+
### Pattern 2: Async Operations
|
|
485
|
+
|
|
486
|
+
**Use case:** Long-running operations (report generation, data export, batch processing).
|
|
487
|
+
|
|
488
|
+
```http
|
|
489
|
+
POST /api/v1/reports
|
|
490
|
+
{ "type": "sales", "start_date": "2024-01-01", "end_date": "2024-01-31" }
|
|
491
|
+
|
|
492
|
+
Response 202 Accepted:
|
|
493
|
+
{
|
|
494
|
+
"data": {
|
|
495
|
+
"job_id": "job_abc123",
|
|
496
|
+
"status": "pending",
|
|
497
|
+
"created_at": "2024-01-15T10:30:00Z"
|
|
498
|
+
},
|
|
499
|
+
"_links": {
|
|
500
|
+
"status": "/api/v1/jobs/job_abc123"
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Check status
|
|
505
|
+
GET /api/v1/jobs/job_abc123
|
|
506
|
+
|
|
507
|
+
Response 200 (in progress):
|
|
508
|
+
{
|
|
509
|
+
"data": {
|
|
510
|
+
"job_id": "job_abc123",
|
|
511
|
+
"status": "processing",
|
|
512
|
+
"progress": 45,
|
|
513
|
+
"created_at": "2024-01-15T10:30:00Z"
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
Response 200 (completed):
|
|
518
|
+
{
|
|
519
|
+
"data": {
|
|
520
|
+
"job_id": "job_abc123",
|
|
521
|
+
"status": "completed",
|
|
522
|
+
"result": { "download_url": "/api/v1/reports/abc123.pdf" },
|
|
523
|
+
"created_at": "2024-01-15T10:30:00Z",
|
|
524
|
+
"completed_at": "2024-01-15T10:32:15Z"
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
**Status values:** `pending`, `processing`, `completed`, `failed`
|
|
530
|
+
|
|
531
|
+
### Pattern 3: Soft Delete with Trash
|
|
532
|
+
|
|
533
|
+
**Use case:** Allow recovery of deleted resources.
|
|
534
|
+
|
|
535
|
+
```http
|
|
536
|
+
// Soft delete (move to trash)
|
|
537
|
+
DELETE /api/v1/users/123
|
|
538
|
+
Response 204 No Content
|
|
539
|
+
|
|
540
|
+
// List trashed items
|
|
541
|
+
GET /api/v1/users/trash
|
|
542
|
+
Response 200:
|
|
543
|
+
{
|
|
544
|
+
"data": [
|
|
545
|
+
{ "id": 123, "name": "John Doe", "deleted_at": "2024-01-15T10:30:00Z" }
|
|
546
|
+
]
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Restore
|
|
550
|
+
POST /api/v1/users/123/restore
|
|
551
|
+
Response 200:
|
|
552
|
+
{
|
|
553
|
+
"data": { "id": 123, "name": "John Doe", "deleted_at": null }
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Permanent delete
|
|
557
|
+
DELETE /api/v1/users/123/permanent
|
|
558
|
+
Response 204 No Content
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Pattern 4: Search Endpoint
|
|
562
|
+
|
|
563
|
+
**Use case:** Complex search across multiple fields.
|
|
564
|
+
|
|
565
|
+
```http
|
|
566
|
+
POST /api/v1/search/users
|
|
567
|
+
{
|
|
568
|
+
"query": "john",
|
|
569
|
+
"filters": {
|
|
570
|
+
"status": ["active", "pending"],
|
|
571
|
+
"created_after": "2024-01-01"
|
|
572
|
+
},
|
|
573
|
+
"sort": ["-created_at", "name"],
|
|
574
|
+
"limit": 20
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
Response 200:
|
|
578
|
+
{
|
|
579
|
+
"data": [...],
|
|
580
|
+
"meta": {
|
|
581
|
+
"total": 47,
|
|
582
|
+
"limit": 20,
|
|
583
|
+
"hasNext": true,
|
|
584
|
+
"query": "john"
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Why POST for search?** Complex filter objects don't fit cleanly in query strings. POST /search is acceptable when GET with query params becomes unwieldy.
|
|
590
|
+
|
|
591
|
+
### Pattern 5: Health & Status Endpoints
|
|
592
|
+
|
|
593
|
+
**Use case:** Monitoring, load balancer health checks.
|
|
594
|
+
|
|
595
|
+
```http
|
|
596
|
+
GET /health
|
|
597
|
+
Response 200:
|
|
598
|
+
{
|
|
599
|
+
"status": "healthy",
|
|
600
|
+
"version": "1.2.3",
|
|
601
|
+
"timestamp": "2024-01-15T10:30:00Z"
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
GET /health/detailed
|
|
605
|
+
Response 200:
|
|
606
|
+
{
|
|
607
|
+
"status": "healthy",
|
|
608
|
+
"version": "1.2.3",
|
|
609
|
+
"checks": {
|
|
610
|
+
"database": { "status": "healthy", "latency_ms": 12 },
|
|
611
|
+
"redis": { "status": "healthy", "latency_ms": 3 },
|
|
612
|
+
"external_api": { "status": "degraded", "latency_ms": 2500 }
|
|
613
|
+
},
|
|
614
|
+
"timestamp": "2024-01-15T10:30:00Z"
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
**IMPORTANT:** `/health` should be unauthenticated for load balancer access.
|
|
619
|
+
|
|
620
|
+
## Anti-Patterns
|
|
621
|
+
|
|
622
|
+
### Anti-Pattern 1: Verbs in Resource URLs ❌
|
|
623
|
+
|
|
624
|
+
```
|
|
625
|
+
BAD:
|
|
626
|
+
POST /api/v1/createUser
|
|
627
|
+
GET /api/v1/getUser/123
|
|
628
|
+
POST /api/v1/updateUser/123
|
|
629
|
+
DELETE /api/v1/deleteUser/123
|
|
630
|
+
|
|
631
|
+
GOOD:
|
|
632
|
+
POST /api/v1/users
|
|
633
|
+
GET /api/v1/users/123
|
|
634
|
+
PUT /api/v1/users/123
|
|
635
|
+
DELETE /api/v1/users/123
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**Why it's bad:** Violates REST principles. HTTP method already expresses the action.
|
|
639
|
+
|
|
640
|
+
### Anti-Pattern 2: Inconsistent Plural/Singular Mixing ❌
|
|
641
|
+
|
|
642
|
+
```
|
|
643
|
+
BAD:
|
|
644
|
+
GET /api/v1/user/123 ❌ singular
|
|
645
|
+
GET /api/v1/products ✅ plural
|
|
646
|
+
GET /api/v1/order/456 ❌ singular
|
|
647
|
+
GET /api/v1/customers ✅ plural
|
|
648
|
+
|
|
649
|
+
GOOD:
|
|
650
|
+
GET /api/v1/users/123 ✅ all plural
|
|
651
|
+
GET /api/v1/products ✅
|
|
652
|
+
GET /api/v1/orders/456 ✅
|
|
653
|
+
GET /api/v1/customers ✅
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**Why it's bad:** Forces clients to memorize which resources are plural vs singular. Cognitive overhead.
|
|
657
|
+
|
|
658
|
+
### Anti-Pattern 3: Using 200 for All Responses ❌
|
|
659
|
+
|
|
660
|
+
```
|
|
661
|
+
BAD:
|
|
662
|
+
POST /api/v1/users
|
|
663
|
+
Response 200:
|
|
664
|
+
{
|
|
665
|
+
"status": "success",
|
|
666
|
+
"data": { "id": 123, ... }
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
DELETE /api/v1/users/123
|
|
670
|
+
Response 200:
|
|
671
|
+
{
|
|
672
|
+
"status": "success",
|
|
673
|
+
"message": "User deleted"
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
GET /api/v1/users/999
|
|
677
|
+
Response 200:
|
|
678
|
+
{
|
|
679
|
+
"status": "error",
|
|
680
|
+
"message": "User not found"
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
GOOD:
|
|
684
|
+
POST /api/v1/users → 201 Created
|
|
685
|
+
DELETE /api/v1/users/123 → 204 No Content
|
|
686
|
+
GET /api/v1/users/999 → 404 Not Found
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
**Why it's bad:** HTTP status codes ARE the status. Duplicating them in the body is redundant and breaks HTTP semantics. Clients can't rely on status codes.
|
|
690
|
+
|
|
691
|
+
### Anti-Pattern 4: No Pagination on List Endpoints ❌
|
|
692
|
+
|
|
693
|
+
```
|
|
694
|
+
BAD:
|
|
695
|
+
GET /api/v1/users
|
|
696
|
+
// Returns ALL users (10,000+ records)
|
|
697
|
+
|
|
698
|
+
GOOD:
|
|
699
|
+
GET /api/v1/users?limit=20
|
|
700
|
+
// Returns paginated results with meta.hasNext
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
**Why it's bad:** Kills performance, timeouts, memory issues. ALWAYS paginate list endpoints.
|
|
704
|
+
|
|
705
|
+
### Anti-Pattern 5: Exposing Internal Implementation in Errors ❌
|
|
706
|
+
|
|
707
|
+
```
|
|
708
|
+
BAD:
|
|
709
|
+
Response 500:
|
|
710
|
+
{
|
|
711
|
+
"error": {
|
|
712
|
+
"message": "SQLException: Duplicate entry 'john@example.com' for key 'users.email'",
|
|
713
|
+
"stack": "at Database.query (/var/www/app/db.js:45:12)..."
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
GOOD:
|
|
718
|
+
Response 409:
|
|
719
|
+
{
|
|
720
|
+
"error": {
|
|
721
|
+
"code": "DUPLICATE_EMAIL",
|
|
722
|
+
"message": "A user with this email already exists"
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**Why it's bad:** Security risk (exposes database structure, file paths). Ugly for consumers. Use semantic error codes.
|
|
728
|
+
|
|
729
|
+
## Integration Notes
|
|
730
|
+
|
|
731
|
+
**Multi-session orchestration context:**
|
|
732
|
+
|
|
733
|
+
1. **Check inbox before starting:**
|
|
734
|
+
```javascript
|
|
735
|
+
// Always first action
|
|
736
|
+
await mcp.team_check_inbox({ name: "api-dev" })
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
2. **Read shared conventions:**
|
|
740
|
+
```javascript
|
|
741
|
+
const conventions = await mcp.artifact_get({
|
|
742
|
+
artifactId: "shared-conventions",
|
|
743
|
+
reader: "api-dev"
|
|
744
|
+
})
|
|
745
|
+
// Use conventions.data.responseFormat, .statusCodes, .enumValues, etc.
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
3. **Coordinate with DB worker:**
|
|
749
|
+
- Resource names must match table names (plural form)
|
|
750
|
+
- Use exact enum values from DB schema
|
|
751
|
+
- Align on `snake_case` vs `camelCase` for JSON fields
|
|
752
|
+
- Ask DB worker for foreign key constraints before designing nested resources
|
|
753
|
+
|
|
754
|
+
4. **Publish API contract artifact:**
|
|
755
|
+
```javascript
|
|
756
|
+
await mcp.artifact_publish({
|
|
757
|
+
artifactId: "api-contract",
|
|
758
|
+
type: "api-contract",
|
|
759
|
+
name: "REST API Contract v1",
|
|
760
|
+
publisher: "api-dev",
|
|
761
|
+
data: {
|
|
762
|
+
version: "v1",
|
|
763
|
+
baseUrl: "/api/v1",
|
|
764
|
+
endpoints: [
|
|
765
|
+
{
|
|
766
|
+
path: "/users",
|
|
767
|
+
methods: ["GET", "POST"],
|
|
768
|
+
auth: "required",
|
|
769
|
+
request: { /* schema */ },
|
|
770
|
+
response: { /* schema */ }
|
|
771
|
+
}
|
|
772
|
+
// ... all endpoints
|
|
773
|
+
],
|
|
774
|
+
statusCodes: { /* map of codes to meanings */ },
|
|
775
|
+
errorCodes: ["VALIDATION_ERROR", "DUPLICATE_EMAIL", ...]
|
|
776
|
+
}
|
|
777
|
+
})
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
5. **Broadcast completion:**
|
|
781
|
+
```javascript
|
|
782
|
+
await mcp.team_broadcast({
|
|
783
|
+
from: "api-dev",
|
|
784
|
+
content: "API contract published with 12 endpoints. All workers: read artifact 'api-contract' for request/response schemas and status codes."
|
|
785
|
+
})
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
6. **File paths:** Use relative paths only (`src/routes/users.js`, NOT `/home/user/project/src/routes/users.js`)
|
|
789
|
+
|
|
790
|
+
7. **Consumed by:** Frontend workers (React, mobile apps) use this artifact to generate API client code and TypeScript types. Backend workers implement the contract.
|
|
791
|
+
|
|
792
|
+
8. **Team communication:**
|
|
793
|
+
- If DB schema is unclear → `team_ask({ to: "db-dev", question: "What's the exact enum for user.status?" })`
|
|
794
|
+
- If frontend needs custom endpoint → they message you, you update contract, republish artifact
|
|
795
|
+
|
|
796
|
+
**Work autonomously:** Use shared conventions + DB schema. Only ask teammates when you hit genuine ambiguity (conflicting requirements, missing specs).
|