OpenAPI document linting
The Speakeasy Linter validates OpenAPI 3.x documents for correctness, style, and SDK generation readiness. It is built on top of the open-source speakeasy-api/openapi linter framework, which provides a fast, index-based validation engine. By default, the linter runs using the speakeasy-recommended ruleset, which can be extended with any of the available rulesets.
The Speakeasy Linter offers:
- Linting and validation of OpenAPI 3.x documents
- 90+ built-in rules across six categories: SDK generation, spec correctness, best practices, security, schema validation, and Speakeasy-specific checks
- Five built-in rulesets:
speakeasy-recommended,speakeasy-generation,speakeasy-openapi,openapi-standard, andowasp - Full configuration of rules and rulesets via
lint.yaml - Custom rules written in TypeScript or JavaScript using
@speakeasy-api/openapi-linter-types
Rules are organized into categories by naming prefix:
| Prefix | Category | Purpose |
|---|---|---|
generator-* | SDK Generation | Collision detection, type validation, SDK-specific checks |
semantic-* | Spec Correctness | Path params, ambiguous paths, operation IDs, security refs |
style-* | Best Practices | Descriptions, tags, kebab-case paths, trailing slashes |
owasp-* | Security | OWASP API security best practices |
oas-/oas3- | Schema Validation | OAS-version-specific schema and example checks |
speakeasy-* | Speakeasy | Speakeasy-specific checks (e.g., document validation) |
Usage
Two options are available for running linting:
- Run manually via the Speakeasy CLI:
speakeasy lint openapi -s openapi.yaml- Integrate into a Speakeasy workflow:
workflowVersion: "1.0.0"
speakeasyVersion: latest
sources:
api-source:
inputs:
- location: ./openapi.yamlRunning speakeasy run lints the document as part of the workflow and generates an HTML report accessible from a link in the command output.
By default, these options use the speakeasy-recommended ruleset to ensure OpenAPI documents meet the Speakeasy quality bar.
Configuration
OpenAPI spec linting is fully configurable. Create custom rulesets by selecting from predefined sets or writing new rules. These custom linting rules work throughout the workflow.
Immediately before SDK generation, the speakeasy-generation ruleset is always applied to ensure compatibility with the code generator. This cannot be overridden.
Configure linting in a lint.yaml file in the .speakeasy folder. The .speakeasy folder can be located in the same directory as the OpenAPI document or in the working directory for running the speakeasy lint or speakeasy run commands.
Basic structure
lintVersion: 2.0.0
defaultRuleset: myRuleset
rulesets:
myRuleset:
rulesets:
- speakeasy-recommended # Extend a built-in ruleset
rules:
- id: generator-validate-enums
severity: warn # Downgrade severity
- id: generator-missing-examples
disabled: true # Disable a rule
customRules: # Optional: load custom TypeScript rules
paths:
- ./rules/*.ts
timeout: 30s # Per-rule execution timeout (default: 30s)Configuration fields
| Field | Type | Description |
|---|---|---|
lintVersion | string | Configuration format version. Use 2.0.0. |
defaultRuleset | string | Name of the ruleset to use by default. |
rulesets | map | Map of ruleset names to ruleset definitions. |
customRules | object | Optional. Paths and timeout for custom TypeScript/JS rules. |
Ruleset definition
Each ruleset can extend built-in rulesets and override individual rules:
rulesets:
myRuleset:
rulesets: # Built-in or other rulesets to extend
- speakeasy-recommended
- owasp
rules: # Per-rule overrides
- id: owasp-string-limit
severity: warn # Downgrade from error to warning
- id: owasp-no-numeric-ids
disabled: true # Disable the ruleRule override options
| Field | Type | Description |
|---|---|---|
id | string | The rule ID to configure. |
severity | string | Override severity: error, warn, or hint. |
disabled | bool | Set to true to disable the rule. |
match | string | Regex pattern. When set, the override only applies to errors whose message matches the pattern. |
Severity levels
| Level | Behavior |
|---|---|
error | Fails validation. Blocks SDK generation. |
warn | Warning only. Does not block. |
hint | Informational. Does not block. |
Match filters
Match filters allow severity overrides to apply only to errors that match a regex pattern. This is useful for selectively downgrading specific error messages without affecting all errors from a rule.
rules:
- id: validation-required-field
match: ".*info\\.title is required.*"
severity: warn # Only downgrade this specific message to warningUsing rulesets
Use rulesets in these ways:
- Set the
defaultRulesetinlint.yamlto use by default. This ruleset applies when no ruleset is specified using thelintcommand orworkflow.yamlfile. - Pass a ruleset name to the
lintcommand with the-rargument, for example,speakeasy lint openapi -r myRuleset -s openapi.yaml. - Define the ruleset for a particular source in the
workflow.yamlfile:
workflowVersion: "1.0.0"
speakeasyVersion: latest
sources:
api-source:
inputs:
- location: ./openapi.yaml
ruleset: myRulesetReal-world example
lintVersion: 2.0.0
defaultRuleset: reviewRuleset
rulesets:
reviewRuleset:
rulesets:
- speakeasy-recommended
rules:
- id: generator-missing-error-response
disabled: true
- id: generator-missing-examples
disabled: trueCustom rules
Custom linting rules can be written in TypeScript or JavaScript and loaded alongside built-in rules. Custom rules use the @speakeasy-api/openapi-linter-types package.
Getting started
Step 1. Install the types package in your rules directory:
npm install @speakeasy-api/openapi-linter-typesStep 2. Create a rule file (e.g., rules/require-description.ts):
import {
Rule,
registerRule,
createValidationError,
} from '@speakeasy-api/openapi-linter-types';
import type {
Context,
DocumentInfo,
RuleConfig,
ValidationError,
} from '@speakeasy-api/openapi-linter-types';
class RequireOperationDescription extends Rule {
id(): string {
return 'custom-require-operation-description';
}
category(): string {
return 'style';
}
description(): string {
return 'All operations must have a description field for documentation.';
}
summary(): string {
return 'Operations must have a description';
}
run(_ctx: Context, docInfo: DocumentInfo, config: RuleConfig): ValidationError[] {
const errors: ValidationError[] = [];
if (!docInfo.index || !docInfo.index.operations) {
return errors;
}
for (const opNode of docInfo.index.operations) {
const op = opNode.node;
const description = op.getDescription ? op.getDescription() : '';
if (!description || description === '') {
const opId = op.getOperationID ? op.getOperationID() : 'unnamed';
errors.push(createValidationError(
config.getSeverity(this.defaultSeverity()),
this.id(),
`Operation "${opId}" is missing a description`,
op.getRootNode ? op.getRootNode() : null
));
}
}
return errors;
}
}
registerRule(new RequireOperationDescription());Step 3. Configure the linter (.speakeasy/lint.yaml):
lintVersion: 2.0.0
defaultRuleset: myRuleset
rulesets:
myRuleset:
rulesets:
- speakeasy-recommended
rules:
- id: custom-require-operation-description
severity: error # Optionally override severity
customRules:
paths:
- ./rules/*.tsRule implementation API
Extend the Rule base class and implement the required methods:
| Method | Required | Description |
|---|---|---|
id() | Yes | Unique identifier. Prefix with custom- to avoid collisions. |
category() | Yes | Category for grouping: style, security, semantic, etc. |
description() | Yes | Full description of what the rule checks. |
summary() | Yes | Short summary for display in reports. |
run(ctx, docInfo, config) | Yes | Main logic. Returns an array of ValidationError. |
link() | No | URL to documentation for this rule. |
defaultSeverity() | No | Default: 'warn'. Options: 'error', 'warn', 'hint'. |
versions() | No | OpenAPI versions this rule applies to (e.g., ['3.0', '3.1']). |
Accessing document data
The DocumentInfo object provides access to the parsed OpenAPI document and pre-computed indices for efficient iteration:
run(ctx: Context, docInfo: DocumentInfo, config: RuleConfig): ValidationError[] {
// Access the document root
const doc = docInfo.document;
const info = doc.getInfo();
// Access file location
const location = docInfo.location;
// Use the pre-computed index for efficient iteration
const index = docInfo.index;
// All operations in the document
for (const opNode of index.operations) {
const operation = opNode.node;
const path = opNode.locations.path;
const method = opNode.locations.method;
}
// All component schemas
for (const schemaNode of index.componentSchemas) {
const schema = schemaNode.node;
const name = schemaNode.locations.name;
}
// Also available: index.inlineSchemas, index.parameters,
// index.requestBodies, index.responses, index.headers,
// index.securitySchemes, and more
}Creating validation errors
Use createValidationError() to create properly formatted errors:
errors.push(createValidationError(
config.getSeverity(this.defaultSeverity()), // Respects user severity overrides
this.id(), // Rule ID
'Description of the issue', // Error message
node.getRootNode() // YAML/JSON node for line/column info
));Configuring custom rules
Custom rules support all standard configuration options in lint.yaml:
rules:
# Override severity
- id: custom-require-operation-description
severity: error
# Disable a custom rule
- id: custom-require-operation-description
disabled: true
# Filter by message pattern
- id: custom-require-operation-description
match: ".*unnamed.*"
severity: hintAvailable rules
The rules available to the Speakeasy Linter are listed below and can be used in custom rulesets or to match and modify default rules in the lint.yaml file.
Validation errors
In addition to linting rules, the following validation errors may be reported when parsing the OpenAPI document:
Available rulesets
The built-in rulesets available to the Speakeasy Linter are listed below and can be composed in custom rulesets.
speakeasy-recommended
The default ruleset. Includes all SDK generation rules, OpenAPI compliance rules, and select style and quality rules. Recommended for ensuring OpenAPI documents meet the Speakeasy quality bar.
speakeasy-generation
The minimum set of rules required for successful SDK generation. This ruleset is always applied before generation and cannot be overridden or reconfigured when using the generator.
Use this as a base when you want a lean ruleset that only enforces what’s strictly necessary for generation.
speakeasy-openapi
A broader set of rules for general OpenAPI compliance. Builds on speakeasy-generation with additional quality rules like unused component detection, example validation, and link operation validation. Note that Speakeasy-specific rules like generator-validate-enums and generator-validate-extensions are not included in this ruleset.
openapi-standard
The most comprehensive ruleset. Includes most generator rules plus every built-in rule from the linter library: full semantic, style, and OAS-version-specific rules. Speakeasy-specific rules like generator-validate-enums and generator-validate-extensions are not included. Use this when you want maximum validation coverage.
owasp
OWASP API Security rules. Covers authentication, rate limiting, input validation, and data exposure. Can be combined with other rulesets:
rulesets:
secureRuleset:
rulesets:
- speakeasy-recommended
- owaspopenapi CLI
The openapi CLI is a standalone, open-source tool for working with OpenAPI specifications. It shares the same core linting engine as the Speakeasy Linter but does not include the Speakeasy-specific generation rules (generator-*). It is a good option for teams that want OpenAPI linting without the full Speakeasy generation pipeline.
| Speakeasy CLI | openapi CLI | |
|---|---|---|
| Generation gating | speakeasy-generation ruleset enforced before SDK generation | No generation gating |
| Built-in rulesets | speakeasy-recommended, speakeasy-generation, speakeasy-openapi, openapi-standard, owasp | all, recommended, security |
| Config format | .speakeasy/lint.yaml with lintVersion/rulesets/customRules | lint.yaml with extends/custom_rules |
Installation
Homebrew (macOS/Linux):
brew install openapiGo Install:
go install github.com/speakeasy-api/openapi/cmd/openapi@latestScript Installation (Linux/macOS):
curl -fsSL https://go.speakeasy.com/openapi.sh | bashScript Installation (Windows/PowerShell):
iwr -useb https://go.speakeasy.com/openapi.ps1 | iexLinting usage
# Lint an OpenAPI specification
openapi spec lint api.yaml
# Lint with a specific config file
openapi spec lint --config lint.yaml api.yaml
# Output as JSON
openapi spec lint --format json api.yaml
# Disable specific rules
openapi spec lint --disable semantic-path-params api.yamlBuilt-in rulesets
The openapi CLI has its own built-in rulesets:
| Ruleset | Description |
|---|---|
all | All available rules (default) |
recommended | Balanced set: semantic rules + essential style + basic security |
security | Comprehensive OWASP security rules |
Configuration uses its own format (extends and custom_rules rather than the Speakeasy lintVersion/rulesets/customRules format):
extends: recommended
rules:
- id: semantic-path-params
severity: error
custom_rules:
paths:
- ./rules/*.tsBy default, the CLI loads config from ~/.openapi/lint.yaml unless --config is provided.
Beyond linting
The openapi CLI also provides additional tools:
openapi spec validate- Validate against the OpenAPI specificationopenapi spec bundle- Bundle external references into componentsopenapi spec inline- Inline all referencesopenapi spec upgrade- Upgrade to the latest OpenAPI versionopenapi spec explore- Interactively explore a spec in the terminalopenapi spec clean- Remove unused componentsopenapi spec optimize- Deduplicate inline schemasopenapi swagger upgrade- Upgrade Swagger 2.0 to OpenAPI 3.0
Last updated on