OpenAPI-First skill for Codex

The spec leads, the server proves it matches. Codex treats openapi.yaml as the contract: it designs paths and components in the spec first, lints them against a Spectral or Redocly style guide, diffs the bundle against main with oasdiff, regenerates server types so handlers fail to compile when they drift, and contract-tests the running service with Schemathesis or response validation.

  • Backend & APIs
  • $openapi-first
  • 198 lines
  • 2 files
  • ~1,850 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- openapi-first

Installs to ~/.agents/skills/openapi-first/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $openapi-first, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Runs a design-first API workflow where the OpenAPI 3.1 document is the source of truth - authoring reusable components, linting with Spectral or Redocly CLI, bundling multi-file specs, detecting breaking changes with oasdiff, generating server types or stubs, mocking with Prism and contract-testing the implementation. Use when starting an API from a spec, adding paths to openapi.yaml, setting up API linting or a style guide in CI, or when the spec and the running server have drifted apart.

Use it when

  • Starting a new API from a spec rather than from code
  • Adding or changing operations in an OpenAPI document
  • Adding API linting or breaking-change gates to CI
  • The published spec and the running server disagree

Use something else when

How to check the result

Spectral or Redocly lint passes, oasdiff breaking reports nothing unexpected against main, regenerated code has no diff, and contract tests pass against the running server.

The mistake it prevents

Keeping a hand-written spec alongside framework annotations that also emit one, so two sources of truth drift apart within weeks.

~/.agents/skills/openapi-first/SKILL.md

--- name: openapi-first description: Runs a design-first API workflow where the OpenAPI 3.1 document is the source of truth - authoring reusable components, linting with Spectral or… license: MIT ---

Design-first is working when the spec changes in the same pull request as the code (or before it), CI fails on lint errors and unapproved breaking changes, server types are generated from the spec rather than hand-copied, and a contract test proves the running server matches the document.

Decide the source of truth first

SituationApproach
Public or partner API, several client teams, long-lived contractDesign-first: hand-written spec, generated server types
Framework already emits a good spec (FastAPI, NestJS, Huma, springdoc) and one team owns both sidesCode-first, but commit the emitted spec and run the same lint and diff gates on it
Spec exists but nobody trusts itContract-test the server against it first, fix drift, then pick a side

Never maintain two sources of truth: a hand-written spec plus annotations that also emit one will drift.

Workflow

- [ ] 1. Locate the spec, tooling and generation steps
- [ ] 2. Design the change in the spec
- [ ] 3. Lint against the style guide
- [ ] 4. Check for breaking changes
- [ ] 5. Regenerate types/stubs and implement
- [ ] 6. Mock and contract-test

1. Locate

  • Read AGENTS.md, then find openapi.yaml|json, api/, spec/, .spectral.yaml, redocly.yaml, and codegen configs (openapi-ts.config.*, oapi-codegen.yaml, openapitools.json). Check package.json scripts or Makefile targets that lint, bundle or generate.
  • Note the OpenAPI version. Default to 3.1 for new specs. OpenAPI 3.2 was published in 2025; adopt it only when every linter, generator and doc tool in the chain supports it.

2. Design in the spec

Structure large specs as multiple files and bundle for distribution:

openapi/
  openapi.yaml            # info, servers, security, paths -> $ref
  paths/invoices.yaml
  components/schemas/Invoice.yaml
  components/responses/Problem.yaml

Authoring rules:

  • Every operation has a unique, stable operationId in camelCase (listInvoices); generators turn it into function names, so renaming it is a breaking change for SDKs.
  • Every operation declares security (or inherits it explicitly), at least one 2xx, the 4xx responses it really returns, and a default error response.
  • Put shared shapes in components/schemas, components/parameters and components/responses; reference them with $ref. Inline only one-off shapes.
  • Use JSON Schema 2020-12 features available in 3.1: type: [string, "null"] instead of nullable, const, examples arrays.
  • Mark server-generated fields readOnly: true and secrets writeOnly: true so one schema serves request and response.
  • Add additionalProperties: false on request bodies only if the server really rejects unknown fields; otherwise the spec lies.
  • Give every schema and property a description and at least one example; documentation and mocks come from them.
