Skip to main content
ApiGlow v0.3.0
Copy page

The audit, rule by rule.

The audit grades an OpenAPI schema: 137 rules in seven categories, a score for each category, a letter from A to F, and for every finding why it matters and how to fix it. It runs in two places, on one engine:

  • In the documentation, at #/audit, on the schema the page already loaded. Nothing is sent anywhere.
  • In CI, as npx apiglow audit openapi.yaml, where it fails the build on what it finds. That half has its own guide: exit status, baseline, SARIF, GitHub and GitLab reports, recipes.

Both produce the same report from the same rules, and read the same rule configuration. This guide covers what is checked, how the grade is computed, and what the report carries. The feature page is the overview.

Opening it, and switching it off

The audit addresses the API’s author rather than its readers, so the page has no nav entry. The entry point is a block at the bottom of the settings drawer, linking to #/audit — a real route you can open or copy directly.

Nothing is computed, or even downloaded, until that page is opened: the engine lives in audit.js, next to app.js, and loads on the first visit to #/audit. A large schema shows the run’s progress rather than a blank page — in a background tab too. The report is then cached in memory, per spec.

features: { "audit": false } removes the route, the settings block and every computation. In multi-spec you can do this per spec: one API graded, its neighbour not. The switch concerns published documentation only — the command line ignores it.

What a finding carries

Every finding states a severity, a message, why it matters and how to fix it — what to write, and where. The fix is on the page, in the Markdown export, in the CLI’s console output, and in the JSON report an agent reads.

SeverityClaim
errorAlmost certainly a schema bug
warningProbably hurts consumers or the docs
infoWorth knowing

A finding on a rendered operation deep-links to it. A finding on a callback links to the operation that declares it, since a callback has no route of its own. A finding on a hidden operation shows a “hidden” badge instead of a link: the audit sees the full schema, but never links to a view that is not routable. Findings elsewhere — components, info, and so on — show their JSON pointer.

Findings are folded by rule, each row carrying its count, so one omission repeated across a schema is one decision rather than two thousand rows. Occurrences are materialized fifty at a time on expansion.

The audit reports one thing to fix once. Undescribed properties make one finding per schema, naming them, rather than one per property: GitHub’s REST schema goes from 28,324 findings to 2,751 that way.

Any file gets a report

The audit reads what you hand it, even broken. A duplicate key, a tab used as indentation, a trailing comma, a file cut short: each is a document-syntax finding at its line and column, and the page quotes the file around it with a caret under the column. The rest of the file is still graded. A file holding no OpenAPI description at all — empty, a list, plain text, Swagger 1.x — gets a report saying so.

The seven categories

Correctness (76 rules) — what contradicts the OpenAPI specification or the document itself, mostly errors. The structure, version by version: a field the object does not have (descripton), a required field missing, a value of the wrong kind (in: body, type: int), fields that exclude each other. Paths and parameters: a malformed path, two paths identical once their variables are renamed, a path parameter missing from the template, a parameter declared twice, a style its location does not allow. References: a $ref that leads nowhere or to the wrong kind of object. Schemas held to their own keywords: an enum its type rejects, bounds no value satisfies, a pattern that is no regular expression, a misspelled keyword (maxLenght), a recursion no finite value satisfies. Examples and defaults held to their whole schema, down to the nested property that breaks it ($.tags[0].name). And HTTP semantics: a body on a GET, content on a 204, a 3.2 QUERY with no body.

Security (11 rules) — what the document lets through, each rule tied to the RFC or OWASP risk it breaks. An http:// server, OAuth URLs without TLS, credentials sent to a cleartext server, an API key in the query string, the OAuth password and implicit flows RFC 9700 retires, operations open without authentication, a format: password field returned in a response, request bodies with no maximum size.

Documentation (15 rules) — what the document leaves unsaid: operations without summary or description, undescribed parameters, properties and tags, mutating operations with no error response, success responses with no example, info missing its description, contact or license. Placeholder text counts as missing: a description reading “TODO”, “string” or “lorem ipsum”, or one that only repeats its field’s name.

Deprecation (4 rules) — a deprecation that names no replacement or sunset, a Deprecation header that is not an RFC 9745 date, a sunset set before the deprecation, a deprecated input still required.

Consistency (6 rules) — the document’s own conventions, held against itself: parameter, property and path names off the dominant convention (detected rather than imposed; header names are exempt, since they follow HTTP’s), and names that code generators fold into one — pet_status and PetStatus become the same type.

