This skill should be used when the user is choosing HTTP methods for API endpoints, designing CRUD operations, implementing idempotent operations, deciding between PUT and PATCH, handling bulk operations, or working with HTTP verb semantics. Covers GET, POST, PUT, PATCH, DELETE with idempotency rules, status code pairings, and real-world patterns.
npx claudepluginhub oborchers/fractional-cto --plugin api-design-principlesThis skill uses the workspace's default tool permissions.
HTTP methods are not suggestions. They are a contract between your API and every client, proxy, cache, browser, and crawler that will ever touch it. When you label an endpoint `GET`, you promise it is safe to retry, safe to cache, and will never modify state. When you use `POST`, you acknowledge that duplicates are possible without explicit safeguards. Getting this wrong does not just break con...
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Retrieves current documentation, API references, and code examples for libraries, frameworks, SDKs, CLIs, and services via Context7 CLI. Ideal for API syntax, configs, migrations, and setup queries.
Uses ctx7 CLI to fetch current library docs, manage AI coding skills (install/search/generate), and configure Context7 MCP for AI editors.
HTTP methods are not suggestions. They are a contract between your API and every client, proxy, cache, browser, and crawler that will ever touch it. When you label an endpoint GET, you promise it is safe to retry, safe to cache, and will never modify state. When you use POST, you acknowledge that duplicates are possible without explicit safeguards. Getting this wrong does not just break conventions — it breaks infrastructure assumptions that the entire web stack relies on.
The five core methods — GET, POST, PUT, PATCH, DELETE — cover virtually every API operation. The rules below are drawn from the HTTP/1.1 specification (RFC 7231), Stripe, GitHub, Google, Zalando, and Microsoft REST API guidelines. Where these sources converge, treat the pattern as settled law.
GET retrieves a resource or collection. It is safe (no side effects), idempotent (same result every call), and cacheable.
Rules:
200 OK with the resource body, or 404 Not Found if the resource does not exist.ETag and If-None-Match headers. Return 304 Not Modified when content has not changed — this is free performance.GET /orders → 200 OK (collection)
GET /orders/789 → 200 OK (single resource)
GET /orders?status=pending&sort=-created_at → 200 OK (filtered, sorted)
GET /orders/789 + If-None-Match: "etag" → 304 Not Modified
BAD:
GET /orders/789/cancel → Modifies state via GET
GET /getOrderById?id=789 → Verb in URL, filter as query param for identity
POST creates a new resource or triggers an action. It is neither safe nor idempotent — two identical POST requests may create two resources.
Rules:
201 Created with a Location header pointing to the new resource for creation operations.200 OK or 202 Accepted for action operations (cancel, refund, send).Idempotency-Key header pattern (see the Idempotency section below).POST /orders → 201 Created + Location: /orders/790
POST /orders/789/cancel → 200 OK (action)
POST /search → 200 OK (complex query body)
POST /orders/batch → 200 OK (batch operation)
BAD:
POST /orders/list → Use GET for retrieval
POST /createOrder → Verb in URL; POST /orders is sufficient
PUT replaces the entire resource at the given URL. Calling PUT twice with the same body produces the same result — this is its defining characteristic.
Rules:
200 OK with the updated resource, or 204 No Content if no body is returned.201 Created if the resource did not exist, 200 OK if it did.PUT /users/42
{
"name": "Oliver Borchers",
"email": "oliver@example.com",
"role": "admin",
"is_active": true
}
→ 200 OK (all fields replaced)
BAD — partial body with PUT semantics:
PUT /users/42
{ "role": "admin" }
→ name, email, is_active are now wiped — this is correct PUT behavior,
but almost certainly not what the client intended.
Use PATCH for partial updates.
PATCH updates only the fields included in the request body. Everything else stays unchanged. This is the method most modern APIs use for updates.
Rules:
200 OK with the full updated resource so the client sees the current state.application/merge-patch+json) for simplicity. Use JSON Patch (application/json-patch+json) when you need operations like remove, move, or test.PATCH /users/42
{ "role": "admin" }
→ 200 OK (only role changed; name, email, is_active preserved)
Why PATCH over PUT for most updates: Stripe, GitHub, and Twilio all default to partial-update semantics. PUT requires the client to know and send every field, creating risk of accidental data loss when a field is forgotten. PATCH is safer for resources with many fields, for mobile clients with bandwidth constraints, and for concurrent updates where multiple clients modify different fields.
DELETE removes the resource at the given URL. It must be idempotent — deleting an already-deleted resource is not an error.
Rules:
204 No Content with no response body, or 200 OK with the deleted resource as confirmation (Stripe returns { "id": "...", "deleted": true }).204 (idempotent) or 404 — both are acceptable, but 204 is more consistent with idempotency guarantees.PATCH /users/42 { "deleted_at": "2024-..." } or POST /users/42/archive to make the intent explicit.POST /users/batch-delete with a body of IDs is the safer pattern).DELETE /users/42 → 204 No Content
DELETE /users/42 (already deleted) → 204 No Content (idempotent)
DELETE /posts/10/comments/5 → 204 No Content
BAD:
POST /users/42/delete → DELETE verb exists for this purpose
GET /deleteUser?id=42 → GET must never modify state
Use this table as a quick reference for every endpoint you design.
| Method | Idempotent? | Safe? | Cacheable? | Typical Success Codes | Request Body |
|---|---|---|---|---|---|
| GET | Yes | Yes | Yes | 200, 304 | No |
| POST | No | No | No | 201, 200, 202 | Yes |
| PUT | Yes | No | No | 200, 201, 204 | Yes (full) |
| PATCH | Usually | No | No | 200, 204 | Yes (partial) |
| DELETE | Yes | No | No | 204, 200 | Avoid |
| HEAD | Yes | Yes | Yes | 200, 304 | No |
| OPTIONS | Yes | Yes | No | 200, 204 | No |
Idempotency means making the same request multiple times produces the same result as making it once. This is critical for reliability — network failures force retries, and retries must not cause duplicate side effects.
Naturally idempotent (safe to retry without extra work):
Not idempotent (requires explicit safeguards):
Idempotency-Key header to make POST safely retryable. The client generates a UUID and includes it in the request header. The server stores the response keyed to this value and returns the cached response on retry.POST /v1/charges
Idempotency-Key: req_unique_abc123
Content-Type: application/json
{ "amount": 2000, "currency": "usd" }
# Client retries with the same key after timeout →
# Server returns cached response, no duplicate charge.
# Response header: Idempotent-Replayed: true
Stripe keys expire after 24 hours. This pattern is now an IETF draft standard adopted by Adyen, PayPal, and others.
GET with side effects. The most dangerous mistake. If a GET handler sends an email, creates a log entry, increments a counter, or modifies a database row, crawlers, prefetchers, and monitoring tools will trigger those side effects silently.
POST for everything. Using POST where GET, PUT, PATCH, or DELETE would be correct destroys cacheability, breaks idempotency assumptions, and forces every client to read documentation instead of relying on HTTP semantics.
PUT with partial bodies. Sending { "email": "new@example.com" } to a PUT endpoint wipes every field not included. If you mean "update one field," use PATCH.
DELETE with complex side effects. If "deleting" an order triggers refunds, emails, and inventory changes, model this as POST /orders/789/cancel instead. DELETE should mean the resource is gone, not that a complex business process kicks off.
Ignoring idempotency on POST. Any POST endpoint that charges money, sends a message, or creates a billable resource must support the Idempotency-Key pattern. Network retries are inevitable; duplicate charges are unacceptable.
Confusing 200 and 201. Return 201 Created when a POST creates a new resource. Return 200 OK when a POST triggers an action or when PUT/PATCH updates an existing resource. The distinction tells clients whether to expect a Location header.
| Scenario | GOOD | BAD | Why |
|---|---|---|---|
| Fetch a user | GET /users/42 | POST /getUser | GET is for retrieval; verbs violate REST |
| Create an order | POST /orders | PUT /orders | PUT requires client to specify ID |
| Update one field | PATCH /users/42 { "role": "admin" } | PUT /users/42 { "role": "admin" } | PUT wipes unspecified fields |
| Delete a resource | DELETE /users/42 | POST /users/42/delete | DELETE verb exists for this |
| Cancel an order | POST /orders/42/cancel | DELETE /orders/42 | DELETE implies removal, not state change |
| Idempotent create | PUT /bookmarks/repo-123 | POST /bookmarks (with dedup) | PUT is naturally idempotent for upserts |
| Simple search | GET /users?q=oliver | POST /users/search | Read-only search should use GET |
| Complex search | POST /search (with body) | GET /search (with body) | GET bodies are unreliable; long queries break URLs |
Working implementations in examples/:
examples/crud-endpoint-patterns.md — Complete CRUD implementation for an /orders resource showing correct method, route, status code, and request/response body for each operation in Node.js/Express and Python/FastAPI.examples/idempotency-key-middleware.md — Idempotency-Key header middleware that prevents duplicate POST operations, with implementations in Node.js/Express and Python/FastAPI.When designing or reviewing API endpoints:
201 Created with a Location header for resource creation204 No Content and is idempotent (succeeds even if resource is already gone)Idempotency-Key headerPOST /resource/{id}/action, not GET or custom verbsPOST /orders, not POST /createOrder)