# 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`](https://github.com/speakeasy-api/openapi) linter framework, which provides a fast, index-based validation engine. By default, the linter runs using the [`speakeasy-recommended`](#speakeasy-recommended) ruleset, which can be extended with any of the [available rulesets](#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`, and `owasp`
- Full configuration of rules and rulesets via `lint.yaml`
- Custom rules written in TypeScript or JavaScript using [`@speakeasy-api/openapi-linter-types`](https://www.npmjs.com/package/@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:

1. Run manually via the Speakeasy CLI:

```bash
speakeasy lint openapi -s openapi.yaml
```

2. Integrate into a Speakeasy workflow:

```yaml
workflowVersion: "1.0.0"
speakeasyVersion: latest
sources:
  api-source:
    inputs:
      - location: ./openapi.yaml
```

Running `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

```yaml
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:

```yaml
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 rule
```

### 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

| 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.

```yaml
rules:
  - id: validation-required-field
    match: ".*info\\.title is required.*"
    severity: warn  # Only downgrade this specific message to warning
```

### Using rulesets

Use rulesets in these ways:

1. Set the `defaultRuleset` in `lint.yaml` to use by default. This ruleset applies when no ruleset is specified using the `lint` command or `workflow.yaml` file.
2. Pass a ruleset name to the `lint` command with the `-r` argument, for example, `speakeasy lint openapi -r myRuleset -s openapi.yaml`.
3. Define the ruleset for a particular source in the `workflow.yaml` file:

```yaml
workflowVersion: "1.0.0"
speakeasyVersion: latest
sources:
  api-source:
    inputs:
      - location: ./openapi.yaml
    ruleset: myRuleset
```

### Real-world example

```yaml
lintVersion: 2.0.0
defaultRuleset: reviewRuleset
rulesets:
  reviewRuleset:
    rulesets:
      - speakeasy-recommended
    rules:
      - id: generator-missing-error-response
        disabled: true
      - id: generator-missing-examples
        disabled: true
```

## 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`](https://www.npmjs.com/package/@speakeasy-api/openapi-linter-types) package.

### Getting started

**Step 1.** Install the types package in your rules directory:

```bash
npm install @speakeasy-api/openapi-linter-types
```

**Step 2.** Create a rule file (e.g., `rules/require-description.ts`):

```typescript

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`):

```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/*.ts
```

### 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

The `DocumentInfo` object provides access to the parsed OpenAPI document and pre-computed indices for efficient iteration:

