skills/openapi-spec-writer/SKILL.md
--- license: Apache-2.0 name: openapi-spec-writer version: 1.0.0 category: Backend & Infrastructure tags: - openapi - swagger - api-specification - documentation - rest --- # OpenAPI Spec Writer Expert in writing OpenAPI 3.0/3.1 specifications. Produces specs that serve as enforceable contracts, not just documentation. Operates API-first: the spec is written and validated before any implementation code. ## Decision Points Navigate these decision trees for every new spec: ### OpenA
npx skillsauth add curiositech/windags-skills openapi-spec-writerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert in writing OpenAPI 3.0/3.1 specifications. Produces specs that serve as enforceable contracts, not just documentation. Operates API-first: the spec is written and validated before any implementation code.
Navigate these decision trees for every new spec:
Is this a greenfield API?
├─ YES → Use OpenAPI 3.1 (JSON Schema 2020-12, better examples)
└─ NO → Are you using existing tooling?
├─ Codegen (Swagger, OpenAPI Generator) → Use 3.0.3 (better tool support)
└─ Documentation only → Use 3.1 (richer schema features)
Who consumes this API?
├─ Public developers → API key in header (simple, cacheable)
├─ Browser apps → OAuth2 authorization_code + PKCE
├─ Mobile apps → OAuth2 authorization_code + PKCE
├─ Service-to-service → OAuth2 client_credentials
└─ Internal only → Bearer token or mTLS
Can this field be missing vs explicitly null?
├─ Both missing and null allowed → Don't list in `required`, type: [string, "null"]
├─ Missing OK, null forbidden → Don't list in `required`, type: string
├─ Required but nullable → List in `required`, type: [string, "null"]
└─ Required and non-null → List in `required`, type: string
Do schemas share 80%+ fields?
├─ YES → Use inheritance
│ ├─ Discriminated unions → `allOf` + `discriminator`
│ └─ Simple extension → `allOf` with base schema
└─ NO → Are there 2-3 common fields?
├─ YES → Extract common fields to separate schema, compose with `allOf`
└─ NO → Keep schemas separate, inline if single-use
What's the max expected dataset size?
├─ < 100 items → No pagination (return array directly)
├─ 100-10K items → Offset/limit (page + size params)
└─ > 10K items → Cursor-based (stable under concurrent writes)
Symptom: Swagger UI shows infinite loading, codegen crashes with stack overflow
Detection: If spectral lint reports "Circular reference" or tools hang on schema processing
Fix: Break cycles at collection boundaries. Parent-child relationships should reference child by ID only in parent, full object only in child → parent direction
Symptom: 50+ schemas in components, most used exactly once, spec file exceeds 2000 lines for basic CRUD
Detection: If >70% of schemas in components/schemas have only 1 $ref usage
Fix: Inline single-use schemas. Extract to components only when 2+ operations share the exact same structure
Symptom: oneOf without discriminator generates useless union types in codegen, runtime type checking fails
Detection: If you have oneOf/anyOf without discriminator property
Fix: Always add discriminator with explicit mapping. Discriminator field must be required in all variants:
discriminator:
propertyName: type
mapping:
email: '#/components/schemas/EmailNotification'
sms: '#/components/schemas/SmsNotification'
Symptom: Client developers guess error format, inconsistent error handling across teams
Detection: If operations only document 200/201 responses, no 4xx/5xx schemas
Fix: Document standard error responses (400, 401, 403, 404, 422, 500) with consistent schema including code, message, and details fields
Symptom: /users/{userId} vs /posts/{post_id}, codegen produces mixed camelCase/snake_case
Detection: If path parameters, query parameters, or schema properties use multiple casing conventions
Fix: Pick one convention (camelCase for JSON APIs), enforce with Spectral rules, document in spec description
Context: Need CRUD API for user accounts in SaaS application
Step 1: Apply Decision Trees
Step 2: Define Core Resource Schema
components:
schemas:
User:
type: object
required: [id, email, createdAt]
properties:
id: {type: string, format: uuid, readOnly: true}
email: {type: string, format: email}
displayName: {type: string, maxLength: 100}
role: {type: string, enum: [member, admin], default: member}
createdAt: {type: string, format: date-time, readOnly: true}
Decision: Extract to components because GET, POST, and PATCH all return this exact structure
Step 3: Handle Create Request
paths:
/v1/users:
post:
requestBody:
content:
application/json:
schema:
type: object
required: [email]
properties:
email: {type: string, format: email}
displayName: {type: string, maxLength: 100}
role: {type: string, enum: [member, admin]}
Decision: Inline create schema (single use) vs User schema (multi-use)
Expert vs Novice: Novice would extract CreateUserRequest to components. Expert inlines because it's used only once, avoiding schema bloat.
Spec is production-ready when all conditions pass:
components/schemas, all single-use schemas inlinedoneOf/anyOf has explicit discriminator with property mapping$ref chains (passes spectral lint without errors)example with actual data valuesoperationId suitable for code generationDon't use this skill for:
graphql-schema-architect insteadevent-schema-design insteaddatabase-architect insteadrest-api-implementation insteadDelegate when:
api-client-generator after spec completionapi-security-specialist for implementation detailsapi-performance-optimizer for implementation tuningapi-gateway-configurator for deploymentdata-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.