{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/blinkpdf-io/json-schema/blinkpdf-io-template-layout-document-body-schema.json",
  "title": "Template.layout-document.Body",
  "description": "Layout document: a Markdown body confined to one writing rect on every page, plus positioned elements (text with per-page numbering, images, calibration frames), an optional full-sheet background, and a printer calibration offset. There is no built-in header, footer, signature, or page counter.",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/blinkpdf-io-openapi.json#/components/schemas/Template.layout-document.Body",
  "additionalProperties": false,
  "type": "object",
  "required": [
    "markdown",
    "metadata",
    "page",
    "bodyRect",
    "elements"
  ],
  "properties": {
    "markdown": {
      "minLength": 0,
      "maxLength": 262144,
      "description": "Markdown source rendered only inside `bodyRect`, continuing onto new pages with the same `bodyRect` when it does not fit. Its images may be `data:` URLs, staged `blink://asset/<id>` handles, or `https://` URLs, fetched under the same private-address and mixed-content rules as every render (and pinned with `pinRemoteImages`).",
      "type": "string"
    },
    "metadata": {
      "$ref": "#/$defs/Template.layout-document.Body.metadata"
    },
    "fontStack": {
      "$ref": "#/$defs/Render.Body.fontStack"
    },
    "googleFontsUrls": {
      "$ref": "#/$defs/Render.Body.googleFontsUrls"
    },
    "typography": {
      "$ref": "#/$defs/Render.Body.typography"
    },
    "glyphFallback": {
      "$ref": "#/$defs/Render.Body.glyphFallback"
    },
    "pinRemoteImages": {
      "title": "Pin each remote image to the calling credential for 24 hours, so later renders paint the same bytes",
      "description": "When true, each `https://` image of the request (its image fields and its Markdown body) is fetched once and its bytes are pinned to the calling credential and reused on later renders for up to 24 hours, so repeated renders produce the same PDF even if the origin changes; the pins are the ones every render reads and writes for the same URL. An image served through a pin reports `image-source-pinned`; without the flag a remote image reports `image-source-remote`. A body whose bytes are no image is never pinned. When pinning is unavailable (an anonymous caller included) the request fails with `service_unavailable` instead of fetching live. Defaults to false.",
      "type": "boolean"
    },
    "bodyFontSize": {
      "minimum": 6,
      "maximum": 24,
      "description": "Base font size of the Markdown body in PDF points (paragraphs, lists, and table cells); headings scale in proportion. Defaults to 11.",
      "type": "number"
    },
    "page": {
      "additionalProperties": false,
      "description": "Sheet size, orientation, and optional background",
      "type": "object",
      "required": [
        "size"
      ],
      "properties": {
        "size": {
          "description": "A named page size or an explicit `[width, height]` tuple in PDF points",
          "anyOf": [
            {
              "const": "A3",
              "type": "string"
            },
            {
              "const": "A4",
              "type": "string"
            },
            {
              "const": "A5",
              "type": "string"
            },
            {
              "const": "B4",
              "type": "string"
            },
            {
              "const": "B5",
              "type": "string"
            },
            {
              "const": "Letter",
              "type": "string"
            },
            {
              "const": "Legal",
              "type": "string"
            },
            {
              "const": "Tabloid",
              "type": "string"
            },
            {
              "const": "HalfLetter",
              "type": "string"
            },
            {
              "$ref": "#/$defs/Render.Body.page.size.anyOf9"
            }
          ]
        },
        "orientation": {
          "description": "Page orientation; defaults to portrait when omitted",
          "type": "string",
          "enum": [
            "portrait",
            "landscape"
          ]
        },
        "background": {
          "additionalProperties": false,
          "description": "Full-sheet background, typically a scan of the preprinted letterhead used to preview placement",
          "type": "object",
          "required": [
            "image"
          ],
          "properties": {
            "image": {
              "maxLength": 4194304,
              "pattern": "^(https://|data:image/|[Bb][Ll][Ii][Nn][Kk]://[Aa][Ss][Ss][Ee][Tt]/)",
              "description": "Raster image (png, jpeg, gif, tiff, or webp) as an `https://` URL, a `data:image/` URL, or a staged `blink://asset/<id>` handle, stretched over the full sheet beneath the body and every element on every page; `printOffset` does not move it. The background paints rasters only: an SVG is refused at this field with `image-unsupported-mime`, and any other source that does not paint with the code a Markdown image of it reports.",
              "type": "string"
            }
          }
        }
      }
    },
    "printOffset": {
      "additionalProperties": false,
      "description": "Printer calibration: moves `bodyRect` and every element together by this shift, so text lands on the preprinted fields of a printer that feeds the sheet off true; the background, which stands for the sheet, stays. Defaults to no shift. A rect the shift moves past the sheet edge is refused at that rect.",
      "type": "object",
      "required": [
        "x",
        "y"
      ],
      "properties": {
        "x": {
          "minimum": -72,
          "maximum": 72,
          "description": "Horizontal shift in PDF points, positive to the right; at most 72 either way",
          "type": "number"
        },
        "y": {
          "minimum": -72,
          "maximum": 72,
          "description": "Vertical shift in PDF points, positive downward; at most 72 either way",
          "type": "number"
        }
      }
    },
    "bodyRect": {
      "additionalProperties": false,
      "description": "Writing area in PDF points (origin top-left). The Markdown body flows only inside this rect on every page; it must lie inside the page once `printOffset` moves it, and be at least 36 pt wide and 36 pt tall.",
      "type": "object",
      "required": [
        "x",
        "y",
        "width",
        "height"
      ],
      "properties": {
        "x": {
          "minimum": 0,
          "maximum": 14400,
          "description": "Left edge in PDF points, measured from the left page edge",
          "type": "number"
        },
        "y": {
          "minimum": 0,
          "maximum": 14400,
          "description": "Top edge in PDF points, measured down from the top page edge",
          "type": "number"
        },
        "width": {
          "exclusiveMinimum": 0,
          "maximum": 14400,
          "description": "Width in PDF points; greater than 0",
          "type": "number"
        },
        "height": {
          "exclusiveMinimum": 0,
          "maximum": 14400,
          "description": "Height in PDF points; greater than 0",
          "type": "number"
        }
      }
    },
    "elements": {
      "minItems": 0,
      "maxItems": 24,
      "description": "Positioned text and images painted outside `bodyRect`, plus calibration frames that may outline any rect including `bodyRect`; later elements paint over earlier ones. Text fragment values and frame labels of all elements together hold at most 16384 characters.",
      "type": "array",
      "items": {
        "additionalProperties": false,
        "description": "Positioned element. Text and image rects must not overlap `bodyRect`; frame rects may.",
        "type": "object",
        "required": [
          "rect",
          "pages",
          "content"
        ],
        "properties": {
          "rect": {
            "additionalProperties": false,
            "description": "Rectangle in PDF points (1 mm = 72 / 25.4 pt) with the origin at the top-left page corner; it must lie inside the page once `printOffset` moves it",
            "type": "object",
            "required": [
              "x",
              "y",
              "width",
              "height"
            ],
            "properties": {
              "x": {
                "minimum": 0,
                "maximum": 14400,
                "description": "Left edge in PDF points, measured from the left page edge",
                "type": "number"
              },
              "y": {
                "minimum": 0,
                "maximum": 14400,
                "description": "Top edge in PDF points, measured down from the top page edge",
                "type": "number"
              },
              "width": {
                "exclusiveMinimum": 0,
                "maximum": 14400,
                "description": "Width in PDF points; greater than 0",
                "type": "number"
              },
              "height": {
                "exclusiveMinimum": 0,
                "maximum": 14400,
                "description": "Height in PDF points; greater than 0",
                "type": "number"
              }
            }
          },
          "pages": {
            "description": "Pages that paint the element: `first`, `last`, or `all`. In a one-page document `first` and `last` are the same page.",
            "type": "string",
            "enum": [
              "first",
              "last",
              "all"
            ]
          },
          "content": {
            "description": "What the element paints: wrapped `text`, a contained `image`, or a thin gray `frame` outline for calibration sheets. A frame may overlap or equal `bodyRect` so a calibration sheet can outline the writing area; its label is clipped to the rect and never fails the request.",
            "anyOf": [
              {
                "additionalProperties": false,
                "type": "object",
                "required": [
                  "kind",
                  "lines",
                  "fontSize"
                ],
                "properties": {
                  "kind": {
                    "const": "text",
                    "type": "string"
                  },
                  "lines": {
                    "minItems": 1,
                    "maxItems": 32,
                    "description": "Logical lines stacked as one block, placed by `verticalAlign`; each one word-wraps to the rect width",
                    "type": "array",
                    "items": {
                      "minItems": 0,
                      "maxItems": 16,
                      "description": "One logical line as concatenated fragments; an empty line paints a blank line",
                      "type": "array",
                      "items": {
                        "description": "A run of text inside a line: literal `text`, the current 1-based `pageNumber`, or the document `totalPages`, each resolved per page. `fontRole` defaults to `regular`.",
                        "anyOf": [
                          {
                            "additionalProperties": false,
                            "type": "object",
                            "required": [
                              "kind",
                              "value"
                            ],
                            "properties": {
                              "kind": {
                                "const": "text",
                                "type": "string"
                              },
                              "value": {
                                "minLength": 1,
                                "maxLength": 1024,
                                "pattern": "^[^\\u0000-\\u001F\\u007F\\u0085\\u2028\\u2029]*$",
                                "description": "Literal text without line breaks or control characters (no CR, LF, TAB, or other C0 control, DEL, NEL, U+2028, or U+2029); start a new entry in `lines` to break the line. Spaces are kept between fragments and are where lines wrap; a no-break space (U+00A0, U+2007, U+202F) keeps its neighbours on one line. Every `value` and frame `label` of the request together hold at most 16384 characters.",
                                "type": "string"
                              },
                              "fontRole": {
                                "description": "Font weight and style to apply to this text within the active font stack",
                                "type": "string",
                                "enum": [
                                  "regular",
                                  "bold",
                                  "italic",
                                  "boldItalic"
                                ]
                              }
                            }
                          },
                          {
                            "additionalProperties": false,
                            "type": "object",
                            "required": [
                              "kind"
                            ],
                            "properties": {
                              "kind": {
                                "const": "pageNumber",
                                "type": "string"
                              },
                              "fontRole": {
                                "description": "Font weight and style to apply to this text within the active font stack",
                                "type": "string",
                                "enum": [
                                  "regular",
                                  "bold",
                                  "italic",
                                  "boldItalic"
                                ]
                              }
                            }
                          },
                          {
                            "additionalProperties": false,
                            "type": "object",
                            "required": [
                              "kind"
                            ],
                            "properties": {
                              "kind": {
                                "const": "totalPages",
                                "type": "string"
                              },
                              "fontRole": {
                                "description": "Font weight and style to apply to this text within the active font stack",
                                "type": "string",
                                "enum": [
                                  "regular",
                                  "bold",
                                  "italic",
                                  "boldItalic"
                                ]
                              }
                            }
                          }
                        ]
                      }
                    }
                  },
                  "fontSize": {
                    "minimum": 4,
                    "maximum": 72,
                    "description": "Font size in PDF points used when the text fits the rect",
                    "type": "number"
                  },
                  "minFontSize": {
                    "minimum": 4,
                    "maximum": 72,
                    "description": "Smallest font size in PDF points the text may shrink to, in 0.5 pt steps from `fontSize`, to fit the rect. Must not exceed `fontSize`; defaults to `fontSize` (no shrinking). Text that does not fit at this size fails the request with 422 `layout_element_overflow`.",
                    "type": "number"
                  },
                  "align": {
                    "description": "Horizontal alignment inside the rect; defaults to left",
                    "type": "string",
                    "enum": [
                      "left",
                      "center",
                      "right"
                    ]
                  },
                  "verticalAlign": {
                    "description": "Vertical position of the text block inside the rect; defaults to top. `middle` centers it, which keeps a single line on a printed rule when the rect straddles it",
                    "type": "string",
                    "enum": [
                      "top",
                      "middle",
                      "bottom"
                    ]
                  },
                  "fontFamily": {
                    "$ref": "#/$defs/Render.Body.rules.item.set.family"
                  }
                }
              },
              {
                "additionalProperties": false,
                "type": "object",
                "required": [
                  "kind",
                  "image"
                ],
                "properties": {
                  "kind": {
                    "const": "image",
                    "type": "string"
                  },
                  "image": {
                    "maxLength": 4194304,
                    "pattern": "^(https://|data:image/|[Bb][Ll][Ii][Nn][Kk]://[Aa][Ss][Ss][Ee][Tt]/)",
                    "description": "Image as an `https://` URL, a `data:image/` URL, or a staged `blink://asset/<id>` handle, read as a Markdown image of the same source is (png, jpeg, gif, tiff, webp, or svg+xml), scaled to fit inside the rect preserving its aspect ratio and anchored to its top. A source that does not paint is refused at this field with the code a Markdown image of it reports; text inside an SVG paints from the body family, then the glyph fallback, then the emoji families of `fontStack`.",
                    "type": "string"
                  },
                  "align": {
                    "description": "Horizontal alignment inside the rect; defaults to left",
                    "type": "string",
                    "enum": [
                      "left",
                      "center",
                      "right"
                    ]
                  }
                }
              },
              {
                "additionalProperties": false,
                "type": "object",
                "required": [
                  "kind"
                ],
                "properties": {
                  "kind": {
                    "const": "frame",
                    "type": "string"
                  },
                  "label": {
                    "minLength": 1,
                    "maxLength": 128,
                    "pattern": "^[^\\u0000-\\u001F\\u007F\\u0085\\u2028\\u2029]*[^\\s\\u0000-\\u001F\\u007F\\u0085\\u2028\\u2029][^\\u0000-\\u001F\\u007F\\u0085\\u2028\\u2029]*$",
                    "description": "Small one-line caption (no line breaks or control characters) painted inside the top-left corner of the outline; counts toward the 16384-character total of element text",
                    "type": "string"
                  }
                }
              }
            ]
          }
        }
      }
    }
  },
  "$defs": {
    "Render.Body.fontStack": {
      "minItems": 1,
      "maxItems": 4,
      "title": "Hosted font cascade",
      "description": "Hosted families of the document cascade: exactly one weighted family (`inter`, `lora`, or `source-serif-4`) sets body and heading text, and emoji and code families add their roles; a stack with no weighted family or with two is refused with a 400 here. Every hosted family is available on every plan and by name without this list: text in another weighted family names it in a `family` field or a `typography` binding. When `googleFontsUrls` is set, the Google family takes the body/heading slot and any weighted family in the stack is ignored (the stack then supplies only the emoji and code roles). Emoji families are appended automatically when omitted",
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "inter",
          "lora",
          "source-serif-4",
          "noto-emoji-mono",
          "twemoji-mozilla",
          "jetbrains-mono"
        ]
      }
    },
    "Render.Body.glyphFallback": {
      "title": "Fallback family for missing characters",
      "description": "The family a character paints in when the run's own font lacks it, tried before the emoji fonts. It takes any family a `typography` role binds, or a `typography.named` role. Omitted, `inter`.",
      "anyOf": [
        {
          "const": "inter",
          "type": "string"
        },
        {
          "const": "lora",
          "type": "string"
        },
        {
          "const": "source-serif-4",
          "type": "string"
        },
        {
          "pattern": "^[A-Za-z][A-Za-z0-9 ]{0,63}$",
          "description": "Google Fonts display name from the declared pool (e.g. `Playfair Display`)",
          "type": "string"
        },
        {
          "pattern": "^[a-z][a-z0-9-]{0,15}$",
          "description": "Reference to a custom role declared in `typography.named` (e.g. `display`)",
          "type": "string"
        },
        {
          "pattern": "^[Bb][Ll][Ii][Nn][Kk]://[Aa][Ss][Ss][Ee][Tt]/[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])",
          "description": "Handle `blink://asset/<sha256>` of a TrueType or OpenType CFF font uploaded through the MCP (`prepare_upload` with `kind: \"font\"`, then `complete_upload`) under the calling credential. The handle names one face, and that face is the whole family: its weight (OS/2 weight class) and italic style are read from the font, and a `weight`, `italic`, or `<role>.<variant>` request for any other cut is rejected at validate",
          "type": "string"
        }
      ]
    },
    "Render.Body.googleFontsUrls": {
      "minItems": 1,
      "maxItems": 2,
      "title": "Google Fonts URL list",
      "description": "One to 2 Google Fonts URLs whose families, at most 4 in all, form one pool. The first family of the first URL paints body text, and the first family of the second URL (or else the second family of the first) paints headings, unless `typography` binds those roles.",
      "type": "array",
      "items": {
        "pattern": "^https?://(fonts\\.(googleapis|google|gstatic)\\.com|www\\.google\\.com/fonts)/\\S+$",
        "maxLength": 2048,
        "title": "Google Fonts URL",
        "description": "A Google Fonts URL in one of these forms: the CSS API (`https://fonts.googleapis.com/css2?family=…`), a family page as the browser shows it (`https://fonts.google.com/specimen/<Family>`, also under `/noto/specimen/` or `https://www.google.com/fonts/specimen/<Family>`, with any tab, query, or anchor), a download link (`https://fonts.google.com/download?family=<Family>`), or a share or selection link (`https://fonts.google.com/share?selection.family=…`). A search page or a `fonts.gstatic.com` font file names no family and is refused with the family page to use, and an `http://` link with its `https://` form. Weight axes list the CSS cuts fetched (100–900, at most 9 per family). BlinkPDF fetches the wght, ital, and opsz axes on every load of that family (body, heading, display, and any other role, including a `typography.pool` override). A declared opsz range that covers 32 uses that optical size; a range that misses it uses the nearer endpoint; a single positive opsz token is kept. Any other axis (for example wdth) is ignored. A family whose URL does not declare opsz, such as Playfair Display, is fetched without an optical size. Up to 4 families per URL, and at most 4 in the merged pool across all URLs (more URLs do not raise the total)",
        "type": "string"
      }
    },
    "Render.Body.page.size.anyOf9": {
      "type": "array",
      "items": {
        "minimum": 1,
        "maximum": 14400,
        "type": "number"
      },
      "minItems": 2,
      "maxItems": 2
    },
    "Render.Body.rules.item.set.family": {
      "description": "A text family: a hosted weighted key, a declared Google Fonts display name, or a `typography.named` role reference",
      "anyOf": [
        {
          "const": "inter",
          "type": "string"
        },
        {
          "const": "lora",
          "type": "string"
        },
        {
          "const": "source-serif-4",
          "type": "string"
        },
        {
          "pattern": "^[A-Za-z][A-Za-z0-9 ]{0,63}$",
          "description": "Google Fonts display name from the declared pool (e.g. `Playfair Display`)",
          "type": "string"
        },
        {
          "pattern": "^[a-z][a-z0-9-]{0,15}$",
          "description": "Reference to a custom role declared in `typography.named` (e.g. `display`)",
          "type": "string"
        }
      ]
    },
    "Render.Body.typography": {
      "additionalProperties": false,
      "title": "Font role selection",
      "description": "The family each text role paints in: `body`, `heading`, and up to 2 `named` roles each bind a hosted weighted key, a declared Google Fonts display name, or the `blink://asset/<sha256>` handle of a font uploaded with prepare_upload; `pool` and `variantMap` choose which Google Fonts weights load and paint.",
      "type": "object",
      "properties": {
        "body": {
          "$ref": "#/$defs/Render.Body.typography.body"
        },
        "heading": {
          "$ref": "#/$defs/Render.Body.typography.body"
        },
        "pool": {
          "minItems": 1,
          "maxItems": 4,
          "description": "Optional override of families parsed from `googleFontsUrls` (max 4 distinct names)",
          "type": "array",
          "items": {
            "additionalProperties": false,
            "description": "Optional override of a family parsed from `googleFontsUrls` (weights and italic flag)",
            "type": "object",
            "required": [
              "name"
            ],
            "properties": {
              "name": {
                "pattern": "^[A-Za-z][A-Za-z0-9 ]{0,63}$",
                "description": "Google Fonts family display name",
                "type": "string"
              },
              "weights": {
                "minItems": 1,
                "maxItems": 9,
                "description": "Explicit upright weights to declare for this family (sorted ascending, unique)",
                "type": "array",
                "items": {
                  "minimum": 100,
                  "maximum": 900,
                  "multipleOf": 100,
                  "description": "Font weight on the CSS scale in steps of 100: one of 100, 200, 300, 400, 500, 600, 700, 800, 900 (intermediate values like 450 are not valid weights)",
                  "type": "integer"
                }
              },
              "italic": {
                "description": "Whether to fetch italic cuts alongside the requested CSS weights",
                "type": "boolean"
              }
            }
          }
        },
        "named": {
          "minItems": 1,
          "maxItems": 2,
          "description": "Up to 2 custom roles binding a pool family, a hosted key, or an uploaded font handle to a name, referenced by a rule `set.font` (`<role>.<variant>`) and by `variantMap.role`",
          "type": "array",
          "items": {
            "additionalProperties": false,
            "description": "Binds a custom role name to a hosted key, a pool display name, or an uploaded font handle",
            "type": "object",
            "required": [
              "role",
              "family"
            ],
            "properties": {
              "role": {
                "pattern": "^[a-z][a-z0-9-]{0,15}$",
                "description": "Custom role identifier referenced by a `FontKey` and by rule `set.family`. Lowercase kebab-case (a leading letter, then lowercase letters, digits, or hyphens, max 16 chars), e.g. `accent`, `code-emphasis` — not camelCase or snake_case, so it can appear in a `<role>.<variant>` font key",
                "type": "string"
              },
              "family": {
                "$ref": "#/$defs/Render.Body.typography.body"
              }
            }
          }
        },
        "headingVariant": {
          "description": "Hosted heading ladder policy. Applies only when the heading family is a hosted weighted key. Omit or `bold` loads the registry heading cuts (Inter: SemiBold base H1–H6, Bold via strong). `regular` forces body-symmetric heading slots (same faces as body). A Google Fonts or uploaded-font heading ignores this field",
          "type": "string",
          "enum": [
            "regular",
            "bold"
          ]
        },
        "variantMap": {
          "minItems": 1,
          "maxItems": 16,
          "description": "Per-family variant-to-weight mapping. Each entry optionally narrows by `role` (body/heading/named) and, for headings, by `depth`. Resolution is by specificity: depth > role > family. Up to 16 entries to allow multiple (role, depth) slots per family.",
          "type": "array",
          "items": {
            "additionalProperties": false,
            "type": "object",
            "required": [
              "family"
            ],
            "properties": {
              "family": {
                "pattern": "^[A-Za-z][A-Za-z0-9 ]{0,63}$",
                "description": "Google Fonts family the variant map applies to",
                "type": "string"
              },
              "role": {
                "description": "Semantic role the weights apply to. Omitted = family-wide (current behavior). `depth` further narrows `role: \"heading\"`.",
                "anyOf": [
                  {
                    "const": "body",
                    "type": "string"
                  },
                  {
                    "const": "heading",
                    "type": "string"
                  },
                  {
                    "pattern": "^[a-z][a-z0-9-]{0,15}$",
                    "description": "Custom role declared in `typography.named` (e.g. `display`)",
                    "type": "string"
                  }
                ]
              },
              "depth": {
                "description": "Heading depth(s) the weights apply to (1–6). Only valid with `role: \"heading\"`. A single int or an array (OR). An entry with `depth` wins over a role-only entry, which wins over a family-only entry.",
                "anyOf": [
                  {
                    "minimum": 1,
                    "maximum": 6,
                    "type": "integer"
                  },
                  {
                    "minItems": 1,
                    "maxItems": 6,
                    "type": "array",
                    "items": {
                      "minimum": 1,
                      "maximum": 6,
                      "type": "integer"
                    }
                  }
                ]
              },
              "regular": {
                "minimum": 100,
                "maximum": 900,
                "multipleOf": 100,
                "description": "Font weight on the CSS scale in steps of 100: one of 100, 200, 300, 400, 500, 600, 700, 800, 900 (intermediate values like 450 are not valid weights)",
                "type": "integer"
              },
              "italic": {
                "minimum": 100,
                "maximum": 900,
                "multipleOf": 100,
                "description": "Font weight on the CSS scale in steps of 100: one of 100, 200, 300, 400, 500, 600, 700, 800, 900 (intermediate values like 450 are not valid weights)",
                "type": "integer"
              },
              "bold": {
                "minimum": 100,
                "maximum": 900,
                "multipleOf": 100,
                "description": "Font weight on the CSS scale in steps of 100: one of 100, 200, 300, 400, 500, 600, 700, 800, 900 (intermediate values like 450 are not valid weights)",
                "type": "integer"
              },
              "boldItalic": {
                "minimum": 100,
                "maximum": 900,
                "multipleOf": 100,
                "description": "Font weight on the CSS scale in steps of 100: one of 100, 200, 300, 400, 500, 600, 700, 800, 900 (intermediate values like 450 are not valid weights)",
                "type": "integer"
              }
            }
          }
        }
      }
    },
    "Render.Body.typography.body": {
      "title": "Typography role family",
      "description": "The family a typography role is bound to: a hosted weighted key, a declared Google Fonts display name, a `typography.named` role reference, or the `blink://asset/<sha256>` handle of a font uploaded with `prepare_upload` (`kind: \"font\"`)",
      "anyOf": [
        {
          "const": "inter",
          "type": "string"
        },
        {
          "const": "lora",
          "type": "string"
        },
        {
          "const": "source-serif-4",
          "type": "string"
        },
        {
          "pattern": "^[A-Za-z][A-Za-z0-9 ]{0,63}$",
          "description": "Google Fonts display name from the declared pool (e.g. `Playfair Display`)",
          "type": "string"
        },
        {
          "pattern": "^[a-z][a-z0-9-]{0,15}$",
          "description": "Reference to a custom role declared in `typography.named` (e.g. `display`)",
          "type": "string"
        },
        {
          "pattern": "^[Bb][Ll][Ii][Nn][Kk]://[Aa][Ss][Ss][Ee][Tt]/[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])",
          "description": "Handle `blink://asset/<sha256>` of a TrueType or OpenType CFF font uploaded through the MCP (`prepare_upload` with `kind: \"font\"`, then `complete_upload`) under the calling credential. The handle names one face, and that face is the whole family: its weight (OS/2 weight class) and italic style are read from the font, and a `weight`, `italic`, or `<role>.<variant>` request for any other cut is rejected at validate",
          "type": "string"
        }
      ]
    },
    "Template.layout-document.Body.metadata": {
      "additionalProperties": false,
      "description": "PDF document metadata embedded in the rendered file. These fields are visible to anyone who opens the PDF — never include PHI, secrets, or other sensitive data.",
      "type": "object",
      "required": [
        "title"
      ],
      "properties": {
        "title": {
          "minLength": 1,
          "maxLength": 1024,
          "pattern": "\\S",
          "description": "Document title embedded in the PDF metadata",
          "type": "string"
        },
        "author": {
          "minLength": 1,
          "maxLength": 1024,
          "pattern": "\\S",
          "description": "Document author embedded in the PDF metadata",
          "type": "string"
        },
        "subject": {
          "minLength": 1,
          "maxLength": 1024,
          "pattern": "\\S",
          "description": "Document subject embedded in the PDF metadata",
          "type": "string"
        },
        "keywords": {
          "maxItems": 32,
          "description": "Keywords embedded in the PDF metadata",
          "type": "array",
          "items": {
            "minLength": 1,
            "maxLength": 256,
            "pattern": "\\S",
            "description": "A single keyword",
            "type": "string"
          }
        },
        "creator": {
          "minLength": 1,
          "maxLength": 1024,
          "pattern": "\\S",
          "description": "Name of the application that authored the source document",
          "type": "string"
        },
        "creationDate": {
          "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$",
          "description": "Creation timestamp as an ISO 8601 date-time",
          "type": "string"
        },
        "modificationDate": {
          "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$",
          "description": "Last-modification timestamp as an ISO 8601 date-time",
          "type": "string"
        },
        "trapped": {
          "description": "Trapping state recorded in the PDF metadata",
          "type": "string",
          "enum": [
            "true",
            "false",
            "unknown"
          ]
        }
      }
    }
  }
}