How would you enforce OpenAPI style rules across many specs in CI using Spectral?
answer
- A rule set the pipeline can check
- given selects, then asserts
- Severity drives the gate
- One shared ruleset, extended everywhere
- Warn first, error later
basics
~20 sPublish one shared Spectral ruleset that each repository extends, define rules as a JSONPath given plus a then function and severity, run spectral lint in CI, and gate the build with --fail-severity. Introduce new rules at warn before promoting them to error.
solid answer
~50 sSpectral lints OpenAPI documents against a ruleset. A ruleset is a YAML file that typically starts by extending the built-in OpenAPI rules — `extends: [[spectral:oas, recommended]]` — and then adds house rules. Each rule has a `description`, a `given` JSONPath selecting the nodes it applies to, a `then` clause naming a built-in function such as `truthy`, `pattern`, `casing` or `length` (with `functionOptions`), and a `severity` of `error`, `warn`, `info` or `hint`. You run `spectral lint openapi.yaml --ruleset .spectral.yaml` in CI and fail the build with `--fail-severity`. At organisation scale the ruleset is published as a package that every repository `extends`, so a new rule ships everywhere at once. Roll rules in at `warn` first, use `overrides` to exempt legacy documents by glob, then promote to `error` once the fleet is clean. A linter enforces consistency, never good design.
code
yaml · 25 lines# .spectral.yaml
extends: [[spectral:oas, recommended]]
rules:
house-operationid-required:
description: Every operation needs an operationId; codegen names methods from it.
given: $.paths[*][get,put,post,delete,patch]
then:
field: operationId
function: truthy
severity: error
house-schema-names-pascal-case:
description: Schema names become generated type names, so use PascalCase.
given: $.components.schemas[*]~
then:
function: casing
functionOptions:
type: pascal
severity: warn
overrides:
- files: ['specs/legacy-*.yaml']
rules:
house-schema-names-pascal-case: offgo deeper
Know that Spectral checks an OpenAPI document against a ruleset and that CI can fail the build when findings reach a given severity.
Explain a rule's parts — given as a JSONPath selector, then with a built-in function, and a severity — and how extends pulls in the built-in OpenAPI rules.
Describe operating it: gating with --fail-severity, linting the published bundle rather than fragments, and scoping exceptions with overrides instead of weakening rules.
Own the style guide as a distributed artefact — a versioned shared ruleset, a rollout path from warn to error, and clarity about which decisions a linter can enforce and which need review.
## What Spectral is Spectral is a linter for structured API documents — OpenAPI among them. It walks the document, selects nodes with JSONPath expressions and evaluates assertions against them, reporting findings with a severity and a location. It is the mechanism by which "our API style guide" becomes something a pipeline can check instead of something reviewers remember to mention. It checks the document, not the service. Spectral cannot tell you the implementation disagrees with the spec, only that the spec breaks a rule. ## Anatomy of a ruleset A ruleset is YAML (or JSON, or JavaScript for custom functions). Two parts matter. **`extends`** pulls in a base. `spectral:oas` is the built-in OpenAPI rule set covering structural sanity — unresolved references, duplicated operation ids, missing descriptions, unused components and similar. `extends: [[spectral:oas, recommended]]` takes its recommended severity profile; `all` turns on everything. **`rules`** adds or overrides individual rules. Each named rule has: - `description` — the message a developer reads when it fires, so write it as advice, not as a restatement of the rule name. - `given` — a JSONPath expression selecting the nodes to test. `$.paths[*][get,put,post,delete,patch]` selects every operation; `$.components.schemas[*]~` selects the schema *keys*, which is how you lint names rather than values. - `then` — what must hold: an optional `field` to descend into, plus a `function` and its `functionOptions`. Built-ins include `truthy`, `falsy`, `defined`, `undefined`, `pattern`, `casing`, `length`, `alphabetical`, `enumeration`, `schema` and `xor`. - `severity` — `error`, `warn`, `info` or `hint`, which is what your gate keys off. When the built-ins do not suffice, a custom function is a small JavaScript module the ruleset points at — worth it for genuinely organisation-specific checks, but each one is code someone must maintain. ## Running it in CI The command is `spectral lint <document> --ruleset <ruleset>`, and the gate is `--fail-severity`. Set it to `error` so warnings surface without blocking, and promote rules to `error` as they become non-negotiable. Two practical points. First, lint the artefact consumers actually see: if you publish a bundled document, lint the bundle, because rules about unused components and unresolved references behave differently before and after bundling. Second, lint on every pull request that touches a spec, not on a nightly job — a finding is cheap to fix while the change is in flight and expensive afterwards. ## Scaling across repositories The failure mode at scale is drift: twenty repositories each with a slightly different `.spectral.yaml`, and no way to introduce a new rule. The fix is to publish the organisation ruleset as a versioned package and have each repository's local file extend it and add nothing but exceptions. Rolling out a new rule then means releasing a ruleset version and letting repositories pick it up. `overrides` handles the legacy problem: it lets you apply different rule severities to files matched by glob, so a frozen legacy document can be exempted from a new naming rule without weakening the rule everywhere. Prefer a scoped, documented override to a blanket downgrade. The rollout pattern that works is: add the rule at `warn`, measure how many findings exist across the fleet, fix or override them, then promote to `error`. Shipping a new rule straight to `error` breaks everyone's build and teaches teams to disable the linter. ## What rules are worth writing The high-value ones are the ones that make downstream tooling work and keep the fleet coherent: every operation has a unique `operationId` (generated method names depend on it), every operation has at least one tag (SDK class grouping depends on it), schema names follow one casing convention (they become type names), every operation documents an error response, descriptions exist on public-facing fields, and no path uses a naming style that contradicts the house guide. ## The limit A linter enforces consistency, not quality. It will confirm that every operation has a description and cannot notice that the descriptions are useless; it will check schema naming and not that the resource model is wrong. Style checks free reviewers to spend their attention on design — they do not replace the design review.
- What does the `given` field in a Spectral rule do?It is a JSONPath expression selecting which nodes the rule applies to. `$.paths[*][get,put,post,delete,patch]` targets every operation; appending `~` as in `$.components.schemas[*]~` selects the property keys instead of their values, which is how you lint component names rather than their contents.
- How do you introduce a strict new rule without breaking every team's build?Ship it at `warn` first so it reports without failing, measure the findings across repositories, then fix or scope-exempt the stragglers with `overrides` matched by file glob, and finally promote it to `error`. Landing a new rule directly at error breaks builds people did not change and teaches teams to bypass the linter.
- What can Spectral not tell you about an API?Anything about the running service or the quality of the design. It lints the document, so it cannot detect that the implementation has drifted from the spec, and it can confirm that every operation has a description without noticing the descriptions are meaningless. Consistency is checkable; judgment is not.
saying these in an interview costs you the question
- Thinks a linter verifies the implementation matches the spec
- Copies a different ruleset into every repository
- Ships new rules straight at error severity
- Lints the source fragments but publishes a bundle
- Treats passing the linter as a completed design review