Skip to content
noemaforgePublic

About

OpenAPI/Swagger JSON to Markdown documentation generator

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Vimanam

CI crates.io License: Apache-2.0 MSRV

Vimanam is a Rust CLI and library that turns OpenAPI/Swagger specs (JSON or YAML), Protocol Buffers/gRPC contracts and GraphQL schemas into focused Markdown documentation and compares API contracts.

Vimanam stands for Aeroplane in Malayalam. Like an aeroplane, it can fly high and give you a 20,000 feet view of the APIs. It can fly low and give you a detailed view of the APIs. You can also run it along the ground to look deep into the API fields and descriptions.

It supports OpenAPI 2.0 (Swagger) and OpenAPI 3.0 specifications, with partial OpenAPI 3.1 support.

Besides producing documentation for humans, Vimanam is built for feeding API specs to LLMs: filter to the operations and fields you need, or generate a linked Skill tree that an agent can read on demand. See Preparing API context for LLMs. Release history is in the Changelog.

Protobuf and gRPC

Read proto2/proto3 source with protoc on PATH, or read a complete binary FileDescriptorSet without external tools. Editions currently return an explicit unsupported-syntax error. See protobuf inputs, rules and Git imports.

vimanam api.proto -I proto --detail full --include-schemas
vimanam api.pb --operation-id example.v1.Service.List --max-tokens 1600
vimanam api.pb --output-mode skill --detail full --include-schemas -o context/
vimanam diff old.pb new.pb --format json --fail-on-breaking --breaking wire
vimanam diff --from-ref v1 --to-ref v2 --spec proto/api.proto -I proto

Protobuf uses neutral JSON contract v2. --breaking wire|source|all scopes its failure gate; source includes JSON name compatibility. --contract 1 remains OpenAPI-only.

GraphQL

Read SDL (.graphql/.graphqls, or a directory whose SDL files are merged with their extend definitions) or an introspection result (__schema JSON). Root fields are operations (QUERY Query.user); types, fields, arguments and enum values use schema coordinates. See GraphQL inputs and rules.

vimanam schema.graphql --operation 'QUERY Query.user' --detail full --include-schemas --max-tokens 1600
vimanam schema/ --output-mode skill --detail full --include-schemas -o context/
vimanam diff old.graphql new.graphql --fail-on-breaking
vimanam diff old.graphql introspection.json --format json
vimanam diff old.graphql new.graphql --context --max-tokens 2000
vimanam diff --from-ref v1 --to-ref v2 --spec api/schema.graphql

GraphQL uses neutral JSON contract v2; --contract 1 remains OpenAPI-only. --context appends focused Markdown for the affected root fields and their reachable types.

Rust library

Use the parser, typed diff, versioned JSON contract, and focused Markdown renderer in-process without the CLI dependencies:

[dependencies]
vimanam = { version = "2", default-features = false }
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let old = vimanam::parse_openapi("old.json")?;
    let new = vimanam::parse_openapi("new.json")?;
    let changes = vimanam::diff::diff(&old, &new);

    let mut context = vimanam::DocConfig::unfiltered();
    context.operation_selector = Some(vimanam::OperationSelector {
        operations: changes.changes.iter()
            .map(|change| vimanam::OperationRef {
                method: change.endpoint.method.clone(),
                path: change.endpoint.path.clone(),
            })
            .filter(|op| new.endpoints.iter().any(|endpoint| op.matches(endpoint)))
            .collect(),
        ..Default::default()
    });
    context.max_tokens = Some(2000);
    let mut markdown = Vec::new();
    vimanam::markdown::generate_markdown(&mut markdown, &new, &context)?;
    std::fs::write("changed-api.md", markdown)?;
    Ok(())
}

This renders changed operations that still exist in the new spec. Render removed operations from the old spec instead. DocConfig::unfiltered() starts with full detail and schemas; a token budget may lower operation detail to fit its estimate.

parse_openapi_bytes accepts already-read bytes so hashing and parsing can use the same input. diff::json::to_json returns the JSON contract types, which support both serialization and deserialization. The library writes only to caller-provided writers, logs through log, and optionally returns rendering notices through generate_markdown_with_notices. The default cli feature keeps normal cargo install vimanam behavior.

The core gitrefs::Repository API reads committed specs using Git on PATH:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let repo = vimanam::gitrefs::Repository::discover(".")?;
    let _candidates = repo.candidates("HEAD")?; // sorted tracked JSON/YAML candidates
    // Choose an explicit repository-relative path when several specs exist.
    let old = repo.materialize_follow("api/openapi.json", "HEAD", "v1.0.0")?;
    let _doc = vimanam::parse_openapi_bytes(
        &old.bytes,
        old.path.extension().and_then(|ext| ext.to_str()).unwrap_or(""),
        Some(&old.path),
    )?;
    Ok(())
}

materialize(ref, path) reads an exact path; materialize_follow(path, path_ref, target_ref) follows Git-detected renames from the explicitly anchored file, including across sibling branches and merge parents. It may use an older shared commit when the file's route crosses a merge parent outside Git's selected merge base; a merge with both a surviving route and a broken route is rejected as ambiguous. Both return exact bytes, the actual path and resolved commit ID without changing the checkout. Candidate sniffing checks a small OpenAPI header and syntax, deferring operation/schema validation to the parser. It reads committed trees, excluding ignored/untracked files and local edits. Git's 50% similarity rename heuristic cannot identify every rewritten rename; missing lineage, ambiguous paths/history and incomplete shallow history return errors so callers can supply explicit paths. This API adds no CLI flags.

See the library API reference for configuration and types.

