> ## Documentation Index
> Fetch the complete documentation index at: https://docs.corti.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Guided Documents for Clinical Synthesis

> Generate structured, schema-controlled clinical notes from stored templates, ad-hoc section assemblies, or fully inline definitions via the Corti API POST /documents endpoint for high-quality, robust healthcare AI document generation

<Badge className="accent-badge" shape="rounded">Beta</Badge>

Generate structured, schema-controlled clinical documents from flexible input sources such as transcript, fact or other documents — utilizing persisted [Sections & Templates](/textgen/sections-templates-overview), ad-hoc section assemblies, or fully inline definitions — on a single REST endpoint, `POST /documents`.
Built for healthcare AI workflows where output shape, provenance and reproducibility matter as much as the generated content itself.

Guided Documents generation is **interaction-optional**: supply explicit `context` (text / transcript / facts) for stateless calls, or supply an `interactionId` to pull facts and transcripts already attached to an interaction. See the full API specification on [POST /documents](/api-reference/guided-documents/generate-a-structured-document).

<Card>
  A single endpoint with four template-supply paths — from a plain `templateRef` for production traffic to a fully inline `dynamicTemplate` for prototyping. Every path returns a typed document plus the resolved `templateId` / `templateVersionId` so generations are auditable and drift-proof.

  <Tip>
    New to Corti's authoring surface? Start with [Sections & Templates](/textgen/sections-templates-overview) to learn how to author the resources you'll reference here.
  </Tip>
</Card>

<Note>
  The `POST /documents` endpoint is available in beta. Feedback welcome at [help@corti.ai](mailto:help@corti.ai) or via your Corti contact.
</Note>

***

## Features

<Tip>Click on the cards to learn more...</Tip>

<CardGroup cols={2}>
  <Card title="Flexible request shape" href="#the-four-template-supply-paths" icon="route">
    One endpoint, four template-supply paths — reference a stored template, patch it with overrides, assemble from stored sections, or define everything inline. Pick the right shape per call.
  </Card>

  <Card title="Combine input context" href="#input-context" icon="message-square-text">
    Pass any combination of `text`, `transcript` or `facts` items explicitly.
  </Card>

  <Card title="Auto-pull context" href="#input-context" icon="workflow">
    Let the API do the work for you and simply reference an `interactionId` to pull and pass attached facts and transcripts implicitly to the model.
  </Card>

  <Card title="Output schema control" href="/textgen/section-schemas" icon="braces">
    Steer generation into typed shapes and structured data outputs — `string`, `number`, `boolean`, `array`, `object` — combined with rendering primitives like `itemFormat` and `fieldFormat` for advanced string outputs.
  </Card>

  <Card title="Drift-proof aggregates" href="#auto-generated-template-aggregates-30-day-retention" icon="camera">
    Paths 2–4 persist a snapshot of the resolved template + sections used. The returned `templateId` reproduces the exact generation.
  </Card>
</CardGroup>

***

## The four template-supply paths

The endpoint is designed for the following needs:

* **Repeatable, governed generation** — reference a stored, versioned template by UUID.
* **Light-touch overrides** — keep your base template, but patch a section's title, instructions or output schema for a single call.
* **Compose at runtime** — pick stored sections out of your library and assemble them on the fly.
* **Try something new** — define everything inline without saving anything first.

Whichever path you pick, the response is a structured document mapped to the resolved template's section keys, plus the `templateId` and `templateVersionId` that were used.

You pick **one** path per call. The four behave differently with respect to whether a new template aggregate is persisted as a side-effect of the call.

Each path has its own page so you can land directly on the one that fits your scenario.

<CardGroup cols={2}>
  <Card title="Path 1 — Plain templateRef" href="/textgen/documents-guided-synthesis/path-1-templateref" icon="circle-1">
    Reference a stored template by UUID. No overrides, no side-effects. The lightest path for production traffic.
  </Card>

  <Card title="Path 2 — templateRef + overrides" href="/textgen/documents-guided-synthesis/path-2-templateref-overrides" icon="circle-2">
    Keep your base template, patch instructions / heading / schema for a single call. Auto-generates a drift-proof aggregate.
  </Card>

  <Card title="Path 3 — assemblyTemplate" href="/textgen/documents-guided-synthesis/path-3-assembly" icon="circle-3">
    Build a template on the fly from existing stored sections. Useful for EHR-field-driven note composition.
  </Card>

  <Card title="Path 4 — dynamicTemplate" href="/textgen/documents-guided-synthesis/path-4-dynamic" icon="circle-4">
    Define the template and every section inline. Ideal for prototyping and one-off generations.
  </Card>
