# 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.

Source: https://voxgig.com/howto/fill-missing-schemas-and-examples-with-an-agent

- Audience: api-producer
- Level: intermediate
- Verified: 2026-09-25
- Published: 2026-09-25

## 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](https://spec.openapis.org/oas/v3.1.0.html#media-type-object) is where a
response's `schema` and `example` live, and the
[Components Object](https://spec.openapis.org/oas/v3.1.0.html#components-object) is where a
schema goes when two responses share it.

## Approaches compared

| Approach | When it fits | What it costs you | When to pick something else |
| --- | --- | --- | --- |
| [Claude Code](https://code.claude.com/docs/en/overview) | Dozens of gaps, payloads on disk, and shared components wanted rather than inline copies | A guessed format or `enum` member in the first batch, and a validation run you have to insist on | There are no captured payloads, so every schema would be a guess |
| [`openapi-examples-validator`](https://github.com/codekie/openapi-examples-validator#readme) | A second opinion on every example from a tool with one job | Another dependency for a check Spectral also performs, and errors on stderr rather than a report | One checker in CI is enough and it is already Spectral |
| [quicktype](https://github.com/glideapps/quicktype#readme) | One payload, one schema, and no judgement wanted | A schema per sample with no reuse, every key required, and a `null` typed as `null` | The same object appears in three responses and should be one component |
| [Spectral](https://docs.stoplight.io/docs/spectral/e5b9616d6d50c-custom-rulesets) | Listing the gaps, then gating the result in CI on every commit | Two custom rules to write, and a ruleset that says where the holes are and fills none of them | The examples need writing, not counting |
| [Stoplight Studio](https://docs.stoplight.io/docs/platform/9f60f2fd436bd-api-design-overview) | A handful of gaps and a person who knows the payloads | A form per schema filled by hand, so nothing is guessed and nothing is fast | The 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.

```yaml title=".spectral.yaml"
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](https://docs.stoplight.io/docs/spectral/cb95cf0d26b83-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`](https://docs.stoplight.io/docs/spectral/4dec24461f3af-open-api-rules)
is the built-in rule that will matter after the agent has written something.

```bash
npx spectral lint openapi.yaml
```

```text output
 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.

```md title="prompt.md"
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](https://code.claude.com/docs/en/common-workflows#reference-files-and-directories)
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.

```bash
npx spectral lint openapi.batch1.yaml
```

```text output
 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.

```bash
npx openapi-examples-validator openapi.batch1.yaml
```

```text output
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.

```bash
npx quicktype -l schema fixtures/meter-list.json
```

```text output
                "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.

```bash
npx spectral lint openapi.filled.yaml
```

```text output
No results with a severity of 'error' found!
```

```bash
npx openapi-examples-validator openapi.filled.yaml
```

```text output
Validating examples
Schemas with examples found: 4
Examples without schema found: 0
Total examples found: 6

No errors found.
```

```ts title="gaps.test.mjs"
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'])
})
```

```bash
node --test gaps.test.mjs
```

```text output
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](https://www.rfc-editor.org/rfc/rfc9457),[^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.

## Related how-tos

- [Serve your OpenAPI examples as mock responses](/howto/serve-openapi-examples-as-mock-responses)

- [Validate responses against the OpenAPI document at runtime](/howto/validate-responses-against-the-openapi-document)

## 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.

[^1]: The two checkers agree on `2026-03-02` because both chose to.
[JSON Schema 2020-12](https://json-schema.org/draft/2020-12/json-schema-validation#section-7.2.1)
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.

[^2]: quicktype writes `"$schema": "http://json-schema.org/draft-06/schema#"`. The
[draft-06 release notes](https://json-schema.org/draft-06/json-schema-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.

[^3]: [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457), 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.