Features

  • Convert OpenAPI JSON or YAML files to Markdown documentation (format detected by .json/.yaml/.yml extension, with automatic fallback)
  • Supports OpenAPI 2.0 (Swagger), OpenAPI 3.0 and supported OpenAPI 3.1 schema fields
  • Group endpoints by service, HTTP method, or path, or list them flat
  • Filter by service, path, or method
  • Select exact operations by method/path or operation ID (--operation, --operation-id)
  • Embed parsing, typed diffs and focused rendering in Rust tools without CLI dependencies
  • Multiple detail levels (summary, basic, standard, full)
  • Token-budget-aware output (--max-tokens): steps the detail level down until the rendering fits, and reports what was trimmed on stderr
  • Linked directory output (--split service|tag|endpoint): a compact overview alongside requested detail pages and shared schema files, with an independent overview budget
  • Agent navigation (--output-mode skill): a compact SKILL.md, service/operation/schema hubs and individual-file read costs
  • Token-budget dry run (--stats): a per-service table of endpoint counts and estimated token sizes, for sizing slices before choosing filters
  • Token-cost analysis (--costs): per-endpoint slice costs with their share of TOTAL, per-schema definition and modeled inline-expansion costs with reference amplification, and hotspot rankings
  • Spec diffing (vimanam diff old.json new.json): compares two versions of a spec on resolved schemas — a change behind a shared $ref is reported on every endpoint that uses it — and classifies each change as breaking, non-breaking or needing review, with an exit code for CI (--fail-on-breaking)
  • Spec hygiene report for endpoint documentation (--no-report to skip): counts and lists operations missing a description, operationId or responses, deprecated and untagged operations, duplicate operationIds, and undescribed parameters
  • Schema expansion at --detail full --include-schemas: renders request/response schemas as nested field tables. Shared component schemas are expanded once into a trailing "Schema Definitions" section and linked from each use site, keeping output compact when schemas are reused across endpoints; --inline-schemas instead expands every $ref inline at each use site (larger, fully self-contained, with cycle detection)
  • Schema reads for oversized graphs: --schema NAME and --schema-field NAME#POINTER render one schema or subtree with its metadata, and --schema-depth N bounds expansion without dropping the selected fields
  • Example rendering at --detail full --include-examples: emits request/response examples as fenced JSON blocks, resolving $refs into components/examples
  • Server URL information extraction and documentation
  • Authentication and security schemes documentation
  • Proper content type detection for responses
  • Sorting options for endpoints (alphabetical, path length)
  • Clean anchor generation for better navigation
  • Deterministic, byte-identical output across runs — friendly to diffs, caching, and LLM prompt caching

Installation

Homebrew (macOS / Linux)

brew install noemaforge/tap/vimanam

No Rust toolchain required. Supports macOS (Apple Silicon & Intel) and x86_64 Linux.

Install script (no toolchain)

# macOS / Linux
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/noemaforge/vimanam/releases/latest/download/vimanam-installer.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://github.com/noemaforge/vimanam/releases/latest/download/vimanam-installer.ps1 | iex"

From crates.io

cargo binstall vimanam   # prebuilt binary, no compile (needs cargo-binstall)
cargo install vimanam    # builds from source

Prebuilt binaries

Download the archive for your platform (Linux, macOS Intel/ARM64, Windows) from the latest release — no Rust toolchain needed. Archives are named vimanam-<target-triple>.tar.xz (.zip on Windows) and ship with a matching .sha256 checksum; each bundles the binary, README.md, CHANGELOG.md, and LICENSE. Extract it and put the vimanam binary on your PATH.

From source

Requires Rust 1.96.0 or later.

# Clone repository
git clone https://github.com/noemaforge/vimanam.git
cd vimanam

# Build and run during development
cargo run -- input.json -o output.md

# Build and run
cargo build --release
./target/release/vimanam input.json -o output.md

# Or install system-wide
cargo install --path .

Development

cargo test
cargo fmt && cargo clippy

Integration tests live in tests/cli/, tests/split.rs and tests/library.rs and run against tests/fixtures/. Use cargo test --no-default-features to exercise the core library without the CLI. scripts/compare-baseline.sh compares a fresh build with a released binary across the CLI matrix — stdout, stderr, exit code, and written files — so a refactor can be checked for byte-for-byte output parity. Setup and the case list are in scripts/README.md.

Shell completions

vimanam completions <SHELL> prints a completion script for bash, zsh, fish, powershell, or elvish. Install it once and re-run after upgrading Vimanam:

# Bash (relies on the bash-completion v2 package, which stock macOS bash 3.2
# lacks; alternatively add `eval "$(vimanam completions bash)"` to ~/.bashrc)
mkdir -p ~/.local/share/bash-completion/completions
vimanam completions bash > ~/.local/share/bash-completion/completions/vimanam

# Zsh (make sure ~/.zfunc is on fpath before compinit runs in ~/.zshrc:
#   fpath+=~/.zfunc; autoload -Uz compinit; compinit)
mkdir -p ~/.zfunc
vimanam completions zsh > ~/.zfunc/_vimanam

# Fish
mkdir -p ~/.config/fish/completions
vimanam completions fish > ~/.config/fish/completions/vimanam.fish
# Elvish: add this line to ~/.config/elvish/rc.elv
eval (vimanam completions elvish | slurp)
# PowerShell: add this line to $PROFILE
vimanam completions powershell | Out-String | Invoke-Expression

Usage

# Basic usage
vimanam input.json -o output.md

# Group by HTTP method
vimanam input.json --method -o output.md

# Group by path (one section per path, methods listed underneath)
vimanam input.json --group-by path -o output.md

# Generate summary only
vimanam input.json --detail summary -o output.md

