How-to › Describe your API

How to fill missing schemas and examples with a coding agent#

List every response with no schema or example with two Spectral rules, hand the gaps and fixtures to a coding agent, and validate every example after every batch.

Audience
API producer
Level
intermediate
Topic
Author or repair a spec with a coding agent
Verified

Your spec has every operation and half the answers. POST /meters returns 201 with an empty application/json object under content, the 404 has a description and no body, and the one schema that exists carries no example. A generator makes an untyped client from it, a mock server has nothing to serve, and the converter that wrote the file never saw a real response.

What you get

You will end up with a ruleset that lists every missing schema and example, a prompt that hands the gaps and fixtures to an agent, and two validators for the result. This is for you if you own a hand-written or converted spec whose responses are empty.

Short answer

Write two Spectral rules that fail a response media type with no schema and one with no example, and run them beside the built-in oas3-valid-media-example. Hand the agent the findings and the captured payloads under fixtures/, and ask it to add each schema under components and reference it. Run Spectral and openapi-examples-validator after every batch, because the first batch tends to carry an example its own schema rejects.

You will need

Node 22 or later, a spec whose operations exist, and response payloads captured from a test suite or a proxy, one file per response. Verified 2026-09-25 against Node 22.22.2, @stoplight/spectral-cli 6.16.3, openapi-examples-validator 7.1.0 and quicktype 26.0.0. The Media Type Object is where a response’s schema and example live, and the Components Object is where a schema goes when two responses share it.

Approaches compared

ApproachWhen it fitsWhat it costs youWhen to pick something else
Claude CodeDozens of gaps, payloads on disk, and shared components wanted rather than inline copiesA guessed format or enum member in the first batch, and a validation run you have to insist onThere are no captured payloads, so every schema would be a guess
openapi-examples-validatorA second opinion on every example from a tool with one jobAnother dependency for a check Spectral also performs, and errors on stderr rather than a reportOne checker in CI is enough and it is already Spectral
quicktypeOne payload, one schema, and no judgement wantedA schema per sample with no reuse, every key required, and a null typed as nullThe same object appears in three responses and should be one component
SpectralListing the gaps, then gating the result in CI on every commitTwo custom rules to write, and a ruleset that says where the holes are and fills none of themThe examples need writing, not counting
Stoplight StudioA handful of gaps and a person who knows the payloadsA form per schema filled by hand, so nothing is guessed and nothing is fastThe gaps run into the dozens, or the payloads already exist as files

The five are not rivals for the same job. Spectral finds the gaps and gates the result. The agent, quicktype and Studio are three ways to fill them, from most judgement to least, and the validator is the check that the filling was right. The agent is the only one of the three that will reuse a component, and the only one that will invent an enum member.

List the gaps before anyone fills them

The Spectral OpenAPI ruleset knows nothing about missing schemas, because a response with no content is valid OpenAPI. Two custom rules say what this spec requires, and the rest of spectral:oas is off so the run reports gaps and nothing else.

extends: [[spectral:oas, off]]
rules:
  oas3-schema: error
  oas3-valid-media-example: error
  oas3-valid-schema-example: error
  response-has-content:
    description: every response except 204 declares a content object
    given: "$.paths[*][*].responses[?(@property != '204')]"
    severity: error
    then:
      field: content
      function: truthy
  response-content-has-schema:
    description: every response media type carries a schema
    given: "$.paths[*][*].responses[*].content[*]"
    severity: error
    then:
      field: schema
      function: truthy
  response-content-has-example:
    description: every response media type carries an example or examples
    given: "$.paths[*][*].responses[*].content[*]"
    severity: error
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          anyOf:
            - required: [example]
            - required: [examples]

truthy and schema are two of the core functions. The third rule uses schema because a media type may carry either example or examples, and a truthy check on one field cannot say “one of these two.” oas3-valid-media-example is the built-in rule that will matter after the agent has written something.

npx spectral lint openapi.yaml
 16:15  error  response-has-content          every response except 204 declares a content object       paths./meters.get.responses[200]
 31:30  error  response-content-has-example  every response media type carries an example or examples  paths./meters.post.responses[201].content.application/json
 31:30  error  response-content-has-schema   every response media type carries a schema                paths./meters.post.responses[201].content.application/json
 35:38  error  response-content-has-example  every response media type carries an example or examples  paths./meters.post.responses[422].content.application/problem+json
 35:38  error  response-content-has-schema   every response media type carries a schema                paths./meters.post.responses[422].content.application/problem+json
 50:30  error  response-content-has-example  every response media type carries an example or examples  paths./meters/{meter_id}.get.responses[200].content.application/json
 53:15  error  response-has-content          every response except 204 declares a content object       paths./meters/{meter_id}.get.responses[404]
 82:30  error  response-content-has-example  every response media type carries an example or examples  paths./meters/{meter_id}/readings.post.responses[201].content.application/json
 82:30  error  response-content-has-schema   every response media type carries a schema                paths./meters/{meter_id}/readings.post.responses[201].content.application/json

