-
Notifications
You must be signed in to change notification settings - Fork 223
feat: add generate-spec command
#2937
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4921950
6b62b28
e40ff10
73ff400
956bf00
bcd25c2
3a11b20
d7ee556
2ae80d8
e731b9c
c025fe0
90a2052
45980fa
b3b9a11
22674ba
b3ce51a
e387bc5
b464f23
896fd97
f7f5917
918faf5
2d41e65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@redocly/cli': minor | ||
| --- | ||
|
|
||
| Added an experimental `generate-spec` command that infers an OpenAPI description from recorded HTTP traffic. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@redocly/openapi-core': minor | ||
| --- | ||
|
|
||
| Added a `singularize` utility that returns the singular form of an English word. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| # `generate-spec` | ||
|
|
||
| The `generate-spec` command infers an OpenAPI description from recorded HTTP traffic. | ||
| The command reads a traffic log (or a folder of logs), builds a deterministic baseline description from the observed exchanges, and can optionally refine that baseline with an AI provider. | ||
|
|
||
| {% admonition type="warning" name="Experimental" %} | ||
| This is an experimental feature. | ||
| Its behavior, command, flags, and output may change in future releases. | ||
|
|
||
| The `generate-spec` command generates OpenAPI 3.1 descriptions only. | ||
| {% /admonition %} | ||
|
|
||
| ## Supported traffic formats | ||
|
|
||
| The traffic input can be provided in any of the following formats. | ||
| By default the format is detected automatically from the file contents: | ||
|
|
||
| - HAR | ||
| - Kong | ||
| - Nginx JSON | ||
| - Apache JSON | ||
| - NDJSON | ||
|
|
||
| Traffic parsing is shared with the [`drift`](./drift.md) command, so any log that works with `drift` works here too. | ||
|
|
||
| ## How it works | ||
|
|
||
| The baseline description is inferred directly from the recorded exchanges: identifier-like path segments become named path parameters, request and response schemas are merged across all observations, repeated object shapes are extracted into `components/schemas`, and consistently formatted string values get `format` or `enum` hints. | ||
|
|
||
| A description inferred from traffic alone is a hypothesis: types are coarse and there are no descriptions, enums, or examples beyond what was observed. | ||
| With `--with-ai`, the command sends each operation together with sample exchanges to an AI provider, which narrows types and adds formats, enums, descriptions, and examples. | ||
|
|
||
| The AI's answer is never trusted blindly: a refined operation is only accepted when it keeps the operation's path, method, and observed response status codes, and passes validation with the `spec` ruleset. | ||
| Rejected refinements keep their baseline version, and if refinement fails entirely the command falls back to the baseline description. | ||
| The validation uses the built-in `spec` ruleset, not your project's `redocly.yaml` — lint the generated description with your own configuration afterward. | ||
|
|
||
| {% admonition type="warning" name="Data sharing" %} | ||
| `--with-ai` sends samples of the recorded traffic (URLs, query strings, request and response bodies) to the selected AI provider. | ||
| Make sure the traffic contains no secrets or personal data you are not allowed to share. | ||
| {% /admonition %} | ||
|
|
||
| ### AI providers | ||
|
|
||
| Refinement runs a locally installed AI CLI in non-interactive mode: `claude` (Claude Code), `codex` (Codex CLI), or `cursor` (Cursor CLI). | ||
| The selected CLI must be installed and authenticated on the machine running the command. | ||
|
|
||
| The provider runs in isolation: project context the CLIs normally load (such as `CLAUDE.md`, `AGENTS.md`, or `.cursor/rules`) and settings like a configured model do not apply. | ||
| Use `--ai-model` to choose a model, or the provider's default is used. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```bash | ||
| redocly generate-spec <traffic> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we call it traffic-log or traffic-record to make it more clear?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The description already says "traffic log file or folder", I would keep it short. |
||
| redocly generate-spec <traffic> [--traffic-format=<option>] [--server=<url>] | ||
| redocly generate-spec <traffic> [--title=<string>] [--output=<file>] | ||
| redocly generate-spec <traffic> --with-ai [--ai-provider=<option>] [--ai-model=<string>] [--ai-concurrency=<number>] | ||
| ``` | ||
|
|
||
| ## Options | ||
|
|
||
| | Option | Type | Description | | ||
| | ---------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | traffic | string | **REQUIRED.** Path to a traffic log file or folder (HAR, Kong, Nginx/Apache JSON, NDJSON). | | ||
| | --type | string | Target API description type.<br/>**Possible values:** `openapi`. Default value is `openapi`. | | ||
| | --traffic-format | string | Traffic input format.<br/>**Possible values:** `auto`, `har`, `kong`, `nginx-json`, `apache-json`, `ndjson`. Default value is `auto`. | | ||
| | --server | string | Server URL the traffic was captured against (host, host + base path, or a path-only prefix like `/api`). Only requests under it are considered, the rest of their URL is treated as the API path, and it becomes the `servers` URL of the generated description. | | ||
| | --title | string | Title for the generated API description. Default value is `Generated API`. | | ||
| | --with-ai | boolean | Refine the inferred description with an AI provider. Default value is `false`. | | ||
| | --ai-provider | string | AI provider used with `--with-ai`. Runs the corresponding CLI in non-interactive mode.<br/>**Possible values:** `claude`, `codex`, `cursor`. Default value is `claude`. | | ||
| | --ai-model | string | Model passed to the selected AI provider. If not set, the provider's default model is used. | | ||
| | --ai-concurrency | number | Number of operations refined in parallel with `--with-ai`. Default value is `4`. | | ||
| | --output, -o | string | Write the generated description to this file instead of stdout. | | ||
| | --config | string | Specify path to the [configuration file](../configuration/index.md). | | ||
| | --help | boolean | Display help. | | ||
| | --version | boolean | Display version number. | | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Generate a description from a HAR capture | ||
|
|
||
| ```bash | ||
| redocly generate-spec ./traffic.har -o ./openapi.yaml | ||
| ``` | ||
|
|
||
| ### Scope the traffic to a server | ||
|
|
||
| When the capture contains traffic for more than one host, or the API lives behind a base path (for example, a gateway that adds `/api`), use `--server` to declare the server the traffic was captured against. | ||
| Only requests under it are considered, and the remaining path becomes the API path: | ||
|
|
||
| ```bash | ||
| redocly generate-spec ./traffic.har --server https://api.example.com/api -o ./openapi.yaml | ||
| ``` | ||
|
|
||
| ### Refine the description with AI | ||
|
|
||
| ```bash | ||
| redocly generate-spec ./traffic.har --with-ai --ai-provider claude -o ./openapi.yaml | ||
| ``` | ||
|
|
||
| ### Speed up refinement for large APIs | ||
|
|
||
| For descriptions with many operations, raise the concurrency and pick a faster model to shorten the run: | ||
|
|
||
| ```bash | ||
| redocly generate-spec ./traffic-logs/ --with-ai --ai-concurrency 12 --ai-model claude-sonnet-5 -o ./openapi.yaml | ||
| ``` | ||
|
|
||
| ### Capture traffic and generate a description | ||
|
|
||
| ```bash | ||
| redocly proxy --target https://api.example.com --har ./capture.har | ||
| # point your client at http://127.0.0.1:4040, then press Ctrl+C to stop | ||
| redocly generate-spec ./capture.har -o ./openapi.yaml | ||
| ``` | ||
|
|
||
| ## Related commands | ||
|
|
||
| - [`proxy`](./proxy.md) captures live HTTP traffic into a HAR file that can be fed into `generate-spec`. | ||
| - [`drift`](./drift.md) detects drift between recorded HTTP traffic and an existing OpenAPI description. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| # generate-spec [experimental] | ||
|
|
||
| Infer an OpenAPI description from recorded HTTP traffic, optionally refined with AI. | ||
|
|
||
| Traffic alone can only ever produce a _hypothesis_ about an API's real shape: types are coarse, every observed property looks required, and there are no descriptions, enums, or formats. | ||
| `generate-spec` first builds that hypothesis deterministically, then can hand it to an AI provider together with real sample exchanges to narrow it toward the true definition. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```bash | ||
| # Deterministic inference only | ||
| redocly generate-spec ./traffic.har | ||
|
|
||
| # Refine the hypothesis with AI | ||
| redocly generate-spec ./traffic.har --with-ai --ai-provider claude -o openapi.yaml | ||
| ``` | ||
|
|
||
| The `<traffic>` argument is a file or folder of recorded traffic. | ||
| Supported formats: HAR, Kong, Nginx/Apache JSON, and NDJSON (auto-detected, or forced with `--traffic-format`). | ||
| The traffic parsing infrastructure is shared with the `drift` command. | ||
| The command generates OpenAPI 3.1 descriptions only. | ||
|
|
||
| ## Deterministic inference | ||
|
|
||
| The baseline is derived from every exchange in the traffic: | ||
|
|
||
| - Identifier-like path segments (numeric, UUID, ULID, CUID, prefixed and opaque tokens) become named path parameters. | ||
| - Body schemas are merged across all observations. | ||
| A property becomes optional as soon as one sample omits it. | ||
| - Alternative body shapes for the same operation are preserved as `oneOf` variants instead of being collapsed, and values observed as `null` produce type unions such as `["string", "null"]`. | ||
| - Object shapes that repeat across the document are extracted into `components/schemas` and referenced with `$ref`. | ||
| The same entity is recognized when it appears as a list item and as a single resource, with different `required` sets, or with near-identical properties (at least 75% shared, with compatible types). | ||
| Components are named from the path entity, the enclosing property name, or `Error` for error responses. | ||
| A shape repeated only because its parent shape repeats stays inline. | ||
| - Observed string values are analyzed conservatively: when every value of a property (or parameter) matches the same well-known pattern it gets a `format` (`uuid`, `date-time`, `date`, `email`, `uri`, `ipv4`). | ||
| When a string only ever takes a small set of identifier-like values with enough repetition (at least 4 observations, at most 5 distinct values, each seen twice on average) it becomes an `enum`. | ||
| Enums apply to body properties and query parameters. | ||
| Path parameters and nullable unions get formats only. | ||
| Evidence is pooled across all operations a shared component was observed in. | ||
| - Responses that were never received (status `0` in HAR captures) are ignored. | ||
|
|
||
| ## AI refinement | ||
|
|
||
| The description is refined one operation at a time, so prompt and response stay small no matter how large the recorded traffic or the resulting description is. | ||
| Each prompt carries a single operation from the baseline, the component schemas it references, the names of the other components (reserved against collisions), and a capped, shape-diverse sample of the real exchanges recorded for that operation (grouped by status and body shape, selected round-robin so every observed payload variant is represented). | ||
| The AI is instructed to narrow types, add formats, enums, descriptions and examples, refine or add `components/schemas`, and model alternative payloads explicitly with `oneOf` (plus `discriminator`) and `allOf` composition. | ||
|
|
||
| Up to `--ai-concurrency` operations (default 4) are refined in parallel, and every accepted refinement is merged back as it arrives, so operations prompted later see already-refined shared components. | ||
| When two concurrent refinements touch the same shared component, the one merged last wins. | ||
| The final whole-document lint still guards the result. | ||
| Set `--ai-concurrency 1` to process operations strictly sequentially. | ||
| Progress is reported per operation as refinements complete. | ||
|
|
||
| The AI's answer is never trusted blindly. | ||
| A refined operation is only accepted when it: | ||
|
|
||
| - keeps the exact path template and method — the AI cannot invent, drop, or rename operations because only the requested operation is merged back | ||
| - keeps every response status code observed in the traffic | ||
| - passes validation with the `spec` ruleset (checked against the description's full component set) | ||
|
|
||
| An operation whose refinement is rejected keeps its deterministic baseline (reported with the reason), components no operation references anymore are pruned, and the final document is linted again as a whole. | ||
| If refinement fails for every operation, the command falls back to the deterministic baseline. | ||
| Each provider call times out after 5 minutes. | ||
|
|
||
| The acceptance lint is intentionally pinned to the `spec` ruleset and does not use the project's `redocly.yaml`: a stricter governance config would reject refinements for problems the baseline itself has, and a looser one could let structurally broken output through. | ||
| Lint the generated description with your own config afterward. | ||
|
|
||
| > **Warning:** `--with-ai` sends samples of the recorded traffic (URLs, query strings, request and response bodies) to the selected AI provider. | ||
| > Make sure the traffic contains no secrets or personal data you are not allowed to share. | ||
|
|
||
| ## Options | ||
|
|
||
| | Option | Description | | ||
| | ------------------ | ----------------------------------------------------------------------------------------------------- | | ||
| | `--type` | Target description type. Only `openapi` is supported. | | ||
| | `--traffic-format` | Traffic input format (`auto`, `har`, `kong`, `nginx-json`, `apache-json`, `ndjson`). | | ||
| | `--server` | Server URL the traffic was captured against. Scopes which requests are considered and sets `servers`. | | ||
| | `--title` | Title for the generated description. | | ||
| | `--with-ai` | Refine the inferred description with an AI provider. | | ||
| | `--ai-provider` | `claude`, `codex`, or `cursor` (default `claude`). | | ||
| | `--ai-model` | Model passed to the selected provider. | | ||
| | `--ai-concurrency` | Number of operations refined in parallel (default `4`). | | ||
| | `-o, --output` | Write the result to a file instead of stdout. | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also mentioned in README the limitation of producing only OAS v3.1.0 description? |
||
|
|
||
| ## AI providers | ||
|
|
||
| Every provider CLI is spawned in an empty temporary directory, so project context the CLIs normally auto-load from the working directory (`CLAUDE.md`, `AGENTS.md`, `.cursor/rules`) never enters the prompts — it would slow every call down and could steer the refinement. | ||
|
|
||
| - **`claude`** — runs the local `claude` CLI in headless mode (`claude -p`), with built-in tools, MCP servers, settings (hooks, skills, plugins), and session persistence disabled so every call is a plain completion. | ||
| Because settings are not loaded, a model configured there does not apply: pass `--ai-model`, or the CLI's built-in default model is used. | ||
| - **`codex`** — runs the local `codex` CLI in non-interactive mode (`codex exec`) with MCP servers and `AGENTS.md` discovery disabled and a read-only sandbox. | ||
| Other settings from `~/.codex/config.toml`, such as `model_reasoning_effort`, still apply. | ||
| - **`cursor`** — runs the local Cursor CLI in print mode: `cursor-agent -p`. | ||
| The renamed `agent` binary is tried as a fallback). | ||
|
|
||
| All providers require the respective CLI to be installed and authenticated on the machine running the command. | ||
|
|
||
| ## Performance for large APIs | ||
|
|
||
| Total time is roughly the per-operation AI time multiplied by the number of operations and divided by `--ai-concurrency`. | ||
| For descriptions with many operations: | ||
|
|
||
| - Raise `--ai-concurrency` (for example to `12`) — calls are network-bound and the provider CLIs retry rate-limit responses themselves. | ||
| - Pick a faster model with `--ai-model`. | ||
| Smaller models roughly halve the per-operation time compared to the largest ones at some cost in refinement depth. | ||
Uh oh!
There was an error while loading. Please reload this page.