From harness
Use when user wants to Generate API endpoints from spec: route definitions, controllers, request/response validation, error handling, pagination, rate limiting. OpenAPI-driven or convention-based.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness:api-scaffold Resource name or OpenAPI spec path (e.g., 'users' or 'openapi.yaml')Resource name or OpenAPI spec path (e.g., 'users' or 'openapi.yaml')software-engineerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generates a complete API resource layer: route definitions, controllers/handlers, request validation, response serialization, error handling, pagination, and rate limiting middleware. Follows REST conventions by default, supports GraphQL where the project uses it.
Generates a complete API resource layer: route definitions, controllers/handlers, request validation, response serialization, error handling, pagination, and rate limiting middleware. Follows REST conventions by default, supports GraphQL where the project uses it.
| Signal | API Style |
|---|---|
express, fastify, koa in deps | REST (Node.js) |
config/routes.rb | REST (Rails) |
urls.py | REST (Django) |
gin, echo, chi in imports | REST (Go) |
type-graphql, @nestjs/graphql | GraphQL |
apollo-server, graphql-yoga | GraphQL |
| Existing OpenAPI/Swagger spec | REST (spec-driven) |
For each resource, define the contract before generating code:
## Resource: [name]
### Endpoints
- POST /api/v1/[resources] → Create
- GET /api/v1/[resources] → List (paginated)
- GET /api/v1/[resources]/:id → Show
- PATCH /api/v1/[resources]/:id → Update
- DELETE /api/v1/[resources]/:id → Soft delete
### Request/Response
- Create: { field1: string, field2: number } → 201 { id, field1, field2, created_at }
- List: ?page=1&per_page=20&sort=created_at&order=desc → 200 { data: [...], meta: { page, per_page, total, total_pages } }
- Show: → 200 { id, field1, field2, created_at, updated_at }
- Update: { field1?: string } → 200 { id, field1, field2, updated_at }
- Delete: → 204 No Content
### Validation Rules
- field1: required, string, max 255 chars
- field2: required, integer, min 0
Follow the project's existing patterns. If no patterns exist, use these conventions:
1. Parse and validate request input
2. Authenticate the caller (verify token/session)
3. Authorize the action (RBAC check — does this role have permission?)
4. Authorize the resource (object-level — does this user own/have access to THIS specific resource?)
5. Call service/use-case layer (never put business logic in controller)
6. Serialize response
7. Return with appropriate status code
Object-level authorization (critical): Never trust the client-provided ID alone. Always verify the authenticated user owns or is permitted to access the specific resource: user.id == record.owner_id or record.tenant_id == user.tenant_id. Scope database queries by the authenticated user's permissions — never fetch by ID without an ownership check.
| Action | Success | Client Error | Not Found |
|---|---|---|---|
| Create | 201 Created | 422 Unprocessable | — |
| List | 200 OK | 400 Bad Request | — |
| Show | 200 OK | — | 404 Not Found |
| Update | 200 OK | 422 Unprocessable | 404 Not Found |
| Delete | 204 No Content | — | 404 Not Found |
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable description",
"details": [
{ "field": "email", "message": "is already taken", "code": "UNIQUE_VIOLATION" }
],
"request_id": "req_abc123"
}
}
Implement cursor-based pagination for performance, offset-based for simplicity:
GET /api/v1/resources?cursor=eyJpZCI6MTAwfQ&limit=20
Response:
{
"data": [...],
"meta": {
"has_next": true,
"next_cursor": "eyJpZCI6MTIwfQ",
"limit": 20
}
}
GET /api/v1/resources?page=2&per_page=20
Response:
{
"data": [...],
"meta": {
"page": 2,
"per_page": 20,
"total": 150,
"total_pages": 8
}
}
Add rate limiting middleware to protect endpoints:
| Endpoint Type | Rate Limit | Window |
|---|---|---|
| Authentication (login, signup) | 5 requests | 15 minutes |
| Password reset | 3 requests | 1 hour |
| API general | 100 requests | 1 minute |
| API search/list | 30 requests | 1 minute |
| Webhooks | 1000 requests | 1 minute |
Response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1679616000
Retry-After: 30 (only on 429)
Account lockout (authentication endpoints):
429 response body:
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Try again in 30 seconds.",
"retry_after": 30
}
}
Validate all input at the boundary:
Use framework-native validation:
Configure security headers and CORS before any endpoint code:
Security Headers (mandatory):
helmet middlewaresecure_headers gemdjango-csp + built-in SecurityMiddlewaresecure packageRequired headers:
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'
Referrer-Policy: strict-origin-when-cross-origin
X-XSS-Protection: 0 (rely on CSP instead)
CORS Configuration (mandatory for APIs consumed by browsers):
* for APIs that handle authenticationcredentials: true only when the client sends cookiesAccess-Control-Allow-Methods to the methods each endpoint actually supportsAccess-Control-Max-Age to cache preflight responses (3600s)SSRF Prevention (for endpoints accepting user-provided URLs):
https://)127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.169.254 (cloud metadata)Default to URL-based versioning (/api/v1/...):
/api/v1/users → Current stable API
/api/v2/users → Next version (breaking changes)
Version in URL is explicit, discoverable, and cacheable. Header-based versioning (Accept: application/vnd.api+json; version=2) is an option but harder to test and debug.
After endpoints are built, generate or update the OpenAPI 3.x spec:
openapi: 3.0.3
info:
title: [Project] API
version: 1.0.0
paths:
/api/v1/resources:
get:
summary: List resources
parameters:
- name: page
in: query
schema: { type: integer, default: 1 }
responses:
'200':
description: Paginated list
Use framework tooling where available:
Verdict: API_SCAFFOLDED
Next: Implement business logic in service layer, then /harness:build-implementation for TDD
Artifacts: [route definitions, controllers, validation schemas, error handlers, OpenAPI spec, rate limit config]
$ARGUMENTS
npx claudepluginhub paulingham/.claude --plugin harnessProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.