JSON Schema vs OpenAPI: what is the difference?
Last updated: September 2026
JSON Schema describes the shape of a single piece of JSON data, such as "an object with a required string email and an optional integer age", and lets you validate data against that description. OpenAPI describes an entire HTTP API (its paths, operations, parameters, responses, servers and authentication) and uses JSON Schema inside it to describe the data those operations send and receive.
So the two are not competitors. JSON Schema is a building block; OpenAPI is the building. The interesting question is how well the block fits, and the answer depends on the OpenAPI version: in OpenAPI 3.0 the fit was awkward, and since OpenAPI 3.1 it is exact.
This guide covers what each one is, how the relationship changed across versions, the keywords that differ, and how to share schemas between standalone JSON Schema files and OpenAPI documents without surprises.
On this page
- The short answer
- What JSON Schema is
- What OpenAPI is
- Side-by-side comparison
- How the relationship changed across OpenAPI versions
- The keywords that differ in OpenAPI 3.0
- Keywords OpenAPI adds on top of JSON Schema
- Dialects: $schema and jsonSchemaDialect
- Sharing schemas between JSON Schema and OpenAPI
- Migrating schemas from 3.0 to 3.1
- Common mistakes
- Frequently asked questions
The short answer
- JSON Schema answers "is this JSON value valid?" It knows nothing about URLs, HTTP methods or status codes.
- OpenAPI answers "what does this API do and how do I call it?" For the data parts of that answer, it delegates to a Schema Object.
- OpenAPI 3.1 and 3.2 Schema Objects are a superset of JSON Schema Draft 2020-12, so any valid 2020-12 schema works unchanged inside an OpenAPI document.
- OpenAPI 3.0 Schema Objects are an "extended subset" of an older JSON Schema draft, with some keywords changed or missing. That gap is the source of most confusion you will find online.
If you are starting a new API, use OpenAPI 3.1 or later and treat your schemas as plain JSON Schema. For the wider version story, read OpenAPI 3.1 vs 3.0.
What JSON Schema is
JSON Schema is a vocabulary for describing and validating JSON documents. A schema is itself JSON (or YAML) and lists constraints: types, required properties, string patterns, number ranges, array lengths, and combinations of other schemas.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.example.com/user.json",
"title": "User",
"type": "object",
"required": ["id", "email"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0 },
"nickname": { "type": ["string", "null"] }
},
"additionalProperties": false
}
JSON Schema is used far beyond APIs: configuration files (editors validate package.json and tsconfig.json with it), form generators, database document validation, and event payloads. It is published by the JSON Schema organization as a series of drafts; Draft 2020-12 is the one OpenAPI 3.1 and 3.2 build on.
What OpenAPI is
The OpenAPI Specification is a format for describing HTTP APIs. A document lists paths and operations, and wherever data appears (a request body, a response body, a query parameter, a header) it uses a Schema Object to describe that data.
Here is the same User schema used inside an OpenAPI 3.1 document:
openapi: 3.1.0
info:
title: Users API
version: 1.0.0
paths:
/users/{id}:
get:
operationId: getUser
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: The user
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required: [id, email]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
age:
type: integer
minimum: 0
nickname:
type: [string, 'null']
additionalProperties: false
Everything under components.schemas.User is JSON Schema. Everything around it (the path, the parameter's location, the status code, the media type) is OpenAPI.
Side-by-side comparison
| JSON Schema | OpenAPI | |
|---|---|---|
| Describes | One JSON value | An entire HTTP API |
| Main building blocks | Types, properties, constraints, composition (allOf, oneOf, anyOf, not) |
Paths, operations, parameters, request bodies, responses, servers, security schemes |
| Knows about HTTP | No | Yes: methods, status codes, headers, media types, authentication |
| Current version | Draft 2020-12 | OpenAPI 3.2 (3.1 is the most widely supported) |
| Relationship | Standalone | Uses JSON Schema for data, via the Schema Object |
| Validation | Validates data instances | Validates as a document; its schemas validate request and response data |
| Typical tools | Validators (Ajv and others), form and type generators | API references, API clients, SDK generators, mock servers, linters |
| File name examples | user.schema.json |
openapi.yaml, openapi.json |
How the relationship changed across OpenAPI versions
OpenAPI has used JSON Schema since the Swagger days, but which JSON Schema, and how faithfully, has changed a lot. The details come from each version's own specification text.
| OpenAPI version | Schema Object is... | Practical effect |
|---|---|---|
| Swagger 2.0 (history) | Based on JSON Schema Draft 4, using "a predefined subset of it" plus extensions | Many JSON Schema keywords unavailable; type: file added for uploads |
| OpenAPI 3.0 | "An extended subset" of JSON Schema Draft Wright-00 (the draft often called Draft 5) | Close to JSON Schema, but with changed keywords such as nullable and single-value type |
| OpenAPI 3.1 | "A superset of the JSON Schema Specification Draft 2020-12" | Any valid 2020-12 schema works; OpenAPI adds a few annotation keywords |
| OpenAPI 3.2 | By default, "a superset of the JSON Schema Specification Draft 2020-12" | Same model as 3.1, with the dialect identified by a dated OAS dialect URI |
The big break is between 3.0 and 3.1. The 3.0 specification spelled out which JSON Schema keywords were allowed and said that any keyword not mentioned "is strictly unsupported". That excluded useful keywords such as const, if/then/else, prefixItems, $defs, unevaluatedProperties and dependentRequired, and it changed the meaning of several others. OpenAPI 3.1 dropped the subset and adopted full JSON Schema.
The keywords that differ in OpenAPI 3.0
If you maintain 3.0 documents, or share schemas with tools that still expect 3.0, these are the differences that bite.
nullable instead of a null type. In 3.0, type must be a single string, so null is expressed with an OpenAPI-specific keyword:
# OpenAPI 3.0
nickname:
type: string
nullable: true
# OpenAPI 3.1 and JSON Schema 2020-12
nickname:
type: [string, 'null']
nullable does not exist in 3.1. A 3.1 validator will ignore it, silently rejecting the null values you meant to allow.
exclusiveMinimum and exclusiveMaximum are booleans in 3.0. In the older draft they modify minimum and maximum. In 2020-12 they are numbers in their own right.
# OpenAPI 3.0: greater than 0
price:
type: number
minimum: 0
exclusiveMinimum: true
# OpenAPI 3.1: greater than 0
price:
type: number
exclusiveMinimum: 0
example vs examples. OpenAPI 3.0 schemas use a singular example. JSON Schema has an examples array. In 3.1 and 3.2 the Schema Object's example field is deprecated in favor of examples:
# OpenAPI 3.1
email:
type: string
format: email
examples:
- ada@example.com
Note this is the schema-level keyword. Media Type Objects, parameters and headers still have their own example and examples fields with a different shape (a map of Example Objects).
$ref with siblings. In 3.0, a Schema Object containing $ref is a Reference Object, and the specification says any other properties added to it "SHALL be ignored". So description next to $ref disappears. In 3.1, a schema $ref is an ordinary JSON Schema keyword and sibling keywords apply.
Binary data. OpenAPI 3.0 described file content with type: string and format: binary or format: byte. OpenAPI 3.1 uses JSON Schema's contentMediaType and contentEncoding instead, and the 3.1 specification says format no longer affects content encoding:
# OpenAPI 3.1: a base64-encoded PNG inside JSON
avatar:
type: string
contentMediaType: image/png
contentEncoding: base64
Keywords OpenAPI adds on top of JSON Schema
Even in 3.1 and 3.2, OpenAPI defines a few fields in the Schema Object that JSON Schema does not. They are annotations for tools; the 3.2 specification states that discriminator, for example, "MUST NOT change the validation outcome of the schema".
discriminator: names the property that tells tools which of severaloneOforanyOfschemas a payload uses, with an optionalmappingfrom values to schemas. SDK generators use it to produce proper union types; see how SDKs are generated from OpenAPI.xml: metadata for serializing the schema as XML (element names, attributes, namespaces, wrapping).externalDocs: a link to further documentation for the schema.example: kept for compatibility, deprecated in favor ofexamples.
A generic JSON Schema validator will treat these as unknown keywords and ignore them, which is exactly the right behavior. A polymorphic schema in 3.1 looks like this:
components:
schemas:
Pet:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
discriminator:
propertyName: petType
mapping:
cat: '#/components/schemas/Cat'
dog: '#/components/schemas/Dog'
Cat:
type: object
required: [petType, livesLeft]
properties:
petType:
const: cat
livesLeft:
type: integer
Dog:
type: object
required: [petType, goodBoy]
properties:
petType:
const: dog
goodBoy:
type: boolean
The const keywords do the actual validation (a Cat must have petType: cat), and discriminator tells tools how to pick the branch quickly. Using const here would not be allowed in 3.0.
Dialects: $schema and jsonSchemaDialect
JSON Schema has "dialects": a $schema URI at the root of a schema says which draft and vocabularies apply. OpenAPI 3.1 and later support this too.
- By default, every Schema Object in a 3.1 document uses the OpenAPI dialect, identified by
https://spec.openapis.org/oas/3.1/dialect/base. In 3.2 the default dialect is identified by a dated URI of the formhttps://spec.openapis.org/oas/3.2/dialect/YYYY-MM-DD. - A document can change that default with the top-level
jsonSchemaDialectfield. - An individual schema can declare its own
$schema, which always wins. Tooling must support the OAS dialect and may support others.
In practice you rarely need either field. They matter when you embed schemas written for another draft, or when you want a validator to treat your schemas as plain 2020-12. The 3.1 specification also recommends setting $schema explicitly in standalone schema files that will be referenced from OpenAPI, "for maximum interoperability".
Sharing schemas between JSON Schema and OpenAPI
Because 3.1 schemas are JSON Schema, a model can live in its own file and be referenced from any number of API descriptions:
# openapi.yaml
components:
schemas:
User:
$ref: './schemas/user.schema.json'
This is how you avoid three slightly different Address models across three APIs. A few practical tips:
- Give shared schemas an
$id. It becomes the schema's canonical identity and base URI, which makes relative references inside it resolve predictably. - Bundle before distributing. Some tools cannot follow external references. Bundling pulls external
$refs intocomponentsso the document is self-contained. The Scalar CLI does this withscalar document bundle. - Version shared models. A change to a shared model is a change to every API that uses it. Publishing shared schemas to a registry with versions makes that visible. The Scalar Registry supports standalone JSON Schemas for exactly this, and they can be referenced across the APIs in your API catalog.
- Validate data with the same schemas. The schema that documents your request body can validate that request at runtime in your server, in a gateway, or in a mock server that returns
422for invalid requests. One definition, used everywhere.
Migrating schemas from 3.0 to 3.1
Most migration work is in the schemas. The checklist:
- Change
openapi: 3.0.xtoopenapi: 3.1.x(or 3.2). - Replace
nullable: truewith anullentry intype(or add{ type: 'null' }to aoneOf). - Convert boolean
exclusiveMinimum/exclusiveMaximumto numbers and remove the pairedminimum/maximum. - Replace schema-level
examplewith anexamplesarray. - Replace
format: binaryandformat: bytewithcontentMediaTypeandcontentEncodingwhere needed. - Review
$refsiblings, which now take effect.
You do not have to do this by hand. The Scalar OpenAPI upgrader converts Swagger 2.0 and OpenAPI 3.0 documents to 3.1, and the CLI exposes it as a single command:
npx @scalar/cli document upgrade openapi.yaml --output openapi-3.1.yaml
Then validate the result with npx @scalar/cli document validate openapi-3.1.yaml and run your linting rules. The Spectral rules guide has examples.
Common mistakes
Using nullable in a 3.1 document. It is not a 3.1 keyword. Validators ignore it and reject null.
Assuming format validates. In JSON Schema 2020-12, and so in OpenAPI 3.1, format is an annotation by default; the 3.1 specification notes that "the ability to validate format varies across implementations". If an email must really be an email, add a pattern or enable format assertion in your validator.
Forgetting that additionalProperties defaults to allowing anything. A schema without additionalProperties: false (or unevaluatedProperties: false when using composition) accepts extra fields. That is often right for responses and wrong for requests.
Mixing up the two examples. Schema examples is an array of values. Media type examples is a map of named Example Objects. Putting one shape where the other belongs is one of the most common validation errors.
Pasting 3.0 schemas into a JSON Schema validator. A 3.0 schema is not valid 2020-12 JSON Schema if it uses nullable, boolean exclusiveMinimum, or relies on ignored $ref siblings. Upgrade first.
Using readOnly and writeOnly as if they validate. They are annotations. The 3.1 specification leaves it to the API to ignore or reject a readOnly field sent in a request.
Frequently asked questions
Is OpenAPI the same as JSON Schema?
No. JSON Schema describes and validates a single JSON value. OpenAPI describes a whole HTTP API, and uses JSON Schema to describe the request and response data inside it. Since OpenAPI 3.1, the Schema Object is a superset of JSON Schema Draft 2020-12.
Which version of JSON Schema does OpenAPI use?
OpenAPI 3.1 and 3.2 use JSON Schema Draft 2020-12 by default. OpenAPI 3.0 uses an extended subset of JSON Schema Draft Wright-00, often called Draft 5. Swagger 2.0 uses a subset of Draft 4.
Can I use my existing JSON Schema files in an OpenAPI document?
Yes, with OpenAPI 3.1 or later. Reference them with $ref from components.schemas or directly where a schema is expected. If a file targets a different draft, declare it with $schema. With OpenAPI 3.0, only schemas that stay within 3.0's supported keywords will work.
Why is nullable not working in OpenAPI 3.1?
Because nullable was removed in OpenAPI 3.1. Use a type array such as type: [string, 'null'] instead, which is standard JSON Schema.
Do I still need OpenAPI if I have JSON Schema for all my models?
If you want documentation, API clients, SDKs, mocks or MCP servers, yes. JSON Schema describes your data, but not your endpoints, methods, parameters, status codes, servers or authentication. OpenAPI adds that layer and reuses your schemas for the data.
Related
- Learn: OpenAPI 3.1 vs 3.0 · What is OpenAPI? · Spectral rules
- Docs: OpenAPI upgrader
- Product: Scalar Registry — publish shared JSON Schemas once and reference them from every API that needs them.