@dynokostya/just-works 1.0.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/.claude/agents/csharp-code-writer.md +32 -0
- package/.claude/agents/diagrammer.md +49 -0
- package/.claude/agents/frontend-code-writer.md +36 -0
- package/.claude/agents/prompt-writer.md +38 -0
- package/.claude/agents/python-code-writer.md +32 -0
- package/.claude/agents/swift-code-writer.md +32 -0
- package/.claude/agents/typescript-code-writer.md +32 -0
- package/.claude/commands/git-sync.md +96 -0
- package/.claude/commands/project-docs.md +287 -0
- package/.claude/settings.json +112 -0
- package/.claude/settings.json.default +15 -0
- package/.claude/skills/csharp-coding/SKILL.md +368 -0
- package/.claude/skills/ddd-architecture-python/SKILL.md +288 -0
- package/.claude/skills/feature-driven-architecture-python/SKILL.md +302 -0
- package/.claude/skills/gemini-3-prompting/SKILL.md +483 -0
- package/.claude/skills/gpt-5-2-prompting/SKILL.md +295 -0
- package/.claude/skills/opus-4-6-prompting/SKILL.md +315 -0
- package/.claude/skills/plantuml-diagramming/SKILL.md +758 -0
- package/.claude/skills/python-coding/SKILL.md +293 -0
- package/.claude/skills/react-coding/SKILL.md +264 -0
- package/.claude/skills/rest-api/SKILL.md +421 -0
- package/.claude/skills/shadcn-ui-coding/SKILL.md +454 -0
- package/.claude/skills/swift-coding/SKILL.md +401 -0
- package/.claude/skills/tailwind-css-coding/SKILL.md +268 -0
- package/.claude/skills/typescript-coding/SKILL.md +464 -0
- package/.claude/statusline-command.sh +34 -0
- package/.codex/prompts/plan-reviewer.md +162 -0
- package/.codex/prompts/project-docs.md +287 -0
- package/.codex/skills/ddd-architecture-python/SKILL.md +288 -0
- package/.codex/skills/feature-driven-architecture-python/SKILL.md +302 -0
- package/.codex/skills/gemini-3-prompting/SKILL.md +483 -0
- package/.codex/skills/gpt-5-2-prompting/SKILL.md +295 -0
- package/.codex/skills/opus-4-6-prompting/SKILL.md +315 -0
- package/.codex/skills/plantuml-diagramming/SKILL.md +758 -0
- package/.codex/skills/python-coding/SKILL.md +293 -0
- package/.codex/skills/react-coding/SKILL.md +264 -0
- package/.codex/skills/rest-api/SKILL.md +421 -0
- package/.codex/skills/shadcn-ui-coding/SKILL.md +454 -0
- package/.codex/skills/tailwind-css-coding/SKILL.md +268 -0
- package/.codex/skills/typescript-coding/SKILL.md +464 -0
- package/AGENTS.md +57 -0
- package/CLAUDE.md +98 -0
- package/LICENSE +201 -0
- package/README.md +114 -0
- package/bin/cli.mjs +291 -0
- package/package.json +39 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rest-api
|
|
3
|
+
description: Apply when designing or implementing REST API endpoints, routes, or controllers. Covers URL conventions, HTTP methods, status codes, error responses, pagination, versioning, authentication, security, caching, file uploads, health checks, and common API antipatterns. Framework-agnostic HTTP-level patterns. Project conventions always override these defaults.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# REST API Design
|
|
7
|
+
|
|
8
|
+
Match the project's existing API conventions. When uncertain, read 2-3 existing endpoints to infer the local style. Check for OpenAPI specs, existing error response formats, and authentication patterns. These defaults apply only when the project has no established convention.
|
|
9
|
+
|
|
10
|
+
## Never rules
|
|
11
|
+
|
|
12
|
+
These are unconditional. They prevent security vulnerabilities, broken contracts, and common API design mistakes regardless of project style.
|
|
13
|
+
|
|
14
|
+
- **Never use singular nouns for collection endpoints.** Use `/users`, not `/user`. Mixing singular and plural creates ambiguity — clients have to guess whether the endpoint is `/user/123` or `/users/123`. A consistent plural convention means the same base path serves both the collection (`GET /users`) and individual resources (`GET /users/123`).
|
|
15
|
+
- **Never nest resources deeper than 3 levels.** `/customers/123/orders/456/items` is the limit. Deeper nesting increases coupling and URL complexity. If the child has a globally unique ID, expose it as a top-level resource.
|
|
16
|
+
- **Never use verbs in URL paths for CRUD operations.** `POST /users/123/delete` duplicates what HTTP methods already express and makes the API unpredictable — clients can't know whether to use `POST /users/delete` or `DELETE /users`. Use `DELETE /users/123`. Verbs are acceptable only for non-CRUD actions as sub-resource endpoints (`POST /charges/ch_123/capture`) or colon syntax (`POST /instances/my-vm:start`).
|
|
17
|
+
- **Never return only the first validation error.** Return all validation errors at once with field paths. Returning one at a time forces clients into frustrating fix-one-discover-another cycles.
|
|
18
|
+
- **Never use offset pagination on datasets exceeding 10K rows.** Performance degrades linearly with offset depth — benchmarks show 17x slowdown at deep offsets. Use cursor-based pagination.
|
|
19
|
+
- **Never omit `Retry-After` on 429 responses.** Rate limit responses without `Retry-After` force clients to guess retry timing, causing thundering herds or aggressive polling.
|
|
20
|
+
- **Never verify webhook signatures against re-serialized JSON.** Re-serialization changes key order or whitespace, invalidating the signature. Always verify against the raw request body bytes.
|
|
21
|
+
- **Never expose sequential integer IDs for resources accessible across trust boundaries.** Sequential IDs enable enumeration attacks (BOLA/IDOR). Use UUIDs or other unpredictable identifiers for user-facing resources.
|
|
22
|
+
- **Never bind raw client input directly to internal models.** This enables mass assignment — attackers adding `is_admin: true` to request bodies. Use explicit allowlists of writable fields via DTOs or schemas.
|
|
23
|
+
- **Never trust user-supplied resource IDs without server-side ownership verification.** BOLA (Broken Object Level Authorization) is the #1 API vulnerability. Every endpoint receiving an object ID must verify the caller owns or has access to that resource.
|
|
24
|
+
- **Never use wildcard `*` for CORS `Access-Control-Allow-Origin` with credentials.** Browsers reject `Access-Control-Allow-Credentials: true` with wildcard origin. Validate the `Origin` header against an allowlist and reflect the specific origin.
|
|
25
|
+
- **Never expose internal error details in production responses.** Stack traces, SQL queries, file paths, and dependency versions give attackers a detailed map of your internals — database schema, framework versions with known CVEs, and directory structure for path traversal. Return generic messages externally; log details internally.
|
|
26
|
+
- **Never skip `Idempotency-Key` on POST endpoints with side effects.** Network retries on non-idempotent mutations cause duplicate charges, duplicate emails, duplicate records. Require an `Idempotency-Key` header on all state-changing POSTs.
|
|
27
|
+
- **Never deploy an API without health check endpoints.** Without health probes, orchestrators (Kubernetes, AWS ELB) can't distinguish a crashed pod from a healthy one — traffic routes to dead instances, and there's no automated recovery. A simple `/livez` returning 200 takes minutes to add and prevents hours of debugging silent outages.
|
|
28
|
+
- **Never support TLS versions below 1.2.** TLS 1.0 and 1.1 have known vulnerabilities (BEAST, POODLE) and are deprecated by all major cloud providers and PCI DSS. Require TLS 1.2 minimum, prefer 1.3.
|
|
29
|
+
- **Never omit security headers from API responses.** Missing `Strict-Transport-Security` allows SSL-stripping attacks on first visit. Missing `X-Content-Type-Options: nosniff` lets browsers reinterpret response content types, enabling XSS. Missing `Cache-Control: no-store` on authenticated endpoints means sensitive data persists in browser and proxy caches.
|
|
30
|
+
|
|
31
|
+
## Resource design
|
|
32
|
+
|
|
33
|
+
Use plural nouns with lowercase letters. For multi-word segments, pick one convention and apply it everywhere — kebab-case (`/payment-intents`) is the web standard, but snake_case (`/payment_intents`) and camelCase are acceptable if consistent.
|
|
34
|
+
|
|
35
|
+
Nest only when the child's lifecycle is coupled to the parent and its ID is scoped to that parent:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
GET /customers/cus_8a3x/addresses → Nested: addresses belong to a customer
|
|
39
|
+
GET /sales-orders/ord_5273gh → Top-level: globally unique ID
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For actions that don't map to CRUD, two patterns dominate:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
# Sub-resource endpoint (Stripe model — most widely adopted)
|
|
46
|
+
POST /v1/charges/ch_123/capture HTTP/1.1
|
|
47
|
+
|
|
48
|
+
# Colon syntax (Google AIP-136 — cleaner for many custom methods)
|
|
49
|
+
POST /v1/instances/my-vm:start HTTP/1.1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Use standard CRUD first. When an action doesn't fit, sub-resource endpoints are safest for public APIs. Colon syntax is cleaner but less familiar to most developers.
|
|
53
|
+
|
|
54
|
+
**Query parameters** for filtering and sorting:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
GET /products?category=electronics&price_gt=10&price_lt=100 HTTP/1.1
|
|
58
|
+
GET /tickets?sort=-priority,created_at&limit=20 HTTP/1.1
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Comma-separated `sort` with `-` prefix for descending. Map filterable fields directly to query parameters. For complex filtering, use a filter expression string parameter.
|
|
62
|
+
|
|
63
|
+
## HTTP methods and status codes
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Is the request malformed or unparseable? → 400 Bad Request
|
|
67
|
+
Valid structure but fails business rules? → 422 Unprocessable Content
|
|
68
|
+
Caller not authenticated? → 401 Unauthorized (+ WWW-Authenticate)
|
|
69
|
+
Authenticated but lacks permission? → 403 Forbidden (or 404 to hide existence)
|
|
70
|
+
Resource doesn't exist? → 404 Not Found
|
|
71
|
+
Resource permanently removed? → 410 Gone
|
|
72
|
+
Conflicts with current state? → 409 Conflict
|
|
73
|
+
Rate limit exceeded? → 429 Too Many Requests (+ Retry-After)
|
|
74
|
+
Batch partially successful? → 207 Multi-Status (per-item results)
|
|
75
|
+
Unexpected server error? → 500 Internal Server Error
|
|
76
|
+
Upstream service failing? → 502 Bad Gateway
|
|
77
|
+
Temporarily unavailable? → 503 Service Unavailable (+ Retry-After)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**400 vs 422:** Return 400 when the server cannot parse the request at all — malformed JSON, wrong Content-Type, missing required headers. Return 422 when the request is structurally valid but fails business rules — invalid email format, duplicate username, insufficient funds. If you don't want to distinguish, 400 is the safer default.
|
|
81
|
+
|
|
82
|
+
**401 vs 403:** Return 401 when credentials are missing or invalid. Return 403 when the caller is authenticated but lacks permission. Security pattern: return 404 instead of 403 for private resources to avoid confirming resource existence.
|
|
83
|
+
|
|
84
|
+
## Error responses
|
|
85
|
+
|
|
86
|
+
Use RFC 9457 (Problem Details) as the base format, extended with a machine-readable `code` and optional `doc_url`:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
HTTP/1.1 422 Unprocessable Content
|
|
90
|
+
Content-Type: application/problem+json
|
|
91
|
+
|
|
92
|
+
{
|
|
93
|
+
"type": "https://api.example.com/errors/validation-error",
|
|
94
|
+
"title": "Validation Failed",
|
|
95
|
+
"status": 422,
|
|
96
|
+
"code": "validation_error",
|
|
97
|
+
"detail": "Two fields failed validation.",
|
|
98
|
+
"doc_url": "https://docs.example.com/errors/validation-error",
|
|
99
|
+
"errors": [
|
|
100
|
+
{"detail": "must be a positive integer", "pointer": "#/age", "code": "invalid_type"},
|
|
101
|
+
{"detail": "must be 'green', 'red' or 'blue'", "pointer": "#/profile/color", "code": "invalid_enum"}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Every error includes: a stable `code` for programmatic handling, a human-readable `detail` (which can change across versions), and field-level `errors[]` with JSON Pointer paths. Clients must key on `code`, never on `detail` text.
|
|
107
|
+
|
|
108
|
+
**Batch operations** use 207 Multi-Status with per-item results — even when all items succeed — to force clients to inspect individual results:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
HTTP/1.1 207 Multi-Status
|
|
112
|
+
|
|
113
|
+
{
|
|
114
|
+
"results": [
|
|
115
|
+
{"id": "item-1", "status": 201, "data": {"id": "new-123"}},
|
|
116
|
+
{"id": "item-2", "status": 422, "error": {"code": "invalid_email", "detail": "Invalid email"}}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For batches exceeding ~1,000 items, accept with 202 and provide a polling endpoint.
|
|
122
|
+
|
|
123
|
+
## PATCH semantics
|
|
124
|
+
|
|
125
|
+
Three approaches exist — none has won:
|
|
126
|
+
|
|
127
|
+
| Approach | Mechanism | Null handling | When to use |
|
|
128
|
+
|----------|-----------|---------------|-------------|
|
|
129
|
+
| **JSON Merge Patch** (RFC 7396) | Send partial JSON | `null` means "delete field" — cannot set a field to null | Simple partial updates, no null-value fields |
|
|
130
|
+
| **JSON Patch** (RFC 6902) | Array of operations (add/remove/replace) | Explicit remove operation | Complex updates, array manipulation, atomic operations |
|
|
131
|
+
| **Field masks** (Google AIP-134) | `update_mask` parameter + merge patch body | Mask controls which fields update | APIs with many optional fields |
|
|
132
|
+
|
|
133
|
+
Most public APIs use custom lightweight PATCH — plain JSON with documented semantics. Whichever you choose, document explicitly: what happens when a field is `null`, which fields are updatable, and whether the operation is atomic.
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
# JSON Merge Patch — simple partial update
|
|
137
|
+
PATCH /users/123 HTTP/1.1
|
|
138
|
+
Content-Type: application/merge-patch+json
|
|
139
|
+
|
|
140
|
+
{"name": "New Name", "bio": null}
|
|
141
|
+
← name updated, bio deleted (null = remove)
|
|
142
|
+
|
|
143
|
+
# JSON Patch — explicit operations
|
|
144
|
+
PATCH /users/123 HTTP/1.1
|
|
145
|
+
Content-Type: application/json-patch+json
|
|
146
|
+
|
|
147
|
+
[
|
|
148
|
+
{"op": "replace", "path": "/name", "value": "New Name"},
|
|
149
|
+
{"op": "remove", "path": "/bio"}
|
|
150
|
+
]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Pagination
|
|
154
|
+
|
|
155
|
+
Cursor-based is the right default. Offset-based is acceptable only for small (<10K), mostly static datasets where "jump to page N" is a hard requirement.
|
|
156
|
+
|
|
157
|
+
Response envelope — minimal metadata, boolean continuation flag:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
GET /v1/customers?limit=20&starting_after=cus_4QFJ HTTP/1.1
|
|
161
|
+
|
|
162
|
+
HTTP/1.1 200 OK
|
|
163
|
+
|
|
164
|
+
{
|
|
165
|
+
"data": [
|
|
166
|
+
{"id": "cus_5RFL", "name": "Jane Doe"},
|
|
167
|
+
{"id": "cus_6SGM", "name": "John Smith"}
|
|
168
|
+
],
|
|
169
|
+
"has_more": true,
|
|
170
|
+
"next_cursor": "cus_6SGM"
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Avoid `total_count` by default — it requires an expensive `COUNT(*)` query. Make it opt-in if needed (`?include=total_count`).
|
|
175
|
+
|
|
176
|
+
Default page size: 20-50. Maximum: 100. Stripe defaults to 10 (max 100), GitHub 30 (max 100), Google Cloud 50.
|
|
177
|
+
|
|
178
|
+
Cursor-based pagination inherently handles real-time data — new inserts don't cause duplicates and deletes don't cause skips. Expire cursor tokens after ~3 days.
|
|
179
|
+
|
|
180
|
+
## Versioning
|
|
181
|
+
|
|
182
|
+
| Strategy | When to use | Example |
|
|
183
|
+
|----------|-------------|---------|
|
|
184
|
+
| **URI path** (most adopted) | Public APIs, simplicity | `/v1/users` |
|
|
185
|
+
| **Date-based header** (Stripe model) | Platform APIs, long-term stability | `API-Version: 2024-10-01` |
|
|
186
|
+
| **Query parameter** (Azure model) | Microsoft ecosystem | `?api-version=2024-01-01` |
|
|
187
|
+
|
|
188
|
+
For most public APIs, URI path versioning with major version only (`/v1/`) is the pragmatic default. Never use more than one versioning mechanism simultaneously.
|
|
189
|
+
|
|
190
|
+
**Breaking changes:** removing/renaming endpoints, parameters, or response fields; adding required parameters; changing field types; removing enum values; changing defaults; reducing rate limits.
|
|
191
|
+
|
|
192
|
+
**Non-breaking changes:** adding endpoints, optional parameters, response fields, response headers. Caveat: adding enum values for *output* parameters can break clients with strict deserialization — treat all output enum sets as extensible.
|
|
193
|
+
|
|
194
|
+
**Deprecation** uses two headers together:
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
HTTP/1.1 200 OK
|
|
198
|
+
Deprecation: Sun, 30 Jun 2025 23:59:59 GMT
|
|
199
|
+
Sunset: Sun, 30 Jun 2026 23:59:59 GMT
|
|
200
|
+
Link: <https://developer.example.com/migration>; rel="deprecation"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Authentication and authorization
|
|
204
|
+
|
|
205
|
+
| Mechanism | Use when |
|
|
206
|
+
|-----------|----------|
|
|
207
|
+
| **API key** | Server-to-server, public data, developer onboarding |
|
|
208
|
+
| **OAuth 2.0 + PKCE** | Third-party access to user data, user consent required |
|
|
209
|
+
| **OAuth 2.0 Client Credentials** | Machine-to-machine with fine-grained scopes |
|
|
210
|
+
| **OAuth 2.0 Device Code** | CLI tools, IoT devices |
|
|
211
|
+
| **JWT bearer** | Stateless token validation across distributed services |
|
|
212
|
+
|
|
213
|
+
API keys authenticate the *application*, not the user. OAuth authenticates *users* through third-party apps. JWT is a *token format*, not a protocol — it cannot be revoked before expiry without a blocklist.
|
|
214
|
+
|
|
215
|
+
Many systems combine: API key at the gateway for identification and rate limiting, OAuth downstream for user-level authorization.
|
|
216
|
+
|
|
217
|
+
Design scopes around least privilege with `resource.action` convention: `users.read`, `users.delete`, `orders.write`. Keep scopes coarse-grained — fine-grained authorization logic belongs in the API layer, not the token.
|
|
218
|
+
|
|
219
|
+
Return rate limit headers with every response:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
HTTP/1.1 200 OK
|
|
223
|
+
X-RateLimit-Limit: 5000
|
|
224
|
+
X-RateLimit-Remaining: 4987
|
|
225
|
+
X-RateLimit-Reset: 1372700873
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Security
|
|
229
|
+
|
|
230
|
+
Beyond authentication, these patterns address the OWASP API Security Top 10:
|
|
231
|
+
|
|
232
|
+
**BOLA/IDOR prevention** — the #1 API attack vector. Every endpoint that receives a resource ID must verify ownership server-side. Never assume that because a user has a valid token, they can access any resource ID they supply:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
# Wrong: trusts user-supplied ID without ownership check
|
|
236
|
+
GET /api/orders/ord_789 HTTP/1.1
|
|
237
|
+
Authorization: Bearer <valid_token>
|
|
238
|
+
← Returns order belonging to different user
|
|
239
|
+
|
|
240
|
+
# Correct: server verifies order belongs to authenticated user
|
|
241
|
+
GET /api/orders/ord_789 HTTP/1.1
|
|
242
|
+
Authorization: Bearer <valid_token>
|
|
243
|
+
← 404 if order doesn't belong to caller
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Mass assignment prevention** — use explicit input schemas that allowlist writable fields. Reject unexpected fields in request bodies. Never pass raw client input to database update operations.
|
|
247
|
+
|
|
248
|
+
**Input validation** — validate length, range, format, and type for all inputs at the API boundary. Constrain string inputs with patterns. Define request size limits and reject oversized payloads with 413.
|
|
249
|
+
|
|
250
|
+
**Security headers** on every response:
|
|
251
|
+
|
|
252
|
+
| Header | Value | Purpose |
|
|
253
|
+
|--------|-------|---------|
|
|
254
|
+
| `Strict-Transport-Security` | `max-age=63072000; includeSubDomains` | Force HTTPS |
|
|
255
|
+
| `X-Content-Type-Options` | `nosniff` | Prevent MIME sniffing |
|
|
256
|
+
| `Cache-Control` | `no-store` (authenticated endpoints) | Prevent sensitive data caching |
|
|
257
|
+
| `X-Frame-Options` | `DENY` | Prevent clickjacking |
|
|
258
|
+
|
|
259
|
+
**CORS** — validate `Origin` against an allowlist and reflect the specific origin. Set `Access-Control-Max-Age` for preflight caching. Never reflect arbitrary origins without validation.
|
|
260
|
+
|
|
261
|
+
**SSRF prevention** — when accepting URLs as input, validate against an allowlist of approved domains. Block internal network ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1). Don't follow redirects to blocked hosts.
|
|
262
|
+
|
|
263
|
+
## Performance and reliability
|
|
264
|
+
|
|
265
|
+
**Caching** — combine `Cache-Control` with `ETag` for conditional requests:
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
GET /api/products/42 HTTP/1.1
|
|
269
|
+
|
|
270
|
+
HTTP/1.1 200 OK
|
|
271
|
+
Cache-Control: public, max-age=300, must-revalidate
|
|
272
|
+
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
273
|
+
Vary: Accept, Accept-Encoding
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Subsequent requests use `If-None-Match` — a 304 response saves bandwidth. For writes, `If-Match` enables optimistic concurrency: return 412 Precondition Failed if the resource changed.
|
|
277
|
+
|
|
278
|
+
Use `public, max-age=N` for shared data, `private, no-cache` for user-specific data requiring revalidation, `no-store` for sensitive data.
|
|
279
|
+
|
|
280
|
+
**Rate limiting** — token bucket is the best default for user-facing APIs. It allows traffic bursts while maintaining an average rate.
|
|
281
|
+
|
|
282
|
+
| Algorithm | Tradeoff | When to use |
|
|
283
|
+
|-----------|----------|-------------|
|
|
284
|
+
| **Token bucket** | Allows bursts, best default | User-facing APIs |
|
|
285
|
+
| **Fixed window** | Simplest, boundary burst problem | Internal APIs |
|
|
286
|
+
| **Sliding window** | Smooth enforcement, low memory | Strict rate control |
|
|
287
|
+
|
|
288
|
+
**Idempotency keys** — require on all POST endpoints with side effects. Use V4 UUIDs. Store results keyed by the idempotency key; replay stored results on duplicate requests. Return an error if parameters differ from the original. Expire keys after 24 hours. GET and DELETE are inherently idempotent — keys are unnecessary.
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
POST /v1/charges HTTP/1.1
|
|
292
|
+
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
|
|
293
|
+
|
|
294
|
+
{"amount": 2000, "currency": "usd"}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Compression** — support gzip at minimum. JSON compresses 70-90%. Set a 1KB minimum threshold below which compression isn't applied. Always include `Vary: Accept-Encoding`.
|
|
298
|
+
|
|
299
|
+
## Async patterns
|
|
300
|
+
|
|
301
|
+
**Long-running operations** — for anything taking more than 10 seconds, return an operation resource immediately and let clients poll:
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
POST /v1/reports HTTP/1.1
|
|
305
|
+
|
|
306
|
+
HTTP/1.1 202 Accepted
|
|
307
|
+
|
|
308
|
+
{
|
|
309
|
+
"operation_id": "op_123",
|
|
310
|
+
"status": "running",
|
|
311
|
+
"progress_percent": 0,
|
|
312
|
+
"poll_url": "/v1/operations/op_123"
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
GET /v1/operations/op_123 HTTP/1.1
|
|
318
|
+
|
|
319
|
+
HTTP/1.1 200 OK
|
|
320
|
+
|
|
321
|
+
{
|
|
322
|
+
"operation_id": "op_123",
|
|
323
|
+
"status": "completed",
|
|
324
|
+
"result": {"report_url": "/v1/reports/rpt_456"}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Include `Retry-After` to guide polling frequency. Provide cancel and delete operations. Expire completed operations after 24 hours minimum. Polling is preferred over callbacks — it works through firewalls, requires no public endpoint, and gives clients control over retry behavior.
|
|
329
|
+
|
|
330
|
+
**Webhooks** require three components: reliable delivery with retries, signature verification, and idempotent processing.
|
|
331
|
+
|
|
332
|
+
Signature verification — compute HMAC-SHA256 of `{timestamp}.{raw_body}` using the endpoint secret. Include the timestamp to prevent replay attacks:
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
POST /webhooks HTTP/1.1
|
|
336
|
+
X-Webhook-Signature: t=1614682800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
|
|
337
|
+
|
|
338
|
+
{"event": "order.completed", "data": {...}}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Verification: extract timestamp and signature, compute HMAC over `{timestamp}.{raw_body}`, compare using constant-time comparison, reject if timestamp exceeds 5 minutes (replay protection).
|
|
342
|
+
|
|
343
|
+
Best practices: return 2xx immediately before processing; enqueue work asynchronously; store processed event IDs for deduplication; return 4xx for permanent errors (stops retries), 5xx for transient errors (triggers retry).
|
|
344
|
+
|
|
345
|
+
## File uploads
|
|
346
|
+
|
|
347
|
+
| File size | Pattern | Mechanism |
|
|
348
|
+
|-----------|---------|-----------|
|
|
349
|
+
| < 5 MB | Direct upload | `multipart/form-data` to your API |
|
|
350
|
+
| 5–100 MB | Presigned URL | Client uploads directly to object storage |
|
|
351
|
+
| > 100 MB | Resumable/chunked | TUS protocol or cloud-native resumable upload |
|
|
352
|
+
|
|
353
|
+
**Presigned URLs** are the cloud-first pattern — the client gets a signed URL and uploads directly to S3/GCS, bypassing your API server entirely:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
# Step 1: Client requests upload URL
|
|
357
|
+
POST /v1/uploads HTTP/1.1
|
|
358
|
+
|
|
359
|
+
{"filename": "report.pdf", "content_type": "application/pdf"}
|
|
360
|
+
|
|
361
|
+
HTTP/1.1 200 OK
|
|
362
|
+
|
|
363
|
+
{
|
|
364
|
+
"upload_url": "https://storage.example.com/bucket/key?X-Amz-Signature=...",
|
|
365
|
+
"expires_in": 3600,
|
|
366
|
+
"resource_id": "file_abc123"
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
# Step 2: Client uploads directly to storage
|
|
370
|
+
PUT https://storage.example.com/bucket/key?X-Amz-Signature=... HTTP/1.1
|
|
371
|
+
Content-Type: application/pdf
|
|
372
|
+
|
|
373
|
+
<binary data>
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
For resumable uploads exceeding 100 MB, the TUS protocol (tus.io) is the emerging standard — used by Cloudflare, Supabase, and Vimeo.
|
|
377
|
+
|
|
378
|
+
## Health checks and observability
|
|
379
|
+
|
|
380
|
+
**Health endpoints** — required for orchestrated deployments:
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
GET /livez HTTP/1.1 → 200 if process is running (Kubernetes liveness)
|
|
384
|
+
GET /readyz HTTP/1.1 → 200 if ready to serve traffic (Kubernetes readiness)
|
|
385
|
+
GET /healthz HTTP/1.1 → 200 with dependency status (load balancer health)
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
HTTP/1.1 200 OK
|
|
390
|
+
|
|
391
|
+
{"status": "pass", "checks": {"database": "pass", "cache": "pass"}}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Health endpoints must not expose internal details (connection strings, versions, error messages). Keep liveness probes trivial — a liveness probe that checks the database causes cascading restarts during DB outages.
|
|
395
|
+
|
|
396
|
+
**Correlation IDs** — attach a unique identifier at the API gateway, propagate through all downstream services, return in every response:
|
|
397
|
+
|
|
398
|
+
```
|
|
399
|
+
HTTP/1.1 200 OK
|
|
400
|
+
X-Request-Id: f058ebd6-02f7-4d3f-942e-904344e8cde5
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
If the client provides an ID, preserve it. If not, generate UUID v4 at the entry point. Include in every log entry.
|
|
404
|
+
|
|
405
|
+
**OpenAPI specification** — maintain an OpenAPI 3.1+ spec as the source of truth for your API contract. Validate requests and responses against the spec in CI. Publish the spec for client SDK generation and documentation.
|
|
406
|
+
|
|
407
|
+
**Content negotiation** — support the `Accept` header. Return `406 Not Acceptable` for unsupported formats. Default to `application/json`. Custom media types (`application/vnd.myapi+json`) are justified only when different response structures are needed for the same resource — avoid them otherwise.
|
|
408
|
+
|
|
409
|
+
## Anti-patterns
|
|
410
|
+
|
|
411
|
+
- **Exposing database internals.** Column names as filter parameters, auto-increment IDs in URLs, database error messages in responses. All are information disclosure and coupling risks.
|
|
412
|
+
- **Pagination as afterthought.** Adding pagination to an existing endpoint is a breaking change. Design it from day one, even for small collections.
|
|
413
|
+
- **God endpoint.** A single endpoint accepting a `action` parameter to dispatch different operations. Use distinct URLs and HTTP methods.
|
|
414
|
+
- **Chatty APIs requiring N+1 calls.** If clients routinely need data from 3 endpoints to render one view, provide a composite endpoint or support field expansion (`?expand=customer,invoice`).
|
|
415
|
+
- **Ignoring `Accept` headers.** Always returning JSON regardless of what the client requested. Return 406 if you don't support the requested format.
|
|
416
|
+
- **Version proliferation.** Maintaining 5+ active API versions. Use additive evolution (non-breaking changes) as the primary strategy. Reserve new versions for genuinely incompatible changes.
|
|
417
|
+
- **Webhooks without signatures.** Any webhook endpoint without cryptographic signature verification is vulnerable to spoofing. HMAC-SHA256 is the minimum.
|
|
418
|
+
- **Rate limits without headers.** Enforcing rate limits but not communicating them via response headers. Clients cannot implement backoff without `X-RateLimit-Remaining` and `Retry-After`.
|
|
419
|
+
- **Silently dropping unknown fields.** Accepting `{"amountt": 2000}` without error because `amountt` isn't a known field. Reject unexpected fields or at minimum log warnings.
|
|
420
|
+
- **Testing against mocks instead of contracts.** Mocks drift from reality. Validate requests and responses against the OpenAPI spec in CI using contract testing tools.
|
|
421
|
+
- **CORS wildcard with credentials.** Using `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true`. This is rejected by browsers and indicates a misunderstanding of the security model.
|