Reference

OpenAPI 3.1 vs 3.0: which to use in 2026

OpenAPI 3.1 vs 3.0 comes down to one big shift: OpenAPI 3.1 uses real JSON Schema 2020-12. That changes how you model null, webhooks, examples, and edge-case schemas, while most everyday paths, operations, auth, and response shapes stay the same.

If you're deciding what to use in 2026, the short answer is: use OpenAPI 3.1 for new APIs, and migrate 3.0 specs when your validators, SDK generators, and docs tooling are ready. The upgrade is usually small, but you should still regenerate SDKs and compare the public surface before shipping.

For SDK generation specifically, the rule is practical: pick the version your generator handles best, then prove the generated package surface. A perfect 3.1 spec is not helpful if your chosen generator mishandles type: [string, "null"]; a 3.0 spec is not "old" if it produces stable customer packages.

What's the difference between OpenAPI 3.0 and 3.1?

The difference is that OpenAPI 3.1 replaces 3.0's custom Schema Object with full JSON Schema 2020-12. In practice that means five concrete changes: null becomes a real type instead of nullable: true, webhooks get a top-level webhooks: section, examples follow JSON Schema rules, exclusiveMinimum/exclusiveMaximum become numbers instead of booleans, and validators can use off-the-shelf JSON Schema libraries. Paths, operations, auth, $ref, and response shapes are unchanged. Here are the five differences in detail.

1. JSON Schema 2020-12 alignment

This is the headline change. OpenAPI 3.0 used an OpenAPI-specific Schema Object based on an older JSON Schema draft. OpenAPI 3.1 aligns with JSON Schema 2020-12 through the OAS dialect.

Concrete result: validators, codegen, and docs tools can use off-the-shelf JSON Schema libraries instead of OpenAPI-specific shims. Bugs in edge cases (recursive schemas, $ref resolution, etc.) tend to be fixed faster because the surface area is shared with the JSON Schema community.

2. null is a real type

In 3.0:

properties:
  phone:
    type: string
    nullable: true

In 3.1:

properties:
  phone:
    type: [string, "null"]

The 3.1 form composes correctly with oneOf / anyOf / allOf. The 3.0 nullable: true was a known awkward special case that didn't compose well.

3. Webhooks at the top level

In 3.0, you could model webhooks using callbacks: on a request, but only if there was a request that triggered them. There was no clean way to model "we send your endpoint a webhook every time X happens."

In 3.1:

webhooks:
  messageReceived:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InboundMessage'
      responses:
        "200":
          description: Acknowledged.

Webhooks live at the same level as paths: and components:. Generators that understand webhooks can produce typed webhook handlers; docs sites get a real "Webhooks" section instead of stuffing them under "Advanced."

4. Examples follow JSON Schema rules

OpenAPI 3.0 had its own example / examples system that was almost but not quite consistent with JSON Schema. 3.1 unifies them. The practical consequence: docs sites can render examples more reliably because they're working with one model.

5. exclusiveMinimum / exclusiveMaximum are numbers, not booleans

In 3.0:

minimum: 1
exclusiveMinimum: true

In 3.1 (and modern JSON Schema):

exclusiveMinimum: 1

Less likely to bite you in practice but worth knowing if you have validators that round-trip specs.

Should you use OpenAPI 3.1 or 3.0?

Use OpenAPI 3.1 unless a tool you cannot replace only reads 3.0. New specs should start at 3.1: the JSON Schema alignment produces cleaner validator and SDK output, and every actively maintained tool reads it in 2026. Stay on 3.0 only while a legacy docs or codegen dependency blocks you — and treat that as a tool problem with an upgrade date, not a version preference.

Question Use 3.1 Stay on 3.0 for now
New API spec? Yes Rarely
Need JSON Schema 2020-12? Yes No
Modeling first-class webhooks? Yes Only if your tooling cannot read 3.1
Legacy docs/codegen tool is 3.0-only? After tool upgrade Yes
Shipping public SDKs this week? Yes, after a surface diff Only if you cannot QA generated output yet

What doesn't change between 3.0 and 3.1?

Almost everything you touch daily is identical: path templating (/messages/{message_id}), the shape of components.schemas and $ref, tags, operationId, servers:, security: / securitySchemes:, and the HTTP method / status code response model all carry over unchanged.

If your spec doesn't touch nullable, webhook-shaped endpoints, or quirky example handling, the upgrade may be small. Still validate and regenerate before publishing.

How do you upgrade OpenAPI 3.0 to 3.1?

Change the openapi: version line, rewrite every nullable: true as a "null" type entry, move provider-initiated events to top-level webhooks:, then re-validate and regenerate SDKs. Step by step:

Step 1: change the version line

openapi: 3.0.3   # before
openapi: 3.1.0   # after

Step 2: rewrite nullable: true

