Lint Rules Reference
- Applies to
- Gatewayruntime for API, AI, and MCP gateways
- Hosting
- Managed EdgeManaged DedicatedSelf-Hosted
Each rule has an ID, a default severity, and the presets that include it. Use
the ID to change the rule's severity, limit it to some routes, or turn it off in
zuplo.lint.jsonc. To add rules of your
own, see Custom lint rules.
Rules by preset
| Rule | Severity | recommended | strict | security | Auto-fix |
|---|---|---|---|---|---|
operation-operationId | error | ✓ | ✓ | ✓ | ✓ |
operation-description | warn | ✓ | ✓ | ✓ | |
operation-tags | warn | ✓ | ✓ | ✓ | |
path-params | error | ✓ | ✓ | ✓ | |
no-ambiguous-paths | error | ✓ | ✓ | ✓ | |
zuplo/no-undefined-policies | error | ✓ | ✓ | ✓ | |
zuplo/no-empty-inbound-policies | warn | ✓ | ✓ | ✓ | ✓ |
zuplo/require-route-label | warn | ✓ | ✓ | ✓ | |
zuplo/schema-policies | error | ✓ | ✓ | ✓ | |
zuplo/schema-policy-options | warn | ✓ | ✓ | ✓ | |
zuplo/schema-x-zuplo-route | error | ✓ | ✓ | ✓ | |
zuplo/require-policy | per policy | ✓ | ✓ | ||
zuplo/require-cors-policy | error | ✓ | |||
info-description | warn | ✓ | |||
info-contact | warn | ✓ | |||
no-http-verbs-in-path | error | ✓ |
Two more IDs can appear in results without being rules you configure:
breaking-change— reported by breaking change detection.lint-execution-error— a rule failed to run against a file. This usually means the file isn't valid JSON or isn't valid OpenAPI, so it has no line number (0:0). Fix the file and run the linter again.
General OpenAPI rules
The Zuplo presets also include these general OpenAPI rules. Each one catches a real defect and stays quiet on a typical Zuplo project.
| Rule | Severity | Presets | Reports |
|---|---|---|---|
duplicate-paths | error | all | The same path defined twice; only the last definition is kept. |
operation-operationId-unique | error | all | Two operations with the same operationId. |
operation-parameters | error | all | The same parameter declared twice on an operation. |
path-declarations-must-exist | error | all | An empty path parameter, such as /users/{}. |
path-not-include-query | error | all | A query string in a path key. |
oas3-operation-security-defined | error | all | A security entry with no matching securitySchemes definition. |
oas-schema-check | error | all | A schema with an invalid type, such as "string ". |
no-eval-in-markdown | error | all | eval() in a Markdown description. |
no-script-tags-in-markdown | error | all | <script> in a Markdown description. |
operation-operationId-valid-in-url | warn | all | An operationId with characters that aren't URL-friendly. |
operation-success-response | warn | all | An operation without a 2xx or 3xx response. |
oas3-valid-schema-example | warn | all | An example that doesn't match its schema. |
typed-enum | warn | all | An enum value that doesn't match the enum's type. |
duplicated-entry-in-enum | warn | all | A repeated enum value. |
nullable-enum-contains-null | warn | all | A nullable enum without null in its values. |
path-keys-no-trailing-slash | warn | all | A path ending in /. |
no-request-body | warn | all | A request body on a GET or DELETE operation. |
operation-4xx-response | warn | strict | An operation without a 4xx error response. |
oas3-parameter-description | warn | strict | A parameter without a description. |
component-description | warn | strict | A component without a description. |
operation-tag-defined | warn | strict | An operation tag missing from the top-level tags list. |
oas3-unused-component | warn | strict | A component nothing references. |
required-fields-defined | warn | strict | A required property that isn't declared in properties. |
no-unnecessary-combinator | warn | strict | An allOf, anyOf, or oneOf with a single item. |
These rules come from vacuum, the engine behind zuplo lint. To use vacuum's
other general OpenAPI rules, or its OWASP security rules, extend vacuum:oas or
spectral:owasp; see extends. Run
npx zuplo lint --list-rules to see every rule and which rulesets include it.
OpenAPI rules
These rules check the OpenAPI document itself. They apply to every operation,
whether or not it has an x-zuplo-route block.
operation-operationId
Every operation must have an operationId. Zuplo uses it to identify the route,
and code generators and the Developer Portal use it to name operations.
Code
Fix: Add a unique operationId to the operation, or run zuplo lint --fix
to generate one from the method and path:
Code
operation-description
Operations should have a description. The Developer Portal shows it as the
operation's documentation.
Code
Fix: Add a description that explains what the operation does.
operation-tags
Operations should have at least one tag. The Developer Portal groups operations by tag.
Code
Fix: Add a tags array, for example "tags": ["Todos"].
path-params
Every parameter in a path template must be declared, either on the path or on the operation. The rule also reports two paths that differ only in the names of their parameters.
Code
Fix: Declare the parameter with "in": "path" and "required": true:
Code
no-ambiguous-paths
Two paths can't match the same request. /users/{id} and /users/{name} are
ambiguous: a request to /users/42 matches both.
Code
This pair is also reported by path-params, so turning off one
of the two rules doesn't silence it.
Fix: Merge the two paths into one, or change one so it no longer overlaps,
for example /users/by-name/{name}.
info-description
zuplo:strict only. The document's info object must have a description.
Code
Fix: Add info.description.
info-contact
zuplo:strict only. The document's info object should have a contact.
Code
Fix: Add info.contact, for example
"contact": { "name": "API Support", "email": "api@example.com" }.
no-http-verbs-in-path
zuplo:strict only. Path segments shouldn't be HTTP methods. The HTTP method
already says what the operation does.
Code
The rule matches whole segments, so /users/get and /post are reported, while
/getUsers and /delete-user aren't.
Fix: Move the action into the method: GET /users instead of
GET /users/get.
Zuplo route rules
These rules check the x-zuplo-route block of each operation. Operations
without an x-zuplo-route block are skipped.
zuplo/no-undefined-policies
Every policy name in a route's policies.inbound or policies.outbound list
must be defined in config/policies.json. A missing policy fails the build.
Code
Fix: Correct the spelling of the policy name in the route, or add the policy
to config/policies.json. The rule only runs when config/policies.json
defines at least one policy.
zuplo/no-empty-inbound-policies
A route shouldn't have an empty policies.inbound array. An empty array has the
same effect as no array, and it's usually left behind after removing a policy.
Code
Fix: Remove the empty array, or run zuplo lint --fix.
zuplo/require-route-label
Routes should have a label in x-zuplo-route. The label names the route in
the Zuplo Portal's route designer, logs, and analytics.
Code
Fix: Add "label": "List todos" to the x-zuplo-route block.
zuplo/require-policy
zuplo:strict and zuplo:security, or any preset with
requiredPolicies. Every
route must include a policy of each required type.
| Preset | Authentication | Rate limiting |
|---|---|---|
zuplo:strict | error | warn |
zuplo:security | error | error |
Code
Requirements at warning severity report as zuplo/require-policy-warn.
Configure both through zuplo/require-policy: setting it in rules or
overrides applies to every requirement. zuplo/require-policy-warn isn't a
rule ID you can configure.
Fix: Add an authentication policy, such as
API key authentication, or a
rate limiting policy to the route's
policies.inbound list. For routes that are intentionally public, such as a
health check, turn the rule off for them with an
override:
Code
To change which policy types count, set your own
requiredPolicies. To require
a policy on only some routes, use a
custom rule.
zuplo/require-cors-policy
zuplo:strict only. Every route must set corsPolicy in x-zuplo-route, so
the CORS behavior of each route is a deliberate choice.
Code
Fix: Set corsPolicy to the name of a custom CORS policy, to
"anything-goes", or to "none". Any value satisfies the rule, including
"none".
Schema rules
These rules validate your configuration against the same JSON schemas Zuplo uses at build time. They're part of every preset.
zuplo/schema-policies
Validates the structure of config/policies.json: each policy has a name,
policyType, and handler, and each name uses only lowercase letters, numbers,
and dashes.
Code
Fix: Correct the field the message points to. The path after
Schema validation error at is the location in the file, so /policies/0/name
is the name of the first policy.
zuplo/schema-policy-options
Validates handler.options of each built-in policy against that policy's
options. This catches missing required options, wrong types, and values outside
an allowed list. The rule reports warnings by default, so these results don't
fail a run unless you set failOn to "warn" or raise the rule to "error".
Code
Custom code policies and policy types the linter doesn't recognize are skipped, so they never produce results from this rule.
Fix: Compare the options with the policy's page in the policy catalog.
zuplo/schema-x-zuplo-route
Validates each x-zuplo-route block in your OpenAPI files: it must have a
handler, policy names must use valid characters, and it can't contain
properties Zuplo doesn't recognize.
Code
An empty location (at '') means the problem is in the x-zuplo-route object
itself.
Fix: Remove or rename the unrecognized property, or add the missing one. A
misspelled property such as corsPolicies shows up here as an additional
property. See x-zuplo-route for the properties it
accepts.
Next steps
- Lint configuration — change severities, pick a preset, and require policies.
- Lint in CI/CD — run these rules on every pull request.