Documentation
Inox compiles one OpenAPI spec into idiomatic SDKs for six languages, an MCP server, and a CLI — self-hosted, zero runtime dependencies, verified on every build.
Overview
Inox is a clean-room, air-gapped OpenAPI SDK generator — the open alternative to Stainless, Speakeasy, and Fern. The pipeline:
OpenAPI spec + sdkgen.yml ──► canonical IR ──► per-language emitters
└► TypeScript · Python · Go · Java · Ruby · C#
└► MCP server · CLI · Terraform · docs
Everything runs locally; your spec never leaves your machine. Generated runtimes have zero third-party dependencies, and every build is checked by runtime endpoint conformance against a spec-derived mock.
Install
# npm — run or install globally
npx @crevious/inox --help
npm install -g @crevious/inox
# one-line installer (git + Node 22+)
curl -fsSL https://raw.githubusercontent.com/CREVIOS/inox/main/install.sh | bash
# Docker — no local toolchain
docker run --rm -v "$PWD:/work" inox generate --out sdk
Quickstart
inox init --force # sample sdkgen.yml + openapi.yaml
inox generate --out sdk # all configured targets
inox verify --out sdk # compile + conformance, every language
A minimal config is just a name, the spec path, and one target — Inox auto-derives the rest:
sdkgen: 1
project: { name: petstore }
spec: { path: ./openapi.yaml }
targets:
typescript: { package_name: "@example/petstore" }
Worked example
The petstore example walks a real spec → typed client (auth, cursor pagination, typed errors) → usage. Generated TypeScript looks like this:
import Petstore from "@example/petstore";
const client = new Petstore({ apiKey: process.env.PETSTORE_API_KEY });
const pet = await client.pets.create({ body: { name: "Rex", species: "dog" } });
const got = await client.pets.retrieve(pet.id);
for await (const p of client.pets.listAutoPaging()) console.log(p.name);
sdkgen.yml reference
| Key | Purpose |
|---|---|
project.name | Package prefix / identifier (required). |
spec.path | Path to the OpenAPI 3.0/3.1 document (required). |
targets | Languages to emit: typescript, python, go, java, ruby, csharp (each takes package/module names). |
client.class_name | Generated client class name. |
client.base_url.default | Default base URL (falls back to the spec's first server). |
client.env_prefix | Env-var prefix, e.g. PETSTORE → PETSTORE_API_KEY. |
client.auth | bearer / api_key / oauth2 / basic — see Auth. |
client.retries | max_retries, retry_statuses (exponential backoff + jitter). |
client.timeout.seconds | Per-request timeout. |
client.idempotency.header | Idempotency-key header auto-set on non-GET. |
environments | Named base URLs (e.g. production/sandbox). |
pagination | Named schemes; see Pagination. |
resources | Optional explicit resources → methods (endpoint, pagination, streaming, skip/only, deprecated). |
webhooks | Standard-Webhooks signing secret + headers for typed verification. |
mcp | MCP defaults: tools (auto/typed/dynamic/code), dynamic_threshold, permissions. |
overrides | Per-operation method-name / resource overrides; per-type renames. |
Commands
| Command | What it does |
|---|---|
inox init | Scaffold a sample sdkgen.yml + openapi.yaml. |
inox lint | Validate config/spec; --governance runs the policy ruleset. |
inox generate | Generate SDKs (+ mock); overlay-merges custom code by default. |
inox verify | Compile + endpoint conformance for every target. |
inox diff | Classify IR changes for SemVer; --gate fails CI on a breaking change. |
inox release | Plan a SemVer release; write CHANGELOG + notes. |
inox products | Generate docs, CLI, MCP server, Terraform provider, CI automation. |
inox mcp | Run the generator itself as an MCP server (agents drive generation). |
inox sbom | Write a CycloneDX SBOM per target. |
inox studio | Serve the offline Studio web UI. |
Common flags: --out <dir>, --target <lang>, -c <config>, --no-overlay.
Auth
client:
auth:
bearer: { env: API_KEY, security_scheme: BearerAuth }
api_key: { env: API_KEY, header: X-Api-Key } # or query: api_key
oauth2: { token_url: /oauth/token, client_id_env: CLIENT_ID, client_secret_env: CLIENT_SECRET }
basic: { username_env: USER, password_env: PASS }
OAuth2 client-credentials tokens are fetched, cached, and refreshed automatically, with 401 re-auth.
Pagination
Declare a scheme, attach it to a list method. Clients expose a single page and an
auto-paginating async iterator (listAutoPaging()).
| type | Driven by |
|---|---|
cursor | request cursor param + response next-cursor field |
cursor_id | request param + the last item's id |
cursor_url | absolute next URL (field or header:Link) |
offset | offset/limit params + total count |
page_number | page/size params + current/total pages |
pagination:
pets_cursor:
type: cursor
items: $.data
request_cursor: cursor
response_next_cursor: $.next_cursor
Streaming & webhooks
SSE/JSONL streaming (dual-mode via a discriminator) and typed WebSocket clients are generated
from method config. The webhooks block adds Standard-Webhooks HMAC verification
+ typed unwrap.
Errors
Clients throw a base ApiError plus typed per-status subclasses, each carrying
status, body, and requestId (from x-request-id).
import { NotFoundError, RateLimitError } from "@example/petstore";
try { await client.pets.retrieve(id); }
catch (e) {
if (e instanceof NotFoundError) { /* 404 */ }
if (e instanceof RateLimitError) { /* 429 */ }
}
Per-language usage
import Petstore from "@example/petstore";
const client = new Petstore({ apiKey: process.env.PETSTORE_API_KEY });
const pet = await client.pets.create({ body: { name: "Rex", species: "dog" } });
for await (const p of client.pets.listAutoPaging()) console.log(p.name);
from petstore import Petstore
client = Petstore(api_key="...") # sync; AsyncPetstore for async
pet = client.pets.create(body={"name": "Rex", "species": "dog"})
client := petstore.NewClient(petstore.WithAPIKey("..."))
pet, err := client.Pets.Create(ctx, petstore.PetsCreateParams{ /* ... */ })
MCP / agents
Every API becomes agent-callable. inox products emits a zero-dependency MCP
server (protocol 2025-06-18) with three tool modes, auto-picked by API size:
| Mode | Shape |
|---|---|
typed | One strongly-typed tool per endpoint (schemas from the spec). |
dynamic | list_api_endpoints + get_api_endpoint_schema + invoke_api_endpoint — for large APIs. |
code | Constrained HTTP execute + search_docs. |
Least-privilege flags: --tools, --scope read (or by tag), --tool,
--allowed-methods, --blocked-methods, --allow-http-gets. Transports: stdio + streamable-HTTP.
Register with any MCP client (Claude Desktop, Cursor):
{
"mcpServers": {
"petstore": {
"command": "node",
"args": ["/abs/path/sdk/mcp/dist/server.js", "--scope", "read"],
"env": { "PETSTORE_API_KEY": "sk-..." }
}
}
}
Custom code overlay
Hand-edits to generated files survive regeneration via a line-based three-way merge with
conflict markers — so you can patch generated code and still pull spec updates. Pass
--no-overlay to overwrite instead.
Supply chain
Generated by default: a CycloneDX SBOM per SDK, SLSA build provenance, and
OIDC trusted-publishing workflows (npm / PyPI / RubyGems / Maven Central / NuGet / Go proxy).
Plus a governance lint and a breaking-change CI gate (inox diff --gate).
CI/CD
Drop SDK generation into any pipeline:
- uses: CREVIOS/inox@main
with: { config: sdkgen.yml, target: all, out: generated, verify: "true" }
Troubleshooting
| Symptom | Fix |
|---|---|
| "method name collision" blocker | Two configured methods share a name — add an overrides.operations entry. (Auto-derived names disambiguate automatically.) |
| Generated TS won't find a type | Run on the latest version: npx @crevious/inox@latest. File an issue with the spec snippet. |
| Stale files after a rename | --no-overlay doesn't delete; rm -rf out then regenerate, or use overlay mode. |
| OAuth2 token errors | Set CLIENT_ID/CLIENT_SECRET env vars matching client.auth.oauth2. |
More questions? See the FAQ or open an issue.