/documents/sections and /documents/templates API. See Create a Section for the full endpoint mechanics.Why schema is the headline feature
outputSchema on a section does two things at once:
- Declares the shape of what the LLM is allowed to emit — a string, a number, a structured object with named fields, an array of objects, and so on. The model is steered to fit this shape.
- Drives the rendering of that output via format strings (
fieldFormat,itemFormat) so the rendered Markdown/text matches your downstream consumer — an EHR field, a structured pipeline, a free-text note block.
Node types at a glance
outputSchema is one of five node types, discriminated by type. The table lists each type and the fields you can set; bold fields are required, the rest are optional.
items can itself be any node — including another array or object. That’s the lever for everything below.
Field reference — what each option does
Most fields are technically optional, butdescription is strongly recommended on every node — it doubles as a prompt to steer the LLM, not just metadata.
Common to all node types
String-specific
Number-specific
Boolean-specific
Array-specific
Object-specific
Composing schemas — quick reference
Where schema interacts with the rest of the prompt
The model receives, in priority order:- Section
instructions—contentPrompt,writingStylePrompt,miscPrompt. - Template-level instructions —
instructions.prompton the parent template (when used in a template). - Schema-level guidance — every
description,enum,pattern,default,minimum,maximum,minItems,maxItemsyou supply on theoutputSchema.
instructions.contentPrompt for the overall section content/scope; use schema descriptions for field-specific behavior.
Applying a schema to a Corti Standard section
A common pattern is: keep a Corti Standard section’s full prompt machinery (heading, contentPrompt, writingStylePrompt, miscPrompt) but swap its outputSchema for one of the patterns above — e.g. take the curated corti-hpi prompts and emit a structured object for an EHR pipeline.
Two ways to do this:
- Fork the Standard (persistent).
POST /documents/sectionswithinheritFromId: <standard-section-uuid>and onlygeneration.outputSchemain the body. Everything else is inherited. See Corti Standards — overrideoutputSchema. - Override per call (ephemeral). Submit the new
outputSchemainsidetemplateRef.overrides.sections[].generation.outputSchemaonPOST /documents. See Guided Synthesis — Path 2 and Customization Cookbook — Recipe 2.
outputSchema overrides are wholesale — whatever you submit fully replaces the parent’s schema; partial schemas are not merged. When the change is structural (e.g. string → object/array), consider overriding writingStylePrompt in the same request so the parent’s wording rules don’t conflict with the new shape.
Related
Create a Section
Corti Standards
Worked clinical examples
The patterns below are pulled from real Corti standard sections — adapted so you can copy/paste them into your ownPOST /documents/sections request, lift specific fields, or reference them as a parent via inheritFromId.
Each example shows the full outputSchema (you’d wrap it in the standard name/language/generation.instructions/generation.outputSchema envelope from Create a Section).
Example 1 — Fixed subheadings with explicit 'not discussed' vs 'nil' defaults
Example 1 — Fixed subheadings with explicit 'not discussed' vs 'nil' defaults
Not discussed. When the patient explicitly denied something, render Nil. Don’t let the model invent in-between phrases like “patient denies anything noteworthy.”Pattern: object with fieldFormat: "{key}\\n{value}\\n" (per-field iteration), fixed fields[], per-field default, and explicit guidance in each field’s description for the negation rule.fieldFormat: "{key}\n{value}\n" — per-field iteration):Example 2a — Dynamic organ-system labels (free)
Example 2a — Dynamic organ-system labels (free)
organ-system string field (no enum) and a finding string field, joined via fieldFormat: "{organ-system}: {finding}". The model creates as many entries as needed.Example 2b — Constrained organ-system labels (enum)
Example 2b — Constrained organ-system labels (enum)
organ-system field’s string node carries an enum of allowed values.Example 3 — Structured test results with measures and free-text findings
Example 3 — Structured test results with measures and free-text findings
result string that the model formats consistently (number + unit, or status placeholder for non-numeric studies), plus a typed status enum and a free-text findings field. Numeric typing is preserved per-test through the description instructions; the rendered line stays clean regardless of test type.result is always non-empty, so there’s no double-space gap, and the rendered line works for quantitative labs and imaging studies alike.Example 4 — Prefixes and standard phrasings via `itemFormat`
Example 4 — Prefixes and standard phrasings via `itemFormat`
Rp., and a separate dictation block where every utterance begins with a standard “Pt reports:” header.Pattern: custom itemFormat on the array. The {item} placeholder is the rendered item; everything else around it is literal text.{key}/{value} levers exist on objects via fieldFormat (e.g. "**{key}**: {value}") for Markdown-bold headings, or "## {key}\n{value}" for full Markdown headings.Example 5 — EHR placeholders that survive verbatim
Example 5 — EHR placeholders that survive verbatim
{treatment_shared_motherhood_ivf} — for an EHR system to substitute downstream. The LLM must not generate or paraphrase these; they’re scaffolding for the EHR, not content.Pattern: bake the placeholders directly into fieldFormat as literal text, escaping the curly braces. fieldFormat parses {fieldKey} as a variable substitution; doubled {{ and }} are escapes that render as a single literal { and } in the output. Any clinician-authored content goes into a normal field that is substituted.fieldFormat:- A single
{fieldKey}substitutes the value of that field. - A doubled
{{renders as a literal{in the output;}}renders as a literal}.
{treatment_var} in the output (single braces around a placeholder name), write {{treatment_var}} in the format string — that’s {{ (literal {) + treatment_var (literal text, no substitution because there’s no matching field) + }} (literal }).To emit literal {<value-of-field_1>} (literal braces around a substituted value), write {{{field_1}}} — that’s {{ + {field_1} + }}.To emit double-brace placeholders like {{treatment_var}} literally (Mustache/Handlebars style), each output brace needs its own escape: write {{{{treatment_var}}}} in the format string.{plan_notes}; the EHR placeholders pass through as literal text because their braces are escaped. No extra fields and no enum tricks needed.Example 6 — Different writing styles per subheader via field `description`
Example 6 — Different writing styles per subheader via field `description`
instructions.writingStylePrompt sets the global default; each field’s description carries the local style rule for that subheader. The model reads both and applies the field-level rule where the two disagree.description is enough vs. when to split into multiple sections. Use this pattern when the styles are contrasts within a coherent section that always renders as one block (e.g. a pain assessment). When the subheaders are large enough to be reused independently across templates — or have meaningfully different contentPrompt rules — it’s cleaner to promote each into its own section in the template instead.Example 7 — OT Activity & Participation (ICF-style fixed subcategories)
Example 7 — OT Activity & Participation (ICF-style fixed subcategories)
object + fieldFormat: "{key}\\n{value}\\n" (per-field iteration) + fixed fields[] with a per-field default of "Not assessed". This is the OT-clinic equivalent of the pre-op screening pattern in Example 1.default is "Not assessed". Downstream consumers see a stable structure across encounters.Example 8 — Standard phrases combined with LLM-generated content
Example 8 — Standard phrases combined with LLM-generated content
object + fieldFormat with full sentences containing {field} placeholders. The fixed phrases are just literal text in the format string; each {field} is substituted with model-generated content from fields[].Example 9 — Bullet points nested inside a numbered list
Example 9 — Bullet points nested inside a numbered list
array with a custom itemFormat containing the {item} placeholder. Each item is an object with a summary string field plus a nested details array whose own itemFormat includes a leading indent + bullet marker (e.g. " - {item}\n"). Compose summary + details via fieldFormat. The outer array applies its itemFormat once per top-level entry; the inner array’s itemFormat controls the indented bullet rendering.- The outer
array.itemFormat: "* {item}\n"puts an asterisk (or any literal marker you choose —-,•,1.) in front of each top-level item. - The inner
array.itemFormat: " - {item}\n"is the key lever: the two leading spaces indent each bullet under its parent, and-is the bullet marker. Adjust spacing for deeper nesting (e.g." * {item}\n"for a four-space-indented sub-sub-list). - The object’s
fieldFormat: "{summary}\n{details}"concatenates the summary line with the sub-bullets directly underneath, preserving the visual nesting. - If a plan item has no sub-bullets, the
detailsarray renders as empty and the entry collapses to just the summary line (item 3 above).
Example 10 — Diagnoses with indented detail bullets
Example 10 — Diagnoses with indented detail bullets
array with a custom itemFormat (e.g. "* {item}\n"), inner items are objects with a diagnosis summary string and a nested details array. The details.itemFormat uses the leading-indent + bullet marker trick to render each sub-point indented under its diagnosis.- Structurally identical schema shape (outer array + object item with
summary+ nesteddetailsarray). The pattern is reusable. - The field descriptions are diagnosis-specific:
diagnosisprompts for label + optional code;detailsprompts for findings, reasoning, status, management. - Same graceful-collapse behavior — if a diagnosis only has a name (and the source material doesn’t support sub-bullets), the entry renders as just the diagnosis line (item 4 above).
itemFormat has no built-in counter, so use string + a contentPrompt that instructs the model to emit numbered lines with two-space-indented sub-bullets. Trade-off: you lose the typed array-of-objects shape and any structuredDocument benefits.Example 11 — Keyword-driven classification (single enum value out)
Example 11 — Keyword-driven classification (single enum value out)
string + enum (closed value set) + a rich description that teaches the model the keyword-to-value mapping. Add default for the “no signal in the source” case so the field always renders something deterministic.enumis the hard constraint. The model can only emit one of the listed values —"admit","discharge","transfer","consult-pending","left-ama", or"undetermined". Free text never leaks through.descriptionis where the classification logic lives. Spell out which input keywords or phrases map to each enum value. The model reads this alongside the section’sinstructions.contentPromptwhen picking the output.defaultcovers the empty-state case — if the source material has nothing to support a disposition decision, the field renders"undetermined"deterministically. (Without a default, the model might still pick a value or return an empty string. With it, your downstream consumer always sees a known value.)
string + enum + descriptive keyword rules. For numeric scoring (Apgar, GCS, pain 0–10), use number + minimum/maximum instead — see Example 3 patterns.