```typescript
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:

```typescript
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`:

```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: hint
```

## 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.

<Table
  data={[
    {
      ruleId: "generator-duplicate-errors",
      defaultSeverity: "hint",
      description:
        "Detect duplicate error schemas that would produce multiple SDK error types with colliding names.",
    },
    {
      ruleId: "generator-duplicate-inline-schemas",
      defaultSeverity: "hint",
      description:
        "Detect duplicate inline schemas that would produce multiple generated SDK types with colliding names.",
    },
    {
      ruleId: "generator-duplicate-model-namespace",
      defaultSeverity: "warn",
      description:
        "Model namespace values (x-speakeasy-model-namespace) must be unique when converted to folder or package names. Different namespace values that only differ by casing can collide when sanitized for a target language.",
    },
    {
      ruleId: "generator-duplicate-operation-name",
      defaultSeverity: "error",
      description:
        "Duplicate operation names can cause SDK method name collisions after naming rules are applied (group + method name).",
    },
    {
      ruleId: "generator-duplicate-path-params",
      defaultSeverity: "warn",
      description:
        "Path parameters must be unique within the path template.",
    },
    {
      ruleId: "generator-duplicate-properties",
      defaultSeverity: "error",
      description:
        "Property names must be unique and not empty within a schema when converted to field names, accounting for language-specific sanitization rules.",
    },
    {
      ruleId: "generator-duplicate-schema-name",
      defaultSeverity: "warn",
      description:
        "Schema names must be unique when converted to class names.",
    },
    {
      ruleId: "generator-duplicate-tag",
      defaultSeverity: "error",
      description:
        "Tag names must be unique when converted to class, field, or file names.",
    },
    {
      ruleId: "generator-missing-error-response",
      defaultSeverity: "hint",
      description:
        "Error responses should be defined for all operations to document failure cases.",
    },
    {
      ruleId: "generator-missing-examples",
      defaultSeverity: "hint",
      description:
        "Examples should be provided where possible to improve API documentation and SDK usability.",
    },
    {
      ruleId: "generator-pagination",
      defaultSeverity: "hint",
      description:
        "Detect operations that appear to support pagination based on request and response patterns.",
    },
    {
      ruleId: "generator-path-params",
      defaultSeverity: "error",
      description:
        "Path parameters must be defined on the operation and used in the path template.",
    },
    {
      ruleId: "generator-retries",
      defaultSeverity: "hint",
      description:
        "Retries should be configured for operations that are safe to retry.",
    },
    {
      ruleId: "generator-validate-composite-schemas",
      defaultSeverity: "error",
      description:
        "Ensure anyOf/allOf/oneOf don't contain duplicate references or identical inline schemas.",
    },
    {
      ruleId: "generator-validate-consts-defaults",
      defaultSeverity: "warn",
      description:
        "Validate const and default values match their declared schema type.",
    },
    {
      ruleId: "generator-validate-content-type",
      defaultSeverity: "error",
      description:
        "Ensure multipart/form-data request schemas are objects as required by OpenAPI.",
    },
    {
      ruleId: "generator-validate-deprecation",
      defaultSeverity: "error",
      description:
        "Ensure correct usage of x-speakeasy-deprecation-replacement and x-speakeasy-deprecation-message extensions.",
    },
    {
      ruleId: "generator-validate-enums",
      defaultSeverity: "error",
      description:
        "Validate enums for type generation by checking value types, uniqueness, and x-speakeasy-enums definitions.",
    },
    {
      ruleId: "generator-validate-extensions",
      defaultSeverity: "error",
      description:
        "Validate x-speakeasy-globals extension usage by enforcing unique parameter names and avoiding collisions with server variables.",
    },
    {
      ruleId: "generator-validate-parameters",
      defaultSeverity: "error",
      description:
        "Validate parameters are unique once converted to field names and have a non-empty name.",
    },
    {
      ruleId: "generator-validate-paths",
      defaultSeverity: "error",
      description:
        "Validate paths conform to RFC 3986 and use valid URI template syntax.",
    },
    {
      ruleId: "generator-validate-requests",
      defaultSeverity: "error",
      description:
        "Validate request body content types are valid MIME types.",
    },
    {
      ruleId: "generator-validate-responses",
      defaultSeverity: "error",
      description:
        "Validate response status codes and content types are syntactically valid.",
    },
    {
      ruleId: "generator-validate-security",
      defaultSeverity: "error",
      description:
        "Validate security schemes have supported types, flows, and required fields.",
    },
    {
      ruleId: "generator-validate-servers",
      defaultSeverity: "hint",
      description:
        "Validate servers, their variables, and the x-speakeasy-server-id extension.",
    },
    {
      ruleId: "generator-validate-types",
      defaultSeverity: "error",
      description:
        "Ensure schema data types and formats are supported for generation.",
    },
    {
      ruleId: "oas-schema-check",
      defaultSeverity: "error",
      description:
        "Schemas must use type-appropriate constraints and have valid constraint values.",
    },
    {
      ruleId: "oas3-example-missing",
      defaultSeverity: "hint",
      description:
        "Schemas, parameters, headers, and media types should include example values.",
    },
    {
      ruleId: "oas3-no-nullable",
      defaultSeverity: "warn",
      description:
        "The nullable keyword is not supported in OpenAPI 3.1+ and should be replaced with a type array that includes null.",
    },
    {
      ruleId: "owasp-additional-properties-constrained",
      defaultSeverity: "hint",
      description:
        "Schemas with additionalProperties should define maxProperties to limit object size.",
    },
    {
      ruleId: "owasp-array-limit",
      defaultSeverity: "error",
      description:
        "Array schemas must specify maxItems to prevent resource exhaustion attacks.",
    },
    {
      ruleId: "owasp-auth-insecure-schemes",
      defaultSeverity: "error",
      description:
        "Authentication schemes using outdated or insecure methods must be avoided.",
    },
    {
      ruleId: "owasp-define-error-responses-401",
      defaultSeverity: "warn",
      description:
        "Operations should define a 401 Unauthorized response to handle authentication failures.",
    },
    {
      ruleId: "owasp-define-error-responses-429",
      defaultSeverity: "warn",
      description:
        "Operations should define a 429 Too Many Requests response to indicate rate limiting.",
    },
    {
      ruleId: "owasp-define-error-responses-500",
      defaultSeverity: "warn",
      description:
        "Operations should define a 500 Internal Server Error response to handle unexpected failures.",
    },
    {
      ruleId: "owasp-define-error-validation",
      defaultSeverity: "warn",
      description:
        "Operations should define validation error responses (400, 422, or 4XX).",
    },
    {
      ruleId: "owasp-integer-format",
      defaultSeverity: "error",
      description:
        "Integer schemas must specify a format of int32 or int64.",
    },
    {
      ruleId: "owasp-integer-limit",
      defaultSeverity: "error",
      description:
        "Integer schemas must specify minimum and maximum values to prevent unbounded inputs.",
    },
    {
      ruleId: "owasp-jwt-best-practices",
      defaultSeverity: "error",
      description:
        "Security schemes using OAuth2 or JWT must declare support for RFC8725.",
    },
    {
      ruleId: "owasp-no-additional-properties",
      defaultSeverity: "error",
      description:
        "Object schemas must not allow arbitrary additional properties.",
    },
    {
      ruleId: "owasp-no-api-keys-in-url",
      defaultSeverity: "error",
      description:
        "API keys must not be passed via URL parameters as they are logged and cached.",
    },
    {
      ruleId: "owasp-no-credentials-in-url",
      defaultSeverity: "error",
      description:
        "URL parameters must not contain credentials like API keys, passwords, or secrets.",
    },
    {
      ruleId: "owasp-no-http-basic",
      defaultSeverity: "error",
      description:
        "Security schemes must not use HTTP Basic authentication without additional security layers.",
    },
    {
      ruleId: "owasp-no-numeric-ids",
      defaultSeverity: "error",
      description:
        "Resource identifiers must use random values like UUIDs instead of sequential numeric IDs.",
    },
    {
      ruleId: "owasp-protection-global-safe",
      defaultSeverity: "hint",
      description:
        "Safe operations (GET, HEAD) should be protected by security schemes or explicitly marked as public.",
    },
    {
      ruleId: "owasp-protection-global-unsafe",
      defaultSeverity: "error",
      description:
        "Unsafe operations (POST, PUT, PATCH, DELETE) must be protected by security schemes.",
    },
    {
      ruleId: "owasp-protection-global-unsafe-strict",
      defaultSeverity: "hint",
      description:
        "Unsafe operations must be protected by non-empty security schemes without explicit opt-outs.",
    },
    {
      ruleId: "owasp-rate-limit",
      defaultSeverity: "error",
      description:
        "2XX and 4XX responses must define rate limiting headers.",
    },
    {
      ruleId: "owasp-rate-limit-retry-after",
      defaultSeverity: "error",
      description:
        "429 Too Many Requests responses must include a Retry-After header.",
    },
    {
      ruleId: "owasp-security-hosts-https-oas3",
      defaultSeverity: "error",
      description:
        "Server URLs must begin with https://.",
    },
    {
      ruleId: "owasp-string-limit",
      defaultSeverity: "error",
      description:
        "String schemas must specify maxLength, const, or enum to prevent unbounded data.",
    },
    {
      ruleId: "owasp-string-restricted",
      defaultSeverity: "error",
      description:
        "String schemas must specify format, const, enum, or pattern to restrict content.",
    },
    {
      ruleId: "semantic-duplicated-enum",
      defaultSeverity: "warn",
      description:
        "Enum arrays should not contain duplicate values.",
    },
    {
      ruleId: "semantic-link-operation",
      defaultSeverity: "error",
      description:
        "Link operationId must reference an existing operation in the API specification.",
    },
    {
      ruleId: "semantic-no-ambiguous-paths",
      defaultSeverity: "error",
      description:
        "Path definitions must be unambiguous and distinguishable from each other.",
    },
    {
      ruleId: "semantic-no-eval-in-markdown",
      defaultSeverity: "error",
      description:
        "Markdown descriptions must not contain eval() statements.",
    },
    {
      ruleId: "semantic-no-script-tags-in-markdown",
      defaultSeverity: "error",
      description:
        "Markdown descriptions must not contain <script> tags.",
    },
    {
      ruleId: "semantic-operation-id-valid-in-url",
      defaultSeverity: "error",
      description:
        "Operation IDs must use URL-friendly characters.",
    },
    {
      ruleId: "semantic-operation-operation-id",
      defaultSeverity: "warn",
      description:
        "Operations should define an operationId for consistent referencing.",
    },
    {
      ruleId: "semantic-path-declarations",
      defaultSeverity: "error",
      description:
        "Path parameter declarations must not be empty.",
    },
    {
      ruleId: "semantic-path-params",
      defaultSeverity: "error",
      description:
        "Path template variables must have corresponding parameter definitions.",
    },
    {
      ruleId: "semantic-path-query",
      defaultSeverity: "error",
      description:
        "Paths must not include query strings. Query parameters should be defined in the parameters array.",
    },
    {
      ruleId: "semantic-typed-enum",
      defaultSeverity: "warn",
      description:
        "Enum values must match the specified type.",
    },
    {
      ruleId: "semantic-unused-component",
      defaultSeverity: "warn",
      description:
        "Components that are declared but never referenced should be removed.",
    },
    {
      ruleId: "speakeasy-validate-document",
      defaultSeverity: "error",
      description:
        "Document must have a paths or webhooks object with at least one entry.",
    },
    {
      ruleId: "style-component-description",
      defaultSeverity: "hint",
      description:
        "Reusable components should include descriptions.",
    },
    {
      ruleId: "style-contact-properties",
      defaultSeverity: "warn",
      description:
        "The contact object should include name, url, and email properties.",
    },
    {
      ruleId: "style-description-duplication",
      defaultSeverity: "warn",
      description:
        "Description and summary fields should not contain identical text.",
    },
    {
      ruleId: "style-info-contact",
      defaultSeverity: "warn",
      description:
        "The info section should include a contact object.",
    },
    {
      ruleId: "style-info-description",
      defaultSeverity: "warn",
      description:
        "The info section should include a description field.",
    },
    {
      ruleId: "style-info-license",
      defaultSeverity: "hint",
      description:
        "The info section should include a license object.",
    },
    {
      ruleId: "style-license-url",
      defaultSeverity: "hint",
      description:
        "The license object should include a URL pointing to the full license text.",
    },
    {
      ruleId: "style-no-ref-siblings",
      defaultSeverity: "warn",
      description:
        "In OpenAPI 3.0.x, a $ref field should not have sibling properties alongside it.",
    },
    {
      ruleId: "style-no-verbs-in-path",
      defaultSeverity: "warn",
      description:
        "Path segments should not contain HTTP verbs.",
    },
    {
      ruleId: "style-oas3-api-servers",
      defaultSeverity: "warn",
      description:
        "OpenAPI 3.x specifications should define at least one server with a valid URL.",
    },
    {
      ruleId: "style-oas3-host-not-example",
      defaultSeverity: "warn",
      description:
        "Server URLs should not point to example.com domains.",
    },
    {
      ruleId: "style-oas3-host-trailing-slash",
      defaultSeverity: "warn",
      description:
        "Server URLs should not end with a trailing slash.",
    },
    {
      ruleId: "style-oas3-parameter-description",
      defaultSeverity: "warn",
      description:
        "Parameters should include descriptions that explain their purpose.",
    },
    {
      ruleId: "style-openapi-tags",
      defaultSeverity: "warn",
      description:
        "The specification should define a non-empty tags array at the root level.",
    },
    {
      ruleId: "style-operation-description",
      defaultSeverity: "warn",
      description:
        "Operations should include either a description or summary field.",
    },
    {
      ruleId: "style-operation-error-response",
      defaultSeverity: "warn",
      description:
        "Operations should define at least one 4xx error response.",
    },
    {
      ruleId: "style-operation-singular-tag",
      defaultSeverity: "warn",
      description:
        "Operations should be associated with only a single tag.",
    },
    {
      ruleId: "style-operation-success-response",
      defaultSeverity: "warn",
      description:
        "Operations should define at least one 2xx or 3xx response code.",
    },
    {
      ruleId: "style-operation-tag-defined",
      defaultSeverity: "warn",
      description:
        "Operation tags should be declared in the global tags array.",
    },
    {
      ruleId: "style-operation-tags",
      defaultSeverity: "warn",
      description:
        "Operations should have at least one tag.",
    },
    {
      ruleId: "style-path-trailing-slash",
      defaultSeverity: "warn",
      description:
        "Path definitions should not end with a trailing slash.",
    },
    {
      ruleId: "style-paths-kebab-case",
      defaultSeverity: "warn",
      description:
        "Path segments should use kebab-case for consistency.",
    },
    {
      ruleId: "style-tag-description",
      defaultSeverity: "hint",
      description:
        "Tags should include descriptions.",
    },
    {
      ruleId: "style-tags-alphabetical",
      defaultSeverity: "warn",
      description:
        "Tags should be listed in alphabetical order.",
    },
  ]}
  columns={[
    { key: "ruleId", header: "Rule ID" },
    { key: "defaultSeverity", header: "Default Severity" },
    { key: "description", header: "Description" },
  ]}
/>

### 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:

```yaml
rulesets:
  secureRuleset:
    rulesets:
      - speakeasy-recommended
      - owasp
```

## `openapi` CLI

The [`openapi` CLI](https://github.com/speakeasy-api/openapi) 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):**

```bash
brew install openapi
```

**Go Install:**

```bash
go install github.com/speakeasy-api/openapi/cmd/openapi@latest
```

**Script Installation (Linux/macOS):**

```bash
curl -fsSL https://go.speakeasy.com/openapi.sh | bash
```

**Script Installation (Windows/PowerShell):**

```powershell
iwr -useb https://go.speakeasy.com/openapi.ps1 | iex
```

### Linting usage

```bash
# 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.yaml
```

### 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):

```yaml
extends: recommended

rules:
  - id: semantic-path-params
    severity: error

custom_rules:
  paths:
    - ./rules/*.ts
```

By 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 specification
- `openapi spec bundle` - Bundle external references into components
- `openapi spec inline` - Inline all references
- `openapi spec upgrade` - Upgrade to the latest OpenAPI version
- `openapi spec explore` - Interactively explore a spec in the terminal
- `openapi spec clean` - Remove unused components
- `openapi spec optimize` - Deduplicate inline schemas
- `openapi swagger upgrade` - Upgrade Swagger 2.0 to OpenAPI 3.0
