Schemas and contracts
A JSON document can be perfectly valid JSON and still be wrong. {"price": "19,90", "currency": "EURO"} parses
without a complaint, yet the price is text, not a number, and no bank knows the currency EURO. JSON checks only the
syntax. What a step needs — which fields, of which types, with which values — is a contract, and the standard way
to write one is JSON Schema.
In a workflow the contract matters most at its borders. An AI model asked for JSON answers with something that
looks right; a supplier feed changes a field without notice; a webhook sends a string where a number was agreed. A
prompt that says “return a number” is a wish, not a guarantee. Validate JSON Schema (utility/json_schema_validate)
turns the wish into a check: the document goes on only when it satisfies the schema, and when it does not, the
errors say exactly what is wrong. It is a trust boundary — it checks structure and declared constraints, not whether
the facts are true — and it costs nothing.
Madoo’s own contracts
Section titled “Madoo’s own contracts”Madoo describes the JSON its own nodes exchange with versioned contracts, named madoo.<name>/v<N>: a dataset, a
bundle of assets, a quality check, a caption track, a dubbing manifest. A JSON port that carries one declares it:
get_node_type shows its schema_ref, and the schema itself is one call away —
GET /api/v1/json-schemas/{reference} on REST, get_json_schema on MCP and in the AI tools, and
GET /api/v1/json-schemas for the whole catalog. Read the schema before extracting a field from such a port: it is
the exact list of fields, types and limits, instead of a guess from one sample.
The catalog has 44 contracts; each one has its own page in the appendix Madoo contracts, with its fields, the nodes that produce and read it, and its complete schema. By area:
Every contract carries its own name in a schema field ("schema": "madoo.caption-track/v1"), so a document says
what it is. A new version gets a new name (/v2); an existing version keeps being readable.
Validate JSON Schema
Section titled “Validate JSON Schema”The node receives a Document and checks it against one schema, from exactly one source:
| Source | The schema is… | Use it for |
|---|---|---|
catalog |
one of Madoo’s contracts, chosen in Catalog Schema | a document that must be a Madoo contract — a bundle manifest or a media policy received from outside |
inline |
written in the node, in Schema Definition | your own contract: a supplier record, the JSON an AI step returns |
input |
received at run time on the Schema input | a contract that changes from run to run, supplied by the caller |
The sources never fall back to one another. A catalog reference that does not exist, or an inline schema that is not a valid schema, is reported when the workflow is validated — before anything runs, not after the paid steps.
On Violation decides what an invalid document does:
| Mode | When the document is invalid |
|---|---|
fail |
the node fails with JSON_SCHEMA_VALIDATION_FAILED, listing the first five errors — where, which rule and why, such as /colours [maxItems]: Value should have at most 6 items — in the step’s error and in the execution’s trace; the run stops |
route |
Normalized JSON is rejected, so every step that reads it is skipped at no cost; Valid is false and Errors lists every problem, to drive a correction or review branch |
warn |
the document goes on anyway; Valid and Errors report the problem |
Wire the next steps from Normalized JSON, never from the unchecked document: that is what makes the check a
barrier. With Apply Declared Defaults, missing properties that declare a default in the schema are filled in
Normalized JSON, and Schema Trace says how many were.
Each error names the field (instancePointer, a JSON Pointer such as /price), the rule (keyword: type,
required, enum, pattern…), what was expected and a message. The list follows the
madoo.json-schema-errors/v1 contract, so it can itself be read by the next step.
Writing a schema
Section titled “Writing a schema”Madoo reads JSON Schema Draft 2020-12. A few rules make a schema useful rather than decorative:
- List what is required.
requiredis the difference between “may have a price” and “has a price”. - Say the type of every field, and close the object with
"additionalProperties": falsewhen an unknown field means a misunderstanding (a supplier who writescolourwhere you expectcolor). - Constrain values:
enumfor a closed list (currencies, statuses),patternfor codes,minimumandexclusiveMinimumfor amounts,minLengthandmaxLengthfor text that goes on a page. - Keep it self-contained. Shared parts go under
$defsand are referenced as#/$defs/…. References to other files or URLs are refused: the check must give the same answer every time, without depending on the network.
The limits: a schema up to 256 KiB, a document up to 2 MiB (read inline or from its stored file), nesting up to 128 levels.
The lab
Section titled “The lab”The schema lab checks a supplier’s product record before it
becomes a listing line. The inline schema requires sku (three capitals, a dash, two digits), name, a positive
price number, a currency among EUR, USD and GBP, and a whole stock, and allows nothing else. The mode is
route: the listing line is made from Normalized JSON, while Valid and Errors are delivered as outputs.
The figure shows the run with the wrong record. The two runs side by side:
{"sku":"LMP-04","name":"Table lamp","price":89,"currency":"EUR","stock":12} |
{"sku":"lmp4","name":"Table lamp","price":"19,90","currency":"EURO","colour":"brass"} |
|
|---|---|---|
| Valid | true |
false |
| Listing | Ready to list: Table lamp |
absent — Name and Listing line skipped |
| Errors | none | required: stock is missing · /sku pattern · /price type: string, should be number · /currency enum · /colour additionalProperties |
Both runs complete in a few seconds at no cost. Every problem of the second record is reported at once, with the field it concerns, so whoever fixes the feed fixes it in one pass.
node madoo-install-example.mjs lab-schema --api <your Madoo API URL> --key <key.json> --runThe wrong record is in run-inputs-invalid.json.
Checking what an AI step returns
Section titled “Checking what an AI step returns”An AI step in JSON mode returns an object, but not necessarily the one you described. Put Validate JSON Schema right
after it, with your schema inline, and read the fields from Normalized JSON: with route, an answer that misses a
field or breaks a limit never reaches the layout or the next paid step. The template gallery
uses this pattern everywhere — the AI writes the copy, the schema guards the page.
When the answer arrives as text — a model without a JSON mode, an answer that wraps the object in prose — the read
and the check are one node: Parse JSON (utility/json_parse) takes the same three schema sources and the same
fail and route modes, reads the text (Find first takes the JSON inside the prose) and hands on json, valid
and errors. A text that is not JSON at all fails with JSON_PARSE_INVALID and the line where it breaks; in route
its errors carry it with the keyword json (Reading JSON).
Errors
Section titled “Errors”| Code | Cause | Fix |
|---|---|---|
JSON_SCHEMA_VALIDATION_FAILED |
mode fail and the document breaks the schema |
read the errors in the message, or use route |
JSON_SCHEMA_CATALOG_REF_UNKNOWN |
Catalog Schema names a contract that does not exist | choose one from GET /api/v1/json-schemas |
JSON_SCHEMA_INVALID |
the inline or received schema is not a valid Draft 2020-12 schema | fix the schema: the message names the problem |
JSON_SCHEMA_REMOTE_REF_FORBIDDEN |
the schema references another file or a URL | move the shared part under $defs and reference #/$defs/… |
JSON_SCHEMA_SOURCE_MISSING, JSON_SCHEMA_SOURCE_AMBIGUOUS |
the selected source is empty, or more than one source is set | set exactly one source |
JSON_SCHEMA_DOCUMENT_MISSING |
nothing JSON reached Document | connect a JSON value |
JSON_DOCUMENT_TOO_LARGE, JSON_SCHEMA_TOO_LARGE |
the document is over 2 MiB, or the schema over 256 KiB | split the document, or simplify the schema |