# Fit the output to a token budget, stepping detail down as needed
vimanam input.json --detail full --max-tokens 8000 -o output.md

# Filter by specific services
vimanam input.json --service-filter Auth,Users -o output.md

# Filter by HTTP method
vimanam input.json --method-filter GET,POST -o output.md

# Show only paths containing a pattern
vimanam input.json --path-filter /api/v1 -o output.md

# Generate full details
vimanam input.json --detail full --include-schemas --include-examples -o output.md

# Include server and authentication information
vimanam input.json --include-auth -o output.md

# Print a shell completion script (see "Shell completions" above)
vimanam completions zsh

# Drop the spec hygiene report appended after the documentation
vimanam input.json --no-report -o output.md

# Size each service before choosing filters: endpoint counts and ~tokens per service
vimanam input.json --stats --detail standard

# Find the most expensive endpoints and schemas before choosing filters
vimanam input.json --costs --detail full --include-schemas

# Compare two versions of a spec; exit 3 if anything breaking changed
vimanam diff v1/openapi.json v2/openapi.json --report --fail-on-breaking

Schema reads and expansion limits

Keep full split/Skill files available and request a smaller schema read when one file is too large:

vimanam spec.json --schema v1FindingSpec
vimanam spec.json --schema-field 'v1FindingSpec#/properties/finding_tags'
vimanam spec.json --schema-field 'v1FindingSpec#/properties/finding_tags/items/properties/tag'
vimanam spec.json --operation-id GetFinding --detail full --include-schemas --schema-depth 2

--schema and --schema-field are repeatable standalone reads. They always render full schema metadata and omit the endpoint hygiene report. Named schemas need not be reachable from an operation. Combining an exact operation selector adds a matched-operation context list; schema selection stays independent. Field reads retain ancestor types, descriptions, requiredness and enum values, pruning unrelated siblings and their references. A pointer crosses references automatically and selects schema subtrees using /properties/NAME, /items, /additionalProperties, or /allOf/INDEX, /oneOf/INDEX, /anyOf/INDEX. Escape property-name ~ as ~0 and / as ~1; an empty pointer selects the complete schema. Invalid names/pointers fail before creating output. Both Swagger 2 definitions and OpenAPI 3 component schemas are supported.

--schema-depth N accepts 0 through 24, matching the recursion safety limit. A root has depth zero; each property, array item, composition variant, additional-properties schema and reference traversal adds one edge. At depth N, the row retains its type/reference, description and requiredness, but stops nested expansion and gives a full retrieval command. Depth zero keeps root metadata. Shared definitions inherit the shortest depth from the original roots, including across deferred definitions and split/Skill pages; limits never restart at every schema. Explicit field selectors protect their ancestor path and leaf metadata even below this depth; the limit then bounds expansion beyond that selection. Omissions are reported in Markdown and stderr.

Without standalone selectors, depth requires --detail full --include-schemas; it works with operation filters, --stats and split/Skill output. Selectors produce dedicated reads and conflict with split/Skill output and service-oriented --stats; use the commands in schema detail pages to read a field separately. Depth/field limits are opt-in; existing full rendering remains available when omitted.

For explicit schema reads, --max-tokens never drops selected metadata or lowers detail. If the selection exceeds the approximate characters/4 budget (including budget zero), Vimanam emits the complete requested read with an over-budget notice in Markdown and stderr. Narrow the field selector or explicitly set depth to reduce it. Other single-file output keeps its existing detail fallback, and split/Skill overview budgets affect only the overview.

Token cost analysis

--costs sizes API context before reading it. It prints a plain-text report of estimated token costs (chars/4 over real renders — never tokenizer counts or billing figures) at the configured detail level, filters and schema mode:

vimanam input.json --costs --detail full --include-schemas
  • TOTAL is one render of the whole filtered document — the same estimate --max-tokens would measure.
  • ENDPOINTS rows are one render per endpoint alone: its service section, the document frame and the schema definitions it links. Rows overlap through the frame and shared schemas, so they neither sum to nor subtract from TOTAL. A multi-tag endpoint is measured once, under its first service (or the first one a --service-filter keeps); a service-grouped document renders it once per tag. A (+tag) suffix marks the extra tags, rendered verbatim after the operation label.
  • SCHEMAS rows show the definition cost (read once in linked mode), the use-site count, and — as a separate, clearly-labeled analysis — the modeled inline-expansion cost (uses × one measured, cycle-guarded expansion) with an amplification ratio. Uses are the linked render's rows: body rows plus the reference rows inside each rendered definition. Definition-internal rows have no inline counterpart, while a schema referenced only inside another definition expands with it, so the model can differ from real --inline-schemas output in either direction. Under --inline-schemas, uses are instead the expansion sites of the analyzed inline render, and the definition cost is the hypothetical linked-mode one (inline mode emits no definitions). Cyclic schemas are noted (they participate in a reference cycle; inline expansion cuts it with a one-row notice).
  • HOTSPOTS rank the most expensive endpoint slices and the most amplifying schemas (by amplification ratio, not absolute size); shares are of TOTAL and overlap with each other.

Like --stats, it is a dry run: the hygiene report is never included, and it conflicts with -o, --max-tokens, split/Skill output, --stats and schema selection (--schema/--schema-field). --inline-schemas is allowed and switches the analyzed mode.

Spec hygiene report

Single-file endpoint documentation appends a short report, separated by a horizontal rule, that flags common gaps in the spec: operations with no summary or description, no operationId, no documented responses, deprecated operations, operations with no tag (attributed to the default service), duplicate operationIds, and parameters without a description (a request body counts once per operation, however many media types it offers). It covers the same endpoints the documentation does, so service, path, method and exact-operation filters, plus --exclude-deprecated, narrow the report too. Detail lists appear only for checks that found something. Split/Skill output writes it to report.md; schema selections, --stats, --costs, and the library Markdown renderer do not append it.

