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 deploymenttools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.