✖ 9 problems (9 errors, 0 warnings, 0 infos, 0 hints)

All nine findings, spread across four operations, carry a JSON path. That list is the work order, and it is what the agent gets, rather than “fill in the spec.”

Hand the agent the gaps and the fixtures

The prompt names the command that lists the gaps, the directory of payloads, the rule about components, and the two checks that end the loop. Everything the agent might otherwise guess is either on disk or forbidden.

Fill the gaps that `npx spectral lint openapi.yaml` reports in openapi.yaml.

Rules:

1. Every response media type gets a `schema` and an `example`. Derive both from
   the payloads under fixtures/: meter.json, meter-list.json, reading.json,
   problem-404.json and problem-422.json are real responses captured from the
   test suite.
2. Put every object schema under components/schemas and reference it with
   `$ref`. Do not inline a schema that a second response could share. The
   error responses share one schema, `Problem`, shaped like RFC 9457.
3. Keep the examples exactly as the fixtures have them. Do not invent values,
   shorten strings, or change an enum member.
4. After each batch of edits, run both checks and stop when both pass:
   `npx spectral lint openapi.yaml` and
   `npx openapi-examples-validator openapi.yaml`.
5. Write the result to openapi.yaml in place. Do not touch fixtures/.

In Claude Code, @fixtures/meter.json in the message puts the file’s content in the conversation. The file reference documentation adds that a directory reference gives a listing and not the contents, so the prompt names each file. Rule 3 exists because an example is evidence, and an agent that tidies mtr_01HZX4 into meter-1 has replaced evidence with an opinion.

No model is called by anything on this page. openapi.batch1.yaml and openapi.filled.yaml were written for it as recorded stand-ins for the agent’s first and second batch. They have the shape an agent produces and the two defects a first batch tends to carry, so every check below runs offline and prints the same thing every time.

Validate after the first batch, not at the end

The first batch closes all nine gaps. Every response has a schema, every schema is a $ref, and every media type has an example. The gap rules are silent, and the built-in example rule is not.

npx spectral lint openapi.batch1.yaml
 30:27  error  oas3-valid-media-example  "kind" property must be equal to one of the allowed values: "electricity", "gas", "water"  paths./meters.get.responses[200].content.application/json.example.items[1].kind
 53:31  error  oas3-valid-media-example  "installed_at" property must match format "date-time"                                      paths./meters.post.responses[201].content.application/json.example.installed_at

Two defects, both of a kind that reads correctly at a glance. The list example says kind: elec where the enum the same batch wrote says electricity, and the create example says installed_at: '2026-03-02' against a schema that says format: date-time.1 The second checker reports the same two, as an array on stderr.

npx openapi-examples-validator openapi.batch1.yaml
Validating examples
Schemas with examples found: 4
Examples without schema found: 0
Total examples found: 6

Errors found.

[
    {
        "type": "Validation",
        "message": "must match format \"date-time\"",
        "instancePath": "/installed_at",
        "schemaPath": "#/properties/installed_at/format",
        "keyword": "format",
        "params": {
            "format": "date-time"
        },
        "examplePath": "/paths/~1meters/post/responses/201/content/application~1json/example"
    },

Run both after every batch, not once at the end. An agent that gets the second batch’s findings fixes two values. An agent that gets thirty findings after ten batches starts rewriting schemas to fit the examples. That is the wrong direction: the examples came from the fixtures, and the schemas were the guess.

Compare with what quicktype infers

quicktype answers the same question with no model and no judgement: one sample in, one JSON Schema out.

npx quicktype -l schema fixtures/meter-list.json
                "next_cursor": {
                    "type": "null"
                }
            },
            "required": [
                "items",
                "next_cursor"
            ],
            "title": "MeterList"
        },
        "Item": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
                "id": {
                    "type": "string"
                },
                "site": {
                    "type": "string"
                },
                "kind": {
                    "type": "string"
                },
                "installed_at": {
                    "type": "string",
                    "format": "date-time"
                }
            },
            "required": [
                "id",
                "installed_at",
                "kind",
                "site"
            ],
            "title": "Item"
        }

Three things in that output are the cost of having no judgement. next_cursor is typed null, because the one sample had a null in it, so a schema derived this way rejects the second page. The array element is named Item after the property, not Meter, and running the tool on meter.json produces a second, identical schema called Meter, so nothing is shared. And additionalProperties is false with every key required, which turns the first response that adds a field into a validation failure.2 What quicktype gets right, it gets right every time: the date-time format on installed_at was inferred from the value, and the agent’s first batch got that one wrong.

Check it worked

The second batch changes two values and nothing else. Both checkers go quiet, and the test pins the counts on all three documents so a regenerated ruleset or a changed fixture fails here.

npx spectral lint openapi.filled.yaml
No results with a severity of 'error' found!
npx openapi-examples-validator openapi.filled.yaml
Validating examples
Schemas with examples found: 4
Examples without schema found: 0
Total examples found: 6

