mirror of
https://github.com/duthaho/claudekit.git
synced 2026-09-03 16:50:51 +03:00
feat: adding new skills, including testing patterns and methodologies, along with bundled resources for better usability.
This commit is contained in:
@@ -1,92 +1,837 @@
|
||||
# OpenAPI
|
||||
---
|
||||
name: openapi
|
||||
description: >
|
||||
Use this skill when designing, documenting, or generating REST API specifications using OpenAPI/Swagger. Trigger on keywords like OpenAPI, Swagger, API spec, REST documentation, API schema, request body, response schema, and API client generation. Also apply when adopting design-first API development, validating API contracts, or setting up auto-generated API documentation for FastAPI, Express, or NestJS endpoints.
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
OpenAPI/Swagger specification patterns for REST API documentation.
|
||||
# OpenAPI & REST API Design
|
||||
|
||||
## When to Use
|
||||
|
||||
- Documenting REST APIs
|
||||
- Generating API clients
|
||||
- API design-first development
|
||||
- Defining webhook contracts
|
||||
- Establishing pagination, versioning, or auth patterns for a new service
|
||||
|
||||
## When NOT to Use
|
||||
|
||||
- Internal-only scripts or automation that do not expose HTTP endpoints
|
||||
- CLI tools and command-line utilities without a REST interface
|
||||
- GraphQL APIs where a different specification format applies
|
||||
|
||||
---
|
||||
|
||||
## Core Patterns
|
||||
|
||||
### Basic Specification
|
||||
### 1. OpenAPI 3.1 Specification Structure
|
||||
|
||||
A complete spec skeleton showing every top-level section. Use `$ref` to split
|
||||
large specs into per-resource files.
|
||||
|
||||
```yaml
|
||||
openapi: 3.0.3
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: My API
|
||||
version: 1.0.0
|
||||
title: Acme API
|
||||
version: 2.0.0
|
||||
description: Public API for the Acme platform.
|
||||
contact:
|
||||
name: API Support
|
||||
email: api@acme.dev
|
||||
license:
|
||||
name: MIT
|
||||
url: https://opensource.org/licenses/MIT
|
||||
|
||||
servers:
|
||||
- url: https://api.acme.dev/v2
|
||||
description: Production
|
||||
- url: https://staging-api.acme.dev/v2
|
||||
description: Staging
|
||||
|
||||
tags:
|
||||
- name: Users
|
||||
description: User management operations
|
||||
- name: Orders
|
||||
description: Order lifecycle operations
|
||||
|
||||
paths:
|
||||
/users:
|
||||
$ref: './paths/users.yaml'
|
||||
/users/{userId}:
|
||||
$ref: './paths/users-by-id.yaml'
|
||||
/orders:
|
||||
$ref: './paths/orders.yaml'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
$ref: './components/schemas/_index.yaml'
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
ApiKeyAuth:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-API-Key
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
|
||||
webhooks:
|
||||
orderCompleted:
|
||||
$ref: './webhooks/order-completed.yaml'
|
||||
```
|
||||
|
||||
**Organizing with `$ref`** -- keep one file per resource under `paths/` and
|
||||
shared schemas under `components/schemas/`. A bundler such as
|
||||
`@redocly/cli bundle` resolves references into a single file for tooling.
|
||||
|
||||
```
|
||||
spec/
|
||||
├── openapi.yaml # Root document
|
||||
├── paths/
|
||||
│ ├── users.yaml
|
||||
│ ├── users-by-id.yaml
|
||||
│ └── orders.yaml
|
||||
├── components/
|
||||
│ └── schemas/
|
||||
│ ├── _index.yaml
|
||||
│ ├── User.yaml
|
||||
│ ├── Order.yaml
|
||||
│ └── ProblemDetail.yaml
|
||||
└── webhooks/
|
||||
└── order-completed.yaml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Path & Operation Patterns
|
||||
|
||||
#### RESTful URL Naming Conventions
|
||||
|
||||
- Use **plural nouns** for collections: `/users`, `/orders`.
|
||||
- Use **path parameters** for single-resource access: `/users/{userId}`.
|
||||
- Nest only one level deep: `/users/{userId}/orders` (not deeper).
|
||||
- Use **query parameters** for filtering, sorting, and pagination.
|
||||
- Avoid verbs in paths -- let HTTP methods convey the action.
|
||||
|
||||
#### CRUD Operations
|
||||
|
||||
```yaml
|
||||
paths:
|
||||
/users:
|
||||
get:
|
||||
operationId: listUsers
|
||||
tags: [Users]
|
||||
summary: List users
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/PageCursor'
|
||||
- $ref: '#/components/parameters/PageSize'
|
||||
- name: status
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [active, inactive]
|
||||
responses:
|
||||
'200':
|
||||
description: Paginated list of users
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserListResponse'
|
||||
|
||||
post:
|
||||
operationId: createUser
|
||||
tags: [Users]
|
||||
summary: Create a user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateUserRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: User created
|
||||
headers:
|
||||
Location:
|
||||
schema:
|
||||
type: string
|
||||
description: URL of the new resource
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
'409':
|
||||
$ref: '#/components/responses/Conflict'
|
||||
'422':
|
||||
$ref: '#/components/responses/ValidationError'
|
||||
|
||||
/users/{userId}:
|
||||
parameters:
|
||||
- name: userId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
get:
|
||||
operationId: getUser
|
||||
tags: [Users]
|
||||
summary: Get a single user
|
||||
responses:
|
||||
'200':
|
||||
description: User found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
|
||||
patch:
|
||||
operationId: updateUser
|
||||
tags: [Users]
|
||||
summary: Partially update a user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateUserRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: User updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
'422':
|
||||
$ref: '#/components/responses/ValidationError'
|
||||
|
||||
delete:
|
||||
operationId: deleteUser
|
||||
tags: [Users]
|
||||
summary: Delete a user
|
||||
responses:
|
||||
'204':
|
||||
description: User deleted
|
||||
'404':
|
||||
$ref: '#/components/responses/NotFound'
|
||||
```
|
||||
|
||||
#### Path Parameters vs Query Parameters
|
||||
|
||||
| Use case | Mechanism | Example |
|
||||
|----------|-----------|---------|
|
||||
| Identify a specific resource | Path parameter | `/orders/{orderId}` |
|
||||
| Filter a collection | Query parameter | `/orders?status=shipped` |
|
||||
| Sort a collection | Query parameter | `/orders?sort=-createdAt` |
|
||||
| Paginate | Query parameter | `/orders?cursor=abc&limit=20` |
|
||||
| Expand nested data | Query parameter | `/orders?expand=items,customer` |
|
||||
|
||||
---
|
||||
|
||||
### 3. Request Body Patterns
|
||||
|
||||
#### JSON Request Body with Validation
|
||||
|
||||
```yaml
|
||||
components:
|
||||
schemas:
|
||||
CreateUserRequest:
|
||||
type: object
|
||||
required:
|
||||
- email
|
||||
- name
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
maxLength: 254
|
||||
name:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 100
|
||||
role:
|
||||
type: string
|
||||
enum: [admin, member, viewer]
|
||||
default: member
|
||||
additionalProperties: false
|
||||
```
|
||||
|
||||
Implementation in **FastAPI** (Python):
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
class CreateUserRequest(BaseModel):
|
||||
email: EmailStr
|
||||
name: str = Field(min_length=1, max_length=100)
|
||||
role: str = Field(default="member", pattern="^(admin|member|viewer)$")
|
||||
|
||||
model_config = {"extra": "forbid"}
|
||||
```
|
||||
|
||||
Implementation in **Express** (TypeScript with Zod):
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
|
||||
const CreateUserRequest = z.object({
|
||||
email: z.string().email().max(254),
|
||||
name: z.string().min(1).max(100),
|
||||
role: z.enum(["admin", "member", "viewer"]).default("member"),
|
||||
}).strict();
|
||||
|
||||
type CreateUserRequest = z.infer<typeof CreateUserRequest>;
|
||||
```
|
||||
|
||||
#### Multipart Form Data (File Uploads)
|
||||
|
||||
```yaml
|
||||
/users/{userId}/avatar:
|
||||
put:
|
||||
operationId: uploadAvatar
|
||||
tags: [Users]
|
||||
summary: Upload user avatar
|
||||
parameters:
|
||||
- name: userId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
required:
|
||||
- file
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
description: Image file (JPEG or PNG, max 5 MB)
|
||||
caption:
|
||||
type: string
|
||||
maxLength: 200
|
||||
encoding:
|
||||
file:
|
||||
contentType: image/jpeg, image/png
|
||||
responses:
|
||||
'200':
|
||||
description: Avatar updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
'413':
|
||||
$ref: '#/components/responses/PayloadTooLarge'
|
||||
```
|
||||
|
||||
#### Content Negotiation
|
||||
|
||||
Support multiple response formats by listing them under `content`:
|
||||
|
||||
```yaml
|
||||
responses:
|
||||
'200':
|
||||
description: Export data
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ExportData'
|
||||
text/csv:
|
||||
schema:
|
||||
type: string
|
||||
application/pdf:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
```
|
||||
|
||||
Clients select a format with the `Accept` header. Document which formats your
|
||||
API actually supports so consumers do not have to guess.
|
||||
|
||||
---
|
||||
|
||||
### 4. Response Patterns
|
||||
|
||||
#### Success Responses
|
||||
|
||||
| Code | Meaning | Typical use |
|
||||
|------|---------|-------------|
|
||||
| `200` | OK | GET, PATCH, general success |
|
||||
| `201` | Created | POST that creates a resource |
|
||||
| `202` | Accepted | Async operation started |
|
||||
| `204` | No Content | DELETE, or PUT with no body returned |
|
||||
|
||||
Always return a `Location` header with `201` pointing to the new resource.
|
||||
|
||||
#### Error Responses -- RFC 7807 Problem Details
|
||||
|
||||
Define a single reusable error schema based on RFC 7807:
|
||||
|
||||
```yaml
|
||||
components:
|
||||
schemas:
|
||||
ProblemDetail:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- title
|
||||
- status
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
format: uri
|
||||
description: URI reference identifying the problem type.
|
||||
example: https://api.acme.dev/problems/validation-error
|
||||
title:
|
||||
type: string
|
||||
description: Short human-readable summary.
|
||||
example: Validation Error
|
||||
status:
|
||||
type: integer
|
||||
description: HTTP status code.
|
||||
example: 422
|
||||
detail:
|
||||
type: string
|
||||
description: Human-readable explanation specific to this occurrence.
|
||||
example: "Field 'email' must be a valid email address."
|
||||
instance:
|
||||
type: string
|
||||
format: uri
|
||||
description: URI identifying this specific occurrence.
|
||||
errors:
|
||||
type: array
|
||||
description: Field-level validation errors (optional extension).
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
field:
|
||||
type: string
|
||||
example: email
|
||||
message:
|
||||
type: string
|
||||
example: Must be a valid email address.
|
||||
code:
|
||||
type: string
|
||||
example: invalid_format
|
||||
|
||||
responses:
|
||||
NotFound:
|
||||
description: Resource not found
|
||||
content:
|
||||
application/problem+json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetail'
|
||||
example:
|
||||
type: https://api.acme.dev/problems/not-found
|
||||
title: Not Found
|
||||
status: 404
|
||||
detail: User with ID '550e8400' was not found.
|
||||
|
||||
ValidationError:
|
||||
description: Request validation failed
|
||||
content:
|
||||
application/problem+json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetail'
|
||||
example:
|
||||
type: https://api.acme.dev/problems/validation-error
|
||||
title: Validation Error
|
||||
status: 422
|
||||
errors:
|
||||
- field: email
|
||||
message: Must be a valid email address.
|
||||
code: invalid_format
|
||||
|
||||
Conflict:
|
||||
description: Resource conflict
|
||||
content:
|
||||
application/problem+json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetail'
|
||||
|
||||
PayloadTooLarge:
|
||||
description: Request payload exceeds limit
|
||||
content:
|
||||
application/problem+json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ProblemDetail'
|
||||
```
|
||||
|
||||
Use the `application/problem+json` media type for all error responses to signal
|
||||
RFC 7807 compliance.
|
||||
|
||||
---
|
||||
|
||||
### 5. Authentication Schemes
|
||||
|
||||
#### Bearer Token (JWT)
|
||||
|
||||
```yaml
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
security:
|
||||
- BearerAuth: []
|
||||
```
|
||||
|
||||
Override per-operation to allow unauthenticated access:
|
||||
|
||||
```yaml
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
security: [] # No auth required
|
||||
responses:
|
||||
'200':
|
||||
description: Healthy
|
||||
```
|
||||
|
||||
#### API Key
|
||||
|
||||
```yaml
|
||||
components:
|
||||
securitySchemes:
|
||||
ApiKeyHeader:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: X-API-Key
|
||||
ApiKeyQuery:
|
||||
type: apiKey
|
||||
in: query
|
||||
name: api_key
|
||||
```
|
||||
|
||||
#### OAuth2 Flows
|
||||
|
||||
```yaml
|
||||
components:
|
||||
securitySchemes:
|
||||
OAuth2:
|
||||
type: oauth2
|
||||
flows:
|
||||
authorizationCode:
|
||||
authorizationUrl: https://auth.acme.dev/authorize
|
||||
tokenUrl: https://auth.acme.dev/token
|
||||
refreshUrl: https://auth.acme.dev/token
|
||||
scopes:
|
||||
users:read: Read user profiles
|
||||
users:write: Create and update users
|
||||
orders:read: Read orders
|
||||
|
||||
paths:
|
||||
/users:
|
||||
get:
|
||||
summary: List users
|
||||
security:
|
||||
- OAuth2: [users:read]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Pagination Patterns
|
||||
|
||||
#### Cursor-Based Pagination (Recommended)
|
||||
|
||||
Best for large, real-time datasets where rows may be inserted or deleted
|
||||
between pages.
|
||||
|
||||
```yaml
|
||||
components:
|
||||
parameters:
|
||||
PageCursor:
|
||||
name: cursor
|
||||
in: query
|
||||
description: Opaque cursor returned by a previous response.
|
||||
schema:
|
||||
type: string
|
||||
PageSize:
|
||||
name: limit
|
||||
in: query
|
||||
description: Maximum items per page.
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
default: 20
|
||||
|
||||
schemas:
|
||||
UserListResponse:
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
- pagination
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
pagination:
|
||||
type: object
|
||||
required:
|
||||
- hasMore
|
||||
properties:
|
||||
nextCursor:
|
||||
type: string
|
||||
nullable: true
|
||||
hasMore:
|
||||
type: boolean
|
||||
```
|
||||
|
||||
#### Offset-Based Pagination
|
||||
|
||||
Simpler but less efficient for large tables and susceptible to drift when data
|
||||
changes between requests.
|
||||
|
||||
```yaml
|
||||
components:
|
||||
parameters:
|
||||
PageOffset:
|
||||
name: offset
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 0
|
||||
PageLimit:
|
||||
name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
default: 20
|
||||
|
||||
schemas:
|
||||
PaginatedResponse:
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
- total
|
||||
- offset
|
||||
- limit
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items: {}
|
||||
total:
|
||||
type: integer
|
||||
description: Total number of matching records.
|
||||
offset:
|
||||
type: integer
|
||||
limit:
|
||||
type: integer
|
||||
```
|
||||
|
||||
#### Response Envelope Pattern
|
||||
|
||||
Wrap every collection in a consistent envelope so clients always know where to
|
||||
find the data and metadata:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [ ... ],
|
||||
"pagination": { "nextCursor": "abc123", "hasMore": true },
|
||||
"meta": { "requestId": "req_xyz", "timestamp": "2026-03-29T12:00:00Z" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. API Versioning
|
||||
|
||||
#### URL Versioning
|
||||
|
||||
```yaml
|
||||
servers:
|
||||
- url: https://api.acme.dev/v1
|
||||
description: Version 1 (deprecated)
|
||||
- url: https://api.acme.dev/v2
|
||||
description: Version 2 (current)
|
||||
```
|
||||
|
||||
Pros: explicit, easy to route, cache-friendly.
|
||||
Cons: duplicates paths across versions, harder to share schemas.
|
||||
|
||||
#### Header Versioning
|
||||
|
||||
```yaml
|
||||
parameters:
|
||||
- name: X-API-Version
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum: ['2024-01-15', '2025-06-01']
|
||||
default: '2025-06-01'
|
||||
description: Date-based API version. Defaults to latest stable.
|
||||
```
|
||||
|
||||
Pros: clean URLs, fine-grained control.
|
||||
Cons: less discoverable, harder to test in a browser.
|
||||
|
||||
#### Trade-offs Summary
|
||||
|
||||
| Approach | Discoverability | URL cleanliness | Caching | Migration effort |
|
||||
|----------|----------------|-----------------|---------|-----------------|
|
||||
| URL path | High | Lower | Easy | Higher (path changes) |
|
||||
| Header | Lower | High | Needs Vary header | Lower |
|
||||
| Query param | Medium | Medium | Easy | Lower |
|
||||
|
||||
Pick one approach and use it consistently. URL versioning is the most common
|
||||
choice for public APIs; header versioning suits internal services.
|
||||
|
||||
---
|
||||
|
||||
### 8. Webhook Specifications
|
||||
|
||||
OpenAPI 3.1 supports a top-level `webhooks` key for documenting outbound
|
||||
event payloads your API will send to consumer-registered URLs.
|
||||
|
||||
```yaml
|
||||
webhooks:
|
||||
orderCompleted:
|
||||
post:
|
||||
operationId: onOrderCompleted
|
||||
summary: Fired when an order reaches "completed" status.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OrderCompletedEvent'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: Webhook received successfully.
|
||||
|
||||
components:
|
||||
schemas:
|
||||
User:
|
||||
WebhookEventBase:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- type
|
||||
- createdAt
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
email:
|
||||
format: uuid
|
||||
type:
|
||||
type: string
|
||||
format: email
|
||||
required:
|
||||
- id
|
||||
- email
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
OrderCompletedEvent:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/WebhookEventBase'
|
||||
- type: object
|
||||
required:
|
||||
- data
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
const: order.completed
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
format: uuid
|
||||
total:
|
||||
type: number
|
||||
format: double
|
||||
currency:
|
||||
type: string
|
||||
example: USD
|
||||
```
|
||||
|
||||
### Request Body
|
||||
Document a shared `WebhookEventBase` so all event payloads have a consistent
|
||||
envelope with `id`, `type`, and `createdAt`.
|
||||
|
||||
```yaml
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateUser'
|
||||
example:
|
||||
email: user@example.com
|
||||
name: John
|
||||
```
|
||||
|
||||
### Error Responses
|
||||
|
||||
```yaml
|
||||
responses:
|
||||
'400':
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
```
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Use $ref for reusable schemas
|
||||
2. Include examples
|
||||
3. Document all error responses
|
||||
4. Use proper HTTP status codes
|
||||
5. Add security schemes
|
||||
1. **Use consistent, plural resource names.** `/users`, `/orders`, `/invoices`
|
||||
-- never mix singular and plural within the same API.
|
||||
|
||||
2. **Make mutating operations idempotent.** Accept an `Idempotency-Key` header
|
||||
on POST endpoints so clients can safely retry without creating duplicates.
|
||||
|
||||
3. **Return rate-limit headers on every response.** Include `X-RateLimit-Limit`,
|
||||
`X-RateLimit-Remaining`, and `X-RateLimit-Reset` so clients can self-throttle.
|
||||
|
||||
4. **Provide `operationId` for every operation.** Code generators use this as
|
||||
the method name; without it, generated clients have meaningless names.
|
||||
|
||||
5. **Include realistic examples in the spec.** Examples power documentation UIs,
|
||||
mock servers, and contract tests. Add them at both the schema and operation
|
||||
level.
|
||||
|
||||
6. **Use `additionalProperties: false` on request schemas.** This catches typos
|
||||
in client payloads early and prevents silently ignored fields.
|
||||
|
||||
7. **Document hypermedia links (HATEOAS basics).** Even a minimal `_links`
|
||||
object with `self` and `next` URIs helps clients navigate without hardcoding
|
||||
paths.
|
||||
|
||||
8. **Version your spec file alongside code.** Store the OpenAPI document in the
|
||||
same repository as the implementation. Run a CI check (e.g., `redocly lint`)
|
||||
to validate the spec on every pull request.
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Missing examples**: Add realistic examples
|
||||
- **No error docs**: Document all errors
|
||||
- **Inconsistent naming**: Use consistent conventions
|
||||
1. **Missing error documentation.** Every operation should list its possible
|
||||
`4xx` and `5xx` responses. Consumers cannot handle errors they do not know
|
||||
about. At minimum document `400`, `401`, `403`, `404`, and `500`.
|
||||
|
||||
2. **Overusing `200 OK` for everything.** Return `201` for resource creation,
|
||||
`204` for deletion, and `202` for asynchronous actions. Correct status codes
|
||||
let generic HTTP clients behave properly (e.g., following `Location` headers).
|
||||
|
||||
3. **Deeply nested resource URLs.** `/users/{uid}/orders/{oid}/items/{iid}/notes`
|
||||
is fragile and hard to cache. Flatten to `/order-items/{iid}/notes` once the
|
||||
relationship is established.
|
||||
|
||||
4. **Inconsistent naming conventions.** Mixing `camelCase` and `snake_case`
|
||||
within the same API confuses consumers. Pick one JSON field casing and enforce
|
||||
it with a linter rule.
|
||||
|
||||
5. **Ignoring `nullable` vs optional.** In OpenAPI 3.1, `nullable` is gone;
|
||||
use `type: ["string", "null"]` instead. A field that is not in `required`
|
||||
may be absent, but that is different from being explicitly `null`. Be precise
|
||||
about which you intend.
|
||||
|
||||
6. **No pagination on list endpoints.** Returning unbounded arrays will
|
||||
eventually cause timeouts or OOM errors. Every collection endpoint should
|
||||
accept `limit` and either `cursor` or `offset` from day one, even if the
|
||||
dataset is currently small.
|
||||
|
||||
---
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `patterns/api-client` - Patterns for consuming and generating API clients from specs
|
||||
- `patterns/error-handling` - Consistent error response structures and handling
|
||||
- `frameworks/fastapi` - FastAPI framework with built-in OpenAPI generation
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
# HTTP Status Codes for REST APIs
|
||||
|
||||
Quick reference for selecting the correct HTTP status code in REST API responses.
|
||||
|
||||
---
|
||||
|
||||
## 2xx Success
|
||||
|
||||
| Code | Name | When to Use |
|
||||
|------|------|-------------|
|
||||
| `200` | OK | General success. GET returns data, PUT/PATCH returns updated resource. |
|
||||
| `201` | Created | POST successfully created a resource. Include `Location` header. |
|
||||
| `202` | Accepted | Request accepted for async processing. Return a job/task ID. |
|
||||
| `204` | No Content | DELETE success or PUT/PATCH with no response body needed. |
|
||||
|
||||
**Guidelines:**
|
||||
- `200` is the default success response for GET, PUT, PATCH
|
||||
- `201` must be used when a new resource is created (POST)
|
||||
- `204` is preferred for DELETE (no body to return)
|
||||
- `202` signals "we got it, processing later" -- return a status URL
|
||||
|
||||
```json
|
||||
// 201 Created response
|
||||
{
|
||||
"id": "usr_abc123",
|
||||
"name": "Jane Doe",
|
||||
"created_at": "2025-01-15T10:30:00Z"
|
||||
}
|
||||
// Header: Location: /api/v1/users/usr_abc123
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3xx Redirection
|
||||
|
||||
| Code | Name | When to Use |
|
||||
|------|------|-------------|
|
||||
| `301` | Moved Permanently | Resource URL changed permanently. Clients should update bookmarks. |
|
||||
| `302` | Found | Temporary redirect. Original URL still valid. |
|
||||
| `304` | Not Modified | Conditional GET -- resource unchanged since `If-None-Match`/`If-Modified-Since`. |
|
||||
| `307` | Temporary Redirect | Like 302 but preserves HTTP method. Use for API redirects. |
|
||||
| `308` | Permanent Redirect | Like 301 but preserves HTTP method. |
|
||||
|
||||
**Guidelines:**
|
||||
- Prefer `307`/`308` over `302`/`301` in APIs (method preservation)
|
||||
- `304` reduces bandwidth when clients cache responses
|
||||
- Always include `Location` header with redirect responses
|
||||
|
||||
---
|
||||
|
||||
## 4xx Client Errors
|
||||
|
||||
| Code | Name | When to Use |
|
||||
|------|------|-------------|
|
||||
| `400` | Bad Request | Malformed syntax, invalid JSON, failed validation. |
|
||||
| `401` | Unauthorized | Missing or invalid authentication credentials. |
|
||||
| `403` | Forbidden | Authenticated but lacks permission for this resource. |
|
||||
| `404` | Not Found | Resource does not exist at this URL. |
|
||||
| `405` | Method Not Allowed | HTTP method not supported on this endpoint. |
|
||||
| `409` | Conflict | Request conflicts with current state (duplicate, version mismatch). |
|
||||
| `410` | Gone | Resource existed but has been permanently deleted. |
|
||||
| `415` | Unsupported Media Type | Content-Type header not supported. |
|
||||
| `422` | Unprocessable Entity | Valid JSON but semantically invalid (business rule violation). |
|
||||
| `429` | Too Many Requests | Rate limit exceeded. Include `Retry-After` header. |
|
||||
|
||||
**Guidelines:**
|
||||
- `400` for structural issues (bad JSON, missing required fields)
|
||||
- `422` for business logic failures (email already taken, invalid state transition)
|
||||
- `401` means "who are you?" -- `403` means "I know who you are, but no"
|
||||
- `409` for optimistic locking failures and unique constraint violations
|
||||
- `429` must include `Retry-After` header with seconds until retry
|
||||
|
||||
```json
|
||||
// 422 Unprocessable Entity
|
||||
{
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Request validation failed",
|
||||
"details": [
|
||||
{ "field": "email", "message": "Email already registered" },
|
||||
{ "field": "age", "message": "Must be 18 or older" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// 429 Too Many Requests
|
||||
// Header: Retry-After: 60
|
||||
{
|
||||
"error": {
|
||||
"code": "RATE_LIMITED",
|
||||
"message": "Rate limit exceeded. Try again in 60 seconds."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5xx Server Errors
|
||||
|
||||
| Code | Name | When to Use |
|
||||
|------|------|-------------|
|
||||
| `500` | Internal Server Error | Unhandled exception. Generic server failure. |
|
||||
| `501` | Not Implemented | Endpoint exists but functionality not built yet. |
|
||||
| `502` | Bad Gateway | Upstream service returned invalid response. |
|
||||
| `503` | Service Unavailable | Server overloaded or in maintenance. Include `Retry-After`. |
|
||||
| `504` | Gateway Timeout | Upstream service did not respond in time. |
|
||||
|
||||
**Guidelines:**
|
||||
- `500` should never expose stack traces in production
|
||||
- `503` should include `Retry-After` header and a maintenance message
|
||||
- Log all 5xx errors with request context for debugging
|
||||
- Return a consistent error body format for all 5xx responses
|
||||
|
||||
```json
|
||||
// 500 Internal Server Error (production)
|
||||
{
|
||||
"error": {
|
||||
"code": "INTERNAL_ERROR",
|
||||
"message": "An unexpected error occurred. Please try again.",
|
||||
"request_id": "req_7f3a9b2c"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decision Flowchart
|
||||
|
||||
```
|
||||
Request received
|
||||
|
|
||||
+-- Is it valid syntax? -- NO --> 400 Bad Request
|
||||
|
|
||||
+-- Is caller authenticated? -- NO --> 401 Unauthorized
|
||||
|
|
||||
+-- Is caller authorized? -- NO --> 403 Forbidden
|
||||
|
|
||||
+-- Does resource exist? -- NO --> 404 Not Found
|
||||
|
|
||||
+-- Is it rate-limited? -- YES --> 429 Too Many Requests
|
||||
|
|
||||
+-- Does it pass business rules? -- NO --> 422 Unprocessable Entity
|
||||
|
|
||||
+-- Any conflicts? -- YES --> 409 Conflict
|
||||
|
|
||||
+-- Server error? -- YES --> 500 Internal Server Error
|
||||
|
|
||||
+-- Success!
|
||||
GET --> 200 OK
|
||||
POST --> 201 Created
|
||||
PUT --> 200 OK
|
||||
PATCH --> 200 OK
|
||||
DELETE --> 204 No Content
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Standard Error Response Format
|
||||
|
||||
Use a consistent structure across all error responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "MACHINE_READABLE_CODE",
|
||||
"message": "Human-readable description",
|
||||
"details": [],
|
||||
"request_id": "req_..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*Reference: [RFC 9110 - HTTP Semantics](https://httpwg.org/specs/rfc9110.html), [RFC 9457 - Problem Details](https://www.rfc-editor.org/rfc/rfc9457)*
|
||||
@@ -0,0 +1,196 @@
|
||||
# REST API Naming Conventions
|
||||
|
||||
Guidelines for consistent, predictable REST endpoint design.
|
||||
|
||||
---
|
||||
|
||||
## Core Rules
|
||||
|
||||
1. **Use plural nouns** for resource collections
|
||||
2. **Use kebab-case** for multi-word resources
|
||||
3. **Use path parameters** for identity, query parameters for filtering
|
||||
4. **Never use verbs** in URLs (HTTP methods convey the action)
|
||||
5. **Use lowercase** exclusively
|
||||
|
||||
---
|
||||
|
||||
## Resource Naming
|
||||
|
||||
| Pattern | Example | Notes |
|
||||
|---------|---------|-------|
|
||||
| Collection | `/users` | Plural noun |
|
||||
| Single resource | `/users/{id}` | Path parameter |
|
||||
| Multi-word resource | `/order-items` | Kebab-case |
|
||||
| Nested resource | `/users/{id}/orders` | Parent-child relationship |
|
||||
| Deep nesting (avoid) | `/users/{id}/orders/{oid}/items` | Max 2 levels deep |
|
||||
| Singleton sub-resource | `/users/{id}/profile` | One-to-one relationship |
|
||||
|
||||
### Good
|
||||
|
||||
```
|
||||
GET /users
|
||||
GET /users/123
|
||||
POST /users
|
||||
PUT /users/123
|
||||
DELETE /users/123
|
||||
GET /users/123/orders
|
||||
GET /order-items
|
||||
GET /user-profiles/123
|
||||
```
|
||||
|
||||
### Bad
|
||||
|
||||
```
|
||||
GET /getUsers # verb in URL
|
||||
GET /user/123 # singular collection
|
||||
GET /Users # uppercase
|
||||
POST /users/create # redundant verb
|
||||
GET /user_profiles # snake_case
|
||||
DELETE /users/123/delete # verb in URL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CRUD Mapping
|
||||
|
||||
| Action | Method | Endpoint | Request Body | Response |
|
||||
|--------|--------|----------|-------------|----------|
|
||||
| List | `GET` | `/resources` | None | `200` + array |
|
||||
| Create | `POST` | `/resources` | Resource data | `201` + created |
|
||||
| Read | `GET` | `/resources/{id}` | None | `200` + object |
|
||||
| Update (full) | `PUT` | `/resources/{id}` | Full resource | `200` + updated |
|
||||
| Update (partial) | `PATCH` | `/resources/{id}` | Partial data | `200` + updated |
|
||||
| Delete | `DELETE` | `/resources/{id}` | None | `204` |
|
||||
|
||||
---
|
||||
|
||||
## Nested Resources
|
||||
|
||||
Use nesting to express clear parent-child relationships.
|
||||
|
||||
```
|
||||
# User's orders (user owns orders)
|
||||
GET /users/{userId}/orders
|
||||
POST /users/{userId}/orders
|
||||
|
||||
# Order's line items
|
||||
GET /orders/{orderId}/items
|
||||
```
|
||||
|
||||
**When to nest vs. top-level:**
|
||||
|
||||
| Scenario | Approach | Example |
|
||||
|----------|----------|---------|
|
||||
| Resource only exists under parent | Nest | `/users/{id}/sessions` |
|
||||
| Resource is independently accessible | Top-level with filter | `/orders?user_id=123` |
|
||||
| Shallow relationship | Top-level | `/comments?post_id=456` |
|
||||
|
||||
**Rule of thumb:** Never nest more than 2 levels. Use query parameters or top-level endpoints instead.
|
||||
|
||||
```
|
||||
# Too deep -- avoid
|
||||
GET /users/{id}/orders/{oid}/items/{iid}/reviews
|
||||
|
||||
# Better alternatives
|
||||
GET /order-items/{iid}/reviews
|
||||
GET /reviews?order_item_id={iid}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Query Parameters
|
||||
|
||||
### Filtering
|
||||
|
||||
```
|
||||
GET /products?category=electronics&brand=acme
|
||||
GET /users?status=active&role=admin
|
||||
GET /orders?created_after=2025-01-01&created_before=2025-02-01
|
||||
```
|
||||
|
||||
| Convention | Example |
|
||||
|-----------|---------|
|
||||
| Exact match | `?status=active` |
|
||||
| Date range | `?created_after=2025-01-01` |
|
||||
| Multiple values | `?status=active,pending` |
|
||||
| Search | `?q=search+term` |
|
||||
|
||||
### Sorting
|
||||
|
||||
```
|
||||
GET /products?sort=price # ascending (default)
|
||||
GET /products?sort=-price # descending (prefix -)
|
||||
GET /products?sort=-created_at,name # multi-field
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
```
|
||||
# Offset-based (simple, common)
|
||||
GET /products?page=2&per_page=25
|
||||
|
||||
# Cursor-based (better for large datasets)
|
||||
GET /products?cursor=eyJpZCI6MTAwfQ&limit=25
|
||||
```
|
||||
|
||||
**Response envelope for paginated results:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [...],
|
||||
"pagination": {
|
||||
"page": 2,
|
||||
"per_page": 25,
|
||||
"total": 150,
|
||||
"total_pages": 6
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Selection
|
||||
|
||||
```
|
||||
GET /users/123?fields=id,name,email
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Non-CRUD Actions
|
||||
|
||||
Some operations do not map cleanly to CRUD. Use sub-resources with a noun or POST with an action resource.
|
||||
|
||||
| Action | Approach | Example |
|
||||
|--------|----------|---------|
|
||||
| Send an email | POST to action resource | `POST /users/{id}/verification-email` |
|
||||
| Archive | PATCH with status | `PATCH /orders/{id} { "status": "archived" }` |
|
||||
| Bulk delete | POST to action | `POST /users/bulk-delete { "ids": [...] }` |
|
||||
| Export | GET with format | `GET /reports/sales?format=csv` |
|
||||
| Search (complex) | POST with body | `POST /products/search { "filters": {...} }` |
|
||||
|
||||
---
|
||||
|
||||
## Versioning
|
||||
|
||||
| Strategy | Example | Pros | Cons |
|
||||
|----------|---------|------|------|
|
||||
| URL path | `/api/v1/users` | Simple, explicit | URL pollution |
|
||||
| Header | `Accept: application/vnd.api.v1+json` | Clean URLs | Hidden |
|
||||
| Query param | `/users?version=1` | Easy to test | Caching issues |
|
||||
|
||||
**Recommended:** URL path versioning (`/api/v1/`) for public APIs due to simplicity.
|
||||
|
||||
---
|
||||
|
||||
## Summary Checklist
|
||||
|
||||
- [ ] Resources are plural nouns (`/users` not `/user`)
|
||||
- [ ] URLs are kebab-case and lowercase
|
||||
- [ ] No verbs in URLs
|
||||
- [ ] Nesting limited to 2 levels
|
||||
- [ ] Filtering uses query parameters
|
||||
- [ ] Sorting supports `-field` for descending
|
||||
- [ ] Pagination included on all list endpoints
|
||||
- [ ] API version in URL path for public APIs
|
||||
- [ ] Consistent error response format
|
||||
|
||||
*Reference: [Google API Design Guide](https://cloud.google.com/apis/design), [Microsoft REST Guidelines](https://github.com/microsoft/api-guidelines)*
|
||||
@@ -0,0 +1,240 @@
|
||||
openapi: "3.1.0"
|
||||
info:
|
||||
title: My API
|
||||
description: Starter API specification. Replace with your project details.
|
||||
version: "1.0.0"
|
||||
contact:
|
||||
name: API Support
|
||||
email: support@example.com
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development
|
||||
- url: https://api.example.com/v1
|
||||
description: Production
|
||||
|
||||
tags:
|
||||
- name: Users
|
||||
description: User management
|
||||
- name: Health
|
||||
description: Service health checks
|
||||
|
||||
paths:
|
||||
/health:
|
||||
get:
|
||||
tags: [Health]
|
||||
summary: Health check
|
||||
operationId: getHealth
|
||||
responses:
|
||||
"200":
|
||||
description: Service is healthy
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status: { type: string, example: ok }
|
||||
timestamp: { type: string, format: date-time }
|
||||
|
||||
/users:
|
||||
get:
|
||||
tags: [Users]
|
||||
summary: List users
|
||||
operationId: listUsers
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/PageParam"
|
||||
- $ref: "#/components/parameters/PerPageParam"
|
||||
- $ref: "#/components/parameters/SortParam"
|
||||
- name: status
|
||||
in: query
|
||||
schema: { type: string, enum: [active, inactive] }
|
||||
responses:
|
||||
"200":
|
||||
description: Paginated list of users
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/User" }
|
||||
pagination: { $ref: "#/components/schemas/Pagination" }
|
||||
"401": { $ref: "#/components/responses/Unauthorized" }
|
||||
|
||||
post:
|
||||
tags: [Users]
|
||||
summary: Create a user
|
||||
operationId: createUser
|
||||
security: [{ bearerAuth: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/CreateUserRequest" }
|
||||
responses:
|
||||
"201":
|
||||
description: User created
|
||||
headers:
|
||||
Location: { schema: { type: string }, description: URL of created user }
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/User" }
|
||||
"400": { $ref: "#/components/responses/BadRequest" }
|
||||
"401": { $ref: "#/components/responses/Unauthorized" }
|
||||
"422": { $ref: "#/components/responses/ValidationError" }
|
||||
|
||||
/users/{userId}:
|
||||
parameters:
|
||||
- name: userId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
|
||||
get:
|
||||
tags: [Users]
|
||||
summary: Get a user by ID
|
||||
operationId: getUser
|
||||
security: [{ bearerAuth: [] }]
|
||||
responses:
|
||||
"200":
|
||||
description: User details
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/User" }
|
||||
"401": { $ref: "#/components/responses/Unauthorized" }
|
||||
"404": { $ref: "#/components/responses/NotFound" }
|
||||
|
||||
patch:
|
||||
tags: [Users]
|
||||
summary: Update a user
|
||||
operationId: updateUser
|
||||
security: [{ bearerAuth: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/UpdateUserRequest" }
|
||||
responses:
|
||||
"200":
|
||||
description: User updated
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/User" }
|
||||
"401": { $ref: "#/components/responses/Unauthorized" }
|
||||
"404": { $ref: "#/components/responses/NotFound" }
|
||||
"422": { $ref: "#/components/responses/ValidationError" }
|
||||
|
||||
delete:
|
||||
tags: [Users]
|
||||
summary: Delete a user
|
||||
operationId: deleteUser
|
||||
security: [{ bearerAuth: [] }]
|
||||
responses:
|
||||
"204": { description: User deleted }
|
||||
"401": { $ref: "#/components/responses/Unauthorized" }
|
||||
"404": { $ref: "#/components/responses/NotFound" }
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT }
|
||||
apiKeyAuth: { type: apiKey, in: header, name: X-API-Key }
|
||||
|
||||
parameters:
|
||||
PageParam:
|
||||
name: page
|
||||
in: query
|
||||
schema: { type: integer, minimum: 1, default: 1 }
|
||||
PerPageParam:
|
||||
name: per_page
|
||||
in: query
|
||||
schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
|
||||
SortParam:
|
||||
name: sort
|
||||
in: query
|
||||
description: "Field to sort by. Prefix with - for descending."
|
||||
schema: { type: string, example: "-created_at" }
|
||||
|
||||
schemas:
|
||||
User:
|
||||
type: object
|
||||
required: [id, email, name, status, created_at]
|
||||
properties:
|
||||
id: { type: string, example: usr_abc123 }
|
||||
email: { type: string, format: email, example: jane@example.com }
|
||||
name: { type: string, example: Jane Doe }
|
||||
status: { type: string, enum: [active, inactive] }
|
||||
created_at: { type: string, format: date-time }
|
||||
updated_at: { type: string, format: date-time }
|
||||
|
||||
CreateUserRequest:
|
||||
type: object
|
||||
required: [email, name]
|
||||
properties:
|
||||
email: { type: string, format: email }
|
||||
name: { type: string, minLength: 1, maxLength: 100 }
|
||||
role: { type: string, enum: [user, admin], default: user }
|
||||
|
||||
UpdateUserRequest:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string, minLength: 1, maxLength: 100 }
|
||||
status: { type: string, enum: [active, inactive] }
|
||||
|
||||
Pagination:
|
||||
type: object
|
||||
properties:
|
||||
page: { type: integer }
|
||||
per_page: { type: integer }
|
||||
total: { type: integer }
|
||||
total_pages: { type: integer }
|
||||
|
||||
Error:
|
||||
type: object
|
||||
required: [error]
|
||||
properties:
|
||||
error:
|
||||
type: object
|
||||
required: [code, message]
|
||||
properties:
|
||||
code: { type: string }
|
||||
message: { type: string }
|
||||
details:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
field: { type: string }
|
||||
message: { type: string }
|
||||
request_id: { type: string }
|
||||
|
||||
responses:
|
||||
BadRequest:
|
||||
description: Bad request
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Error" }
|
||||
example: { error: { code: BAD_REQUEST, message: Malformed request body } }
|
||||
Unauthorized:
|
||||
description: Authentication required
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Error" }
|
||||
example: { error: { code: UNAUTHORIZED, message: Missing or invalid token } }
|
||||
NotFound:
|
||||
description: Resource not found
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Error" }
|
||||
example: { error: { code: NOT_FOUND, message: Resource not found } }
|
||||
ValidationError:
|
||||
description: Validation failed
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Error" }
|
||||
example:
|
||||
error:
|
||||
code: VALIDATION_ERROR
|
||||
message: Request validation failed
|
||||
details: [{ field: email, message: Email already registered }]
|
||||
Reference in New Issue
Block a user