# schema.json (/docs/agents/schema)

> The page schema and one schema per Section type, as JSON Schema, and the rules JSON Schema does not carry, so agents check with PageSchema.

| Address | Holds |
| --- | --- |
| [`/schema.json`](https://next.mdxui.dev/schema.json) | `PageSchema.toJSONSchema()`: the whole record, every bound as `maxLength`, `minItems` and `maxItems` |
| `/schema/sections/<type>.json` | One Section type's schema, such as [`/schema/sections/stats.json`](https://next.mdxui.dev/schema/sections/stats.json), for all 27 types |

Hand `/schema.json` to a model as its structured output or tool schema. Then check what it returns with `PageSchema.safeParse`, never with a JSON Schema validator alone.

## What JSON Schema leaves out

Some rules span fields, or allow only some counts inside a range. `/schema.json` does not carry them; `PageSchema` does. Each section below keeps every bound in `/schema.json`, and `schema-gap.ts` shows `PageSchema` refusing it:

```ts
// Three sections that keep every maxLength, minItems and maxItems in /schema.json,
// and that PageSchema still refuses: rules JSON Schema has no keyword for.
const brand = { href: '/' };
const probes: Record<string, unknown> = {
  'a brand with neither wordmark nor mark': { type: 'nav', menu: 'none', brand, links: [] },
  'a feature grid of five': { type: 'feature-grid', cellHead: 'none', items: Array.from({ length: 5 }, () => ({ title: 'A feature' })) },
  'a footer in columns with no "For agents" column': {
    type: 'footer', layout: 'columns', brand: { href: '/', text: 'Vanroster' }, note: ['Vanroster', 'Rendered by @mdxui/site.'],
    columns: [{ title: 'Product', links: [['/pricing', 'Pricing']] }, { title: 'Company', links: [['/contact', 'Contact']] }],
  },
};

export function gaps(): string[] {
  return Object.entries(probes).map(([what, block]) => {
    const page = { theme: 'callboard', title: 'Probe', description: 'A probe.', frame: 'none', sections: Array.from({ length: 3 }, () => ({ kind: 'section', shell: { inset: 'contained', density: 'section' }, block })) };
    const result = PageSchema.safeParse(page);
    const issue = result.success ? undefined : result.error.issues[0];
    return `${what}: ${issue ? `${issue.path.join('.')}: ${issue.message}` : 'accepted'}`;
  });
}
```

```text title="node schema-gap.ts"
a brand with neither wordmark nor mark: sections.0.block.brand: a brand needs a wordmark or a mark
a feature grid of five: sections.0.block.items: a feature grid holds 2, 3, 4, 6, 8 cells: a count that fills its rows at every width
a footer in columns with no "For agents" column: sections.0.block: a footer in columns has a "For agents" column
```

[Refusals](https://next.mdxui.dev/docs/reference/refusals) lists every rule with its message.

