How-to

How to fix "exclusiveMinimum must be boolean" in OpenAPI

exclusiveMinimum must be boolean (or the equivalent exclusiveMaximum error) means your document is declared as OpenAPI 3.0 but still has a JSON Schema 2020-12-style numeric exclusiveMinimum left over from a 3.1 source spec. The fix is mechanical: replace the number with a boolean and move the actual limit to minimum/maximum. This post reproduces the error with two real validators and shows the exact edit that clears it.

Why does this error happen?

Because OpenAPI 3.0 and OpenAPI 3.1 define exclusiveMinimum as two different, incompatible types, and a straight copy-paste (or an incomplete downgrade tool run) between the two versions leaves the wrong type behind. Per the official spec text: OpenAPI 3.0's Schema Object uses exclusiveMinimum: true as a boolean modifier of minimum (inherited from an older JSON Schema draft), while JSON Schema 2020-12 — which OpenAPI 3.1 adopted directly — states plainly: "The value of exclusiveMinimum MUST be a number, representing an exclusive lower limit for a numeric instance." Same keyword name, incompatible value type, one version apart.

How do you reproduce the error?

We ran the exact scenario end to end on 2026-09-16. Starting from this OpenAPI 3.1 schema:

type: integer
exclusiveMinimum: 0

Downgrading with the community CLI openapi-down-convert v0.14.2 (npx @apiture/openapi-down-convert --verbose --input api-3.1.yaml --output api-3.0.yaml) produces an openapi: 3.0.3 document with the schema unchanged — the numeric exclusiveMinimum: 0 passes straight through with no warning, because the tool only rewrites nullable, const, and webhooks, not this field.

Validating that output two ways gives two real, reproduced errors:

swagger-cli validate v4.0.4 (AJV against the official OpenAPI 3.0 JSON Schema):

Swagger schema validation failed.
  #/components/schemas/Message/properties/retries/exclusiveMinimum must be boolean

redocly lint v2.53.2 (Redocly CLI, default ruleset):

[3] api-3.0.yaml:28:29 at #/components/schemas/Message/properties/retries/exclusiveMinimum
Expected type `boolean` but got `integer`.

Both tools reject the identical file for the identical reason, phrased differently. Neither one tells you the fix directly — that requires knowing the type flipped between spec versions.

How do you fix it?

Replace the number with minimum + a boolean flag:

# OpenAPI 3.1 (source)
type: integer
exclusiveMinimum: 0

# OpenAPI 3.0 (fixed)
type: integer
minimum: 0
exclusiveMinimum: true

The mapping is always: the 3.1 numeric value becomes the 3.0 minimum/maximum, and exclusiveMinimum/exclusiveMaximum becomes true. We re-validated the corrected file with the same swagger-cli validate command used above — api-3.0-fixed.yaml is valid, exit code 0.

Does a JSON-Schema-only check catch this?

No — and that's worth knowing before you trust a green checkmark. Running the same broken file through swagger-cli validate on its own schema layer reports the type mismatch (as shown above) because the official OpenAPI 3.0 JSON Schema pins exclusiveMinimum to boolean. But a generic JSON Schema validator checking your data against that schema — rather than checking the schema document itself — won't flag it, because exclusiveMinimum: 0 is syntactically valid JSON; it's only wrong relative to what the OpenAPI 3.0 meta-schema demands. This is why the error shows up in schema/spec validators (swagger-cli, Redocly CLI, Spectral) and not in a plain JSON well-formedness check.

How to avoid this the next time you downgrade

  1. Never hand-maintain the 3.0 copy. Treat it as a generated artifact from a 3.1 source, the same way our 3.0-vs-3.1 downgrade walkthrough recommends — regenerate it in CI on every spec change.
  2. Validate the downgraded output, not just the source. A conversion tool exiting 0 means it ran without crashing, not that the result is valid 3.0. Run the free in-browser OpenAPI validator or redocly lint on the 3.0 file specifically.
  3. Grep for numeric exclusiveMinimum/exclusiveMaximum before you ship a 3.0 copy. grep -n "exclusiveM\(in\|ax\)imum: [0-9]" openapi-3.0.yaml catches exactly this pattern in seconds, before a customer's CI catches it for you.

If you're maintaining both a 3.1 source and a 3.0 copy for one legacy consumer, Sourced reads your 3.1 spec directly and previews the generated SDK and docs surface without requiring a hand-maintained downgrade at all — start a free report.