Inox

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

KeyPurpose
project.namePackage prefix / identifier (required).
spec.pathPath to the OpenAPI 3.0/3.1 document (required).
targetsLanguages to emit: typescript, python, go, java, ruby, csharp (each takes package/module names).
client.class_nameGenerated client class name.
client.base_url.defaultDefault base URL (falls back to the spec's first server).
client.env_prefixEnv-var prefix, e.g. PETSTOREPETSTORE_API_KEY.
client.authbearer / api_key / oauth2 / basic — see Auth.
client.retriesmax_retries, retry_statuses (exponential backoff + jitter).
client.timeout.secondsPer-request timeout.
client.idempotency.headerIdempotency-key header auto-set on non-GET.
environmentsNamed base URLs (e.g. production/sandbox).
paginationNamed schemes; see Pagination.
resourcesOptional explicit resources → methods (endpoint, pagination, streaming, skip/only, deprecated).
webhooksStandard-Webhooks signing secret + headers for typed verification.
mcpMCP defaults: tools (auto/typed/dynamic/code), dynamic_threshold, permissions.
overridesPer-operation method-name / resource overrides; per-type renames.
No config needed to start. Resources, methods, schemas, pagination, and auth are auto-derived from the spec. The keys above only override the defaults.

Commands

CommandWhat it does
inox initScaffold a sample sdkgen.yml + openapi.yaml.
inox lintValidate config/spec; --governance runs the policy ruleset.
inox generateGenerate SDKs (+ mock); overlay-merges custom code by default.
inox verifyCompile + endpoint conformance for every target.
inox diffClassify IR changes for SemVer; --gate fails CI on a breaking change.
inox releasePlan a SemVer release; write CHANGELOG + notes.
inox productsGenerate docs, CLI, MCP server, Terraform provider, CI automation.
inox mcpRun the generator itself as an MCP server (agents drive generation).
inox sbomWrite a CycloneDX SBOM per target.
inox studioServe 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()).

typeDriven by
cursorrequest cursor param + response next-cursor field
cursor_idrequest param + the last item's id
cursor_urlabsolute next URL (field or header:Link)
offsetoffset/limit params + total count
page_numberpage/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

TypeScript Python Go
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:

ModeShape
typedOne strongly-typed tool per endpoint (schemas from the spec).
dynamiclist_api_endpoints + get_api_endpoint_schema + invoke_api_endpoint — for large APIs.
codeConstrained 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

SymptomFix
"method name collision" blockerTwo configured methods share a name — add an overrides.operations entry. (Auto-derived names disambiguate automatically.)
Generated TS won't find a typeRun 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 errorsSet CLIENT_ID/CLIENT_SECRET env vars matching client.auth.oauth2.

More questions? See the FAQ or open an issue.