For every nullable: true, change type: string to type: [string, "null"]. A regex replacement gets you 90% of the way there; spot-check anything unusual.

Step 3: model provider-initiated webhooks

Only move a callback to top-level webhooks: when it represents a provider-initiated event independent of the triggering operation. Keep operation-scoped callbacks as callbacks. The shape inside (request body schema, response schema) stays familiar, but the event model is different.

Step 4: re-validate

Run your validator (Spectral, Stoplight, openapi-cli, the free in-browser OpenAPI validator, or Sourced's report). The validator will flag anything you missed.

Step 5: regenerate

Generate your SDKs against the new spec. Sourced's compatibility report (what's in one) tells you whether the regenerated SDK has any breaking changes vs the 3.0 version. In most cases the diff is zero — the wire format is identical, only the spec representation changed.

How do you downgrade OpenAPI 3.1 to 3.0?

Reverse the same five differences: rewrite "null" type entries as nullable: true, convert const to a single-value enum, turn numeric exclusiveMinimum/exclusiveMaximum back into minimum/maximum plus a boolean, and move or drop top-level webhooks: (3.0 has no equivalent — that content is lost unless you re-model it as callbacks: or documentation). Teams usually need this when one consumer — an older gateway, a partner's import tool, a 3.0-only codegen — refuses a 3.1 file.

The mechanical mapping:

In your 3.1 spec For the 3.0 copy
type: [string, "null"] type: string + nullable: true
const: sent enum: [sent]
exclusiveMinimum: 0 minimum: 0 + exclusiveMinimum: true
top-level webhooks: no equivalent — drop, or re-model as callbacks:
jsonSchemaDialect, $schema remove
openapi: 3.1.x openapi: 3.0.3

A worked example, tested

We ran 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) on a small 3.1 spec that used a nullable field, a const, a numeric exclusiveMinimum, and a top-level webhook. Input schema:

openapi: 3.1.0
webhooks:
  messageReceived:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Message'
      responses:
        "200":
          description: Acknowledged.
components:
  schemas:
    Message:
      type: object
      properties:
        phone:
          type: [string, "null"]
        retries:
          type: integer
          exclusiveMinimum: 0
        status:
          const: sent

What the tool actually produced:

openapi: 3.0.3
components:
  schemas:
    Message:
      type: object
      properties:
        phone:
          type: string
          nullable: true
        retries:
          type: integer
          exclusiveMinimum: 0
        status:
          enum:
            - sent

Three things to take from that run:

  • The easy conversions are handled. type: [string, "null"] became nullable: true and const became a one-value enum, each with a console warning.
  • Webhooks are deleted, with only a warning. The output has no trace of messageReceived beyond the Warning: Deleted webhooks object line. If your webhooks matter to the consumer, re-model them by hand before calling the downgrade done.
  • It is not a full 3.0 guarantee. The numeric exclusiveMinimum: 0 passed through unchanged and without a warning — but 3.0 requires a boolean there, so a strict 3.0 validator will reject the output. Always run the downgraded file through a validator rather than trusting the exit code.

Treat the downgraded file as a generated artifact: keep 3.1 as the source of truth, regenerate the 3.0 copy in CI, and never hand-edit the output. And before you ship SDKs from the downgraded copy, diff the generated package surface against what the 3.1 spec produces — that's the check that catches a silently narrowed type.

Which tools support OpenAPI 3.1 in 2026?

Almost all of them. In 2026, most actively maintained OpenAPI tools read 3.1, but support quality still varies around JSON Schema edge cases:

  • Sourced — supported for the normal SDK/docs preview path; still test edge-case JSON Schema before publishing.
  • OpenAPI Generator — broad support, with generator-specific edge cases.
  • Spectral / Stoplight — yes.
  • Mintlify / ReadMe / Fern / Redocly / Scalar / Bump.sh — generally yes for docs, with platform-specific behavior.
  • Swagger UI / Swagger Editor — yes, since 2022.

Old tools that have not been updated since 2021 may still be 3.0-only. If you encounter one, that's a signal about the tool's maintenance level.

What to do this week

  • If you're on 2.0, plan a direct jump to 3.1 instead of 2.0 → 3.0 → 3.1. The intermediate stop adds work without lasting value.
  • If you're on 3.0 with a clean spec, the upgrade is half a day. Schedule it during a regular maintenance window.
  • If you're on 3.0 with a messy spec, the upgrade is worth doing alongside a cleanup pass — see the OpenAPI best practices post for what "clean" looks like.
  • If you're still on Swagger 2.0, use the Swagger 2.0 to OpenAPI 3 converter and then validate the result with the OpenAPI validator.

If you want a 3.1 spec generated against existing code or migrated from a 3.0 file, Sourced reads either version — start a free report and the generation pipeline builds SDK/docs previews and flags practical differences before anything reaches npm, PyPI, or production docs.