paths:
  /invoices/{invoiceId}:
    get:
      operationId: getInvoice
      tags: [Invoices]
      parameters:
        - $ref: '#/components/parameters/InvoiceId'
      responses:
        '200':
          description: The invoice
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Invoice'
        '404':
          $ref: '#/components/responses/NotFound'
        default:
          $ref: '#/components/responses/Problem'

3. Lint

Pick one linter; do not run two overlapping rule sets.

Spectral (.spectral.yaml):

extends: ["spectral:oas"]
rules:
  operation-operationId: error
  operation-tag-defined: error
  paths-kebab-case:
    description: Path segments must be kebab-case
    severity: error
    given: "$.paths[*]~"
    then:
      function: pattern
      functionOptions:
        match: "^(/([a-z0-9-]+|\\{[a-zA-Z]+\\}))+$"
npx @stoplight/spectral-cli lint openapi/openapi.yaml --fail-severity=warn

Redocly CLI (redocly.yaml with extends: [recommended]):

npx @redocly/cli lint openapi/openapi.yaml
npx @redocly/cli bundle openapi/openapi.yaml -o dist/openapi.yaml

npx downloads packages on first use, which needs network; in an offline sandbox prefer the versions pinned in devDependencies or ask before installing.

4. Breaking-change gate

Compare the bundled spec against the base branch with oasdiff:

git show origin/main:dist/openapi.yaml > /tmp/base-openapi.yaml
oasdiff breaking /tmp/base-openapi.yaml dist/openapi.yaml --fail-on ERR

Breaking examples: removing a path, operation, response field or enum value; adding a required request field or parameter; narrowing a type; changing operationId. When a break is intended, it goes with a version bump or deprecation plan (see api-versioning), not a quiet merge.

5. Generate and implement

StackGenerate
TypeScript server or client typesopenapi-typescript to a .d.ts; validate requests with the spec at runtime (e.g. express-openapi-validator, Fastify route schemas)
Gooapi-codegen with a config file producing models plus a strict server interface
Java / Kotlin Springopenapi-generator spring generator with interfaceOnly=true
Pythondatamodel-code-generator for Pydantic models; Connexion for spec-routed apps
  • Generated code is never edited by hand. Commit it or regenerate in CI, matching what the repo already does.
  • Implement against the generated interface so a spec change produces a compile error where the handler no longer matches.
  • Add a script (npm run api:generate, make api) so the step is one command.

6. Mock and contract-test

  • Mock for frontend or partner work before the server exists: npx @stoplight/prism-cli mock dist/openapi.yaml (serves examples; add -d for dynamic data).
  • Contract-test the real server against the spec with Schemathesis (property-based requests from the schema; the base URL flag is --url in v4) or with response validation middleware enabled in the test environment.
  • Fail CI when the server returns a status or shape the spec does not declare.

Gotchas

  • nullable: true is ignored in 3.1; use a type array. Mixing 3.0 and 3.1 syntax passes some linters and breaks some generators.
  • allOf with additionalProperties: false in each branch rejects every object; use unevaluatedProperties: false at the top level in 3.1.
  • oneOf without a discriminator generates poor types in most generators.
  • Example values that violate their own schema make mocks lie; Spectral’s oas3-valid-schema-example rule catches them.
  • Path templating must match the server router exactly ({invoiceId} vs :id); a mismatch makes validation middleware skip routes silently.

Verification

  • Lint passes with zero errors; warnings are fixed or explicitly waived in config.
  • oasdiff breaking shows no unexpected breaks against the base branch.
  • Regenerated code has no diff after regeneration (git diff --exit-code on the generated directory).
  • Contract tests pass against the running server.

Output template

## API spec change
- Operations: <operationId, method, path>
- Lint: <tool, result>
- Breaking changes: <none | list + versioning plan>
- Generated artifacts: <paths, regenerated with command>
- Contract test: <tool, result>

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "OpenAPI-First"
  short_description: "The spec leads, the server proves it matches"
  default_prompt: "$openapi-first Add the invoices endpoints to openapi.yaml before we implement them"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem