
Api Design Framework
- 13 installs
- 213 repo stars
- Updated August 4, 2026
- yonatangross/orchestkit
Helps with backend & apis tasks.
About
api-design-framework is a Claude Code skill for backend & apis. It helps solo builders move faster with AI-assisted coding.
- api-design-framework
- Backend & APIs
- AI-coding skill
Api Design Framework by the numbers
- 13 all-time installs (skills.sh)
- Ranked #3,516 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/yonatangross/orchestkit --skill api-design-frameworkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 13 |
|---|---|
| repo stars | ★ 213 |
| Last updated | August 4, 2026 |
| Repository | yonatangross/orchestkit ↗ |
What it does
Helps with backend & apis tasks.
Files
API Design Framework
This skill provides comprehensive guidance for designing robust, scalable, and developer-friendly APIs. Whether building REST, GraphQL, or gRPC services, this framework ensures consistency, usability, and maintainability.
Overview
- Designing new API endpoints or services
- Establishing API conventions for a team or organization
- Reviewing API designs for consistency and best practices
- Migrating or versioning existing APIs
- Creating API documentation (OpenAPI, AsyncAPI)
- Choosing between REST, GraphQL, or gRPC
API Design Principles
1. Developer Experience First
APIs should be intuitive and self-documenting:
- Clear, consistent naming conventions
- Predictable behavior and responses
- Comprehensive documentation
- Helpful error messages
2. Consistency Over Cleverness
Follow established patterns rather than inventing new ones:
- Standard HTTP methods and status codes (REST)
- Conventional query structures (GraphQL)
- Idiomatic proto definitions (gRPC)
3. Evolution Without Breaking Changes
Design for change from day one:
- API versioning strategy
- Backward compatibility considerations
- Deprecation policies
- Migration paths
4. Performance by Design
Consider performance implications:
- Pagination for large datasets
- Filtering and partial responses
- Caching strategies
- Rate limiting
---
Bundled Resources
assets/openapi-template.yaml- OpenAPI 3.1 specification templateassets/asyncapi-template.yaml- AsyncAPI specification templatereferences/rest-api.md- REST API design patternsreferences/graphql-api.md- GraphQL API design patternsreferences/grpc-api.md- gRPC API design patternsreferences/frontend-integration.md- Frontend API integration patterns
---
Protocol References
REST API Design
See: `references/rest-api.md`
Key topics covered:
- Resource naming conventions (plural nouns, hierarchical relationships)
- HTTP methods (GET, POST, PUT, PATCH, DELETE)
- Status codes (2xx, 4xx, 5xx)
- Request/response formats
- Pagination (cursor-based vs offset-based)
- Filtering, sorting, field selection
- API versioning strategies
- Rate limiting headers
- Authentication patterns (Bearer, API Key)
GraphQL API Design
See: `references/graphql-api.md`
Key topics covered:
- Schema design principles (nullable by default)
- Connection pattern for lists (edges, nodes, pageInfo)
- Input types for mutations
- Query design patterns
- Field-level error handling
gRPC API Design
See: `references/grpc-api.md`
Key topics covered:
- Proto file structure
- Service and message definitions
- gRPC status codes mapping to HTTP equivalents
Frontend API Integration
See: `references/frontend-integration.md`
Key topics covered:
- Runtime validation with Zod
- Request interceptors with ky
- Error enrichment pattern
- TanStack Query integration
---
Quick Reference: HTTP Status Codes
| Code | Name | Use Case |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid request |
| 401 | Unauthorized | Missing auth |
| 403 | Forbidden | No permission |
| 404 | Not Found | Resource missing |
| 409 | Conflict | Duplicate |
| 422 | Unprocessable | Validation failed |
| 429 | Too Many Requests | Rate limited |
| 500 | Internal Error | Server error |
Quick Reference: Error Response Format
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "email", "message": "Email is already registered" }
],
"request_id": "req_abc123"
}
}---
Common Pitfalls
| Pitfall | Bad | Good |
|---|---|---|
| Verbs in URLs | POST /createUser | POST /users |
| Inconsistent naming | /users, /userOrders | /users, /orders |
| Ignoring HTTP methods | POST /users/123/delete | DELETE /users/123 |
| Exposing internals | /users-table | /users |
| Generic errors | "Something went wrong" | "Email already exists" |
---
Best Practices Summary
1. Use plural nouns for resources: /users, /orders 2. Use kebab-case for multi-word: /user-preferences 3. Use hierarchical URLs: /users/123/orders 4. Cursor pagination for large datasets 5. URI versioning for public APIs: /api/v1/users 6. Include rate limit headers in responses 7. Validate with Zod on frontend boundary 8. Include request_id in error responses
---
Integration with Agents
| Agent | Usage |
|---|---|
| backend-system-architect | Designs new APIs using this framework |
| frontend-ui-developer | Reviews contracts, integrates with APIs |
| code-quality-reviewer | Validates API designs against standards |
---
Related Skills
fastapi-advanced- FastAPI-specific implementation patterns for the API designs in this skillerror-handling-rfc9457- RFC 9457 Problem Details standard for structured error responsesapi-versioning- Detailed versioning strategies beyond the basics covered hererate-limiting- Advanced rate limiting implementations and algorithms
Key Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Pagination default | Cursor-based | More efficient for large datasets, stable under inserts/deletes |
| Error format | Structured JSON with request_id | Enables debugging, correlation, and consistent client handling |
| Versioning strategy | URI path (/api/v1/) | Most explicit, works with all clients, easy to document |
| Resource naming | Plural nouns, kebab-case | Industry standard, consistent, avoids verb confusion |
---
Skill Version: 1.2.0 Last Updated: 2026-01-14
Changelog
v1.2.0 (2026-01-14)
- Split into reference files for progressive loading
- Added
references/rest-api.md - Added
references/graphql-api.md - Added
references/grpc-api.md - Added
references/frontend-integration.md - Fixed malformed YAML frontmatter
v1.1.0 (2025-12-29)
- Added Frontend API Integration section
- Added Zod runtime validation patterns
- Added request interceptors with ky
- Added TanStack Query integration examples
Capability Details
rest-design
Keywords: rest, restful, http, endpoint, route, path, resource, CRUD Solves:
- How do I design RESTful APIs?
- REST endpoint patterns and conventions
- HTTP methods and status codes
- API versioning and pagination
graphql-design
Keywords: graphql, schema, query, mutation, connection, relay Solves:
- How do I design GraphQL APIs?
- Schema design best practices
- Connection pattern for pagination
grpc-design
Keywords: grpc, protobuf, proto, rpc, streaming Solves:
- How do I design gRPC services?
- Proto file structure
- gRPC status codes
endpoint-design
Keywords: endpoint, route, path, resource, CRUD Solves:
- How do I structure API endpoints?
- What's the best URL pattern for this resource?
- RESTful endpoint naming conventions
pagination
Keywords: pagination, paginate, paging, offset, cursor, limit Solves:
- How do I add pagination to an endpoint?
- Cursor vs offset pagination
- Pagination best practices
versioning
Keywords: version, v1, v2, api version, breaking change Solves:
- How do I version my API?
- When to create a new API version
- URL vs header versioning
error-handling
Keywords: error, exception, status code, error response, validation error Solves:
- How do I structure error responses?
- Which HTTP status codes to use
- Error message best practices
rate-limiting
Keywords: rate limit, throttle, quota, requests per second, 429 Solves:
- How do I implement rate limiting?
- Rate limit headers and responses
- Tiered rate limiting strategies
authentication
Keywords: auth, authentication, bearer, jwt, oauth, api key Solves:
- How do I secure API endpoints?
- JWT vs API key authentication
- OAuth2 flow for APIs
frontend-integration
Keywords: zod, validation, fetch, ky, tanstack, react-query Solves:
- How do I consume APIs with type safety?
- Runtime validation of API responses
- Request interceptors and error handling
asyncapi: 3.0.0
# AsyncAPI specification for event-driven and message-based APIs
# Use for: Kafka, RabbitMQ, WebSockets, MQTT, Server-Sent Events
info:
title: Order Processing Events API
version: 1.0.0
description: |
Event-driven API for order processing system.
Publishes events when orders are created, updated, or completed.
Consumers can subscribe to relevant events for their services.
contact:
name: API Support Team
email: api-support@example.com
url: https://api.example.com/support
license:
name: MIT
url: https://opensource.org/licenses/MIT
# Server definitions
servers:
production:
host: kafka.production.example.com:9092
protocol: kafka
description: Production Kafka cluster
security:
- $ref: '#/components/securitySchemes/saslScram'
tags:
- name: env:production
- name: region:us-east-1
staging:
host: kafka.staging.example.com:9092
protocol: kafka
description: Staging Kafka cluster
tags:
- name: env:staging
development:
host: localhost:9092
protocol: kafka
description: Local Kafka for development
tags:
- name: env:development
# Channel definitions (topics, queues, etc.)
channels:
# Order events topic
order/events:
address: order.events.v1
description: All order lifecycle events
messages:
orderCreated:
$ref: '#/components/messages/OrderCreated'
orderUpdated:
$ref: '#/components/messages/OrderUpdated'
orderCompleted:
$ref: '#/components/messages/OrderCompleted'
orderCancelled:
$ref: '#/components/messages/OrderCancelled'
# Payment events topic
payment/events:
address: payment.events.v1
description: Payment processing events
messages:
paymentAuthorized:
$ref: '#/components/messages/PaymentAuthorized'
paymentCaptured:
$ref: '#/components/messages/PaymentCaptured'
paymentFailed:
$ref: '#/components/messages/PaymentFailed'
# Notification requests topic
notification/requests:
address: notification.requests.v1
description: Notification delivery requests
messages:
emailNotification:
$ref: '#/components/messages/EmailNotification'
smsNotification:
$ref: '#/components/messages/SMSNotification'
# Operations (publish/subscribe)
operations:
# Publishing operations
publishOrderCreated:
action: send
channel:
$ref: '#/channels/order~1events'
summary: Publish order created event
description: Triggered when a new order is placed
messages:
- $ref: '#/channels/order~1events/messages/orderCreated'
traits:
- $ref: '#/components/operationTraits/kafka'
publishPaymentAuthorized:
action: send
channel:
$ref: '#/channels/payment~1events'
summary: Publish payment authorized event
messages:
- $ref: '#/channels/payment~1events/messages/paymentAuthorized'
# Subscribing operations
subscribeToOrderEvents:
action: receive
channel:
$ref: '#/channels/order~1events'
summary: Subscribe to order events
description: |
Listen for order lifecycle events.
Consumers should implement idempotency (events may be delivered multiple times).
messages:
- $ref: '#/channels/order~1events/messages/orderCreated'
- $ref: '#/channels/order~1events/messages/orderUpdated'
- $ref: '#/channels/order~1events/messages/orderCompleted'
- $ref: '#/channels/order~1events/messages/orderCancelled'
subscribeToPaymentEvents:
action: receive
channel:
$ref: '#/channels/payment~1events'
summary: Subscribe to payment events
messages:
- $ref: '#/channels/payment~1events/messages/paymentAuthorized'
- $ref: '#/channels/payment~1events/messages/paymentCaptured'
- $ref: '#/channels/payment~1events/messages/paymentFailed'
# Reusable components
components:
# Message definitions
messages:
# Order messages
OrderCreated:
name: OrderCreated
title: Order Created Event
summary: Fired when a new order is placed
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/OrderCreatedPayload'
examples:
- name: Standard Order
summary: Typical order creation
payload:
eventId: evt_1a2b3c4d5e6f
eventType: order.created
timestamp: '2024-11-01T10:30:00Z'
version: '1.0'
data:
orderId: ord_abc123
customerId: cust_xyz789
items:
- productId: prod_001
quantity: 2
price: 29.99
totalAmount: 59.98
currency: USD
status: pending
createdAt: '2024-11-01T10:30:00Z'
OrderUpdated:
name: OrderUpdated
title: Order Updated Event
summary: Fired when order details change
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/OrderUpdatedPayload'
OrderCompleted:
name: OrderCompleted
title: Order Completed Event
summary: Fired when order is fulfilled
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/OrderCompletedPayload'
OrderCancelled:
name: OrderCancelled
title: Order Cancelled Event
summary: Fired when order is cancelled
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/OrderCancelledPayload'
# Payment messages
PaymentAuthorized:
name: PaymentAuthorized
title: Payment Authorized Event
summary: Payment authorization successful
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/PaymentAuthorizedPayload'
PaymentCaptured:
name: PaymentCaptured
title: Payment Captured Event
summary: Payment funds captured
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/PaymentCapturedPayload'
PaymentFailed:
name: PaymentFailed
title: Payment Failed Event
summary: Payment processing failed
contentType: application/json
traits:
- $ref: '#/components/messageTraits/commonHeaders'
payload:
$ref: '#/components/schemas/PaymentFailedPayload'
# Notification messages
EmailNotification:
name: EmailNotification
title: Email Notification Request
summary: Request to send email
contentType: application/json
payload:
$ref: '#/components/schemas/EmailNotificationPayload'
SMSNotification:
name: SMSNotification
title: SMS Notification Request
summary: Request to send SMS
contentType: application/json
payload:
$ref: '#/components/schemas/SMSNotificationPayload'
# Schema definitions
schemas:
# Base event schema
BaseEvent:
type: object
required:
- eventId
- eventType
- timestamp
- version
properties:
eventId:
type: string
description: Unique event identifier
example: evt_1a2b3c4d5e6f
eventType:
type: string
description: Type of event
example: order.created
timestamp:
type: string
format: date-time
description: Event occurrence time (ISO 8601)
version:
type: string
description: Event schema version
example: '1.0'
correlationId:
type: string
description: Request correlation ID for tracing
example: req_abc123xyz
metadata:
type: object
description: Additional metadata
additionalProperties: true
# Order event payloads
OrderCreatedPayload:
allOf:
- $ref: '#/components/schemas/BaseEvent'
- type: object
required:
- data
properties:
data:
type: object
required:
- orderId
- customerId
- items
- totalAmount
- currency
- status
- createdAt
properties:
orderId:
type: string
example: ord_abc123
customerId:
type: string
example: cust_xyz789
items:
type: array
items:
type: object
properties:
productId:
type: string
quantity:
type: integer
minimum: 1
price:
type: number
format: decimal
totalAmount:
type: number
format: decimal
currency:
type: string
pattern: '^[A-Z]{3}$'
example: USD
status:
type: string
enum: [pending, processing, completed, cancelled]
createdAt:
type: string
format: date-time
OrderUpdatedPayload:
allOf:
- $ref: '#/components/schemas/BaseEvent'
- type: object
properties:
data:
type: object
properties:
orderId:
type: string
updates:
type: object
description: Fields that were updated
additionalProperties: true
OrderCompletedPayload:
allOf:
- $ref: '#/components/schemas/BaseEvent'
- type: object
properties:
data:
type: object
properties:
orderId:
type: string
completedAt:
type: string
format: date-time
OrderCancelledPayload:
allOf:
- $ref: '#/components/schemas/BaseEvent'
- type: object
properties:
data:
type: object
properties:
orderId:
type: string
reason:
type: string
example: Customer requested cancellation
cancelledAt:
type: string
format: date-time
# Payment event payloads
PaymentAuthorizedPayload:
allOf:
- $ref: '#/components/schemas/BaseEvent'
- type: object
properties:
data:
type: object
properties:
paymentId:
type: string
orderId:
type: string
amount:
type: number
format: decimal
currency:
type: string
authorizationCode:
type: string
PaymentCapturedPayload:
allOf:
- $ref: '#/components/schemas/BaseEvent'
- type: object
properties:
data:
type: object
properties:
paymentId:
type: string
orderId:
type: string
amount:
type: number
capturedAt:
type: string
format: date-time
PaymentFailedPayload:
allOf:
- $ref: '#/components/schemas/BaseEvent'
- type: object
properties:
data:
type: object
properties:
paymentId:
type: string
orderId:
type: string
errorCode:
type: string
example: insufficient_funds
errorMessage:
type: string
# Notification payloads
EmailNotificationPayload:
type: object
required:
- to
- subject
- body
properties:
to:
type: string
format: email
subject:
type: string
body:
type: string
from:
type: string
format: email
default: noreply@example.com
cc:
type: array
items:
type: string
format: email
attachments:
type: array
items:
type: object
properties:
filename:
type: string
url:
type: string
format: uri
SMSNotificationPayload:
type: object
required:
- to
- message
properties:
to:
type: string
pattern: '^\+[1-9]\d{1,14}$'
example: '+14155551234'
message:
type: string
maxLength: 160
from:
type: string
# Security schemes
securitySchemes:
saslScram:
type: scramSha256
description: SASL/SCRAM authentication for Kafka
apiKey:
type: apiKey
in: user
description: API key for authentication
# Message traits (reusable message properties)
messageTraits:
commonHeaders:
headers:
type: object
properties:
sentAt:
type: string
format: date-time
description: Message send timestamp
correlationId:
type: string
description: Correlation ID for request tracing
messageId:
type: string
description: Unique message identifier
# Operation traits (reusable operation properties)
operationTraits:
kafka:
bindings:
kafka:
groupId:
type: string
description: Kafka consumer group ID
# Tags for categorization
tags:
- name: order
description: Order-related events
- name: payment
description: Payment-related events
- name: notification
description: Notification-related events
# External documentation
externalDocs:
description: Full API documentation
url: https://docs.example.com/async-api
openapi: 3.1.0
info:
title: Your API Name
version: 1.0.0
description: |
Brief description of what this API does.
## Authentication
This API uses Bearer tokens for authentication.
## Rate Limiting
- 1000 requests per hour per API key
- Rate limit headers included in all responses
## Support
For questions, contact: api-support@company.com
contact:
name: API Support Team
email: api-support@company.com
url: https://api.company.com/support
license:
name: MIT
url: https://opensource.org/licenses/MIT
termsOfService: https://company.com/terms
servers:
- url: https://api.company.com/v1
description: Production server
- url: https://api-staging.company.com/v1
description: Staging server
- url: http://localhost:3000/v1
description: Local development
tags:
- name: users
description: User management operations
- name: orders
description: Order processing operations
- name: authentication
description: Authentication and authorization
paths:
/users:
get:
summary: List users
description: Retrieve a paginated list of users with optional filtering
operationId: listUsers
tags:
- users
security:
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/PerPageParam'
- name: status
in: query
description: Filter by user status
schema:
type: string
enum: [active, inactive, pending]
- name: role
in: query
description: Filter by user role
schema:
type: string
enum: [admin, developer, viewer]
responses:
'200':
description: Successful response
headers:
X-RateLimit-Limit:
$ref: '#/components/headers/X-RateLimit-Limit'
X-RateLimit-Remaining:
$ref: '#/components/headers/X-RateLimit-Remaining'
X-RateLimit-Reset:
$ref: '#/components/headers/X-RateLimit-Reset'
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
'401':
$ref: '#/components/responses/UnauthorizedError'
'429':
$ref: '#/components/responses/RateLimitError'
post:
summary: Create user
description: Create a new user account
operationId: createUser
tags:
- users
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created successfully
headers:
Location:
description: URL of the created resource
schema:
type: string
format: uri
example: /users/123
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
$ref: '#/components/responses/BadRequestError'
'401':
$ref: '#/components/responses/UnauthorizedError'
'422':
$ref: '#/components/responses/ValidationError'
/users/{userId}:
parameters:
- $ref: '#/components/parameters/UserIdParam'
get:
summary: Get user by ID
description: Retrieve detailed information about a specific user
operationId: getUser
tags:
- users
security:
- bearerAuth: []
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
$ref: '#/components/responses/NotFoundError'
put:
summary: Update user
description: Replace entire user resource
operationId: updateUser
tags:
- users
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateUserRequest'
responses:
'200':
description: User updated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
$ref: '#/components/responses/NotFoundError'
'422':
$ref: '#/components/responses/ValidationError'
delete:
summary: Delete user
description: Permanently delete a user account
operationId: deleteUser
tags:
- users
security:
- bearerAuth: []
responses:
'204':
description: User deleted successfully
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
$ref: '#/components/responses/NotFoundError'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: Enter your JWT token
apiKey:
type: apiKey
in: header
name: X-API-Key
description: API key for service-to-service authentication
parameters:
UserIdParam:
name: userId
in: path
required: true
description: Unique identifier of the user
schema:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
PageParam:
name: page
in: query
description: Page number for pagination
schema:
type: integer
minimum: 1
default: 1
example: 1
PerPageParam:
name: per_page
in: query
description: Number of items per page
schema:
type: integer
minimum: 1
maximum: 100
default: 20
example: 20
headers:
X-RateLimit-Limit:
description: The maximum number of requests allowed per hour
schema:
type: integer
example: 1000
X-RateLimit-Remaining:
description: The number of requests remaining in the current time window
schema:
type: integer
example: 987
X-RateLimit-Reset:
description: Unix timestamp when the rate limit resets
schema:
type: integer
format: int64
example: 1635724800
schemas:
User:
type: object
required:
- id
- email
- name
- role
- created_at
properties:
id:
type: string
format: uuid
description: Unique identifier
example: "550e8400-e29b-41d4-a716-446655440000"
email:
type: string
format: email
description: User's email address
example: "jane@example.com"
name:
type: string
minLength: 2
maxLength: 100
description: User's full name
example: "Jane Smith"
role:
type: string
enum: [admin, developer, viewer]
description: User's role in the system
example: "developer"
status:
type: string
enum: [active, inactive, pending]
description: Current account status
default: active
example: "active"
avatar:
type: string
format: uri
nullable: true
description: URL to user's avatar image
example: "https://cdn.example.com/avatars/jane.jpg"
created_at:
type: string
format: date-time
description: Timestamp when user was created
example: "2025-10-31T10:30:00Z"
updated_at:
type: string
format: date-time
description: Timestamp when user was last updated
example: "2025-10-31T10:30:00Z"
CreateUserRequest:
type: object
required:
- email
- name
- role
properties:
email:
type: string
format: email
example: "jane@example.com"
name:
type: string
minLength: 2
maxLength: 100
example: "Jane Smith"
role:
type: string
enum: [admin, developer, viewer]
example: "developer"
avatar:
type: string
format: uri
nullable: true
example: "https://cdn.example.com/avatars/jane.jpg"
UpdateUserRequest:
type: object
properties:
email:
type: string
format: email
example: "jane.smith@example.com"
name:
type: string
minLength: 2
maxLength: 100
example: "Jane A. Smith"
role:
type: string
enum: [admin, developer, viewer]
example: "admin"
status:
type: string
enum: [active, inactive, pending]
example: "active"
avatar:
type: string
format: uri
nullable: true
example: "https://cdn.example.com/avatars/jane-new.jpg"
Pagination:
type: object
required:
- page
- per_page
- total
- total_pages
properties:
page:
type: integer
description: Current page number
example: 2
per_page:
type: integer
description: Items per page
example: 20
total:
type: integer
description: Total number of items
example: 487
total_pages:
type: integer
description: Total number of pages
example: 25
Error:
type: object
required:
- error
properties:
error:
type: object
required:
- code
- message
properties:
code:
type: string
description: Machine-readable error code
example: "VALIDATION_ERROR"
message:
type: string
description: Human-readable error message
example: "Request validation failed"
details:
type: array
description: Detailed error information
items:
type: object
properties:
field:
type: string
description: Field that caused the error
example: "email"
message:
type: string
description: Error message for this field
example: "Email is already registered"
code:
type: string
description: Field-specific error code
example: "DUPLICATE_EMAIL"
timestamp:
type: string
format: date-time
description: When the error occurred
example: "2025-10-31T10:30:00Z"
request_id:
type: string
description: Unique request identifier for support
example: "req_abc123xyz"
responses:
UnauthorizedError:
description: Authentication required or token invalid
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "UNAUTHORIZED"
message: "Authentication required"
timestamp: "2025-10-31T10:30:00Z"
request_id: "req_abc123"
NotFoundError:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "NOT_FOUND"
message: "User not found"
timestamp: "2025-10-31T10:30:00Z"
request_id: "req_abc123"
BadRequestError:
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "BAD_REQUEST"
message: "Invalid request body"
timestamp: "2025-10-31T10:30:00Z"
request_id: "req_abc123"
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 is already registered"
code: "DUPLICATE_EMAIL"
- field: "name"
message: "Name must be at least 2 characters"
code: "NAME_TOO_SHORT"
timestamp: "2025-10-31T10:30:00Z"
request_id: "req_abc123"
RateLimitError:
description: Rate limit exceeded
headers:
Retry-After:
description: Number of seconds to wait before retrying
schema:
type: integer
example: 3600
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error:
code: "RATE_LIMIT_EXCEEDED"
message: "API rate limit exceeded"
details:
- field: "retry_after"
message: "Retry after 1 hour"
code: "RETRY_AFTER_3600"
timestamp: "2025-10-31T10:30:00Z"
request_id: "req_abc123"
API Design Review Checklist
Use this checklist when designing or reviewing APIs to ensure consistency, usability, and best practices.
Pre-Design Checklist
- [ ] Requirements Gathered: Clear understanding of what the API needs to accomplish
- [ ] Stakeholders Identified: Know who will use this API (frontend teams, partners, public)
- [ ] API Style Chosen: REST, GraphQL, or gRPC based on requirements
- [ ] Versioning Strategy: Decided how API will evolve (URI, header, or query param)
- [ ] Authentication Method: Chosen auth approach (JWT, API keys, OAuth2)
---
REST API Design Checklist
Resource Naming
- [ ] Plural Nouns: Resources use plural nouns (
/users, not/user) - [ ] Hierarchical: Relationships expressed through hierarchy (
/users/123/orders) - [ ] Kebab-Case: Multi-word resources use kebab-case (
/shopping-carts) - [ ] No Verbs: URLs don't contain actions (
/users, not/getUsers) - [ ] Consistent Naming: Same naming pattern across all resources
HTTP Methods
- [ ] GET for Retrieval: Read operations use GET
- [ ] POST for Creation: New resources use POST
- [ ] PUT for Replace: Full replacement uses PUT
- [ ] PATCH for Partial: Partial updates use PATCH
- [ ] DELETE for Removal: Deletions use DELETE
- [ ] Idempotent Operations: PUT, DELETE, GET are idempotent
- [ ] Safe Operations: GET, HEAD don't modify resources
Status Codes
- [ ] 2xx for Success: Appropriate success codes (200, 201, 204)
- [ ] 4xx for Client Errors: Correct client error codes (400, 401, 403, 404, 422, 429)
- [ ] 5xx for Server Errors: Server errors use 5xx (500, 502, 503)
- [ ] Consistent Usage: Same code for same scenarios across API
- [ ] Location Header: 201 responses include
Locationheader
Request/Response
- [ ] JSON Format: Using
application/jsoncontent type - [ ] Consistent Structure: Same response structure across endpoints
- [ ] Error Format: Standardized error response with code, message, details
- [ ] Timestamp Format: ISO 8601 format for all dates/times
- [ ] Field Naming: Consistent convention (snake_case or camelCase)
Pagination
- [ ] Pagination Implemented: Large lists are paginated
- [ ] Cursor or Offset: Chosen appropriate pagination strategy
- [ ] Page Info Included: Response includes pagination metadata
- [ ] Configurable Limit: Clients can specify page size
- [ ] Max Limit Enforced: Prevent excessive page sizes
Filtering & Sorting
- [ ] Filter Parameters: Query params for filtering (e.g.,
?status=active) - [ ] Sort Parameter: Query param for sorting (e.g.,
?sort=created_at:desc) - [ ] Field Selection: Support partial responses (e.g.,
?fields=id,name) - [ ] Consistent Syntax: Same filter/sort syntax across endpoints
Versioning
- [ ] Version Strategy Chosen: URI, header, or query param versioning
- [ ] Version Number Visible: Clear which version is being used
- [ ] Backward Compatibility: Older versions supported for migration period
- [ ] Deprecation Policy: Plan for sunsetting old versions
Authentication & Security
- [ ] Auth Required: Protected endpoints require authentication
- [ ] Authorization Checked: Verify user permissions for actions
- [ ] HTTPS Only: API only accessible over HTTPS in production
- [ ] API Keys Secure: Keys not exposed in URLs or logs
- [ ] Rate Limiting: Implemented to prevent abuse
Rate Limiting
- [ ] Limits Defined: Clear rate limits per endpoint/user
- [ ] Headers Included:
X-RateLimit-*headers in responses - [ ] 429 Status: Returns 429 when limit exceeded
- [ ] Retry-After Header: Tells client when to retry
Error Handling
- [ ] Consistent Format: All errors follow same structure
- [ ] Error Codes: Machine-readable error codes included
- [ ] Helpful Messages: Clear, actionable error messages
- [ ] Field-Level Errors: Validation errors specify which fields failed
- [ ] Request IDs: Each response includes unique request ID for support
---
GraphQL API Design Checklist
Schema Design
- [ ] Nullable by Default: Fields nullable unless explicitly required (!)
- [ ] Connections for Lists: Use Connection pattern for paginated lists
- [ ] Input Types: Mutations use Input types, not inline args
- [ ] Enum Types: Use enums for fixed sets of values
- [ ] Interface/Union Types: Reuse types appropriately
Queries
- [ ] Single Resource Queries: Can fetch individual items by ID
- [ ] List Queries: Can fetch lists with filtering and pagination
- [ ] Nested Queries: Related data fetchable in single query
- [ ] N+1 Prevention: DataLoader or similar for batching
Mutations
- [ ] Input/Payload Pattern: Mutations use
createUserInput→CreateUserPayload - [ ] Return Complete Object: Mutations return updated resource
- [ ] Error Handling: Payload includes errors array
- [ ] Optimistic UI: Mutations designed for optimistic updates
Subscriptions
- [ ] Real-Time Events: Subscriptions for live updates
- [ ] Filtered Subscriptions: Clients can filter events
- [ ] Subscription Cleanup: Proper cleanup on disconnect
---
gRPC API Design Checklist
Proto Files
- [ ] Package Name: Follows convention (company.service.v1)
- [ ] Versioned: Version included in package name
- [ ] Imports Organized: Standard imports (google/protobuf/*)
- [ ] Comments: Services and messages documented
Service Design
- [ ] CRUD Operations: Standard operations defined
- [ ] Request/Response Messages: Each RPC has dedicated messages
- [ ] Streaming Where Appropriate: Uses streaming for large data or live updates
- [ ] Empty Responses: Uses google.protobuf.Empty for no-content responses
Message Design
- [ ] Field Numbers: Sequential, never reused
- [ ] Required Fields: Minimal required fields
- [ ] Repeated Fields: For lists/arrays
- [ ] Oneof Fields: For mutually exclusive fields
- [ ] Enums Have Zero: First enum value is UNSPECIFIED = 0
Error Handling
- [ ] gRPC Status Codes: Uses standard status codes
- [ ] Error Details: Rich error info using google.rpc.Status
- [ ] Retry Logic: Idempotent operations identified
---
API Documentation Checklist
OpenAPI/AsyncAPI Specification
- [ ] Specification Created: OpenAPI 3.1 or AsyncAPI 3.0 document exists
- [ ] Complete Coverage: All endpoints documented
- [ ] Examples Provided: Request/response examples for each endpoint
- [ ] Schema Definitions: Reusable schemas in components section
- [ ] Security Schemes: Authentication methods documented
Documentation Quality
- [ ] Getting Started Guide: Clear intro for new users
- [ ] Authentication Guide: How to authenticate explained
- [ ] Error Handling Guide: Common errors and solutions
- [ ] Code Examples: Working code samples in multiple languages
- [ ] Changelog: Version history and breaking changes documented
API Reference
- [ ] Endpoint List: All endpoints listed with descriptions
- [ ] Parameters Documented: Query, path, header params explained
- [ ] Status Codes: All possible status codes documented
- [ ] Rate Limits: Limits and quotas clearly stated
- [ ] Deprecation Notices: Deprecated endpoints marked
---
Performance Checklist
- [ ] Pagination Default: Reasonable default page size (20-50)
- [ ] Field Selection: Support for partial responses
- [ ] Caching Headers: Cache-Control, ETag headers where appropriate
- [ ] Compression: Gzip/Brotli compression enabled
- [ ] Response Times: < 200ms for simple queries, < 1s for complex
- [ ] N+1 Queries Avoided: Efficient database queries
- [ ] Indexes Created: Database indexes on frequently queried fields
---
Testing Checklist
- [ ] Unit Tests: Business logic tested
- [ ] Integration Tests: API endpoints tested end-to-end
- [ ] Contract Tests: API contracts validated
- [ ] Load Tests: Performance under load verified
- [ ] Security Tests: Common vulnerabilities tested (OWASP)
- [ ] Documentation Tests: Examples in docs actually work
---
Compliance & Standards
- [ ] REST Principles: Follows RESTful conventions (if REST)
- [ ] GraphQL Spec: Adheres to GraphQL specification (if GraphQL)
- [ ] gRPC Style Guide: Follows protobuf style guide (if gRPC)
- [ ] Naming Conventions: Consistent with org standards
- [ ] Security Standards: Meets security requirements
- [ ] Privacy Compliance: GDPR, CCPA compliance where applicable
---
Pre-Launch Checklist
- [ ] All Tests Passing: 100% pass rate on test suite
- [ ] Documentation Complete: All endpoints documented
- [ ] Security Review: Security team approved
- [ ] Load Testing: Performance validated under expected load
- [ ] Monitoring Setup: Metrics, logging, alerting configured
- [ ] Error Tracking: Error monitoring (Sentry, etc.) configured
- [ ] Rollback Plan: Can revert if issues found
- [ ] Stakeholder Approval: Frontend/client teams signed off
---
Post-Launch Checklist
- [ ] Monitor Metrics: Track API usage, error rates, latency
- [ ] Collect Feedback: Gather developer feedback
- [ ] Document Issues: Track bugs and feature requests
- [ ] Iterate: Plan improvements based on real usage
- [ ] Deprecation Plan: Plan for sunsetting old versions if applicable
---
Common API Anti-Patterns to Avoid
❌ Chatty APIs: Too many round-trips required ✅ Fix: Batch operations, nested resources, GraphQL
❌ Overfetching: Returning more data than needed ✅ Fix: Field selection, GraphQL, partial responses
❌ Underfetching: Requiring multiple calls for related data ✅ Fix: Include related resources, nested endpoints, GraphQL
❌ Breaking Changes: Backward-incompatible changes without versioning ✅ Fix: Version API, deprecation periods, additive changes
❌ Unclear Errors: Generic "Error 500" messages ✅ Fix: Specific error codes, helpful messages, troubleshooting info
❌ No Pagination: Returning thousands of items ✅ Fix: Implement pagination with reasonable defaults
❌ Ignoring HTTP: Using POST for everything ✅ Fix: Use appropriate HTTP methods (GET, POST, PUT, DELETE)
❌ Exposing Internal Details: Database fields in API ✅ Fix: Map to business domain, hide implementation
---
Reviewer Sign-Off
Technical Review
- [ ] Backend Architect: Architectural soundness verified
- [ ] Frontend Developer: Developer experience validated
- [ ] Security Team: Security implications reviewed
- [ ] DevOps: Operational concerns addressed
Business Review
- [ ] Product Manager: Business requirements met
- [ ] API Governance: Compliance with API standards
---
Checklist Version: 1.0.0 Skill: api-design-framework v1.0.0 Last Updated: 2025-10-31
OrchestKit API Design Decisions
Real-world API design decisions from the OrchestKit project, documenting endpoint structure, versioning strategy, and architectural choices.
Project Context
OrchestKit: Intelligent Learning Integration Platform - Multi-agent system for analyzing technical content.
Stack: FastAPI (Python) + React 19 frontend API Base: http://localhost:8500/api/v1 Development Ports:
- Backend API:
localhost:8500 - Frontend:
localhost:5173 - PostgreSQL:
localhost:5437
API Structure
URI Versioning
Decision: Use URI-based versioning (/api/v1/)
Location: backend/app/core/config.py
API_V1_PREFIX = "/api/v1"Rationale:
- Clear visibility in URLs for debugging
- Easy to route different versions to different handlers
- Frontend can easily target specific API versions
- Cache-friendly (CDNs can cache different versions separately)
Implementation: backend/app/main.py
from app.core.config import settings
# Include analysis router with versioned prefix
app.include_router(
analysis_router,
prefix=f"{settings.API_V1_PREFIX}/analyze"
)
# Include artifact router
app.include_router(
artifact_router,
prefix=settings.API_V1_PREFIX
)Endpoint Design
Analysis Endpoints
Location: backend/app/api/v1/analysis/endpoints.py
1. Create Analysis (Async Task Pattern)
POST /api/v1/analyze
Content-Type: application/json
{
"url": "https://example.com/article",
"analysis_id": "optional-custom-id", # Optional
"skill_level": "beginner" # Optional: beginner|intermediate|advanced
}Response: 201 Created
{
"analysis_id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://example.com/article",
"content_type": "article",
"status": "pending",
"sse_endpoint": "/api/v1/analyze/550e8400-e29b-41d4-a716-446655440000/stream"
}Design Decision: Return immediately with analysis_id + SSE endpoint
- Why: Analysis workflow takes 30-120 seconds to complete
- Pattern: Async task creation + progress streaming (see SSE section)
- Client flow: Create analysis → Connect to SSE endpoint → Receive progress updates
Implementation:
@router.post(
"/analyze",
status_code=status.HTTP_201_CREATED,
responses={
422: {"model": ErrorResponse, "description": "Validation error"},
500: {"model": ErrorResponse, "description": "Internal server error"}
}
)
async def create_analysis(
request: AnalyzeRequest,
fastapi_request: Request,
analysis_repo: Annotated[IAnalysisRepository, Depends(get_analysis_repository)]
) -> AnalyzeCreateResponse:
"""Create analysis and start workflow asynchronously."""
# 1. Detect content type
content_type = detect_content_type(str(request.url))
# 2. Normalize custom analysis_id if provided (optional)
analysis_uuid = (
normalize_analysis_id_to_uuid(request.analysis_id)
if request.analysis_id
else None # Let DB generate UUID v7 via server_default
)
# 3. Create Analysis record (status: pending)
# PostgreSQL 18 generates UUID v7 via server_default=text("uuidv7()")
created_analysis = await analysis_repo.create_analysis(
analysis_id=analysis_uuid, # None → DB generates UUID v7
url=url_str,
content_type=content_type,
status="pending"
)
analysis_uuid = cast("AnalysisID", created_analysis.id)
# 4. Start workflow asynchronously (fire-and-forget)
task = asyncio.create_task(
run_workflow_task(analysis_uuid, url_str, request.skill_level)
)
background_tasks = fastapi_request.app.state.background_tasks
background_tasks.add(task)
task.add_done_callback(partial(_handle_task_completion, background_tasks=background_tasks))
# 5. Return immediately with SSE endpoint
sse_endpoint = f"{settings.API_V1_PREFIX}/analyze/{analysis_uuid}/stream"
return AnalyzeCreateResponse(
analysis_id=str(analysis_uuid),
url=url_str,
content_type=content_type,
status="pending",
sse_endpoint=sse_endpoint
)2. Get Analysis Status
GET /api/v1/analyze/{analysis_id}Response: 200 OK
{
"analysis_id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://example.com/article",
"content_type": "article",
"status": "completed",
"title": "Understanding React Server Components",
"artifact_id": "660e8400-e29b-41d4-a716-446655440001",
"created_at": "2025-12-21T10:30:00Z",
"updated_at": "2025-12-21T10:32:45Z"
}Design Decision: Return latest artifact_id in status response
- Why: Frontend needs artifact_id to fetch results
- Alternative considered: Separate endpoint for artifact lookup (rejected: extra round trip)
3. Stream Analysis Progress (SSE)
GET /api/v1/analyze/{analysis_id}/stream
Accept: text/event-streamResponse: Server-Sent Events stream
event: progress
data: {"type":"progress","stage":"extraction","status":"running","timestamp":"2025-12-21T10:30:15Z"}
event: progress
data: {"type":"progress","stage":"extraction","status":"complete","word_count":5234}
event: progress
data: {"type":"progress","stage":"analysis","status":"running","agent":"tech_comparator"}
event: complete
data: {"type":"complete","stage":"artifact_generation","timestamp":"2025-12-21T10:32:45Z"}Design Decision: Use SSE instead of WebSockets
- Why: Unidirectional (server→client) is sufficient for progress updates
- Benefit: Simpler client code (native EventSource API), automatic reconnection
- Trade-off: No client→server messaging (not needed for this use case)
See references/sse-deep-dive.md in streaming-api-patterns skill for details.
Artifact Endpoints
Location: backend/app/api/v1/analysis/artifacts.py
1. Get Artifact by Analysis
GET /api/v1/analyze/{analysis_id}/artifactResponse: 200 OK
{
"artifact_id": "660e8400-e29b-41d4-a716-446655440001",
"analysis_id": "550e8400-e29b-41d4-a716-446655440000",
"markdown_content": "# Understanding React Server Components\n\n...",
"artifact_metadata": {
"word_count": 5234,
"section_count": 8
},
"trace_id": "trace_abc123",
"created_at": "2025-12-21T10:32:45Z"
}Design Decision: Hierarchical URL (/analyze/{id}/artifact)
- Why: Expresses relationship: "artifact belongs to analysis"
- Alternative considered:
/artifacts?analysis_id={id}(rejected: less RESTful)
2. Get Artifact by ID
GET /api/v1/artifacts/{artifact_id}Response: Same as above
Design Decision: Provide both hierarchical AND direct ID lookup
- Why: Support different frontend access patterns
- Use case 1: After analysis complete → use hierarchical endpoint
- Use case 2: Direct link to artifact → use ID endpoint
3. Download Artifact
GET /api/v1/artifacts/{artifact_id}/downloadResponse: 200 OK (file download)
Content-Type: text/markdown
Content-Disposition: attachment; filename="understanding-react-server-components-550e8400.md"
# Understanding React Server Components
...Design Decision: Separate download endpoint with different response type
- Why: Different headers (Content-Disposition) and analytics (download_count)
- Benefit: Clean separation of view vs. download use cases
Implementation:
@router.get("/artifacts/{artifact_id}/download", response_class=Response)
async def download_artifact(
artifact_id: uuid.UUID,
repo: Annotated[IArtifactRepository, Depends(get_artifact_repository)]
) -> Response:
# Get artifact with analysis (for title)
result = await repo.get_artifact_with_analysis(artifact_id)
if not result:
raise HTTPException(status_code=404, detail="Artifact not found")
artifact, analysis = result
# Extract title from analysis metadata
title = None
if analysis.extraction_metadata:
title = analysis.extraction_metadata.get("title")
# Generate filename: "article-title-uuid.md"
filename = generate_filename(title, str(artifact.analysis_id))
# Increment download_count for analytics
await repo.increment_download_count(artifact_id)
# Return with download headers
return Response(
content=artifact.markdown_content,
media_type="text/markdown",
headers={"Content-Disposition": f'attachment; filename="{filename}"'}
)Health Check Endpoint
Location: backend/app/api/v1/health.py
GET /api/v1/healthResponse: 200 OK
{
"status": "healthy",
"version": "0.1.0",
"environment": "development",
"database": {
"status": "connected"
}
}Design Decision: Include database connectivity check
- Why: Kubernetes readiness/liveness probes need to verify DB connection
- Timeout: 5 seconds (configurable via DB_TIMEOUT constant)
- Error response: Still returns 200 OK, but with
database.status: "disconnected"
Error Handling
Standardized Error Format
Location: backend/app/api/schemas/errors.py
class ErrorResponse(BaseModel):
error: dict[str, Any]
class Config:
json_schema_extra = {
"example": {
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"timestamp": "2025-12-21T10:30:00Z"
}
}
}Example Error Responses
404 Not Found:
{
"error": {
"code": "NOT_FOUND",
"message": "Artifact 660e8400-e29b-41d4-a716-446655440001 not found",
"timestamp": "2025-12-21T10:30:00Z"
}
}422 Validation Error:
try:
content_type = detect_content_type(url_str)
except ContentTypeError as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Invalid URL format: {e!s}"
) from eResponse:
{
"error": {
"code": "UNPROCESSABLE_ENTITY",
"message": "Invalid URL format: Must be a valid HTTP/HTTPS URL",
"timestamp": "2025-12-21T10:30:00Z"
}
}URL Normalization
UUID Analysis IDs
Decision: Always use UUIDs for analysis_id (not string slugs)
Normalization logic: backend/app/core/utils.py
def normalize_analysis_id_to_uuid(analysis_id: str) -> uuid.UUID:
"""Normalize analysis_id to UUID format.
Supports:
- Full UUID: "550e8400-e29b-41d4-a716-446655440000"
- Short form: "550e8400" (first 8 chars)
"""
# Try parsing as full UUID
try:
return uuid.UUID(analysis_id)
except ValueError:
pass
# Try short form (8 chars)
if len(analysis_id) == 8:
try:
# Pad to full UUID format
full_uuid = f"{analysis_id}-0000-0000-0000-000000000000"
return uuid.UUID(full_uuid)
except ValueError:
pass
raise ValueError(f"Invalid analysis_id format: {analysis_id}")Benefit: Allows short URLs while maintaining UUID uniqueness
Repository Pattern
Dependency Injection
Pattern: Use FastAPI Depends() for repository injection
from typing import Annotated
@router.get("/artifacts/{artifact_id}")
async def get_artifact(
artifact_id: Annotated[uuid.UUID, Path(description="Artifact UUID")],
repo: Annotated[IArtifactRepository, Depends(get_artifact_repository)]
) -> ArtifactMetadataResponse:
artifact = await repo.get_artifact_by_id(artifact_id)
...Benefits:
- Easy testing (mock repository)
- Clean separation of concerns
- Type-safe with Annotated
API Documentation
OpenAPI Spec
Auto-generated: Available at /docs (Swagger UI) and /redoc (ReDoc)
Custom documentation:
@router.get(
"/analyze/{analysis_id}/stream",
responses={
404: {"model": ErrorResponse, "description": "Analysis not found"},
500: {"model": ErrorResponse, "description": "Internal server error"}
}
)
async def stream_analysis_progress_endpoint(
analysis_id: Annotated[uuid.UUID, Path(description="Analysis UUID")],
request: Request
):
"""Stream real-time analysis progress via Server-Sent Events (SSE).
See app.api.v1.sse_handler.stream_analysis_progress for full documentation.
"""
return await stream_analysis_progress_handler(analysis_id, request)Design Principles
1. Immediate Response for Long Operations
Pattern: Create → Return ID + Progress URL
- Example: POST /analyze → Returns analysis_id + sse_endpoint
- Why: Prevents timeout on long-running operations
- Client UX: Show loading state with progress updates
2. Include Related Resource URLs
Pattern: Include navigation URLs in responses
{
"analysis_id": "123",
"sse_endpoint": "/api/v1/analyze/123/stream", ← Progress URL
"artifact_id": "456" ← Related resource
}Benefit: Frontend doesn't need to construct URLs
3. Hierarchical URLs for Relationships
Pattern: /parent/{id}/child for 1:1 or 1:many relationships
/analyze/{analysis_id}/artifact- Analysis has one latest artifact/teams/{team_id}/members- Team has many members
Benefit: Clear relationship modeling
4. UUID Path Parameters
Pattern: Use typed UUID path parameters
analysis_id: Annotated[uuid.UUID, Path(description="Analysis UUID")]Benefit: Automatic validation (400 if not valid UUID)
5. Repository + Dependency Injection
Pattern: Abstract database access behind repository interface
class IArtifactRepository(Protocol):
async def get_artifact_by_id(self, artifact_id: uuid.UUID) -> Artifact | None: ...
def get_artifact_repository() -> IArtifactRepository:
return ArtifactRepository(get_db_session())Benefits:
- Easy to mock for testing
- Clean architecture
- Database-agnostic API layer
Related Files
- SSE Implementation:
backend/app/api/v1/analysis/sse_handler.py - Event Broadcaster:
backend/app/shared/services/messaging/broadcaster.py - Error Schemas:
backend/app/api/schemas/errors.py - Config:
backend/app/core/config.py - API Schemas:
backend/app/domains/analysis/schemas/api.py
References
- See
references/rest-patterns.mdfor general REST patterns - See
streaming-api-patternsskill for SSE implementation details - See
assets/openapi-template.yamlfor OpenAPI specification template
Frontend API Integration (2026 Patterns)
Type-safe API consumption with runtime validation.
Runtime Validation with Zod
CRITICAL: TypeScript types are erased at runtime. API responses MUST be validated:
import { z } from 'zod'
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
name: z.string(),
role: z.enum(['admin', 'developer', 'viewer']),
created_at: z.string().datetime(),
})
const UsersResponseSchema = z.object({
data: z.array(UserSchema),
pagination: z.object({
next_cursor: z.string().nullable(),
has_more: z.boolean(),
}),
})
type User = z.infer<typeof UserSchema>
async function fetchUsers(cursor?: string): Promise<UsersResponse> {
const response = await fetch(`/api/v1/users${cursor ? `?cursor=${cursor}` : ''}`)
const data = await response.json()
return UsersResponseSchema.parse(data) // Runtime validation!
}Request Interceptors (ky)
import ky from 'ky'
export const api = ky.create({
prefixUrl: import.meta.env.VITE_API_URL,
timeout: 30000,
retry: {
limit: 2,
methods: ['get', 'head', 'options'],
statusCodes: [408, 429, 500, 502, 503, 504],
},
hooks: {
beforeRequest: [
async (request) => {
const token = await getAccessToken()
if (token) {
request.headers.set('Authorization', `Bearer ${token}`)
}
},
],
afterResponse: [
async (request, options, response) => {
if (response.status === 401) {
const newToken = await refreshToken()
if (newToken) {
request.headers.set('Authorization', `Bearer ${newToken}`)
return ky(request, options)
}
}
return response
},
],
},
})Error Enrichment Pattern
class ApiError extends Error {
constructor(
public status: number,
public code: string,
message: string,
public details?: Array<{ field: string; message: string }>
) {
super(message)
this.name = 'ApiError'
}
get isValidationError(): boolean {
return this.status === 422
}
get isAuthError(): boolean {
return this.status === 401 || this.status === 403
}
get isRateLimited(): boolean {
return this.status === 429
}
}TanStack Query Integration
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
export function useUsers(cursor?: string) {
return useQuery({
queryKey: ['users', { cursor }],
queryFn: () => getUsers(cursor),
staleTime: 30_000,
})
}
export function useCreateUser() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (input: CreateUserInput) =>
api.post('users', { json: input }).json().then(UserSchema.parse),
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['users'] })
},
})
}Anti-Patterns
// NEVER: Trust API response types blindly
const data = await response.json() as User // Unsafe cast!
// NEVER: Skip validation
const user: User = await response.json() // Runtime crash waiting
// ALWAYS: Validate at the boundary
const user = UserSchema.parse(await response.json())GraphQL API Design
Schema Design Principles
Nullable by Default
type User {
id: ID! # Non-null (required)
email: String! # Non-null
name: String # Nullable (optional)
avatar: String # Nullable
}Use Connections for Lists
type Query {
users(first: Int, after: String): UserConnection!
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
node: User!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}Input Types for Mutations
input CreateUserInput {
email: String!
name: String!
role: UserRole!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
}
type CreateUserPayload {
user: User!
errors: [UserError!]
}
type UserError {
field: String!
message: String!
code: String!
}Query Design
Fetch single resource:
query GetUser {
user(id: "123") {
id
name
email
posts {
id
title
}
}
}Fetch list with filters:
query GetUsers {
users(
first: 10
after: "cursor123"
filter: { role: DEVELOPER, status: ACTIVE }
) {
edges {
node {
id
name
email
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Error Handling
Field-Level Errors:
type CreateUserPayload {
user: User
errors: [UserError!]
}Response:
{
"data": {
"createUser": {
"user": null,
"errors": [
{
"field": "email",
"message": "Email is already taken",
"code": "DUPLICATE_EMAIL"
}
]
}
}
}gRPC API Design
Proto File Structure
syntax = "proto3";
package company.user.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
rpc WatchUsers(WatchUsersRequest) returns (stream UserEvent);
}
message User {
string id = 1;
string email = 2;
string name = 3;
UserRole role = 4;
google.protobuf.Timestamp created_at = 5;
}
enum UserRole {
USER_ROLE_UNSPECIFIED = 0;
USER_ROLE_ADMIN = 1;
USER_ROLE_DEVELOPER = 2;
USER_ROLE_VIEWER = 3;
}
message GetUserRequest {
string id = 1;
}
message GetUserResponse {
User user = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
string filter = 3;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
int32 total_size = 3;
}gRPC Status Codes
| Code | HTTP Equivalent | Use Case |
|---|---|---|
| OK | 200 | Success |
| INVALID_ARGUMENT | 400 | Invalid request |
| NOT_FOUND | 404 | Resource not found |
| ALREADY_EXISTS | 409 | Duplicate |
| PERMISSION_DENIED | 403 | Forbidden |
| UNAUTHENTICATED | 401 | Auth required |
| RESOURCE_EXHAUSTED | 429 | Rate limit |
| INTERNAL | 500 | Server error |
REST API Design
Resource Naming Conventions
Use plural nouns for resources:
GET /users
GET /users/123
GET /users/123/ordersUse hierarchical relationships:
GET /users/123/orders # Orders for specific user
GET /teams/5/members # Members of specific team
POST /projects/10/tasks # Create task in project 10Use kebab-case for multi-word resources:
/shopping-carts
/order-items
/user-preferencesHTTP Methods
| Method | Purpose | Idempotent | Safe | Example |
|---|---|---|---|---|
| GET | Retrieve resource(s) | Yes | Yes | GET /users/123 |
| POST | Create resource | No | No | POST /users |
| PUT | Replace entire resource | Yes | No | PUT /users/123 |
| PATCH | Partial update | No* | No | PATCH /users/123 |
| DELETE | Remove resource | Yes | No | DELETE /users/123 |
| HEAD | Metadata only (no body) | Yes | Yes | HEAD /users/123 |
| OPTIONS | Allowed methods | Yes | Yes | OPTIONS /users |
Status Codes
Success (2xx)
- 200 OK: Successful GET, PUT, PATCH, or DELETE
- 201 Created: Successful POST (include
Locationheader) - 202 Accepted: Request accepted, processing async
- 204 No Content: Successful DELETE or PUT with no response body
Client Errors (4xx)
- 400 Bad Request: Invalid request body or parameters
- 401 Unauthorized: Missing or invalid authentication
- 403 Forbidden: Authenticated but not authorized
- 404 Not Found: Resource doesn't exist
- 409 Conflict: Resource conflict (e.g., duplicate)
- 422 Unprocessable Entity: Validation failed
- 429 Too Many Requests: Rate limit exceeded
Server Errors (5xx)
- 500 Internal Server Error: Generic server error
- 502 Bad Gateway: Upstream service error
- 503 Service Unavailable: Temporary unavailability
Request/Response Formats
Request Body (POST/PUT/PATCH):
POST /users
Content-Type: application/json
{
"email": "jane@example.com",
"name": "Jane Smith",
"role": "developer"
}Success Response:
HTTP/1.1 201 Created
Location: /users/123
{
"id": 123,
"email": "jane@example.com",
"name": "Jane Smith",
"created_at": "2025-10-31T10:30:00Z"
}Pagination
Cursor-Based (Recommended)
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
Response:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTQzfQ",
"has_more": true
}
}Use for: Large datasets, real-time data, infinite scroll
Offset-Based
GET /users?page=2&per_page=20
Response:
{
"data": [...],
"pagination": {
"page": 2,
"per_page": 20,
"total": 487,
"total_pages": 25
}
}Use for: Small datasets, admin panels, known bounds
Filtering and Sorting
GET /users?status=active&role=developer
GET /users?sort=created_at:desc
GET /users?fields=id,name,emailAPI Versioning
URI Versioning (Recommended)
/api/v1/users
/api/v2/usersHeader Versioning
GET /api/users
Accept: application/vnd.company.v2+jsonRate Limiting Headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1635724800Authentication
Bearer Token (JWT):
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...API Key:
X-API-Key: sk_live_abc123...RESTful API Design Patterns
Comprehensive guide to RESTful API design patterns including resource modeling, HTTP methods, status codes, versioning, pagination, filtering, and error handling.
Resource Modeling
Naming Conventions
Use plural nouns for collections:
✅ GET /api/v1/analyses
✅ GET /api/v1/artifacts
✅ GET /api/v1/users
❌ GET /api/v1/analysis
❌ GET /api/v1/getArtifactHierarchical relationships:
✅ GET /api/v1/analyses/{analysis_id}/artifact
✅ GET /api/v1/teams/{team_id}/members
✅ POST /api/v1/projects/{project_id}/tasks
❌ GET /api/v1/artifact?analysis_id={id} # Query param for relationship
❌ GET /api/v1/analysis_artifact/{id} # Flat structureUse kebab-case for multi-word resources:
✅ /api/v1/shopping-carts
✅ /api/v1/user-preferences
✅ /api/v1/order-items
❌ /api/v1/shoppingCarts (camelCase)
❌ /api/v1/shopping_carts (snake_case in URL)HTTP Methods (CRUD Operations)
| Method | Purpose | Idempotent | Safe | Response | Example |
|---|---|---|---|---|---|
| GET | Retrieve resource(s) | ✅ | ✅ | 200 OK | GET /analyses/123 |
| POST | Create resource | ❌ | ❌ | 201 Created | POST /analyses |
| PUT | Replace entire resource | ✅ | ❌ | 200 OK | PUT /analyses/123 |
| PATCH | Partial update | ⚠️ | ❌ | 200 OK | PATCH /analyses/123 |
| DELETE | Remove resource | ✅ | ❌ | 204 No Content | DELETE /analyses/123 |
| HEAD | Metadata only | ✅ | ✅ | 200 OK | HEAD /analyses/123 |
| OPTIONS | Allowed methods | ✅ | ✅ | 200 OK | OPTIONS /analyses |
Idempotency Note: PATCH can be designed to be idempotent by using absolute values instead of relative operations.
HTTP Status Codes
Success (2xx)
200 OK - Successful GET, PUT, PATCH, DELETE with response body
@router.get("/analyses/{analysis_id}")
async def get_analysis(analysis_id: uuid.UUID) -> AnalysisResponse:
return AnalysisResponse(...) # 200 OK201 Created - Successful POST, include Location header
@router.post("/analyses", status_code=status.HTTP_201_CREATED)
async def create_analysis(request: AnalyzeRequest) -> AnalyzeCreateResponse:
# Include SSE endpoint in response
return AnalyzeCreateResponse(
analysis_id=str(analysis_uuid),
sse_endpoint=f"/api/v1/analyze/{analysis_uuid}/stream"
)202 Accepted - Request accepted, processing asynchronously
@router.post("/long-running-task", status_code=status.HTTP_202_ACCEPTED)
async def start_task() -> TaskStatusResponse:
# Start background task
return TaskStatusResponse(
task_id="...",
status="pending",
status_url="/tasks/123/status"
)204 No Content - Successful DELETE or PUT with no response body
@router.delete("/analyses/{analysis_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_analysis(analysis_id: uuid.UUID) -> None:
await repo.delete(analysis_id)Client Errors (4xx)
400 Bad Request - Invalid request syntax or malformed parameters
{
"error": {
"code": "INVALID_REQUEST",
"message": "Request body is not valid JSON",
"timestamp": "2025-12-21T10:30:00Z"
}
}401 Unauthorized - Missing or invalid authentication
{
"error": {
"code": "UNAUTHORIZED",
"message": "Missing or invalid authentication token",
"timestamp": "2025-12-21T10:30:00Z"
}
}403 Forbidden - Authenticated but not authorized
{
"error": {
"code": "FORBIDDEN",
"message": "You do not have permission to access this resource",
"timestamp": "2025-12-21T10:30:00Z"
}
}404 Not Found - Resource doesn't exist
@router.get("/artifacts/{artifact_id}")
async def get_artifact(artifact_id: uuid.UUID) -> ArtifactResponse:
artifact = await repo.get_artifact_by_id(artifact_id)
if not artifact:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Artifact {artifact_id} not found"
)422 Unprocessable Entity - Validation failed
try:
content_type = detect_content_type(url_str)
except ContentTypeError as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Invalid URL format: {e!s}"
) from e429 Too Many Requests - Rate limit exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1703163600
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "API rate limit exceeded. Try again in 1 hour.",
"retry_after": 3600
}
}Server Errors (5xx)
500 Internal Server Error - Generic server error
except Exception as e:
logger.error(
"analysis_creation_failed",
error=str(e),
exc_info=True
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to create analysis record"
) from e502 Bad Gateway - Upstream service error 503 Service Unavailable - Temporary unavailability (maintenance) 504 Gateway Timeout - Upstream timeout
API Versioning
Strategy 1: URI Versioning (Recommended for Public APIs)
OrchestKit uses this approach:
# app/core/config.py
API_V1_PREFIX = "/api/v1"
# app/main.py
app.include_router(
analysis_router,
prefix=f"{settings.API_V1_PREFIX}/analyze"
)URL structure:
/api/v1/analyses
/api/v2/analyses # New version with breaking changesPros:
- Clear and visible in URLs
- Easy to test and debug
- Cache-friendly
- Can route different versions to different servers
Cons:
- Verbose URLs
- Need to maintain multiple codebases
Strategy 2: Header Versioning
GET /api/analyses
Accept: application/vnd.orchestkit.v2+json
API-Version: v2Pros:
- Clean URLs
- RESTful purist approach
Cons:
- Not visible in browser
- Harder to test manually
- Need custom headers
Strategy 3: Query Parameter (Avoid)
GET /api/analyses?version=2Cons:
- Mixes with business logic parameters
- Can be forgotten
- Not cache-friendly
Pagination
Cursor-Based Pagination (Recommended for Large Datasets)
Best for: Real-time data, infinite scroll, datasets that change frequently
@router.get("/analyses")
async def list_analyses(
cursor: str | None = None,
limit: int = Query(default=20, le=100)
) -> PaginatedResponse:
results = await repo.get_paginated(cursor=cursor, limit=limit)
return {
"data": results,
"pagination": {
"next_cursor": encode_cursor(results[-1].id) if results else None,
"has_more": len(results) == limit
}
}Response:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIzfQ",
"has_more": true
}
}Client usage:
// First page
const page1 = await fetch('/api/v1/analyses?limit=20')
const { data, pagination } = await page1.json()
// Next page
if (pagination.has_more) {
const page2 = await fetch(`/api/v1/analyses?cursor=${pagination.next_cursor}&limit=20`)
}Offset-Based Pagination (For Known Bounds)
Best for: Admin panels, small datasets, "jump to page N" UX
@router.get("/analyses")
async def list_analyses(
page: int = Query(default=1, ge=1),
per_page: int = Query(default=20, le=100)
) -> PaginatedResponse:
offset = (page - 1) * per_page
results, total = await repo.get_paginated(offset=offset, limit=per_page)
return {
"data": results,
"pagination": {
"page": page,
"per_page": per_page,
"total": total,
"total_pages": (total + per_page - 1) // per_page
}
}Response:
{
"data": [...],
"pagination": {
"page": 2,
"per_page": 20,
"total": 487,
"total_pages": 25
}
}Filtering and Sorting
Query Parameter Filtering
@router.get("/analyses")
async def list_analyses(
status: str | None = None,
content_type: str | None = None,
created_after: datetime | None = None,
created_before: datetime | None = None
) -> list[AnalysisResponse]:
filters = {}
if status:
filters["status"] = status
if content_type:
filters["content_type"] = content_type
# ...
return await repo.find_all(filters=filters)Usage:
GET /api/v1/analyses?status=completed&content_type=article
GET /api/v1/analyses?created_after=2025-01-01&created_before=2025-12-31Sorting
@router.get("/analyses")
async def list_analyses(
sort: str = Query(default="-created_at")
) -> list[AnalysisResponse]:
# Parse sort parameter: "-created_at" -> ("created_at", "desc")
direction = "desc" if sort.startswith("-") else "asc"
field = sort.lstrip("-")
return await repo.find_all(
order_by=field,
direction=direction
)Usage:
GET /api/v1/analyses?sort=-created_at # Newest first
GET /api/v1/analyses?sort=title # Alphabetical
GET /api/v1/analyses?sort=-status,title # Multiple fieldsField Selection (Sparse Fieldsets)
@router.get("/analyses")
async def list_analyses(
fields: str | None = None
) -> list[dict[str, Any]]:
selected_fields = fields.split(",") if fields else None
results = await repo.find_all()
if selected_fields:
return [
{k: v for k, v in item.dict().items() if k in selected_fields}
for item in results
]
return resultsUsage:
GET /api/v1/analyses?fields=id,title,statusError Response Format
Standard Error Structure
# app/api/schemas/errors.py
class ErrorDetail(BaseModel):
field: str
message: str
code: str
class ErrorResponse(BaseModel):
error: dict[str, Any]
class Config:
json_schema_extra = {
"example": {
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "url",
"message": "Invalid URL format",
"code": "INVALID_URL"
}
],
"timestamp": "2025-12-21T10:30:00Z",
"request_id": "req_abc123"
}
}
}FastAPI Exception Handlers
# app/main.py
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"code": exc.status_code,
"message": exc.detail,
"timestamp": datetime.now(UTC).isoformat(),
"path": request.url.path
}
}
)
@app.exception_handler(ValidationError)
async def validation_exception_handler(request: Request, exc: ValidationError):
errors = []
for error in exc.errors():
errors.append({
"field": ".".join(str(x) for x in error["loc"]),
"message": error["msg"],
"code": error["type"]
})
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": errors,
"timestamp": datetime.now(UTC).isoformat()
}
}
)Rate Limiting
Response Headers
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@router.get("/analyses")
@limiter.limit("100/minute")
async def list_analyses(request: Request) -> list[AnalysisResponse]:
# Rate limited to 100 requests per minute
passResponse headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1703163600When exceeded:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1703163600
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again in 60 seconds.",
"retry_after": 60,
"timestamp": "2025-12-21T10:30:00Z"
}
}Best Practices
1. Always Return Consistent Response Format
# Good: Consistent structure
{
"data": {...},
"metadata": {...}
}
# Bad: Inconsistent structure
{...} # Sometimes flat object
{"results": [...]} # Sometimes wrapped2. Use Pydantic for Request/Response Validation
from pydantic import BaseModel, HttpUrl, Field
class AnalyzeRequest(BaseModel):
url: HttpUrl
analysis_id: str | None = None
skill_level: str = Field(default="beginner", pattern="^(beginner|intermediate|advanced)$")3. Include Metadata in Responses
{
"analysis_id": "123",
"url": "https://example.com",
"created_at": "2025-12-21T10:30:00Z",
"updated_at": "2025-12-21T11:00:00Z"
}4. Use OpenAPI Documentation
@router.get(
"/analyses/{analysis_id}",
responses={
404: {"model": ErrorResponse, "description": "Analysis not found"},
500: {"model": ErrorResponse, "description": "Internal server error"}
},
summary="Get analysis details",
description="Retrieve detailed information about a specific analysis including status and artifacts"
)
async def get_analysis(
analysis_id: Annotated[uuid.UUID, Path(description="Analysis UUID")]
) -> AnalysisResponse:
...5. Handle Edge Cases
# Empty collections: Return empty array, not null
{"data": []} # ✅
{"data": null} # ❌
# Deleted resources: Return 404, not null
# ❌ {"data": null}
# ✅ 404 Not Found
# Null fields: Be explicit
{
"title": null, # ✅ Explicitly null
"description": "" # ✅ Empty string if required
}Related Files
- See
assets/openapi-template.yamlfor full OpenAPI specification example - See
examples/orchestkit-api-design.mdfor OrchestKit-specific patterns - See SKILL.md for GraphQL and gRPC patterns
Create OpenAPI spec: $ARGUMENTS
API Context (Auto-Detected)
- API Name: $ARGUMENTS
- Existing Endpoints: !
grep -r "@router\.\|@app\.\|@api\.\|router\.get\|router\.post" . --include="*.py" --include="*.ts" 2>/dev/null | wc -l | tr -d ' ' || echo "0" - API Base URL: !
grep -r "API_URL\|BASE_URL\|VITE_API" .env* 2>/dev/null | head -1 | cut -d'=' -f2 || echo "https://api.example.com" - Framework: !
grep -r "fastapi\|express\|next" package.json pyproject.toml 2>/dev/null | head -1 | grep -oE 'fastapi|express|next' || echo "FastAPI" - Version: !
grep -r '"version"' package.json pyproject.toml 2>/dev/null | head -1 | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' || echo '"1.0.0"'
OpenAPI Specification
openapi: 3.1.0
info:
title: $ARGUMENTS
version: !`grep -r '"version"' package.json pyproject.toml 2>/dev/null | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "1.0.0"`
description: |
API for $ARGUMENTS
Generated: !`date +%Y-%m-%d`
Framework: !`grep -r "fastapi\|express" package.json pyproject.toml 2>/dev/null | head -1 | grep -oE 'fastapi|express' || echo "Unknown"`
servers:
- url: !`grep -r "API_URL\|BASE_URL" .env* 2>/dev/null | head -1 | cut -d'=' -f2 || echo "https://api.example.com/v1"`
description: Production server
- url: http://localhost:3000/v1
description: Local development
paths:
# Add your endpoints here
# Detected endpoints: !`grep -r "@router\.\|router\.get\|router\.post" . --include="*.py" --include="*.ts" 2>/dev/null | head -5 || echo "None detected"`Usage
1. Review detected endpoints above 2. Add paths based on your routes 3. Save to: openapi.yaml or api-spec.yaml