---

## Spec Hygiene Report

**6 endpoints** across **2 services**

| Check | Count |
|-------|------:|
| Missing description | 1 |
| Missing operationId | 1 |
| No responses documented | 1 |
| Deprecated | 2 |
| Untagged (no service tag) | 1 |
| Duplicate operationIds | 1 |
| Parameters without description | 3 |

### Missing description (1)
- `GET /health`

### Deprecated (2)
- `GET /ping`
- `DELETE /users/{id}`

### Duplicate operationIds (1)
- `getUser`
  - `DELETE /users/{id}`
  - `GET /users/{id}`

### Parameters without description (3)
- `GET /users` — `limit`
- `POST /users` — `requestBody`
- `DELETE /users/{id}` — `id`

Pass --no-report to omit it. The report is not counted against --max-tokens — the budget fits the documentation body only — so combine --max-tokens with --no-report when the whole output must stay within the budget.

Linked pages with a compact overview

Keep complete API detail available on disk while loading only the relevant pages:

vimanam openapi.json --split endpoint -o ./api-docs \
  --detail full --include-schemas --include-examples --overview-max-tokens 2000

Start at api-docs/index.md. Its entries show method/path, operation ID and a brief description, and link to detail pages. The linked api.md retains the complete API description and global usage guidance outside the overview budget. Endpoint detail pages link to shared schemas/*.md files; each reachable schema is rendered once, including cyclic references. --split service produces one detail page per service, and --split tag uses the spec's tags (the parser's service names). Multi-tag operations appear under each selected service/tag. Endpoint splitting produces one page per operation. Filtering applies to the generated tree; omitted operations are disclosed in the overview with a command to retrieve the full tree.

--detail controls detail pages; the overview stays compact independently. --overview-max-tokens uses the approximate chars/4 estimate only for index.md. When operation entries do not fit, index-all.md retains complete navigation. Even a tiny budget preserves the navigation notice and can therefore exceed the estimate. Linked detail pages keep the requested detail level and schema tables. The hygiene report, when enabled, is a separate report.md page.

--max-tokens remains a single-document detail-fallback option and conflicts with --split; use --overview-max-tokens for a split overview. Split output uses shared schema links, so --inline-schemas also conflicts with --split. Schemas and examples still require --detail full and their respective inclusion flags. Each reduced-detail page tells the reader how to retrieve fuller detail into another directory.

Generated paths combine a readable slug with a hash of the operation, service name or schema reference. They stay stable when filters or unrelated operations change. Regeneration uses .vimanam-manifest.json to track owned files: it removes obsolete generated pages only when their contents are unchanged, preserves unrelated files, and refuses to overwrite edited generated pages or unmanaged collisions. Move edited pages aside or use a fresh output directory before regenerating. Symlink output paths and parents are rejected; use their resolved paths (for example /private/tmp instead of /tmp on macOS). Keep the manifest alongside the generated tree.

Agent-navigable Skill tree

vimanam openapi.json --output-mode skill -o ./api-skill \
  --detail full --include-schemas --include-examples --include-auth \
  --overview-max-tokens 1600

Start with api-skill/SKILL.md, which has YAML frontmatter (name, description, API version) and explains how to choose reads. Follow a service hub to select an endpoint by method/path, operation ID and short description, then follow that endpoint's schema links as needed. endpoints/index.md lists all selected operations; schemas/index.md lists reachable shared schemas; services/index.md lists service hubs. Multi-tag operations share one endpoint file. api.md retains global API guidance.

Hub entries show the approximate cost of each linked file, using characters/4 rounded up from its final emitted contents. An endpoint estimate covers that endpoint file alone; reading linked schemas incurs their separate costs. Estimates help the agent choose what to load without loading the full referenced graph first.

--overview-max-tokens affects only SKILL.md (default estimate: 1600). When service entries do not fit, the root links to the complete index.md map; all detail files and directory hubs stay unchanged. Essential navigation may exceed an extremely small budget. Existing filters, sorting, detail levels and schema/example flags apply to detail files. Reduced-detail pages include retrieval commands, and omitted documentation is distinguished from content absent in the source spec. Schema tables still require --detail full --include-schemas. This profile uses the same stable paths and ownership protections as split output and conflicts with --split, --max-tokens, --stats and --inline-schemas.

Options

Usage: vimanam [OPTIONS] <FILE>
       vimanam <COMMAND>

Commands:
  completions  Generate shell completions and print them to stdout
  diff         Compare two versions of a spec and report what changed, classified as breaking, non-breaking or needing review
  help         Print this message or the help of the given subcommand(s)

Arguments:
  <FILE>  Path to the OpenAPI JSON or YAML file

Options:
  -o, --output <FILE>                      Output file path, or directory for split/skill output
      --split <SPLIT>                      Write linked Markdown pages to the --output directory [service, tag, endpoint]
      --output-mode <OUTPUT_MODE>          Write an agent-navigable SKILL.md tree to the --output directory [skill]
      --overview-max-tokens <N>            Token budget for the split index or SKILL.md only; detail pages stay at the requested level
      --method                             Group endpoints by HTTP method instead of by service
      --group-by <service|method|path>     Grouping method for endpoints [default: service]
      --flat                               Generate a flat list without hierarchical structure
      --service-filter <SERVICE[,...]>     Include only specific services (comma-separated)
      --path-filter <PATTERN>              Filter endpoints by path pattern
      --method-filter <METHOD[,...]>       Filter by HTTP methods (comma-separated)
      --operation <METHOD PATH>            Render exactly this operation, as "METHOD /path/template" (repeatable; exact path match, Swagger 2 basePath excluded)
      --operation-id <ID>                  Render exactly the operation(s) with this operationId (repeatable, case-sensitive)
      --exclude-deprecated                 Hide deprecated endpoints
      --required-only                      Only show required parameters
      --detail <summary|basic|standard|full> Control amount of information [default: summary]
      --include-schemas                    Include request/response schemas
      --inline-schemas                     Fully inline every $ref schema instead of linking to a shared "Schema Definitions" section
      --schema-depth <N>                   Maximum schema traversal edges from a root (0..=24). Zero retains only roots
      --schema <NAME>                      Read a named schema directly (repeatable); full metadata, independent of reachability
      --schema-field <NAME#POINTER>        Read a schema subtree and its ancestors (repeatable; JSON-pointer escaping)
      --include-examples                   Include request/response examples
      --include-auth                       Show authentication requirements
      --toc                                Include the table of contents (the default; when both are given, the later of --toc/--no-toc wins)
      --no-toc                             Skip table of contents
      --sort <alpha|path-length|none>      Sorting method [default: alpha]
      --max-tokens <N>                     Fit single-file output to a token budget, stepping detail down (full → summary). The hygiene report is outside the budget
      --no-report                          Skip the spec hygiene report appended after the documentation
      --stats                              Dry run: per-service endpoint counts and estimated tokens (chars/4). TOTAL is one whole-document render, not the sum of the rows
      --costs                              Dry run: token-cost analysis (chars/4) with per-endpoint and per-schema costs, reference amplification and hotspots. Rows overlap through shared schemas and the document frame
  -h, --help                               Print help
  -V, --version                            Print version
Compare two versions of a spec and report what changed, classified as breaking, non-breaking or needing review

Usage: vimanam diff [OPTIONS] [OLD] [NEW]

Arguments:
  [OLD]  The older spec (JSON or YAML)
  [NEW]  The newer spec (JSON or YAML)

Options:
      --from-ref <REF>    Read the older spec from this Git ref (requires --to-ref; conflicts with OLD/NEW)
      --to-ref <REF>      Read the newer spec from this Git ref (requires --from-ref)
      --spec <PATH>       Repository-relative spec path at --to-ref; follow its renames to --from-ref. Omit only when --to-ref has exactly one tracked OpenAPI candidate
      --from-spec <PATH>  Literal repository-relative path at --from-ref; bypass rename discovery
      --to-spec <PATH>    Literal repository-relative path at --to-ref (requires --from-spec)
      --report            Append a Deltas section: spec hygiene counts for both specs and the estimated token size of each at --detail full --include-schemas
      --contract <CONTRACT> JSON contract version [default: 2] [possible values: 1, 2]
      --format <FORMAT>   Output format: a Markdown report or machine-readable JSON with stable change IDs [default: markdown] [possible values: markdown, json]
      --fail-on-breaking  Exit with status 3 when any breaking change is found, after writing the full report
  -o, --output <FILE>     Write the diff to FILE instead of stdout
  -h, --help              Print help (see more with '--help')

Preparing API context for LLMs

Large API specs are a poor fit for LLM context windows: a 3 MB swagger file is hundreds of thousands of tokens of JSON, most of it boilerplate. Vimanam's detail levels and filters act as a token-budget dial, letting you hand an LLM (or a coding agent) exactly the slice of the API it needs, as compact Markdown.

For an agent that should choose its own reads, write a Skill tree instead of one file. SKILL.md is a compact map; service hubs, endpoint pages, and schema pages stay on disk at the requested detail, each with an approximate read cost. --split is the same idea as ordinary linked pages. When one schema is still too large, read it with --schema or --schema-field, or bound expansion with --schema-depth. Those reads keep field types, requiredness, descriptions, and enums. Single-file --max-tokens still steps detail down and can drop schema tables, so use it for a bounded overview, not for field research.

# Agent entry point: compact SKILL.md, full detail on the linked pages
vimanam openapi.json --output-mode skill -o ./api-skill \
  --detail full --include-schemas --include-examples

# Linked pages with a small index and full endpoint files
vimanam openapi.json --split endpoint -o ./api-docs \
  --detail full --include-schemas --overview-max-tokens 2000

# One field, or a depth-bounded operation, when a schema page is still too big
vimanam openapi.json --schema-field 'Finding#/properties/tags'
vimanam openapi.json --operation-id GetFinding --detail full \
  --include-schemas --schema-depth 2 --no-report
# 20,000-ft view: a compact map of every service and operation name.
# Good as always-loaded context so the model knows what the API can do.
vimanam openapi.json --detail summary -o api-map.md

# Zoom into one service when the task touches it — parameters and responses
# included, everything else excluded
vimanam openapi.json --service-filter Findings --detail standard -o findings-api.md

# Slice by path or method instead
vimanam openapi.json --path-filter /v1/scans --detail standard -o scans-api.md
vimanam openapi.json --method-filter GET --detail basic -o read-api.md

# Or let Vimanam pick the detail level: ask for as much of a service as fits a
# token budget. It starts at the requested detail and steps down as needed,
# reporting any reduction on stderr.
vimanam openapi.json --service-filter Findings --detail full --max-tokens 8000 -o findings-api.md

--max-tokens uses a chars/4 token estimate. It is approximate, not a tokenizer count or an exact cap: if even summary output exceeds it, the summary is emitted with an over-budget notice. Explicit schema selections also remain intact when over budget. When the output is fed to a model, add --no-report: the endpoint hygiene report is appended outside the body budget.

Selecting exact operations

When a task concerns one or two operations, --operation and --operation-id render exactly those and nothing else:

vimanam openapi.json --operation "GET /users" --detail full --include-schemas --flat
vimanam openapi.json --operation "GET /users" --operation "DELETE /users/{id}" --detail standard
vimanam openapi.json --operation-id listUsers --detail full

Unlike --path-filter, which is a substring match (--path-filter /users also picks up /users/{id}, /users/{id}/keys and /admin/users), --operation "<METHOD> <PATH>" matches the spec's path template byte for byte: the method is case-insensitive, but {id} and {userId} are different templates and /users/ is not /users. For Swagger 2 specs the path is the key under paths, without basePath. --operation-id matches operation IDs exactly (case-sensitive); if the spec reuses an ID, every operation carrying it is selected. Both flags are repeatable and can be combined — the selection is their union — and they are ANDed with the other filters, so --operation "GET /users" --exclude-deprecated renders nothing if that operation is deprecated (with a warning on stderr naming the filter). A value that matches no operation in the spec is an error (exit 1) rather than an empty document.

Under the default service grouping, services with no selected operation are left out, and an operation with several tags appears once under each of its services; add --flat to get each selected operation exactly once. The selection also scopes --stats, --max-tokens and the hygiene report.

The selectors round-trip with diff --format json: every legacy v1 change record's endpoint.method and endpoint.path can be passed back as --operation "<method> <path>" to render that operation's contract from the old spec (for endpoint_removed) or the new one:

vimanam diff v1.json v2.json --format json --contract 1 \
  | jq -r '.changes[] | select(.kind != "endpoint_removed") | "\(.endpoint.method) \(.endpoint.path)"' \
  | sort -u | while read -r op; do vimanam v2.json --operation "$op" --detail full --include-schemas --flat --no-report; done

Before choosing which slice to generate, --stats sizes the candidates without writing any Markdown: it prints one row per service with its visible endpoint count and the estimated token size of rendering that service alone, at whatever --detail, grouping and filter flags you pass, plus a TOTAL row for the whole document. A service left with no visible endpoint (for example, one whose only operations are dropped by --exclude-deprecated) is omitted from the table.

$ vimanam openapi.json --stats --detail standard
SERVICE    ENDPOINTS   ~TOKENS
Findings          42      6231
Scans             18      2890
Projects          31      4410
TOTAL             91     13102

Read it as a menu: a service that fits your budget can be pulled with --service-filter <name> at that detail level; one that does not can be re-measured at a lower --detail or handed to --max-tokens. Like --max-tokens, ~TOKENS is a chars/4 estimate of the documentation body (the hygiene report is excluded). Each row is a separate render of that service alone, while TOTAL is a single render of the whole filtered document, so the rows do not necessarily add up to it: an operation tagged with several services is counted in each of their rows but once in the total, and the preamble and shared schema definitions are counted once per row.

A workflow that works well with coding agents: generate the --detail summary map once and reference it from the project's agent instructions (e.g. CLAUDE.md); have the agent regenerate a --service-filter ... --detail standard slice on demand when a task involves specific endpoints.

Output is deterministic — the same spec and flags produce byte-identical Markdown — so generated context files diff cleanly in git and don't needlessly invalidate LLM prompt caches.

Comparing spec versions

vimanam diff <OLD> <NEW> compares two versions of a spec and prints a Markdown report of what changed, so a spec update can be reviewed (or gated in CI) before clients find out the hard way.

vimanam diff v1/openapi.json v2/openapi.json --report --fail-on-breaking

To compare committed specs, run inside the Git worktree (including a nested directory). Git must be on PATH; tags, branch names and revision expressions are supported. These flags replace the positional file pair:

vimanam diff --from-ref v1.6.0 --to-ref HEAD --report --fail-on-breaking
vimanam diff --from-ref main --to-ref feature --spec api/openapi.yaml --format json -o diff.json

--spec PATH anchors a repository-relative path at --to-ref, then follows its Git-detected rename history to --from-ref. Without --spec, the newer ref must have exactly one tracked JSON/YAML OpenAPI candidate; zero candidates or multiple candidates produce an error naming the ref and listing ambiguous paths. Selection reads committed blobs, so local edits and untracked files do not affect the result. Missing or ambiguous history is an error, with no fallback to a different spec.

For a move combined with a rewrite that Git cannot match, select both paths explicitly instead of using --spec:

vimanam diff --from-ref v1.6.0 --to-ref HEAD \
  --from-spec old/swagger.json --to-spec api/openapi.yaml --report

--from-spec and --to-spec must be supplied together and read literal paths at their respective refs. They conflict with --spec and bypass rename discovery. Git's 50% similarity threshold applies only to automatic filename matching; once both paths are known, semantic comparison has no similarity restriction. Rename chains followed by later major rewrites work when the rename itself is detectable. Historical paths and extensions select the parser, and JSON hashes cover the exact committed bytes. Both Git input forms share the file-pair report, JSON, --report, -o and --fail-on-breaking behavior. Inputs are validated before an output file is created or truncated.

# API Diff: Widgets API 1.0.0 → 1.1.0

**Summary:** 1 endpoint added, 1 removed, 4 changed; 4 breaking, 8 non-breaking, 1 to review

## Breaking changes (4)

| Change | Endpoint | Detail |
|--------|----------|--------|
| Parameter newly required | `GET /widgets/{id}` | `fields` (query) |
| Response removed | `GET /widgets/{id}` | 404 |
| operationId changed | `DELETE /widgets/{id}` | `Widgets_DeleteWidget` → `Widgets_RemoveWidget` |
| Endpoint removed | `GET /legacy` | was deprecated |

## Non-breaking changes (8)

| Change | Endpoint | Detail |
|--------|----------|--------|
| Response schema changed | `GET /widgets` | 200 `/properties/pricing` added |
| Response schema changed | `GET /widgets` | 200 `/properties/pricing` added to `required` |
| ... | | |

## Needs review (1)

| Change | Endpoint | Detail |
|--------|----------|--------|
| Request schema changed | `POST /widgets` | `/properties/weight/format` changed `float` → `double` |

What is compared. Endpoints are matched by method and path, parameters by name and location, responses by status code. Request and response bodies are compared as resolved schemas: every $ref is inlined first, so a change to a shared component schema shows up on every endpoint that references it — even when the operation object itself is byte-identical, which is the case a path-level or operation-level diff misses. Field-level differences are reported as JSON pointers into the resolved schema (/properties/pricing, /items/type). Descriptions, titles, examples and x-* extensions are ignored, and the order of required and enum members is irrelevant.

Severity. Every change is classified from the point of view of an existing client:

Severity Examples
Breaking endpoint or response code removed; parameter removed, newly required or moved; operationId changed; a type changed anywhere; request: property removed, property added to required, enum member removed, additionalProperties set to false, nullable set to false (clients sending null now fail); response: schema removed entirely, property removed, property removed from required, enum member removed, nullable set to true
Non-breaking endpoint or response code added; optional parameter added; parameter made optional; deprecated toggled; operationId added; request: property added, required member removed, enum member added, nullable set to true; response: property added, required member added, nullable set to false
Needs review a response enum gaining a member (strictly typed clients reject values they do not know); anything else in a schema — format, minimum/maximum, pattern, items shape, allOf/oneOf/anyOf variants, other additionalProperties changes. Never trips --fail-on-breaking

When a property is removed, the accompanying "removed from required" row for that property is not reported — the property going away is the change.

Exit codes. 0 — no breaking changes (or --fail-on-breaking not given); 1 — input discovery, reading or parsing failed, or the output could not be written; 2 — usage error; 3 — breaking changes found and --fail-on-breaking was given. The full report is always written before exiting.

--report appends a ## Deltas section with the spec hygiene counts for both versions and the estimated token size of each at --detail full --include-schemas, so a spec update's documentation cost is visible alongside its API changes. -o FILE writes the report to a file (the subcommand has its own -o; the conversion flags do not apply to diff).

Known limitations. Only the first media type of a request body or response is compared. A path-template rename (/pets/{id} → /pets/{petId}) appears as a removal plus an addition. allOf/oneOf/anyOf lists are compared index-wise, so reordering variants is reported as changes. The model drops null from OpenAPI 3.1 type arrays (type: ["string", "null"] is folded to string), so a nullability change expressed as a type array is not detected — only nullable: true/false is.

JSON output

--format json defaults to schema version 2, with neutral services, operations, arguments/results, named types, fields and enum values on both sides. Changes carry a stable subject ID, neutral kind, adapter-classified severity, native location/kind and factual details. vc2_ change IDs exclude severity and raw input hashes. Independent named-type facts and inferred pagination facts supplement operation changes; unused or mixed-use type changes and pagination inference receive review verdicts. V2 summary counts include these facts, and its breaking count controls --fail-on-breaking. See the model, identifiers, adapter mappings and pagination rules.

vimanam diff old.json new.json --format json                 # neutral v2 default
vimanam diff old.json new.json --format json --contract 2    # explicit v2
vimanam diff old.json new.json --format json --contract 1    # legacy v1

For library use, contract::openapi::adapt builds a neutral surface, contract::openapi::diff returns neutral facts, and contract::json::openapi builds the v2 envelope. contract::Adapter and contract::compare support other adapter-created surfaces without HTTP identity. The existing diff::json::to_json remains the v1 library serializer.

The reference below describes explicit --contract 1 output.

--format json prints the same comparison as a single pretty-printed JSON document (2-space indentation, trailing newline) on stdout or in -o FILE, for CI tools, review bots and coding agents. Exit codes are identical across formats, and under --fail-on-breaking the complete document is written and flushed before exit status 3. Diagnostics go to stderr only, so stdout is always one parseable document. -o FILE and stdout carry byte-identical bytes.

vimanam diff old.json new.json --format json --contract 1 --report -o diff.json --fail-on-breaking
{
  "schema_version": 1,
  "generator": { "name": "vimanam", "version": "2.0.0" },
  "old": { "title": "Widgets API", "version": "1.0.0", "file_sha256": "<64 lowercase hex>" },
  "new": { "title": "Widgets API", "version": "1.1.0", "file_sha256": "<64 lowercase hex>" },
  "summary": {
    "endpoints_added": 1, "endpoints_removed": 1, "endpoints_changed": 4,
    "breaking": 4, "non_breaking": 8, "review": 1
  },
  "changes": [
    {
      "id": "vc1_<64 lowercase hex>",
      "endpoint": { "method": "GET", "path": "/widgets/{id}" },
      "kind": "parameter_required_changed",
      "severity": "breaking",
      "details": { "name": "fields", "location": "query", "now_required": true }
    },
    {
      "id": "vc1_…",
      "endpoint": { "method": "GET", "path": "/widgets" },
      "kind": "response_schema_changed",
      "severity": "non_breaking",
      "details": {
        "status": "200",
        "schema_change": {
          "pointer": "/properties/pricing",
          "target": "property",
          "member": "pricing",
          "operation": "added",
          "before": { "present": false },
          "after": { "present": true, "value": { "type": "number" } }
        }
      }
    }
  ],
  "deltas": { "hygiene": [ { "check": "Missing description", "old": 3, "new": 1 } ],
              "tokens": { "old": 12345, "new": 12890, "estimate": "chars/4", "detail": "full+schemas" } }
}

Field reference. schema_version is 1; bumping it (or the vc1_ ID prefix below) signals a contract change. generator.version is the vimanam version that produced the document. old/new carry each spec's title, version and file_sha256. summary mirrors the Markdown summary line (its counts always agree); changes lists every change in the order the Markdown tables use before grouping by severity; severity is one of breaking, non_breaking, review. deltas is present only with --report — the key is omitted, never null — with hygiene rows in the same order as the Markdown ## Deltas table and the same chars/4 token estimate the --report Markdown shows. Every kind maps one-to-one to an internal ChangeKind variant, and its details object carries exactly that variant's fields (e.g. parameter_required_changed → name, location, now_required; response_schema_changed → status, schema_change; operation_id_changed → old/new, which may be null because a spec may lack an operationId). status stays a string ("200", "default", "4XX").

Presence encoding. before/after are each either { "present": false } or { "present": true, "value": <json> }. A present value may legitimately be JSON null (default: null, a null member of enum) — that is distinct from absence. One special case: "no schema at all" is encoded internally as a null at the root pointer "", so a root-level null side is reported as { "present": false } with operation flipped to added/removed (a body appeared or disappeared) rather than a change to/from null.

Pointers. schema_change.pointer is an RFC 6901 pointer exactly as the differ emits it, and target/member classify it according to the schema grammar: target is one of type, required_member, enum_member, property, additional_properties, nullable, other, and member is the decoded last pointer segment for the targets that address a named member (null otherwise). A property named type is a property, not the type keyword. Pointers address the resolved, canonicalised schema — $refs are inlined and description/title/example(s)/deprecated/x-* annotations are stripped before comparing — so a pointer is a location in that normalised form, not necessarily in your input file. required/enum members appear as /required/<name> and /enum/<value>.

Change identity. Every record's id is "vc1_" + SHA-256 (lowercase hex) of a canonical JSON object { "v": 1, "endpoint": …, "kind": …, "details": … } — the same endpoint, kind and details values the record emits, serialised with object keys sorted recursively and no whitespace. Severity, display strings, timestamps, file paths, file_sha256 and the generator version are deliberately not part of the hash, so a future change to the severity rules cannot rewrite any ID; the vc1_ prefix and the "v": 1 field version the construction and must move together if it ever changes. Consequences you can rely on:

  • the same pair of specs yields identical IDs on every run;
  • reformatting a spec (whitespace, key order) changes file_sha256 but no ID or record;
  • an unrelated edit elsewhere in the spec leaves existing IDs unchanged;
  • two different changes at the same pointer (string → integer vs string → boolean) get different IDs;
  • a change behind a shared $ref produces one record per affected endpoint, each with its own ID, because the endpoint is part of the hash input;
  • IDs are unique within a document.

Known limits for IDs. allOf/oneOf/anyOf members are compared by index, so reordering them changes pointers and therefore IDs, even when the set of variants is unchanged. A path-template rename is a removal plus an addition with two fresh IDs — there is no rename detection.

Continuous integration

Generate your API docs in CI with the vimanam GitHub Action — it downloads the matching prebuilt binary (no Rust toolchain on the runner), verifies its SHA256 checksum, and runs vimanam:

- uses: noemaforge/vimanam-action@4599a14c84d9d7bce1ec34ed9f12f3036f06b518 # v1
  with:
    version: v2.0.0
    spec: openapi.json
    output: docs/api-map.md
    detail: summary

# Output is deterministic, so this fails CI when the committed docs drift from the spec:
- run: git diff --exit-code -- docs/api-map.md

To block a pull request that breaks API clients, diff the proposed spec against the one on the default branch; exit status 3 means breaking changes were found (see Comparing spec versions):

- run: git fetch --no-tags --depth=1 origin main:refs/remotes/origin/main
- run: git show origin/main:openapi.json > /tmp/openapi-main.json
- run: vimanam diff /tmp/openapi-main.json openapi.json --report --fail-on-breaking -o api-diff.md

Write the report with -o rather than piping it through tee: a pipeline would return tee's exit status, not vimanam's, unless the shell runs with pipefail. api-diff.md is still written when the step fails, so a later step (if: always()) can upload it or post it as a pull-request comment.

Pin the action to a commit SHA, not a mutable tag — see the action's Pinning notes. More patterns in examples/.

Set version explicitly to choose the Vimanam release; pinning the action itself does not select the latest binary.

Supported OpenAPI Versions

Vimanam supports:

  • OpenAPI 2.0 (Swagger) documents using the swagger field
  • OpenAPI 3.0 documents using the openapi field
  • OpenAPI 3.1 documents using the supported schema fields, including type arrays; this is not a full JSON Schema implementation. See the diff limitations for type-array nullability handling.

Reference resolution supports internal #/... references. References to other files or URLs are not loaded; bundle those specs before using Vimanam.

Output Examples

The generated documentation includes:

Server and Authentication Information

## Server URLs
* https://api.example.com/v1
* https://dev-api.example.com/v1

## Authentication
* **apiKeyAuth**: API Key authentication (apiKey)
* **oauth2**: OAuth 2.0 authorization (oauth2)

Endpoint Documentation

### createUser {#createuser}
**Operation:** POST /users

**Description:** Create a new user account
**Operation ID:** `createUser`

#### Parameters
| Name | In | Required | Description |
|------|----|---------:|-------------|
| `body` | body | Yes | User information |

#### Responses
| Code | Type | Description |
|------|------|-------------|
| 201 | application/json | User created successfully |
| 400 | application/json | Invalid request |

License

Apache License 2.0

About

OpenAPI/Swagger JSON to Markdown documentation generator

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages