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) |
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: latestsources: 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
Section titled “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
Section titled “Basic structure”lintVersion: 2.0.0defaultRuleset: myRulesetrulesets: 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 rulecustomRules: # Optional: load custom TypeScript rules paths: - ./rules/*.ts timeout: 30s # Per-rule execution timeout (default: 30s)Configuration fields
Section titled “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
Section titled “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
Section titled “Rule 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
Section titled “Severity levels”| Level | Behavior |
|---|---|
error | Fails validation. Blocks SDK generation. |
warn | Warning only. Does not block. |
hint | Informational. Does not block. |
Match filters
Section titled “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
Section titled “Using 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: latestsources: api-source: inputs: - location: ./openapi.yaml ruleset: myRulesetReal-world example
Section titled “Real-world example”lintVersion: 2.0.0defaultRuleset: reviewRulesetrulesets: reviewRuleset: rulesets: - speakeasy-recommended rules: - id: generator-missing-error-response disabled: true - id: generator-missing-examples disabled: trueCustom rules
Section titled “Custom 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
Section titled “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.0defaultRuleset: myRulesetrulesets: myRuleset: rulesets: - speakeasy-recommended rules: - id: custom-require-operation-description severity: error # Optionally override severitycustomRules: paths: - ./rules/*.tsRule implementation API
Section titled “Rule 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
Section titled “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
Section titled “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
Section titled “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
Section titled “Available 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
Section titled “Validation errors”In addition to linting rules, the following validation errors may be reported when parsing the OpenAPI document:
Available rulesets
Section titled “Available rulesets”The built-in rulesets available to the Speakeasy Linter are listed below and can be composed in custom rulesets.
speakeasy-recommended
Section titled “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
Section titled “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
Section titled “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
Section titled “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 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
Section titled “openapi 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
Section titled “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
Section titled “Linting usage”# Lint an OpenAPI specificationopenapi spec lint api.yaml
# Lint with a specific config fileopenapi spec lint --config lint.yaml api.yaml
# Output as JSONopenapi spec lint --format json api.yaml
# Disable specific rulesopenapi spec lint --disable semantic-path-params api.yamlBuilt-in rulesets
Section titled “Built-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
Section titled “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