# JSON Schema Validator

> Validate JSON against a JSON Schema (2020-12, 2019-09, 7, 4) with every error as a JSON Pointer.

Category: Text & Data · Page: https://util.caruana.me/tools/json-schema/

## API, MCP and A2A

### Validate JSON against a JSON Schema (`json_schema_validate`)

`GET|POST https://util.caruana.me/api/json/schema/validate` · MCP tool `json_schema_validate` · A2A skill `json_schema_validate` · [reference](https://util.caruana.me/reference/json_schema_validate/)

Validates `instance` against `schema` with @cfworker/json-schema (an interpreter: no code generation). The draft comes from `draft`, else from the schema's `$schema`, else 2020-12. Returns `valid` and, for an invalid instance, each error with the instance location and keyword location as JSON Pointers, the location of the keyword in the schema document (after following `$ref`), and a message; `leaf: false` marks summary errors (from `properties`, `items`, `$ref`, `anyOf`, …) that are followed by the specific errors beneath them. An invalid instance is a successful response with `valid: false`. Before validating, the schema is checked for usability (keyword types, regular expressions, unresolved references); problems are a 422 error. Only local references resolve: `$ref` to a remote URL is never fetched (the server makes no network requests); embed such schemas under `$defs` with their `$id`. `format` is asserted for the formats listed in the docs. `$dynamicRef` is not supported. At most 1000 errors are returned.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `schema` | string (≤ 100000 chars) | yes | The JSON Schema, as JSON text (an object or a boolean). |
| `instance` | string (≤ 200000 chars) | yes | The JSON document to validate, as JSON text. |
| `draft` | auto \| 2020-12 \| 2019-09 \| 7 \| 4 | no | `auto` uses the draft named by the schema's `$schema` (draft 6 is validated as 7) and 2020-12 if there is none. Otherwise `2020-12`, `2019-09`, `7` or `4` overrides `$schema`. |
| `all_errors` | boolean | no | `true` reports every error. `false` stops at the first failing keyword (faster; `valid` is the same either way). |

Privacy: The server receives the schema and the instance, processes it in memory inside the request, and returns the result. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. The server makes no network requests. For confidential documents use the page, which does the same work in your browser and sends nothing.


## User guide

The tool sits in a window that works like a desktop app: menus on the top row and the **full-screen** icon at its right (**Esc** leaves full screen). The set-up options described below are in **Settings → Options…**, an overlay, and the **Settings** menu also lists the common choices directly. To keep the options in view instead, choose **View → Inline config**; the page remembers that choice. **Help → Keyboard shortcuts…** lists the keys.

1. **Paste the schema** on the left and **the JSON to validate** (the _instance_) on the right. Validation runs as you type. **Sample** loads an example with several errors.
2. Read the verdict:
   - **Valid** means the instance satisfies every keyword of the schema.
   - **Invalid** lists each problem: where it is in the instance (a JSON Pointer such as `/items/2/price`), the keyword that failed, a message, and where that keyword is written in the schema.
3. If the schema or the instance is not valid JSON, the error shows the line and column, and **Go to error** selects it. If the schema is valid JSON but cannot be used (a misspelt type, a broken regular expression, a `$ref` that points nowhere), each problem is listed with its location in the schema.

**Which draft?** **From $schema** uses the draft the schema declares in `$schema`, and 2020-12 if it declares none. Choose a draft explicitly to override it. The drafts differ in a few places that matter: in draft 7 and earlier, keywords next to a `$ref` are ignored; in draft 4, `exclusiveMinimum` and `exclusiveMaximum` are booleans.

**Summary errors.** When a nested value fails, the validator also reports the keywords that led there (`properties`, `items`, `$ref`, `anyOf`…), such as _Property "id" does not match schema_. These are hidden by default; tick **Show summary errors** to see the full chain. They are useful with `anyOf` and `oneOf`, where the summary is the actual failure and the errors under it explain why each alternative did not match.

**Warnings** point out things that do not stop validation but probably do not do what you meant: unknown keywords (often typos such as `requried`, or OpenAPI's `nullable`), formats this validator does not check, keywords from a different draft, and duplicate keys in either document.

## Specification

**Validator.** [@cfworker/json-schema](https://github.com/cfworker/cfworker/tree/main/packages/json-schema) 4.1, which interprets the schema rather than compiling it to JavaScript, so it runs in a browser with a strict Content Security Policy and in Cloudflare Workers, where generating code at runtime is forbidden. It is tested against the official JSON-Schema-Test-Suite.

**Drafts.**

| Draft   | Selected by                                                     | Notes                                                                                 |
| ------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| 2020-12 | `https://json-schema.org/draft/2020-12/schema`, or no `$schema` | `$dynamicRef` and `$dynamicAnchor` are not supported (ignored, with a warning).       |
| 2019-09 | `https://json-schema.org/draft/2019-09/schema`                  | `$recursiveRef` / `$recursiveAnchor` are supported.                                   |
| 7       | `http://json-schema.org/draft-07/schema#`                       | Keywords beside `$ref` are ignored.                                                   |
| 6       | `http://json-schema.org/draft-06/schema#`                       | Validated with draft 7 rules, with a warning (draft 7 only added `if`/`then`/`else`). |
| 4       | `http://json-schema.org/draft-04/schema#`                       | Boolean `exclusiveMinimum`/`exclusiveMaximum`; keywords beside `$ref` are ignored.    |
| 3       | `http://json-schema.org/draft-03/schema#`                       | Not supported: rejected.                                                              |

`$schema` URIs match with `http` or `https` and with or without a trailing `#`. An unrecognised `$schema` is validated as 2020-12, with a warning. Apart from the differences in the table, every keyword is applied whichever draft is selected: `prefixItems` or `dependentRequired` in a draft 7 schema still take effect, with a warning that they belong to a later draft. In draft 2020-12 the array form of `items` is applied as a tuple (the older meaning), with a warning to use `prefixItems`.

**Formats** are asserted (an invalid value is an error) for: `date`, `time`, `date-time`, `duration`, `uri`, `uri-reference`, `uri-template`, `url`, `email`, `hostname`, `ipv4`, `ipv6`, `regex`, `uuid`, `json-pointer`, `json-pointer-uri-fragment`, `relative-json-pointer`. Any other format (for example `idn-email`, `iri`, or a custom one) is not checked and produces a warning.

**References.**

- **Resolved:** JSON Pointer fragments (`#/$defs/name`), `$anchor` names, and schemas embedded in the same document with their own `$id` (a `$ref` to that `$id`, absolute or relative, resolves to the embedded copy). A schema without an absolute `$id` uses the base URI `https://schema.invalid/root.json`, which never resolves to anything real.
- **Never fetched:** a `$ref` to anything not in the document (for example `https://example.com/address.json`, or a meta-schema URL) is an error that names the reference and its location. Neither the page nor the server makes network requests. To use an external schema, copy it under `$defs` with its `$id`.
- **Cycles:** recursive schemas are fine as long as each step goes deeper into the instance. A cycle that does not (such as `{"$ref": "#"}`) is reported instead of hanging.

**Checks before validating.** These make the schema unusable and are reported as problems with their schema location; nothing is validated until they are fixed.

- A schema (or subschema) that is not an object or a boolean.
- `type` that is not one of `array`, `boolean`, `integer`, `null`, `number`, `object`, `string` (with a suggestion for `int`, `bool`, `float`, `str`, `dict`, `list`, …), or an empty array.
- `required` that is not an array of strings; `required: true` is identified as draft 3 syntax.
- `pattern` or a `patternProperties` key that is not a valid ECMA-262 regular expression with the `u` (Unicode) flag. Needless escapes such as `\_` are errors under that flag.
- `enum` that is not an array; `minimum`, `maximum`, `multipleOf` (greater than 0) that are not numbers; length and count keywords that are not non-negative integers; `allOf`/`anyOf`/`oneOf` that are not non-empty arrays; `$ref` that is not a string.
- A `$ref` that does not resolve (see above).

**Errors.** Each error has:

| Field              | Meaning                                                                                                                                                                 |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `instanceLocation` | JSON Pointer (RFC 6901) to the failing value; `""` is the whole instance. Keys containing `/` or `~` are escaped as `~1` and `~0`.                                      |
| `keyword`          | The failing keyword, or `false` where the schema is `false`.                                                                                                            |
| `keywordLocation`  | The path taken through the schema, as a JSON Pointer; a `$ref` segment marks a reference that was followed, e.g. `/properties/id/$ref/minimum`.                         |
| `schemaLocation`   | Where that keyword is written in the schema document, after following references, e.g. `/$defs/positive/minimum`. Null only when the path goes through `$recursiveRef`. |
| `message`          | What is wrong.                                                                                                                                                          |
| `leaf`             | `false` for a summary error that is immediately followed by the more specific errors that caused it.                                                                    |

Errors are in evaluation order. With **All errors** off (API: `all_errors=false`), the result is the first specific error and the summary errors leading to it; `valid` is the same either way. At most 1,000 errors are returned; `errorCount` is the total.

**Corrections to the library's output.** The raw library output has three quirks, which are corrected here:

- An error from a `false` subschema reported the instance location as its schema location.
- With all errors enabled, a property listed in `properties` that failed its own schema was also reported, wrongly, as an "additional" property.
- Required and declared properties were looked up including inherited JavaScript names, so `"required": ["toString"]` passed on `{}`. Both documents are now plain data with no inherited names.

**Semantics worth knowing.**

- Numbers are compared as IEEE 754 doubles. `1.0` is an `integer` in every draft. `multipleOf` avoids binary rounding artefacts: 0.07 is a multiple of 0.01.
- `pattern` uses JavaScript regular expressions with the `u` flag and is not anchored: add `^…$` to match the whole string. A pattern with catastrophic backtracking can use up the request's CPU time; on the page it can make the tab unresponsive.
- `uniqueItems`, `const` and `enum` compare objects and arrays structurally.
- Duplicate keys in either document are reported as warnings; the last value is used.

**Limits.** The instance may be nested at most 100 levels deep (the validator recurses several stack frames per level). API: `schema` at most 100,000 characters and `instance` at most 200,000 characters.

**API.** `POST /api/json/schema/validate` with `schema` and `instance` as JSON text, `draft` (`auto`, `2020-12`, `2019-09`, `7`, `4`) and `all_errors`. An invalid instance is a 200 response with `valid: false`. A schema or instance that is not valid JSON, or a schema that cannot be used, is a 422 error whose message gives the location.