Docs readiness (17 rules) — how the schema behaves in this renderer, the category no generic linter has. Each message names the concrete degradation: no operationId means unstable deep links, no servers means environment seeding has nothing to offer, a server left on example.com means the try-it calls nobody, an OAuth2 flow missing its URLs means the “Get a token” block cannot run, HTML the sanitizer removes never reaches the reader, a header the browser refuses to send cannot be tried here.

Agent readiness (8 rules) — what an AI agent gets when it calls your API through a tool: an MCP bridge, a GPT Action, Semantic Kernel. Inputs with no type or no shape, unions an agent cannot tell apart, a body that is not an object, enum values nothing explains, a parameter and a body property sharing a name, request examples written where no tool sees them, errors returned as prose.

Version awareness

Rules judge the document against the version it declares, in both directions. A spelling that a later version replaced (nullable: true, from 3.1 on) and a construct used ahead of the declared version (webhooks or type arrays in a 3.0 document, 3.2-only constructs in a 3.1 one) are both correctness findings. The same nullable in a 3.0 document passes, because it is correct there.

A converted Swagger 2.0 document also gets one finding per construct the conversion could only approximate.

Scoring

Each rule application is a pass/fail check against a target: an operation, a parameter, a component, or the document.

  • Category score is the weighted pass rate over its applicable checks. Weights follow severity: error 3, warning 2, info 1.
  • Partial credit. A check standing for several items — a schema’s properties, an enum’s values, an operation’s inputs — loses part of its credit, more with each gap but less for each next one. One gap in fifteen costs a quarter of the check; all fifteen, the whole of it.
  • Not-applicable checks do not count. An API with no deprecations scores 100% on deprecation rather than zero, and a category with no applicable check is absent from the report rather than scored.
  • Letter grade comes from the mean of the category scores: A ≥ 90, B ≥ 80, C ≥ 65, D ≥ 50, F below.

The report opens on its verdict: the letter in a ring drawn to the score, beside one bar per category. Each category heading carries its own severity counts, so a section can be weighed without scrolling back.

Configure the rules

A real API has deliberate, permanent exceptions. The audit block of the config switches a rule off, or changes its severity, for the whole document or only under some JSON pointers:

{
  "audit": {
    "rules": { "property-described": "off", "parameter-described": "error" },
    "overrides": [
      { "paths": ["/paths/~1legacy~1*"], "rules": { "response-example": "off" }, "reason": "frozen legacy API" }
    ]
  }
}
  • rules maps a rule id to error, warning, info or off. Rule ids are in the report, and apiglow audit --list-rules prints them all.
  • overrides applies the same settings under pointer patterns: * matches within one segment, ** any number of segments, and a pattern covers everything below what it matches. reason is free text for the next reader of the config, since JSON has no comments.
  • Precedence: the rule’s own severity, then rules, then every override covering the pointer in declaration order — the last word wins.
  • A rule that is off does not count at all — neither as a finding nor in the score.
  • Per spec, in multi-spec: rules merge by id, the spec winning; overrides accumulate, root first.

The page and apiglow audit read the same block, so the published grade and the CI grade agree. A grade computed with any of this is labelled custom rule set next to the letter, in both exports and in the JSON, so it never passes for the default one. An entry the page cannot read is named in the browser console and left out; the command line refuses to run on it.

There are no custom rules, and the baseline is not an ignore list: it changes which findings fail a CI job, never which ones are reported.

The report names what it graded

A report is often read out of context — a pasted screenshot, a tab left open — so the page states its identity and perimeter: the API’s title, info.version and declared OpenAPI version, contact and license when the document carries them, and the document in figures (operations, groups, webhooks, security schemes, schemas).

Figures are counted on the document, so hidden operations are included. Zeros are shown, since “no security scheme” is something the report goes on to grade.

Export

“Copy as Markdown” carries everything the page shows — identity, figures, per-category counts, and every finding with why it matters and how to fix it — plus a timestamp to the second, so a report pasted into a ticket says when it was produced.

Two differences from the page: JSON pointers are kept whole, since they locate the finding in the file you are about to edit, and the export travels in the language it was read in.

Paste it into an issue, fix, reload, re-audit. When the finding is in a schema you do not own, overlays fix it without a fork. When you want the check on every merge, put it in CI.