Create API Design
Overview
Design a complete, production-ready API from a feature specification or resource description. The output is a comprehensive API design document covering endpoints, schemas, error contracts, pagination, auth, rate limiting, and caching -- consistent with existing API patterns in the project.
Workflow
Read existing API patterns -- Read .chalk/docs/engineering/ for:
- Existing API design documents (match naming, URL structure, and conventions)
- Architecture docs describing the current API layer
- Auth patterns and middleware
- Error handling conventions
- If no docs exist, scan the codebase for route definitions to infer patterns
Scan codebase for current conventions -- Use Grep to find:
- Route definitions (e.g.,
router.get, app.post, @GetMapping, @api_view)
- Error response shapes (look for error middleware, error classes)
- Pagination patterns (cursor vs. offset, parameter names)
- Auth middleware usage (JWT, API key, OAuth scopes)
- Response envelope patterns (do responses wrap in
{ data, meta } or return raw?)
- Store these conventions; the new API must follow them exactly
Determine the next document number -- List files in .chalk/docs/engineering/ matching *_api_design_*.md. Find the highest number and increment by 1.
Clarify the resource and operations -- From $ARGUMENTS and conversation context, identify:
- The resource(s) being designed (nouns, not verbs)
- The operations needed (CRUD, plus any domain-specific actions)
- Who consumes this API (frontend, mobile, third-party, internal service)
- Auth requirements (public, authenticated, role-based, scope-based)
- Ask the user for clarification if the resource boundaries are unclear
Design the endpoints -- Follow REST conventions strictly:
- Use plural nouns for resource paths (
/users, not /user)
- Use nested resources for ownership (
/users/{id}/posts, not /user-posts)
- Limit nesting to 2 levels maximum
- Use HTTP methods correctly (GET reads, POST creates, PUT replaces, PATCH updates, DELETE removes)
- Use query parameters for filtering, sorting, and pagination on collection endpoints
- Use path parameters only for resource identifiers
Define schemas -- For each endpoint, define:
- Request body JSON schema (for POST/PUT/PATCH)
- Response body JSON schema (for all methods)
- Query parameter schema (for GET collection endpoints)
- Use consistent field naming (camelCase or snake_case -- match existing convention)
Define error contract -- Design a consistent error response shape used across all endpoints. Include validation errors, business logic errors, and system errors.
Write the document -- Save to .chalk/docs/engineering/<n>_api_design_<resource_slug>.md.
Confirm -- Tell the user the API design was created with its path and a summary of the endpoints defined.
Filename Convention
<number>_api_design_<snake_case_resource>.md
Examples:
4_api_design_user_profiles.md
8_api_design_billing_subscriptions.md
11_api_design_notification_preferences.md
API Design Document Format
# API Design: <Resource Name>
Last updated: <YYYY-MM-DD>
## Overview
<1-2 sentences describing what this API enables and who consumes it.>
## Base URL
<e.g., `/api/v1` -- match existing project convention>
## Authentication
<Describe auth requirements. Reference existing auth middleware if applicable.>
| Endpoint Pattern | Auth Required | Scopes / Roles |
|-----------------|---------------|----------------|
| `GET /resources` | Yes | `read:resources` |
| `POST /resources` | Yes | `write:resources` |
| `GET /resources/public` | No | — |
## Endpoints
### Resource Collection
#### List Resources
`GET /resources`
**Query Parameters:**
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `page` | integer | No | 1 | Page number (if offset pagination) |
| `limit` | integer | No | 20 | Items per page (max 100) |
| `sort` | string | No | `created_at` | Sort field |
| `order` | string | No | `desc` | Sort direction: `asc` or `desc` |
| `filter[status]` | string | No | — | Filter by status |
**Response: `200 OK`**
```json
{
"data": [
{
"id": "res_abc123",
"type": "resource",
"attributes": {}
}
],
"meta": {
"total": 142,
"page": 1,
"limit": 20,
"total_pages": 8
}
}
Create Resource
POST /resources
Request Body:
{
"name": "string (required, 1-255 chars)",
"description": "string (optional, max 2000 chars)",
"status": "string (optional, enum: draft|active|archived, default: draft)"
}
Response: 201 Created
{
"data": {
"id": "res_abc123",
"type": "resource",
"attributes": {
"name": "Example",
"description": null,
"status": "draft",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
}
}
Individual Resource
Get Resource
GET /resources/{id}
Update Resource
PATCH /resources/{id}
Delete Resource
DELETE /resources/{id}
Request / Response Schemas
Resource Schema
| Field |
Type |
Constraints |
Description |
id |
string |
Read-only, prefixed |
Unique identifier |
name |
string |
Required, 1-255 chars |
Display name |
created_at |
ISO 8601 |
Read-only |
Creation timestamp |
updated_at |
ISO 8601 |
Read-only |
Last modification timestamp |
Error Contract
All errors follow a consistent shape:
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "The requested resource does not exist.",
"status": 404,
"details": []
}
}
Validation Errors (422)
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"status": 422,
"details": [
{
"field": "name",
"constraint": "required",
"message": "Name is required."
},
{
"field": "email",
"constraint": "format",
"message": "Email must be a valid email address."
}
]
}
}
Error Codes
| HTTP Status |
Error Code |
When Used |
| 400 |
BAD_REQUEST |
Malformed request syntax |
| 401 |
UNAUTHORIZED |
Missing or invalid auth token |
| 403 |
FORBIDDEN |
Valid auth but insufficient permissions |
| 404 |
RESOURCE_NOT_FOUND |
Resource does not exist |
| 409 |
CONFLICT |
Resource state conflict (e.g., duplicate) |
| 422 |
VALIDATION_ERROR |
Request body fails validation |
| 429 |
RATE_LIMITED |
Too many requests |
| 500 |
INTERNAL_ERROR |
Unexpected server error |
Pagination Strategy
Offset Pagination (simpler, suitable for most cases)
- Parameters:
page (1-indexed), limit (default 20, max 100)
- Response meta:
total, page, limit, total_pages
- Drawback: inconsistent results if data changes between pages
Cursor Pagination (for large or frequently changing datasets)
- Parameters:
cursor (opaque string), limit (default 20, max 100)
- Response meta:
next_cursor, has_more
- Advantage: consistent results regardless of data changes
Rate Limiting
| Tier |
Limit |
Window |
Scope |
| Standard |
100 requests |
1 minute |
Per API key |
| Elevated |
1000 requests |
1 minute |
Per API key |
| Webhook delivery |
10 requests |
1 second |
Per endpoint |
Rate limit headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1705312800
Caching
| Endpoint |
Cache Strategy |
TTL |
Invalidation |
GET /resources |
Private, no-store |
— |
— |
GET /resources/{id} |
Private, max-age |
60s |
On PATCH/DELETE |
GET /resources/{id}/stats |
Private, max-age |
300s |
On data change |
Headers:
Cache-Control: private, max-age=60
ETag: "abc123"
Example Requests
cURL
# List resources
curl -X GET "https://api.example.com/api/v1/resources?limit=10&sort=name" \
-H "Authorization: Bearer <token>" \
-H "Accept: application/json"
# Create resource
curl -X POST "https://api.example.com/api/v1/resources" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name": "My Resource", "status": "active"}'
Fetch (JavaScript)
// List resources
const response = await fetch('/api/v1/resources?limit=10', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
},
});
const { data, meta } = await response.json();
// Create resource
const response = await fetch('/api/v1/resources', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'My Resource', status: 'active' }),
});
## URL Design Rules
- **Plural nouns**: `/users`, `/orders`, `/invoices` -- never singular
- **Kebab-case**: `/user-profiles`, not `/userProfiles` or `/user_profiles`
- **No verbs in URLs**: `/orders/{id}/cancel` (POST) is acceptable for non-CRUD actions, but prefer state transitions via PATCH when possible
- **Max 2 levels of nesting**: `/users/{id}/orders` is fine; `/users/{id}/orders/{id}/items/{id}/variants` is not -- flatten it
- **Consistent ID format**: Use prefixed IDs (`usr_abc123`) or UUIDs, never auto-increment integers in URLs
- **Version in URL**: `/api/v1/` -- match existing project convention; if no convention exists, use URL-based versioning
## Field Naming Rules
- Match existing project convention (camelCase or snake_case) -- never mix
- Boolean fields: prefix with `is_`, `has_`, `can_` (e.g., `is_active`, `has_password`)
- Timestamps: suffix with `_at` (e.g., `created_at`, `deleted_at`)
- Counts: suffix with `_count` (e.g., `comment_count`)
- IDs: suffix with `_id` for foreign keys (e.g., `user_id`)
## Anti-patterns
- **Inconsistent error shapes** -- Every endpoint must return errors in the same structure. If one endpoint returns `{ "error": "message" }` and another returns `{ "errors": [{ "msg": "..." }] }`, clients cannot write generic error handling. Define the contract once and enforce it everywhere.
- **No pagination** -- Any endpoint that returns a list must be paginated. Unbounded list responses will eventually cause timeouts, OOM errors, or degraded client performance. There is no "the list is small" exception -- lists grow.
- **RPC-style URLs** -- `/api/getUser`, `/api/createOrder`, `/api/deleteInvoice` are RPC, not REST. Use resource nouns with HTTP methods: `GET /users/{id}`, `POST /orders`, `DELETE /invoices/{id}`.
- **No auth specification** -- Every endpoint must document its auth requirements. "Auth: TBD" is not a design -- it is a security gap. If auth is genuinely not yet decided, flag it as an unresolved question with your recommendation.
- **Breaking existing naming conventions** -- If the existing API uses `camelCase`, the new endpoints must use `camelCase`. Inconsistency across endpoints is worse than a suboptimal convention applied consistently.
- **Exposing internal IDs** -- Auto-increment database IDs leak information (total count, creation order) and are enumerable. Use UUIDs or prefixed opaque IDs.
- **No versioning strategy** -- APIs evolve. If there is no versioning mechanism, the first breaking change will be a crisis. Decide on URL-based or header-based versioning before shipping.
- **Inconsistent status codes** -- POST that returns 200 instead of 201, DELETE that returns 204 sometimes and 200 other times. Map each operation to its correct status code and be consistent.
1---2name: create-api-design3description: Design a REST or GraphQL API from a feature specification when the user asks to design an API, create endpoints, define an API contract, or plan API resources4---5
6# Create API Design
7
8## Overview
9
10Design a complete, production-ready API from a feature specification or resource description. The output is a comprehensive API design document covering endpoints, schemas, error contracts, pagination, auth, rate limiting, and caching -- consistent with existing API patterns in the project.
11
12## Workflow
13
141. **Read existing API patterns** -- Read `.chalk/docs/engineering/` for:
15 - Existing API design documents (match naming, URL structure, and conventions)
16 - Architecture docs describing the current API layer
17 - Auth patterns and middleware
18 - Error handling conventions
19 - If no docs exist, scan the codebase for route definitions to infer patterns
20
212. **Scan codebase for current conventions** -- Use Grep to find:
22 - Route definitions (e.g., `router.get`, `app.post`, `@GetMapping`, `@api_view`)
23 - Error response shapes (look for error middleware, error classes)
24 - Pagination patterns (cursor vs. offset, parameter names)
25 - Auth middleware usage (JWT, API key, OAuth scopes)
26 - Response envelope patterns (do responses wrap in `{ data, meta }` or return raw?)
27 - Store these conventions; the new API must follow them exactly
28
293. **Determine the next document number** -- List files in `.chalk/docs/engineering/` matching `*_api_design_*.md`. Find the highest number and increment by 1.
30
314. **Clarify the resource and operations** -- From `$ARGUMENTS` and conversation context, identify:
32 - The resource(s) being designed (nouns, not verbs)
33 - The operations needed (CRUD, plus any domain-specific actions)
34 - Who consumes this API (frontend, mobile, third-party, internal service)
35 - Auth requirements (public, authenticated, role-based, scope-based)
36 - Ask the user for clarification if the resource boundaries are unclear
37
385. **Design the endpoints** -- Follow REST conventions strictly:
39 - Use plural nouns for resource paths (`/users`, not `/user`)
40 - Use nested resources for ownership (`/users/{id}/posts`, not `/user-posts`)
41 - Limit nesting to 2 levels maximum
42 - Use HTTP methods correctly (GET reads, POST creates, PUT replaces, PATCH updates, DELETE removes)
43 - Use query parameters for filtering, sorting, and pagination on collection endpoints
44 - Use path parameters only for resource identifiers
45
466. **Define schemas** -- For each endpoint, define:
47 - Request body JSON schema (for POST/PUT/PATCH)
48 - Response body JSON schema (for all methods)
49 - Query parameter schema (for GET collection endpoints)
50 - Use consistent field naming (camelCase or snake_case -- match existing convention)
51
527. **Define error contract** -- Design a consistent error response shape used across all endpoints. Include validation errors, business logic errors, and system errors.
53
548. **Write the document** -- Save to `.chalk/docs/engineering/<n>_api_design_<resource_slug>.md`.
55
569. **Confirm** -- Tell the user the API design was created with its path and a summary of the endpoints defined.
57
58## Filename Convention
59
60```
61<number>_api_design_<snake_case_resource>.md
62```
63
64Examples:
65- `4_api_design_user_profiles.md`
66- `8_api_design_billing_subscriptions.md`
67- `11_api_design_notification_preferences.md`
68
69## API Design Document Format
70
71```markdown
72# API Design: <Resource Name>
73
74Last updated: <YYYY-MM-DD>
75
76## Overview
77
78<1-2 sentences describing what this API enables and who consumes it.>
79
80## Base URL
81
82<e.g., `/api/v1` -- match existing project convention>
83
84## Authentication
85
86<Describe auth requirements. Reference existing auth middleware if applicable.>
87
88| Endpoint Pattern | Auth Required | Scopes / Roles |
89|-----------------|---------------|----------------|
90| `GET /resources` | Yes | `read:resources` |
91| `POST /resources` | Yes | `write:resources` |
92| `GET /resources/public` | No | — |
93
94## Endpoints
95
96### Resource Collection
97
98#### List Resources
99
100`GET /resources`
101
102**Query Parameters:**
103
104| Parameter | Type | Required | Default | Description |
105|-----------|------|----------|---------|-------------|
106| `page` | integer | No | 1 | Page number (if offset pagination) |
107| `limit` | integer | No | 20 | Items per page (max 100) |
108| `sort` | string | No | `created_at` | Sort field |
109| `order` | string | No | `desc` | Sort direction: `asc` or `desc` |
110| `filter[status]` | string | No | — | Filter by status |
111
112**Response: `200 OK`**
113
114```json
115{
116 "data": [
117 {
118 "id": "res_abc123",
119 "type": "resource",
120 "attributes": {}
121 }
122 ],
123 "meta": {
124 "total": 142,
125 "page": 1,
126 "limit": 20,
127 "total_pages": 8
128 }
129}
130```
131
132#### Create Resource
133
134`POST /resources`
135
136**Request Body:**
137
138```json
139{
140 "name": "string (required, 1-255 chars)",
141 "description": "string (optional, max 2000 chars)",
142 "status": "string (optional, enum: draft|active|archived, default: draft)"
143}
144```
145
146**Response: `201 Created`**
147
148```json
149{
150 "data": {
151 "id": "res_abc123",
152 "type": "resource",
153 "attributes": {
154 "name": "Example",
155 "description": null,
156 "status": "draft",
157 "created_at": "2024-01-15T10:30:00Z",
158 "updated_at": "2024-01-15T10:30:00Z"
159 }
160 }
161}
162```
163
164### Individual Resource
165
166#### Get Resource
167
168`GET /resources/{id}`
169
170#### Update Resource
171
172`PATCH /resources/{id}`
173
174#### Delete Resource
175
176`DELETE /resources/{id}`
177
178<Continue for all endpoints...>
179
180## Request / Response Schemas
181
182### Resource Schema
183
184| Field | Type | Constraints | Description |
185|-------|------|------------|-------------|
186| `id` | string | Read-only, prefixed | Unique identifier |
187| `name` | string | Required, 1-255 chars | Display name |
188| `created_at` | ISO 8601 | Read-only | Creation timestamp |
189| `updated_at` | ISO 8601 | Read-only | Last modification timestamp |
190
191## Error Contract
192
193All errors follow a consistent shape:
194
195```json
196{
197 "error": {
198 "code": "RESOURCE_NOT_FOUND",
199 "message": "The requested resource does not exist.",
200 "status": 404,
201 "details": []
202 }
203}
204```
205
206### Validation Errors (`422`)
207
208```json
209{
210 "error": {
211 "code": "VALIDATION_ERROR",
212 "message": "Request validation failed.",
213 "status": 422,
214 "details": [
215 {
216 "field": "name",
217 "constraint": "required",
218 "message": "Name is required."
219 },
220 {
221 "field": "email",
222 "constraint": "format",
223 "message": "Email must be a valid email address."
224 }
225 ]
226 }
227}
228```
229
230### Error Codes
231
232| HTTP Status | Error Code | When Used |
233|-------------|-----------|-----------|
234| 400 | `BAD_REQUEST` | Malformed request syntax |
235| 401 | `UNAUTHORIZED` | Missing or invalid auth token |
236| 403 | `FORBIDDEN` | Valid auth but insufficient permissions |
237| 404 | `RESOURCE_NOT_FOUND` | Resource does not exist |
238| 409 | `CONFLICT` | Resource state conflict (e.g., duplicate) |
239| 422 | `VALIDATION_ERROR` | Request body fails validation |
240| 429 | `RATE_LIMITED` | Too many requests |
241| 500 | `INTERNAL_ERROR` | Unexpected server error |
242
243## Pagination Strategy
244
245<Choose one and document it. Match existing project convention.>
246
247### Offset Pagination (simpler, suitable for most cases)
248
249- Parameters: `page` (1-indexed), `limit` (default 20, max 100)
250- Response meta: `total`, `page`, `limit`, `total_pages`
251- Drawback: inconsistent results if data changes between pages
252
253### Cursor Pagination (for large or frequently changing datasets)
254
255- Parameters: `cursor` (opaque string), `limit` (default 20, max 100)
256- Response meta: `next_cursor`, `has_more`
257- Advantage: consistent results regardless of data changes
258
259## Rate Limiting
260
261| Tier | Limit | Window | Scope |
262|------|-------|--------|-------|
263| Standard | 100 requests | 1 minute | Per API key |
264| Elevated | 1000 requests | 1 minute | Per API key |
265| Webhook delivery | 10 requests | 1 second | Per endpoint |
266
267**Rate limit headers:**
268
269```
270X-RateLimit-Limit: 100
271X-RateLimit-Remaining: 87
272X-RateLimit-Reset: 1705312800
273```
274
275## Caching
276
277| Endpoint | Cache Strategy | TTL | Invalidation |
278|----------|---------------|-----|--------------|
279| `GET /resources` | Private, no-store | — | — |
280| `GET /resources/{id}` | Private, max-age | 60s | On PATCH/DELETE |
281| `GET /resources/{id}/stats` | Private, max-age | 300s | On data change |
282
283**Headers:**
284
285```
286Cache-Control: private, max-age=60
287ETag: "abc123"
288```
289
290## Example Requests
291
292### cURL
293
294```bash
295# List resources
296curl -X GET "https://api.example.com/api/v1/resources?limit=10&sort=name" \
297 -H "Authorization: Bearer <token>" \
298 -H "Accept: application/json"
299
300# Create resource
301curl -X POST "https://api.example.com/api/v1/resources" \
302 -H "Authorization: Bearer <token>" \
303 -H "Content-Type: application/json" \
304 -d '{"name": "My Resource", "status": "active"}'
305```
306
307### Fetch (JavaScript)
308
309```javascript
310// List resources
311const response = await fetch('/api/v1/resources?limit=10', {
312 headers: {
313 'Authorization': `Bearer ${token}`,
314 'Accept': 'application/json',
315 },
316});
317const { data, meta } = await response.json();
318
319// Create resource
320const response = await fetch('/api/v1/resources', {
321 method: 'POST',
322 headers: {
323 'Authorization': `Bearer ${token}`,
324 'Content-Type': 'application/json',
325 },
326 body: JSON.stringify({ name: 'My Resource', status: 'active' }),
327});
328```
329```
330
331## URL Design Rules
332
333- **Plural nouns**: `/users`, `/orders`, `/invoices` -- never singular
334- **Kebab-case**: `/user-profiles`, not `/userProfiles` or `/user_profiles`
335- **No verbs in URLs**: `/orders/{id}/cancel` (POST) is acceptable for non-CRUD actions, but prefer state transitions via PATCH when possible
336- **Max 2 levels of nesting**: `/users/{id}/orders` is fine; `/users/{id}/orders/{id}/items/{id}/variants` is not -- flatten it
337- **Consistent ID format**: Use prefixed IDs (`usr_abc123`) or UUIDs, never auto-increment integers in URLs
338- **Version in URL**: `/api/v1/` -- match existing project convention; if no convention exists, use URL-based versioning
339
340## Field Naming Rules
341
342- Match existing project convention (camelCase or snake_case) -- never mix
343- Boolean fields: prefix with `is_`, `has_`, `can_` (e.g., `is_active`, `has_password`)
344- Timestamps: suffix with `_at` (e.g., `created_at`, `deleted_at`)
345- Counts: suffix with `_count` (e.g., `comment_count`)
346- IDs: suffix with `_id` for foreign keys (e.g., `user_id`)
347
348## Anti-patterns
349
350- **Inconsistent error shapes** -- Every endpoint must return errors in the same structure. If one endpoint returns `{ "error": "message" }` and another returns `{ "errors": [{ "msg": "..." }] }`, clients cannot write generic error handling. Define the contract once and enforce it everywhere.
351- **No pagination** -- Any endpoint that returns a list must be paginated. Unbounded list responses will eventually cause timeouts, OOM errors, or degraded client performance. There is no "the list is small" exception -- lists grow.
352- **RPC-style URLs** -- `/api/getUser`, `/api/createOrder`, `/api/deleteInvoice` are RPC, not REST. Use resource nouns with HTTP methods: `GET /users/{id}`, `POST /orders`, `DELETE /invoices/{id}`.
353- **No auth specification** -- Every endpoint must document its auth requirements. "Auth: TBD" is not a design -- it is a security gap. If auth is genuinely not yet decided, flag it as an unresolved question with your recommendation.
354- **Breaking existing naming conventions** -- If the existing API uses `camelCase`, the new endpoints must use `camelCase`. Inconsistency across endpoints is worse than a suboptimal convention applied consistently.
355- **Exposing internal IDs** -- Auto-increment database IDs leak information (total count, creation order) and are enumerable. Use UUIDs or prefixed opaque IDs.
356- **No versioning strategy** -- APIs evolve. If there is no versioning mechanism, the first breaking change will be a crisis. Decide on URL-based or header-based versioning before shipping.
357- **Inconsistent status codes** -- POST that returns 200 instead of 201, DELETE that returns 204 sometimes and 200 other times. Map each operation to its correct status code and be consistent.