Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Design RESTful APIs with OpenAPI 3.1/3.2, resource modeling, HTTP semantics, versioning, pagination, HATEOAS, and OWASP API Security.
.claude/skills/williamzujkowski-rest-api-designer/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 357% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 327% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 402% | 0% |
| case-11 | ✓→✓ | = Same ✓ | 300% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 127% | 0% |
Purpose: Design production-ready RESTful APIs following industry best practices with OpenAPI 3.1/3.2 specifications, resource-oriented architecture, HTTP semantics compliance, flexible pagination/filtering strategies, HATEOAS hypermedia support (Richardson Level 3), and OWASP API Security Top 10 2023 mitigations.
When to Use:
Complements:
api-graphql-designer: Use GraphQL for flexible client-driven queries; use REST for simple CRUD and public APIs.api-contract-testing: Validate OpenAPI specs with Pact or Spring Cloud Contract.security-api-gateway-configurator: Deploy REST APIs with gateway-level auth, throttling, and monitoring.Delegates to:
api-design-validator: Validates generated OpenAPI specs for schema compliance and security hardening.Mandatory Inputs:
domain_model: At least one entity with attributes (e.g., User: {id, email, name}).use_cases: Minimum one use case (e.g., "List all users", "Create a new order").Validation Steps:
maturity_target = 3, verify client can handle HATEOAS links (many clients only support Level 2).domain_model.Goal: Generate a basic RESTful API with OpenAPI 3.1 spec for a single resource using standard CRUD operations.
Steps:
domain_model (e.g., "User").GET /users, POST /usersGET /users/{id}, PUT /users/{id}, PATCH /users/{id}, DELETE /users/{id}GET /users: List all users (paginate with ?limit=20&offset=0 by default).POST /users: Create a new user (return 201 Created with Location header).GET /users/{id}: Retrieve a single user (return 200 OK or 404 Not Found).PUT /users/{id}: Replace entire user (idempotent, return 200 OK).PATCH /users/{id}: Partial update (return 200 OK or 204 No Content).DELETE /users/{id}: Delete user (idempotent, return 204 No Content).yaml openapi: 3.1.0 info: title: User API version: 1.0.0 paths: /users: get: summary: List users parameters:
in: query schema: {type: integer, default: 20}
in: query schema: {type: integer, default: 0} responses: 200: description: List of users content: application/json: schema: type: array items: {$ref: '#/components/schemas/User'} post: summary: Create user requestBody: required: true content: application/json: schema: {$ref: '#/components/schemas/User'} responses: 201: description: User created headers: Location: {schema: {type: string}} components: schemas: User: type: object required: email, name] properties: id: {type: string, format: uuid} email: {type: string, format: email} name: {type: string}
Token Budget: ≤2k tokens (single resource, basic CRUD).
Goal: Design a multi-resource API with versioning, pagination (cursor or offset), filtering, and OWASP API Security mitigations.
Steps:
GET /orders?userId=123 (query filtering).GET /users/{userId}/orders (if relationship is always accessed via parent)./users/{id}/settings where settings don't exist independently)./v1/users, /v2/users (most common, clear, cacheable).X-API-Version: 2 (clean URIs, harder to test in browser).Accept: application/vnd.api.v2+json (REST purist, complex for clients).pagination_preference):GET /orders?limit=20&offset=40{data: [...], pagination: {total: 150, limit: 20, offset: 40}}GET /orders?limit=20&after=cursorXYZ{data: [...], pagination: {nextCursor: "abc123", hasMore: true}}GET /orders?limit=20&afterId=1000&afterCreatedAt=2025-10-26T12:00:00ZGET /orders?status=completed&minAmount=100GET /orders?sort=createdAt:desc,amount:asc (multi-column).id as tiebreaker).userId matches authenticated user before returning /users/{userId}.yaml openapi: 3.1.0 info: title: E-commerce API version: 2.0.0 servers:
paths: /orders: get: summary: List orders parameters:
in: query schema: {type: integer, default: 20, maximum: 100}
in: query schema: {type: string}
in: query schema: {type: string, enum: pending, completed, cancelled]} security:
responses: 200: description: List of orders content: application/json: schema: type: object properties: data: {type: array, items: {$ref: '#/components/schemas/Order'}} pagination: type: object properties: nextCursor: {type: string} hasMore: {type: boolean} components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT schemas: Order: type: object required: id, userId, status, total] properties: id: {type: string, format: uuid} userId: {type: string, format: uuid} status: {type: string, enum: pending, completed, cancelled]} total: {type: number, format: decimal}
Token Budget: ≤6k tokens (multi-resource, versioning, pagination, security).
Goal: Design a Richardson Level 3 REST API with HATEOAS hypermedia links, webhooks, bulk operations, and advanced security.
Steps:
_links section with self, related, and action links.json { "id": "order-123", "status": "pending", "total": 99.99, "_links": { "self": {"href": "/v2/orders/order-123"}, "user": {"href": "/v2/users/user-456"}, "items": {"href": "/v2/orders/order-123/items"}, "cancel": {"href": "/v2/orders/order-123/cancel", "method": "POST"}, "pay": {"href": "/v2/orders/order-123/pay", "method": "POST"} } }
webhooks section:yaml webhooks: orderCreated: post: requestBody: content: application/json: schema: type: object properties: event: {type: string, example: "order.created"} data: {$ref: '#/components/schemas/Order'} responses: 200: description: Webhook received
GET /users?ids=1,2,3 (return array of users).POST /users/batch with array of users in body (return array of created users).PATCH /users/batch with array of {id, changes} objects.Idempotency-Key header for POST/PATCH to prevent duplicate operations.?createdAt[gte]=2025-01-01&createdAt[lt]=2025-02-01?q=laptop (searches across multiple fields).?status[in]=pending,completed (OR), ?status[ne]=cancelled (NOT EQUAL).GET /users/{id} returns ETag: "abc123" header.If-None-Match: "abc123" on next request.304 Not Modified if resource unchanged (saves bandwidth).json { "type": "https://api.example.com/errors/validation-error", "title": "Validation Error", "status": 400, "detail": "Email field is required", "instance": "/v2/users", "errors": [ {"field": "email", "message": "Email is required"} ] }
X-RateLimit-Limit: 100X-RateLimit-Remaining: 95X-RateLimit-Reset: 1698345600 (Unix timestamp)429 Too Many Requests with Retry-After header.Deprecation: true and Sunset: 2026-04-26 headers to v1 responses.Token Budget: ≤12k tokens (HATEOAS, webhooks, bulk ops, advanced security, caching).
Ambiguity Resolution:
versioning_strategy not specified:/v1/, /v2/) as it's most widely adopted and easiest to test.pagination_preference not specified:maturity_target not specified:/users, not /user)./createUser is wrong; use POST /users instead).GET /orders?userId=123) for flexibility.GET /users/{id}/profile where profile can't exist without user).Stop Conditions:
DELETE /users/{id}.id + createdAt.Thresholds:
limit=100 to prevent resource exhaustion. Return 400 Bad Request if limit > 100./users/{id}/orders/{orderId} is OK; /users/{id}/orders/{orderId}/items/{itemId} is too deep → flatten to /order-items/{id}).Required Fields:
typescript{ openapi_spec: { openapi: "3.1.0" | "3.2.0"; info: { title: string; version: string; // Semantic version (1.0.0, 2.1.0) description?: string; }; servers: Array<{ url: string; // https://api.example.com/v2 description?: string; }>; paths: { [path: string]: { // /users, /users/{id}, etc. [method: string]: { // get, post, put, patch, delete summary: string; parameters?: Array<{ name: string; in: "query" | "path" | "header"; schema: object; // JSON Schema required?: boolean; }>; requestBody?: { required: boolean; content: { "application/json": { schema: object; }; }; }; responses: { [statusCode: string]: { description: string; content?: { "application/json": { schema: object; }; }; }; }; security?: Array<object>; }; }; }; components: { schemas: { [name: string]: object; // JSON Schema definitions }; securitySchemes?: { [name: string]: { type: "http" | "apiKey" | "oauth2" | "openIdConnect" | "mutualTLS"; scheme?: "bearer" | "basic"; bearerFormat?: "JWT"; }; }; }; webhooks?: { // OpenAPI 3.1+ only [name: string]: { post: object; // Outbound webhook definition }; }; }; resource_design: { resources: Array<{ name: string; // User, Order, Product uri_template: string; // /users, /users/{id} http_methods: { GET?: string; // Description (e.g., "List all users") POST?: string; PUT?: string; PATCH?: string; DELETE?: string; }; relationships: Array<{ related_resource: string; relationship_type: "one-to-many" | "many-to-many" | "one-to-one"; uri_pattern: string; // /users/{userId}/orders or /orders?userId={userId} }>; }>; richardson_level: 0 | 1 | 2 | 3; }; versioning_config: { strategy: "uri" | "header" | "media-type"; current_version: string; // v2, 2.0.0 supported_versions: string[]; // [v1, v2] deprecation_timeline?: { deprecated_version: string; sunset_date: string; // ISO 8601 }; migration_guide: string; // Breaking changes, timeline }; pagination_config: { method: "offset" | "cursor" | "keyset"; parameters: { limit: { default: number; // 20 max: number; // 100 }; offset?: number; // For offset-based cursor?: string; // For cursor-based sortKey?: string; // For keyset-based }; response_format: { data_field: string; // "data" or "items" metadata_field: string; // "pagination" or "meta" metadata_shape: object; // {total, limit, offset} or {nextCursor, hasMore} }; }; security_recommendations: Array<{ owasp_category: string; // API1: BOLA, API2: Broken Auth, etc. risk: "high" | "medium" | "low"; mitigation: string; // Specific action (e.g., "Validate userId matches JWT") implementation: string; // Code snippet or config example }>; hateoas_links?: { // Only if maturity_target >= 3 link_relations: Array<{ rel: string; // self, related, action href_template: string; // /users/{id}, /users/{id}/orders method?: string; // POST, DELETE (for action links) }>; example_response: object; // JSON with _links section }; }
Optional Fields:
webhooks: Array of webhook definitions (event name, payload schema, delivery guarantees).caching_strategy: Object with ETag usage, Cache-Control headers, max-age values.rate_limiting: Object with limits per endpoint, throttling algorithm (token bucket, leaky bucket).bulk_operations: Array of batch endpoints (/users/batch, etc.) with idempotency requirements.Format: OpenAPI spec in YAML or JSON. Resource design and recommendations in Markdown.
Input:
yamldomain_model: User: {id: uuid, email: string, name: string} Order: {id: uuid, userId: uuid, status: enum, total: decimal} Product: {id: uuid, name: string, price: decimal} use_cases: - "List all orders for a user" - "Create a new order" - "Search products by name" versioning_strategy: "uri" pagination_preference: "cursor" security_requirements: auth: "OAuth2 (Bearer JWT)" rate_limit: "100 requests/minute per user"
Output (T2 Summary):
yamlRichardson Level: 2 (HTTP verbs + multiple resources) Versioning: URI-based (/v1/, /v2/) Pagination: Cursor-based (after parameter, nextCursor in response) Resources: - /v1/users (GET, POST) - /v1/users/{id} (GET, PUT, PATCH, DELETE) - /v1/orders (GET, POST) + ?userId filter + ?after cursor - /v1/orders/{id} (GET, PUT, PATCH, DELETE) - /v1/products (GET, POST) + ?q=search query Security: OAuth2 Bearer JWT, rate limit 100/min via X-RateLimit headers OWASP Mitigations: - API1 BOLA: Validate userId in JWT matches /users/{id} access - API4 Rate Limit: 100 req/min via API gateway (429 response if exceeded) - API8 Misconfiguration: No stack traces in production (generic 500 message)
Link to Full Example: See skills/api-rest-designer/examples/ecommerce-api-design.txt
HATEOAS Response Example:
json{ "id": "order-456", "status": "pending", "total": 149.99, "_links": { "self": {"href": "/v2/orders/order-456"}, "user": {"href": "/v2/users/user-789"}, "items": {"href": "/v2/orders/order-456/items"}, "cancel": {"href": "/v2/orders/order-456/cancel", "method": "POST"}, "pay": {"href": "/v2/payments", "method": "POST", "body": {"orderId": "order-456"}} } }
Webhook Definition (OpenAPI 3.1):
yamlwebhooks: orderCompleted: post: summary: Notifies when an order is completed requestBody: content: application/json: schema: type: object properties: event: {type: string, example: "order.completed"} timestamp: {type: string, format: date-time} data: {$ref: '#/components/schemas/Order'} responses: 200: description: Webhook acknowledged
Token Budget Compliance:
Validation Checklist:
/users, not /user).total, nextCursor, hasMore).201 Created or 200 OK with resource in body.swagger-cli validate).maturity_target (or Level 2 by default).Safety & Auditability:
deprecated: true in OpenAPI and Sunset header in responses.Access-Control-Allow-Origin settings if API is public-facing.Determinism:
id as tiebreaker if primary sort isn't unique).Idempotency-Key header to prevent duplicate resource creation.Official Specifications:
Security:
REST Design Patterns:
Pagination:
Versioning:
Complementary Skills:
api-graphql-designer: Alternative to REST for flexible client queries.api-contract-testing: Validate OpenAPI specs with Pact, Spring Cloud Contract.security-api-gateway-configurator: Deploy REST APIs with gateway-level auth, rate limiting, monitoring.api-design-validator: Automated OpenAPI spec validation and security hardening.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-11 | pass→pass | 13,561 | 19,039 | +40% | 1 | 1 | 0% | 3,146 | 12,576 | +300% | 0 | 0 | — |
case-01 | fail→fail | 25,180 | 21,965 | -13% | 1 | 1 | 0% | 6,261 | 13,447 | +115% | 0 | 0 | — |
case-02 | fail→fail | 21,192 | 16,982 | -20% | 1 | 1 | 0% | 5,635 | 12,258 | +118% | 0 | 0 | — |
case-03 | pass→pass | 23,881 | 22,039 | -8% | 1 | 1 | 0% | 6,267 | 14,196 | +127% | 0 | 0 | — |
case-04 | pass→pass | 11,157 | 12,472 | +12% | 1 | 1 | 0% | 2,614 | 11,113 | +325% | 0 | 0 | — |
case-05 | fail→fail | 11,902 | 14,395 | +21% | 1 | 1 | 0% | 2,944 | 11,569 | +293% | 0 | 0 | — |
case-06 | fail→pass | 9,159 | 10,838 | +18% | 1 | 1 | 0% | 2,327 | 10,645 | +357% | 0 | 0 | — |
case-07 | pass→pass | 12,728 | 10,758 | -15% | 1 | 1 | 0% | 3,273 | 10,656 | +226% | 0 | 0 | — |
case-08 | fail→fail | 14,466 | 15,485 | +7% | 1 | 1 | 0% | 3,396 | 11,438 | +237% | 0 | 0 | — |
case-09 | fail→fail | 12,925 | 15,381 | +19% | 1 | 1 | 0% | 3,007 | 11,601 | +286% | 0 | 0 | — |
case-10 | fail→pass | 14,053 | 19,281 | +37% | 1 | 1 | 0% | 2,941 | 12,547 | +327% | 0 | 0 | — |
case-12 | pass→pass | 13,495 | 17,156 | +27% | 1 | 1 | 0% | 2,871 | 12,066 | +320% | 0 | 0 | — |
case-13 | pass→pass | 11,742 | 11,080 | -6% | 1 | 1 | 0% | 2,345 | 10,200 | +335% | 0 | 0 | — |
case-14 | pass→pass | 14,364 | 19,225 | +34% | 1 | 1 | 0% | 3,006 | 12,289 | +309% | 0 | 0 | — |
case-15 | pass→pass | 13,112 | 11,361 | -13% | 1 | 1 | 0% | 2,941 | 10,670 | +263% | 0 | 0 | — |
case-16 | pass→pass | 11,731 | 14,479 | +23% | 1 | 1 | 0% | 2,990 | 11,363 | +280% | 0 | 0 | — |
case-17 | pass→pass | 9,899 | 13,438 | +36% | 1 | 1 | 0% | 2,151 | 11,318 | +426% | 0 | 0 | — |
case-18 | fail→pass | 10,229 | 10,620 | +4% | 1 | 1 | 0% | 2,075 | 10,414 | +402% | 0 | 0 | — |
case-19 | pass→pass | 4,801 | 9,863 | +105% | 1 | 1 | 0% | 1,217 | 10,084 | +729% | 0 | 0 | — |
case-20 | pass→pass | 10,394 | 9,025 | -13% | 1 | 1 | 0% | 2,433 | 9,855 | +305% | 0 | 0 | — |
case-21 | pass→pass | 5,006 | 6,393 | +28% | 1 | 1 | 0% | 1,118 | 9,332 | +735% | 0 | 0 | — |
case-22 | fail→fail | 17,315 | 21,889 | +26% | 1 | 1 | 0% | 4,438 | 12,811 | +189% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.