A deterministic CLI that lints OpenAPI (Swagger) 3.x specs for design quality
and consistency — broken $refs, missing responses/descriptions, duplicate
operationIds, undeclared path params, REST naming, security gaps — with a
score, A–F grade, and JSON/Markdown reports. Works on JSON and YAML, with a
built-in parser (no heavyweight deps).
apilint reads your openapi.yaml/.json, runs 25+ opinionated quality rules,
and reports issues with locations, fixes, and a score you can gate in CI — no
config required, nothing uploaded.
Your OpenAPI spec is the single source of truth for your API: docs, SDKs, mock servers, partner integrations and client code are all generated from it. But hand-written or code-generated specs drift into a mess:
- A
$refpoints at a schema that was renamed — SDK generation breaks. - An endpoint has no documented response (or no error response) — consumers can't handle it.
- Two operations share an
operationId— generated client methods collide. - Paths like
/getUserList/mix verbs, casing and trailing slashes — an inconsistent API that's painful to use and impossible to standardize.
The tool everyone reaches for, Spectral, is powerful but has a real learning
curve and ruleset configuration overhead. apilint is the opinionated,
zero-config alternative: install, run, get a graded report. It's deterministic
and static, so it drops straight into CI.
- 🔗 Structure —
$refintegrity, OpenAPI version, paths present. - 🧱 Operations —
operationIdpresence/uniqueness, summaries, tags, and success and error responses. - 🧭 Parameters — every
{templated}path segment must be declared; missing descriptions flagged. - 🏷️ Naming — REST conventions: no verbs in paths, kebab-case segments, no
trailing slashes,
operationIdcasing. - 🔐 Security — security schemes defined; operations require auth (toggle).
- 📝 Docs quality — info metadata, response/parameter/schema descriptions.
- 📊 Score + A–F grade per category and overall.
- 📄 JSON & Markdown export, colored console output, CI gate exit codes.
- 🧰 JSON + YAML input via a small built-in parser. Deterministic & offline.
# run without installing
npx @didrod2539/apilint scan openapi.yaml
# or install
npm install -g @didrod2539/apilint # global CLI (provides `apilint`)
npm install -D @didrod2539/apilint # project dev-dependency (for CI)Node ≥ 18. ESM + CJS + TypeScript types.
apilint scan openapi.yamlopenapi.yaml 85/100 (B) Messy API
3 paths · 4 operations · 1 schemas · 0 tags
Document structure 90
Operations 74
Parameters 70
...
✗ Broken $ref "#/components/schemas/MissingSchema" (paths./getUserList/.get…)
✗ Duplicate operationId "FetchProfile" (paths./user_profiles/{id}.post)
✗ Path parameter "{id}" in GET /user_profiles/{id} is not declared
⚠ Path "/getUserList/" has a trailing slash → Use "/getUserList".
Overall 85/100 (B) 7 error(s), 11 warning(s), 13 info
apilint scan [...targets] # lint spec files or directories
apilint report <input.json> # re-render a saved JSON report as Markdown
apilint init # scaffold apilint.config.json
apilint --help
apilint --versionscan options:
| Option | Description |
|---|---|
--config <file> |
Path to a config file (otherwise auto-detected) |
--json <file> |
Write a JSON report |
--md <file> |
Write a Markdown report |
--min-score <n> |
Exit non-zero if any spec scores below this (CI gate) |
--quiet |
Hide info-level issues in the console |
Pointed at a directory, scan finds files named like *openapi*/*swagger*/
*api*.{json,yaml,yml} recursively.
Full reports for the bundled sample spec are in
examples/sample-report.md and
examples/sample-report.json.
📸 Screenshot / demo GIF placeholder:
./docs/screenshot.png— record the terminal runningnpx @didrod2539/apilint scan examples/petstore-bad.yaml.
Create apilint.config.json (or run apilint init):
{
"requireSecurity": true,
"requireTags": true,
"operationIdStyle": "camelCase",
"minScore": 80,
"disableCategories": [],
"disableRules": ["path-kebab-case"],
"ruleSeverity": { "operation-tags": "warning" },
"categoryWeights": { "structure": 1.4, "responses": 1.2 }
}| Field | Meaning |
|---|---|
requireSecurity |
Flag operations with no security (and no global default) |
requireTags |
Flag untagged operations |
operationIdStyle |
camelCase, snake_case, or any |
minScore |
CI gate threshold (overridable with --min-score) |
disableCategories |
Skip whole categories |
disableRules |
Skip individual rules by id |
ruleSeverity |
Override severity per rule id |
categoryWeights |
Re-weight categories in the overall score |
Categories: structure, info, paths, operations, parameters,
responses, schemas, security, naming. See src/types.ts for the full
rule-id list.
- Gate API quality in CI. Add
apilint scan openapi.yaml --min-score 85. A PR that introduces a broken$refor a duplicateoperationIdfails before it breaks SDK generation downstream. - Standardize a team's API style. Set
operationIdStyleand naming rules inapilint.config.json, commit it, and every new endpoint is held to the same conventions automatically. - Audit an inherited or generated spec. Run
apilint scan ./spec --md api-audit.mdfor a graded, per-category report of what's missing or inconsistent before you publish docs or a client SDK.
import { analyze, toMarkdown } from "@didrod2539/apilint";
const report = analyze(yamlOrJsonText, config, { source: "openapi.yaml" });
console.log(report.score, report.grade, report.issues);
await fs.writeFile("api.md", toMarkdown(report));- More rules: example/
requestBodycoverage, response media types, enum/format hygiene, unused components, consistent error schema. - Resolve external
$refs (other files / URLs, opt-in). - SARIF output for code-scanning integration.
- A GitHub Action with PR annotations on changed operations.
- Custom rule plugins and shareable config presets.
- OpenAPI 3.1 / JSON Schema 2020-12 specific checks.
Is this a Spectral replacement? For most teams' needs, yes — apilint ships an opinionated default ruleset with no configuration. Spectral is more extensible (custom DSL rules); apilint trades that for zero-config simplicity, a score/grade, and a tiny dependency footprint.
Does it need a network or a browser? No. It parses JSON/YAML with a built-in parser and runs entirely locally — no API key, no uploads.
Does it validate against the full OpenAPI JSON Schema? It focuses on high-value design-quality rules (the things that actually break SDKs, docs and consumers), not exhaustive schema validation. Use it alongside a strict validator if you need byte-level conformance.
Why a hand-written YAML parser? To stay dependency-light and deterministic. It supports the YAML subset OpenAPI specs use (block mappings/sequences, scalars, quotes, flow collections, block scalars). If your spec uses exotic YAML, convert to JSON.
My spec scored lower/higher than expected — can I tune it?
Yes. disableRules, ruleSeverity, categoryWeights, and the require*
toggles all change what's flagged; --min-score decides what fails CI.
Contributions welcome! Each check is a small, self-contained rule in
src/rules/. See CONTRIBUTING.md and the
Code of Conduct.
git clone https://github.com/didrod205/apilint.git
cd apilint
npm install
npm test
npm run build
node dist/cli.js scan examples/petstore-bad.yamlMIT © apilint contributors
apilint is free, MIT-licensed, and built in spare time. If it caught a spec bug before it broke your SDK build, please consider supporting it:
- ⭐ Star this repo — free, and it helps others find it.
- 🍋 Sponsor via Lemon Squeezy — one-time or recurring.
Where your support goes: more rules, external $ref resolution, SARIF
output, a PR-annotating GitHub Action, custom rule plugins, and fast issue
responses.