---
audience: developers
summary: "Read an existing .docx, mutate a section, re-render \u2014 with fidelity-warning\
  \ handling."
title: Ingest + edit
path: tutorials/ingest-and-edit
status: published
---


# Tutorial: Ingest + edit

You'll: ingest an existing `.docx` you didn't author, inspect the fidelity envelope, surgically edit one paragraph, and re-render. Then you'll do the same with a ScaiScribe-authored document and see the difference the §4.7 breadcrumb makes.

## Setup

```python
import os, httpx
from scaiscribe import Client, ScaiKeyAuth

client = Client(
    "https://scaiscribe.scailabs.ai",
    auth=ScaiKeyAuth(
        client_id=os.environ["SCAIKEY_CLIENT_ID"],
        client_secret=os.environ["SCAIKEY_CLIENT_SECRET"],
    ),
    default_tenant_id=os.environ["SCAISCRIBE_TENANT_ID"],
)
```

## Part 1 — Externally-authored DOCX

### Step 1 — Ingest

```python
with open("contract.docx", "rb") as f:
    result = client.ingest_document(
        file=f.read(),
        format="docx",
        theme="office",
    )

print("Doc ID:", result.doc_id)
print("Fidelity score:", result.spec.fidelity.score)
for w in result.spec.fidelity.warnings:
    print(f"  {w.code}: {w.message}")
```

Sample output for a typical contract:

```
Doc ID: doc_01HXYZ...
Fidelity score: 0.85
  UNMAPPED_STYLE: Custom paragraph style 'ContractHeader' not mapped to the theme.
  INGEST_PANDOC_FOOTNOTE_DETECTED: Pandoc detected 3 footnote(s)…
  INGEST_IMAGE_DROPPED: Image dropped during ingest…
```

### Step 2 — Decide

| Score | What to do |
|---|---|
| ≥ 0.9, no critical warnings | Edit and re-render with confidence. |
| 0.6–0.9 | Inspect warnings; structural edits may not survive cleanly. |
| < 0.6 | Treat as a content extract; don't expect round-trip fidelity. |

For this contract at 0.85, edits to text bodies will work; footnotes will degrade to inline text on re-render.

### Step 3 — Edit one paragraph

```python
doc = client.get_document(result.doc_id)
spec = doc.spec

# Find the paragraph mentioning "twelve months" and update to "twenty-four months"
target_id = None
for el in spec["body"]:
    if el.get("type") == "paragraph" and "twelve months" in el.get("markdown", ""):
        target_id = el["elem_id"]
        break

assert target_id, "couldn't find the target paragraph"

client.patch_document(doc.doc_id, [{
    "op": "edit_element",
    "elem_id": target_id,
    "element": {
        "type": "paragraph",
        "markdown": "The agreement term is twenty-four months from the Effective Date.",
    },
}])
```

### Step 4 — Re-render

```python
result = client.finalize(doc.doc_id, formats=["docx", "pdf"])
for r in result.outputs:
    print(f"{r.format}: {r.download_url}")
```

The re-rendered document carries your edit. The original footnotes degrade to inline text (per the ingest warning); everything else is preserved.

## Part 2 — ScaiScribe-authored DOCX (the breadcrumb advantage)

### Setup — render a doc first

```python
doc = client.create_document(format="docx", theme="office", title="Quarterly")
client.add_element(doc.doc_id, {"type": "heading", "level": 1, "text": "Q4"})
client.add_element(doc.doc_id, {
    "type": "code_block",
    "language": "python",
    "content": "def revenue():\n    return 1_200_000",
})
client.add_element(doc.doc_id, {
    "type": "callout",
    "style": "info",
    "markdown": "Strong Q4 — exceeds plan.",
})
client.add_element(doc.doc_id, {"type": "page_break"})
client.add_element(doc.doc_id, {"type": "paragraph", "markdown": "Page 2 content."})

result = client.finalize(doc.doc_id, formats=["docx"])
docx_url = result.outputs[0].download_url

# Stream it down
with httpx.stream("GET", docx_url) as resp:
    with open("/tmp/q4.docx", "wb") as f:
        for chunk in resp.iter_bytes():
            f.write(chunk)
```

### Ingest the round-trip

```python
with open("/tmp/q4.docx", "rb") as f:
    result = client.ingest_document(
        file=f.read(),
        format="docx",
        theme="office",
    )

print("Fidelity score:", result.spec.fidelity.score)
print("Element types:", [el["type"] for el in result.spec.body])
```

Output:

```
Fidelity score: 1.0
Element types: ['heading', 'code_block', 'callout', 'page_break', 'paragraph']
```

Every element survives — including `code_block`, `callout`, and `page_break`. The §4.7 JSON breadcrumb at `scaiscribe/breadcrumbs.json` inside the .docx provided full equivalence; the ingester saw it and used it verbatim instead of reconstructing from mammoth.

### Why this matters

External documents lose information on ingest, because the OOXML doesn't always carry the structural distinctions we'd need (a "Code Block" paragraph and a regular "monospace text" paragraph look the same in raw .docx).

ScaiScribe-authored documents carry a small JSON sidecar declaring what each piece *is*. That makes the round-trip lossless within ScaiScribe even though Word/LibreOffice happily ignore the sidecar.

## Part 3 — Slow-path recovery without the breadcrumb

What if someone strips the breadcrumb (zip-edits the file, or it goes through a third-party tool that doesn't preserve unknown parts)?

For `code_block`, `callout`, and `page_break`, ScaiScribe's ingester has a backup path. It looks at the OOXML paragraph style names (`Code Block`, `Callout Info`, `Page Break`) and reconstructs the element type from those alone. This means:

- Strip the breadcrumb from a ScaiScribe DOCX → ingest still recovers code_block, callout, page_break correctly.
- Other lossy elements (chart, TOC) need the breadcrumb. Without it they degrade to image / inline-text approximations.

External documents that happen to use the same style names (Word's built-in "Code" style, etc.) won't trigger the recovery — the names have to be exact matches.

## Common patterns

### Inspect before edit

Always read the fidelity envelope before mutating. A `UNMAPPED_STYLE` warning on the section you're about to edit means your text edits will still work but the original visual styling won't survive.

### Batch your edits

PATCH accepts a list of operations applied atomically. Don't do 10 single-element PATCH calls — do one PATCH with 10 ops. Faster and safer.

### Don't re-ingest your own output

If you ingested a document, edited the spec, and re-rendered, the result is ScaiScribe-authored — has the breadcrumb. If you then re-ingest THAT, fidelity will be 1.0 and the spec round-trips perfectly. Don't be surprised when this looks better than the first ingest.

### Avoid double-render loops

A common antipattern: ingest → edit → render → store → later, ingest the stored render → edit → render → store again. Each ingest is fine, but you'll accumulate slight visual drift each time you go through formats that have lossy compilation (e.g. the breadcrumb covers element types but not custom formatting). Keep the spec as your source of truth and only render when you need a binary.

## Where to next

- [Ingestion concept](/docs/scaiscribe/concepts/ingestion) — full warning catalogue, format-specific paths.
- [Tutorials → Templates](/docs/scaiscribe/tutorials/templates) — when you don't want to edit existing content.
- [Reference → API](/docs/scaiscribe/reference/api) — `/v1/documents/ingest` shape.
