Open-source OpenAPI generator. Point it at a repository and it detects the web framework (one LLM call, cached), extracts REST endpoints, and produces a valid OpenAPI 3.0 swagger.json plus a self-contained HTML endpoint catalog you can open in any browser. Routes, methods, and prefixes come from static analysis of code and of the OpenAPI contracts the repo provably serves; the LLM only writes schemas and descriptions, never routes.
Built to be driven by humans or by AI agents: one non-interactive command in, one machine-readable spec out, with honest exit codes and a coverage report inside the spec.
cd /path/to/your/repo
docker run --pull always -it --rm -v $(pwd):/workspace \
-e OPENAI_API_KEY=your_key \
qodexai/apimesh:latest --api-host https://api.yourservice.comInteractive prompts only appear on a real terminal; every value can be supplied by flag or environment variable instead. On Linux, if your repo is not owned by UID 1000, add --user "$(id -u):$(id -g)".
cd /path/to/your/repo
mkdir -p apimesh && \
curl -fsSL https://raw.githubusercontent.com/qodex-ai/apimesh/refs/heads/main/run.sh -o apimesh/run.sh && \
chmod +x apimesh/run.sh && \
apimesh/run.sh --openai-api-key your_key --api-host https://api.yourservice.comcurl -f https://raw.githubusercontent.com/qodex-ai/apimesh/main/swagger_mcp.py -o swagger_mcp.py{
"mcpServers": {
"apimesh": {
"command": "uv",
"args": ["run", "/absolute/path/to/swagger_mcp.py"]
}
}
}The file carries inline dependency metadata, so uv run works as-is. The tool takes openai_api_key, repo_path, and an optional api_host, and raises a tool error when generation fails rather than returning a success payload.
Everything is settable without a TTY. A complete non-interactive run:
docker run --pull always --rm -v $(pwd):/workspace \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
qodexai/apimesh:latest --api-host https://api.yourservice.com --no-htmlFlags (docker image, run.sh, and the Python CLI all accept them):
| Flag | Effect |
|---|---|
--api-host <url> |
servers[0].url in the spec. Without it, a placeholder is used and a warning printed. |
--model <name> |
OpenAI model for generation. Default: gpt-5.6-terra. |
--no-html |
Write swagger.json only, skip the HTML catalog. |
--redetect-framework |
Forget the cached framework and detect again. |
--openai-api-key <key> |
The key (script and docker wrappers; the env var works everywhere). |
Environment variables: OPENAI_API_KEY, APIMESH_API_HOST, APIMESH_OPENAI_MODEL, APIMESH_SKIP_HTML=1, APIMESH_INGEST_SPECS=0 (disable OpenAPI spec ingestion), APIMESH_TELEMETRY=0 (opt out of usage telemetry). Flags beat environment variables beat stored config.
Exit codes:
| Code | Meaning |
|---|---|
| 0 | Spec written (and HTML, unless skipped). |
| 1 | Fatal failure, nothing written. Common causes: no endpoints found, no OpenAI key, framework detection or generation errors. The message says which. |
| 2 | Spec written, but the requested HTML catalog failed to render. |
Outputs, all inside the apimesh/ folder of the scanned repo (docker) or next to it:
| File | What it is |
|---|---|
swagger.json |
The OpenAPI 3.0 spec. |
apimesh-docs.html |
Self-contained, offline endpoint catalog (search, filters, sort). No server needed. |
api_index.json |
Per-endpoint state for incremental runs (dependencies, content hashes). |
config.json |
Stored key (mode 0600, auto-gitignored), model, host, framework. |
metadata_cache/ |
Content-addressed parse cache; makes reruns cheap. Safe to delete. |
repo_profile.json |
The contract lane's full account: every spec found, served, excluded (with reason) or awaiting confirmation, and the state of every override. Written only when the repo carries OpenAPI documents. |
Trust, but verify: every spec carries info.x-apimesh-coverage with endpoints_extracted, generated, skipped_unchanged, and failed counts, plus a contract subblock (specs found/served/excluded, operations, conflicts, whether the sweep was truncated). If failed is nonzero, treat the spec as incomplete: new endpoints that failed are absent, and a changed endpoint that failed may still show its previous operation until a rerun succeeds; rerunning retries just the failed endpoints. Routes are never invented: an operation is present only when a parser proved it in code or a spec this repo provably serves declares it, and each one names its origin in x-apimesh-source. Custom metadata lives in x- extension fields (x-authorization-tag, x-module-tag, x-sensitive-information), so strict OpenAPI validators accept the output.
| Language | Frameworks | How |
|---|---|---|
| Python | Flask, FastAPI, Django (URLconf), Django REST Framework, connexion (spec-first, see below) | AST analysis + spec ingestion |
| Node.js / TypeScript | Express (incl. mounted routers), NestJS | tree-sitter |
| Ruby on Rails | resources/resource, namespaces, scopes, member/collection, concerns, shallow nesting, engines, split route files | tree-sitter |
| Go | gin, echo, chi, fiber, gorilla/mux, net/http (incl. Go 1.22 patterns), oapi-codegen (spec-first, see below) | tree-sitter + spec ingestion |
| Java | Spring annotation-based controllers (@RestController, @RequestMapping, verb mappings) and OpenAPI-first codebases (see below) | tree-sitter + spec ingestion |
Anything else yields an honest zero (exit 1, nothing written). ApiMesh never asks an LLM to guess routes: a guessed route set can include third-party client calls and paths nobody serves, which poisons every consumer of the spec.
Many codebases keep their API in OpenAPI YAML/JSON files and generate or register the serving code from them, so the routing never appears in committed handlers. ApiMesh reads those spec files directly, but only after proving this repo serves them. The accepted proofs: a live Maven, Gradle or Bazel build step naming the spec as input to a server-mode generator (spring, kotlin-spring, delegate pattern included); a //go:generate oapi-codegen directive with a server flavor whose generated output is committed; or a connexion app.add_api("spec.yaml") registration in application code. Profile-gated plugins and go:generate directives without committed output are provisional: the spec is listed as a candidate for confirmation, never served on a hint. Vendored specs of third-party APIs (client generation, gateway upstreams, stale documentation files) are excluded, each with its reason in repo_profile.json. Spec content (schemas, parameters, descriptions) passes through as authored, at zero LLM cost, with references rewritten onto per-spec namespaced components.
A spec ApiMesh cannot prove either way is listed as a candidate with an eligibility_hash. To confirm one, commit .apimesh-overrides.json at the repo root:
{
"specs": [
{"path": "specs/internal-api.yaml", "action": "include", "eligibility_hash": "<from repo_profile.json>", "prefix": "/api"},
{"path": "vendor/partner.yaml", "action": "exclude", "reason": "partner's API, not ours"}
]
}exclude always applies. include activates only while the hash matches the current spec, build files and implementing controllers, and goes dormant (reported) the moment that evidence changes.
- Detect the framework (cached after the first run).
- Extract endpoints: deterministic parsers own routes, methods, and prefixes; the contract lane ingests OpenAPI specs the repo provably serves; overlapping routes are documented from the spec, not generated twice.
- Generate schemas and descriptions with the LLM for code-proven endpoints only, batched per source file under a hard token budget, with per-endpoint failure isolation.
- Rerun cheaply: unchanged endpoints are skipped via content hashes, edits to shared helpers invalidate their dependents, failed endpoints retry automatically, and the spec-sourced portion is rebuilt deterministically every run.
Only two things leave your machine: source context sent to the OpenAI API for schema generation, and anonymous usage telemetry (an install UUID and run timings, disable with APIMESH_TELEMETRY=0). Nothing is created inside your repository except the apimesh/ output folder.
See CONTRIBUTING.md for the dev setup, test suite, and PR checklist. Issues and PRs welcome, especially framework coverage.