</CardGroup>

### When to use which path

<Card>
  <Tabs>
    <Tab title="Production ambient flow">
      Use **Path 1 (plain `templateRef`)** with a pinned `templateVersionId` for reproducibility. No side-effects, fastest path, easiest to audit.
    </Tab>

    <Tab title="Per-user customization">
      Use **Path 2 (`templateRef` + overrides)** so end users can tweak section titles, writing style or schema without you maintaining a template per user. Persist the returned `templateId` if you want their customization to become a saved variant.
    </Tab>

    <Tab title="EHR-field-driven note">
      Use **Path 3 (`assemblyTemplate`)** to map relevant EHR fields to sections at runtime, then assemble exactly the sections you need for that encounter.
    </Tab>

    <Tab title="Prototype / experimental">
      Use **Path 4 (`dynamicTemplate`)** when iterating on prompts or schemas before promoting them into your stored library.
    </Tab>
  </Tabs>
</Card>

<Note>
  **Drift-proof snapshots.** For every path except plain `templateRef`, the auto-generated template aggregate **snapshots the fully resolved content at request time**. Subsequent edits to base templates or sections do **not** affect previously generated documents. You can also reuse that auto-generated template in future calls by reading its ID from the response.
</Note>

## Input context

Every call needs to tell the model **what to reason over**. Supply **exactly one** of:

* **`context`** — an ordered array of explicit context items. Each item is one of three discriminated types (see below).
* **`interactionId`** — when supplied, all non-discarded facts and transcripts already attached to the referenced interaction are passed implicitly as input context.

<Note>`context` and `interactionId` are currently **mutually exclusive** — supply one or the other, never both. Combining them to extend the implicit interaction context with extra explicit items is on the roadmap; see the [release notes](/release-notes/textgen) for status.</Note>

### Context item types

| `type`       | Required field                                                                                         | Use when                                                                    |
| :----------- | :----------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- |
| `text`       | `text` *(string)*                                                                                      | Pasting a referral letter, dictation, EHR field, or any free-form snippet   |
| `transcript` | `transcript` *(minimal shape — `transcripts[]` of segments, optional `metadata.participantsRoles`)*    | Feeding a conversation transcript directly without going through `/streams` |
| `facts`      | `facts` *(array, required)* — each entry has `text` *(string, required)*; `group` *(string, optional)* | Supply clinical facts as a `facts` array on a `type: "facts"` context item. |

```json title="Mixed context array" expandable theme={null}
"context": [
  { "type": "text", "text": "Referral letter:\nPatient referred by GP for evaluation of …" },
  {
    "type": "transcript",
    "transcript": {
      "metadata": {
        "participantsRoles": [
          { "channel": 0, "role": "clinician" },
          { "channel": 1, "role": "patient" }
        ]
      },
      "transcripts": [
        { "channel": 1, "text": "I've had this itchy rash for a week.", "start": 1200, "end": 4100 },
        { "channel": 0, "text": "Any new soaps or detergents?", "start": 4500, "end": 6300 }
      ]
    }
  },
  {
    "type": "facts",
    "facts": [
      { "text": "Allergic to birch pollen since childhood", "group": "allergies" },
      { "text": "Itchy rash, started last week", "group": "history-of-present-illness" }
    ]
  }
]
```

<Tip>If transcript segments include `start`/`end` timestamps, the model interleaves them with other context items by time where possible. Otherwise array order is honored.</Tip>

## Common request shape

All four paths share three top-level fields and differ only in which template-supply field they carry:

```json title="Shared shape" theme={null}
{
  "context":        [ /* OR omit and use interactionId */ ],
  "interactionId":  "<uuid, optional>",
  "outputLanguage": "en-US",

  // Exactly one of:
  "templateRef":      { /* Path 1 or 2 */ },
  "assemblyTemplate": { /* Path 3 */ },
  "dynamicTemplate":  { /* Path 4 */ }
}
```

`outputLanguage` is required and uses a BCP-47 tag.

## Response shape

All four paths return the same response shape: a `document` object plus a `usageInfo` block. When the request uses the default retention policy, the document is also persisted with its own `id`, `createdAt` and `updatedAt`. With `X-Corti-Retention-Policy: none`, the response is the ephemeral variant — the same `document` body without the persisted-resource fields.