No errors found.
test('batch one closes every gap and fails example validation twice', () => {
  const { code, results } = spectral('openapi.batch1.yaml')
  assert.equal(code, 1)
  assert.deepEqual(results.map((r) => r.code), ['oas3-valid-media-example', 'oas3-valid-media-example'])
  assert.deepEqual(results.map((r) => r.path.at(-1)), ['kind', 'installed_at'])

  const v = validator('openapi.batch1.yaml')
  assert.equal(v.code, 1)
  assert.deepEqual(v.errors.map((e) => e.keyword).sort(), ['enum', 'format'])
})
node --test gaps.test.mjs
1..5
# tests 5
# suites 0
# pass 5
# fail 0
# cancelled 0
# skipped 0
# todo 0

The fourth test reads the filled document and asserts every response schema is a $ref, and the fifth that every example is its fixture, verbatim. Those are the two rules in the prompt that no validator checks. A schema inlined three times validates three times, and so does a list example with one of its two meters dropped.

When it goes wrong

The gap rules pass, but the client is still untyped. The agent inlined the schemas, so every response validates and nothing is shared. Add the $ref check to the ruleset as a fourth rule, with field: schema.$ref and resolved: false, rather than trusting the prompt. Spectral resolves every $ref before a rule runs, so without that flag the rule fails every response that got it right.

The example passes Spectral and fails in production. The example was validated against a schema the agent also wrote, so both agree and both are wrong. Keep the examples verbatim from captured payloads, which is rule 3, and validate the payloads against the schema with the second checker rather than the other way round.

A date passes one validator and fails the other. JSON Schema treats format as an annotation unless a validator is told otherwise, and different tools decide differently. Both tools here enforce date-time, which is why the first batch was caught; a third tool may not, so run the two that do.

The agent rewrites the Problem schema to fit an example. The prompt said the error responses share one schema shaped like RFC 9457,3 and a fixture carried a detail the agent’s first draft left out. Adding the optional property is right; dropping the $ref to give one response its own copy is the failure to reject in review.

When not to do this

Do not hand an agent a spec with no fixtures and ask it to fill the schemas. Without a captured payload, every property name, every type and every format is a guess, and a guessed schema that validates its own guessed example proves nothing. Capture responses first, from tests or a proxy, and then fill.

Do not run the example check once, at the end. Ten batches of unvalidated edits produce a document where the examples and the schemas have drifted from each other in both directions, and the fix becomes a rewrite rather than two values.

Do not accept a schema the agent inlined because the gap rule went green. The rule asks whether a schema exists, not where. An inline copy under three responses is three schemas to keep in step, and the next agent to work on the file will edit one of them.

Do not let the quicktype output into components unedited. Its schemas are faithful to one sample: a null becomes a null type, every key becomes required, and unknown keys are forbidden. It is a draft for a person to edit, and as a draft it is faster than any agent.

Last verified

Verified 2026-09-25 against Node 22.22.2, @stoplight/spectral-cli 6.16.3, openapi-examples-validator 7.1.0 and quicktype 26.0.0. Every output block is what the command preceding it printed, run in the page’s code directory after npm ci. The two filled documents are committed stand-ins for an agent’s batches and were not produced by a model.

Footnotes

  1. The two checkers agree on 2026-03-02 because both chose to. JSON Schema 2020-12 says the format keyword is collected as an annotation, that an implementation may also treat it as an assertion, and that such evaluation must be switched off by default. It goes on to allow an implementation to validate any format as a no-op that always returns true. The same paragraph remarks that this matches the reality of implementations, which provide widely varying levels of validation. A date-time check is therefore something a validator opts into on your behalf, and two of them did. ↩︎ Back to text

  2. quicktype writes "$schema": "http://json-schema.org/draft-06/schema#". The draft-06 release notes open by noting that draft-07 has been released, then list what draft-06 changed from draft-04. id became $id, $ref became legal only where a schema is expected, and exclusiveMinimum turned from a boolean into a number. They also answer the question of what happened to draft-05, which was a readability rewrite whose name implementations are asked not to advertise. OpenAPI 3.1 uses 2020-12, three drafts later, and a generator reads both without complaint. ↩︎ Back to text

  3. RFC 9457, by Nottingham, Wilde and Dalal, was published in July 2023 and obsoletes RFC 7807. Its problem object has five members: type, title, status, detail, and instance. The rule for a member whose value has the wrong type is to ignore it and continue as if it were absent. A validator with a schema is stricter than the standard it validates. ↩︎ Back to text

Read this page as markdown · All how-to guides

Generate the client instead of writing it#

Retries, timeouts, pagination and auth are the same problems in every client. Voxgig generates them from your OpenAPI description, in 23 languages, from one model.

Get the Voxgig dispatch

Short notes on building SDKs, CLIs, REPLs, and MCPs for API-first teams, plus the occasional Fireside episode pick.

By signing up you agree to our Terms and Conditions.