REST/GraphQL design — naming, status codes, pagination, idempotency.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add Dev-Toolbelt/dev-team-agents --skill api-design --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Api Design?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/dev-toolbelt-api-design)More formats (shields.io, HTML) on the badges page.
---
name: api-design
description: REST/GraphQL design — naming, status codes, pagination, idempotency.
---
# API Design
## REST API Principles
### Resource Naming
- Use **nouns**, not verbs: `/users`, `/orders`, not `/getUsers`, `/createOrder`
- Use **plural** for collections: `/products`, `/invoices`
- Use **kebab-case** for multi-word resources: `/fuel-types`, `/gas-stations`
- Nest resources to express ownership (max 2 levels): `/users/{id}/orders`
- Avoid deep nesting — use query parameters instead: `/orders?userId=X`
### HTTP Methods
| Method | Semantics | Idempotent | Safe |
|--------|-----------|-----------|------|
| GET | Read resource(s) | Yes | Yes |
| POST | Create resource | No | No |
| PUT | Replace resource entirely | Yes | No |
| PATCH | Partial update | No | No |
| DELETE | Remove resource | Yes | No |
### HTTP Status Codes
| Code | When to use |
|------|-------------|
| 200 OK | Successful GET, PUT, PATCH |
| 201 Created | Successful POST that created a resource |
| 204 No Content | Successful DELETE or action with no response body |
| 400 Bad Request | Invalid input, validation error |
| 401 Unauthorized | Not authenticated |
| 403 Forbidden | Authenticated but not authorized |
| 404 Not Found | Resource does not exist |
| 409 Conflict | State conflict (duplicate, optimistic lock) |
| 422 Unprocessable Entity | Syntactically valid but semantically invalid |
| 429 Too Many Requests | Rate limit exceeded |
| 500 Internal Server Error | Unexpected server failure |
### Response Envelope
All responses use a consistent envelope:
```json
{ "status": "success", "data": { ... } }
```
```json
{
"status": "error",
"message": "Human-readable summary",
"code": "MACHINE_READABLE_CODE",
"details": [
{ "field": "email", "message": "must be a valid email address" }
]
}
```
- `status`: `"success"` or `"error"` — always present
- `data`: payload on success; omitted on error
- `message`: human-readable description of the error
- `code`: machine-readable identifier in SCREAMING_SNAKE_CASE
- `details`: optional array of field-level validation errors
### Pagination
For list endpoints, always paginate:
```json
{
"status": "success",
"data": {
"items": [...],
"meta": {
"total": 150,
"per_page": 20,
"current_page": 1,
"last_page": 8
}
}
}
```
Use cursor-based pagination for large datasets or real-time feeds.
### Filtering & Sorting
Use query parameters:
```
GET /orders?status=active&sort=created_at&direction=desc
GET /products?category=fuel&min_price=10&max_price=50
```
### Versioning
Use URL prefix versioning — never header-based versioning:
```
/api/v1/users
/api/v2/users
```
- Never remove a field without a major version bump
- Deprecate with `Deprecation` and `Sunset` response headers
- Maintain at least one previous major version during deprecation cycles
### Idempotency
- `GET`, `PUT`, `DELETE` must be idempotent — repeated calls with the same payload produce the same result
- `POST` is not idempotent by default
- For payment processing and other critical operations, support the `Idempotency-Key` request header:
- Client generates a unique key per operation attempt
- Server stores the result and returns the same response for duplicate keys
- Key expiry: 24 hours minimum
- Guard against duplicate `POST` submissions when relevant (e.g., order placement, account creation)
---
## API Security
- **Always use HTTPS** — no exceptions in production
- **Auth**: prefer stateless JWT (Bearer token) or OAuth 2.0; avoid session cookies for APIs
- **Rate limiting**: apply per-user and per-IP; return 429 with `Retry-After` header
- **Input validation**: validate all query params, body fields, and path params server-side
- **Never expose internal IDs** — use UUIDs or opaque external identifiers in public APIs
---
## GraphQL Schema Conventions
> When the project exposes a GraphQL API, load `skills/architecture/graphql/SKILL.md` for the full reference. The rules below are always active.
- **Schema-first**: define `.graphql` schema files before implementing resolvers
- Types use `PascalCase`: `UserProfile`, `OrderItem`
- Fields use `camelCase`: `firstName`, `totalAmount`
- Queries: descriptive names — `userById`, `activeOrders`
- Mutations: verb + noun — `createUser`, `updateOrderStatus`, `deleteProduct`
- **Mutations always return** the affected resource plus any errors — never return `Boolean` alone
- Use `input` types for mutation arguments: `CreateUserInput`, `UpdateProductInput`
- **DataLoader is mandatory** for any resolver that fetches related entities — prevents N+1 queries
- Pagination: use Relay Connection spec (`edges`, `node`, `pageInfo`)
- Errors: expose `errors` alongside `data` in mutation payloads (not via HTTP 4xx):
```graphql
type UpdateUserPayload {
user: User
errors: [UserError!]!
}
```
---
## Documentation Standards
Document every endpoint with:
1. **Method + Path**: `POST /api/v1/users`
2. **Description**: what it does, when to use it
3. **Auth required**: yes/no, which role/scope
4. **Request**: headers, body schema with types and required fields
5. **Response**: status codes, response schema with examples
6. **Errors**: possible error codes and their meaning
Generate OpenAPI 3.0 spec for machine-readable documentation.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!