```json title="Response (default retention)" expandable theme={null}
{
  "document": {
    "id":                "<your-document-id>",
    "name":              "Allergy follow-up note",
    "templateId":        "<your-template-id>",
    "templateVersionId": "<your-version-id>",
    "language":          "en-US",
    "stringDocument": {
      "<your-section-id>": "32-year-old female presenting with an itchy rash that began last week …",
      "<your-section-id>": "- Initiate corticosteroid cream\n- Follow up in 4 weeks"
    },
    "structuredDocument": {
      "<your-section-id>": "32-year-old female presenting with an itchy rash that began last week …",
      "<your-section-id>": ["Initiate corticosteroid cream", "Follow up in 4 weeks"]
    },
    "createdAt": "2026-05-20T10:00:00Z",
    "updatedAt": "2026-05-20T10:00:00Z"
  },
  "usageInfo": {
    "creditsConsumed": 0.029016
  }
}
```

<Note>The keys in `stringDocument` (and `structuredDocument`) are **section UUIDs**, not slugs or section keys. The slug-looking placeholders above stand in for the actual UUIDs. See [Mapping response section IDs back to your sections](#mapping-response-section-ids-back-to-your-sections) below for how to correlate them to the sections in your request.</Note>

* **`document.id`** — the persisted document UUID. Only present on the default-retention response; omitted when the request sets `X-Corti-Retention-Policy: none`.
* **`document.templateId` / `document.templateVersionId`** — the template (and version) that was actually used. For Path 1 this is the referenced template; for Paths 2–4 it is the newly persisted auto-generated aggregate. Store these if you want full traceability of what produced each document.
* **`document.stringDocument`** — an object keyed by **section ID** containing the rendered string output for each section. Always present.
* **`document.structuredDocument`** — present when one or more sections declare an `outputSchema`. Keyed by section ID; each entry holds the value typed as that section's schema declared — a string, number, boolean, object, or array. A string leaf is itself a valid `outputSchema`, so string-typed sections appear here too; presence of this field reflects *whether* a schema was declared, not whether its type is non-string.
* **`usageInfo.creditsConsumed`** — credits consumed for this request. Same shape as every other Corti endpoint that returns usage information.

### Mapping response section IDs back to your sections

Both `stringDocument` and `structuredDocument` are **keyed by section ID**. For Path 1 (plain `templateRef`) and Path 2 (`templateRef` + overrides) those IDs match the sections of the referenced template — you can map them straight back. For **Path 3 (`assemblyTemplate`)** and **Path 4 (`dynamicTemplate`)** the keys are IDs Corti generates for the auto-saved aggregate. Path 3 carries explicit `sectionRefs[].sectionId` you can correlate by, but **Path 4** is the gotcha: your request supplies an ordered list of `SectionGeneration` objects with no IDs of your own, so the response keys arrive as opaque server-side IDs with no obvious mapping back to your inline section definitions — meaning you can't reliably attach the right heading to each rendered section just from the response.

**Workaround until this is improved.** Use the returned `templateVersionId` to read the saved aggregate:

```
GET /documents/templates/{templateId}/versions/{templateVersionId}
```

The version's `sections[]` array preserves your request order and carries the resolved section IDs. Build a `position → sectionId → heading` mapping from that response, then key the rendered output against it.

<Note>This is a known DX gap for Path 4 specifically. The roadmap includes returning client-supplied identifiers (or section ordering) directly on the response so the follow-up GET isn't needed.</Note>

<Warning>The auto-generated aggregate the follow-up GET reads is itself subject to the [30-day retention window](#auto-generated-template-aggregates-30-day-retention). Do the mapping read soon after generation (typically in the same processing pass), or persist the mapping in your service. Don't rely on the aggregate still being there weeks later.</Warning>

## Auto-generated template aggregates — 30-day retention

Paths 2, 3 and 4 persist an **auto-generated template aggregate** as a side-effect of generation — a drift-proof snapshot of the resolved template + sections that were actually used. Path 1 (plain `templateRef`) is the exception: it uses the referenced template as-is and persists nothing new.

<Warning>
  **Auto-generated aggregates are retained for 30 days, then automatically cleaned up.**

  A scheduled job deletes auto-generated templates (and their versions) **30 days after they were created**. After cleanup, the `templateId` returned in the response will no longer resolve — `GET /documents/templates/{templateId}` and the version reads will return `404`.

  **If you need the aggregate to outlive the 30-day window** — for traceability, audit, or to keep it as a reusable template — convert it into a permanent project-owned resource **before the window expires**:

  1. `GET /documents/templates/{templateId}/versions/{templateVersionId}` to read the version raw — its `sections[]` array carries the resolved section IDs in request order.
  2. For each `sectionId` referenced, `GET /documents/sections/{sectionId}/versions/{versionId}` to read each section's resolved configuration.
  3. Re-create the configuration via fresh `POST /documents/sections` and `POST /documents/templates` requests in your project — **without** `inheritFromId` pointing at the auto-generated aggregate (since the parent will be deleted). The new resources are `source: user`, fully owned by your project, and stay live until you delete them.

  The auto-generated aggregate is a **convenience snapshot for the request that produced it**, not a long-term storage tier. Treat it as ephemeral.
</Warning>

## Errors and validation

| Status                     | When it happens                                                                                                                                                                                                                                                                                          |
| :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | Request body fails basic validation (e.g. missing `outputLanguage`, malformed JSON).                                                                                                                                                                                                                     |
| `404 Not Found`            | A referenced `templateId`, `templateVersionId` or `sectionId` does not exist.                                                                                                                                                                                                                            |
| `422 Unprocessable Entity` | Request is structurally valid but semantically rejected — e.g. none of `templateRef`/`assemblyTemplate`/`dynamicTemplate` supplied, the referenced template has no published version, an override targets a section not linked to the base template version, or you supplied more than one of the three. |
| `502 Bad Gateway`          | Document generation failed downstream.                                                                                                                                                                                                                                                                   |

<Note>Provide exactly **one** of `templateRef`, `assemblyTemplate`, `dynamicTemplate`. Supplying zero or more than one yields a `422`.</Note>

## Failure modes & generation behavior

Behaviors and edge cases worth knowing about before you build. Expand the topic you need.

<AccordionGroup>
  <Accordion title="All-or-nothing on generation failure">
    If any step in the generation pipeline fails — even after internal retries — the **entire request fails** with an appropriate error response. You will not receive a partial document with some sections filled and others silently missing.

    This is deliberate: partial documents are more dangerous in clinical workflows than an explicit failure. If your integration needs resilience for individual sections, generate them in separate requests so a single failure doesn't take down the whole document.
  </Accordion>

  <Accordion title="Duplicate sections in the same request">
    The API validates against using the same `sectionId` in the same POST /documents request and fail the request.
  </Accordion>

  <Accordion title="Empty sections — when the source has nothing relevant">
    How a section renders when the source material doesn't support meaningful content depends on what you defined:

    | Defined on the section                                                                                                                  | Behavior when nothing relevant in source                                                                                                       |
    | :-------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------- |
    | **`default` on the `outputSchema`**                                                                                                     | The section renders the default (e.g. `"Not discussed"`, `"Nil"`, `0`, `false`). Deterministic — downstream consumers can rely on it.          |
    | **Fallback phrasing in `instructions`** (e.g. `contentPrompt` says *"output 'Not assessed' if the source doesn't mention this domain"*) | The model emits the prompted fallback. Reliable in practice but ultimately steered by the LLM.                                                 |
    | **Neither set**                                                                                                                         | The LLM decides. It will often emit a brief "not discussed" phrasing; if it truly has nothing to say, the section returns an **empty string**. |

    For sections that always need to render a clear placeholder so downstream consumers can rely on consistent structure, **set `default` on the schema** *or* encode the fallback in `instructions`. Don't rely on the LLM picking the right empty-state phrasing on its own.
  </Accordion>
</AccordionGroup>

## How guided synthesis relates to other endpoints

* It is **separate** from `POST /interactions/{id}/documents` (Documents Classic). The classic endpoint continues to work as deprecated if you're still using `templateKey`-based generation. See [Migrate from Classic](/textgen/migrate-from-classic).
* It consumes the new resources authored via the [Sections](/textgen/section-creation) and [Templates](/textgen/template-creation) APIs.
* The `outputSchema` reference is shared with section authoring — see the [section-creation guide](/textgen/section-creation#configuring-the-generation-block).

## Next steps

<Columns cols={2}>
  <Card title="Create a Section" href="/textgen/section-creation" icon="list-tree">
    Author and version the sections you'll reference in Paths 1–3.
  </Card>

  <Card title="Create a Template" href="/textgen/template-creation" icon="puzzle">
    Compose sections into versioned templates ready for Path 1.
  </Card>

  <Card title="Corti Standards" href="/textgen/corti-standards" icon="library">
    Reference the Corti-curated section and template library from any of the four paths.
  </Card>

  <Card title="Migrate from Classic" href="/textgen/migrate-from-classic" icon="arrow-right-arrow-left">
    Field-by-field mapping from `POST /interactions/{id}/documents` to `POST /documents`.
  </Card>
</Columns>
