JSON output contract

The format of the json output: each page as the build resolved it, with every node’s place in its source, for your own tools.

For AI agents: the documentation index is at llms.txt, and this page is available as Markdown.

Each build writes a json output beside its site and plain outputs: each page the build publishes, as the build resolved it, with every node’s place in its source, for your own tools. This contract says what each page’s document holds. The output-layout contract says where the files are.

Who Uses this contract to
ascribe build Write the json output
A tool of your own Read the pages: a search index, a check of your own, another renderer

1. The files

The json output has one document per page, at the page’s source path with .json for .md: guides/install.md is guides/install.json. Beside the pages, _ascribe/nav.json is the build’s navigation, as the site output has it, with each formatted label as pieces (the navigation contract).

Each page’s document is a JSON object, whose schema is schemas/json-page.schema.json: JSON Schema (draft 2020-12), generated from the code that writes the documents, so the two can’t disagree. The schema has it in full.

2. Rules

The JSON report contract’s rules hold here too:

  • schema_version, 2, changes only when a field is removed or changes meaning. Fields can be added without a new version, so ignore fields you don’t know. The schema allows fields it doesn’t list.
  • A field whose values are a fixed set of names, such as a block’s type, may gain a value without a new version. A reader skips a block or an inline whose type it doesn’t know.
  • A field the schema doesn’t list as required is left out when it has no value. A required field that can be null is always written.
  • Keys are snake_case, and so are the names a field’s values are.

A span is [start, end], counted in UTF-8 bytes from the start of the file, frontmatter included. Lines count from 1. Every file is a path relative to the content root, so a tool can point back at the source.

3. The page

Field Type Meaning
schema_version number 2
format string "ascribe-page"
build string The build’s name
path string The page’s source path, relative to the content root
route string The page’s root-relative URL, as the router made it
site string or null [consumer] site, the origin to put before a root-relative URL
title string or null The frontmatter title, phrases substituted, as plain text
frontmatter object or null The frontmatter, with phrases substituted in the fields that ask for it, and fields that set inline = "code" as plain text. Its keys are the content model’s, as written
formatted object, when there is one Each field that sets inline = "code", as a list of {type: "text" | "code", value}
availability object or null The page’s own availability (frontmatter available)
headings array Every surviving heading: level, text, id (its page id), source_id, explicit, source
assets array Every surviving asset reference on the page: source (its source path), path (the copy’s path in this output), reference (how this page refers to it), kind (image or link), fragment, written_in (the file the reference is written in), span
blocks array The page’s blocks

4. Blocks

Every block has a type, and:

Field Meaning
source file, span, lines (first and last), and via: the includes it came through, outermost first (file, span, line), absent for a page’s own blocks
availability Its effective availability below page level: the innermost spec, with enclosing chaining outward to the scope it sits in. Absent when nothing but the page’s availability applies
links The block’s own links and images, resolved: span, kind, destination, target
glossary Glossary terms linked in its prose, and its links to a term’s heading: term, text, url, definition
substitutions Phrases replaced in its text: span (over the source), key, value
snippet For a code block a @snippet became: address (as written), path (the code file, from the project root), and lines (the first and last lines of the file it covers), and tested when the file is a tested example. Its source is the @snippet line

links, glossary, and substitutions are absent when empty, and snippet is absent on every other block.

A snippet’s tested is present only when its source’s ascribe.examples.toml makes the file an example (tested examples): status, one of current, stale, and unobserved, and observations, the observations current for the file in name order, each with its name and its environment as the observation has it, os and arch first, such as { "os": "linux", "arch": "x64", "node": "v24.9.0" }. observations is empty unless the status is current.

type Fields
heading level, id, source_id, explicit, text, inlines, and summary when its section has one
paragraph inlines
code fenced, info, literal
block_quote children
list ordered, start, tight, items (each {children})
html literal
thematic_break
table alignments: each column’s none, left, center, or right; rows: each {header, cells}, a cell being {inlines}, and a row’s own availability when it has one
directive A line-form directive that survived: name, attributes, title, primary, binding (own, heading, following_block, unbound), annotation
operation An @operation that names an operation the spec has: api, operation_id when it has one, method, path, summary when it has one, deprecated, and address, the operation’s in the published reference. Its source is the @operation line
container name, attributes, title, children
group name, arms: each {label, attributes, title, span, children}

An @available that survived the build is a directive with an annotation: text (the spec as shown; for a feature key, the spec it stands for), display (with the content model’s labels), and feature. attributes is a list of {key, values} (a set has several values, a bare key none). A title and a text primary are inline lists. A primary is {kind: "text", inlines}, {kind: "identifier", text}, or {kind: "line", text}.

An @operation whose link names no operation the spec has, which ascribe check reports, stays a directive block, with its link as its identifier primary.

A heading’s summary is its section’s: form, text for @summary: text and paragraph for @summary above a paragraph; inlines, the summary’s text; and source, the @summary line for the text form and the paragraph for the other. The @summary line is a directive block too, where it’s written, and the paragraph a paragraph.

A group keeps its surviving arms only: a group a selection reduced to one arm isn’t a group in the tree, but that arm’s blocks, and one with none is gone. An arm’s label is its title as plain text, or the display labels of the values it names.

5. Inlines

Every inline has a type and a span in the block’s file: text (value, decoded), code (value), soft_break, hard_break, html (value), emphasis and strong (children), link, image, phrase (key, for a phrase the resolution left, which it doesn’t).

A link has destination (a page link’s is its route and id, an external one’s is as written), title, children, and target: the block’s resolved link at the same span, or null for a glossary link (an ordinary link, listed in the block’s glossary). An image has destination, title, alt (plain text), attributes ({key, values}, such as width), and target.

A target has a type:

type Fields
external
page page (its source path), id (a page id, or null), url, text_filled (the link had no text and took the page’s title), and, for a link out of a line of a versioned build, line (the line it leads to)
asset source, path (the copy), reference (as written on this page), url, fragment
operation api (its [apis.<name>]), operation_id, or method and path for an operation with no id, and address, the operation’s in the published reference
unresolved The build couldn’t resolve it; the checks report why

6. Example

{
  "schema_version": 2,
  "format": "ascribe-page",
  "build": "cloud",
  "path": "keys.md",
  "route": "/keys/",
  "site": "https://docs.quill.dev",
  "title": "API keys",
  "headings": [{ "level": 2, "text": "Create a key", "id": "create-key", "source_id": "create-key", "explicit": true, "source": { "file": "keys.md", "span": [93, 108], "lines": [5, 5] } }],
  "assets": [],
  "blocks": [{ "type": "heading", "level": 2, "id": "create-key", "…": "…" }]
}