How to Ingest Product Specification Documents with Claude Code

How to ingest product specification documents in a Claude Code pipeline with EnConvert, returning heading-aware JSONL chunks with tables and code blocks intact

Get API key

A developer building a Claude Code agent needs to ingest product specification documents, including feature specs, API references, requirements documents, and technical design docs, before passing them into an agent’s context or retrieval layer. These documents are dense with structured content: tables, code blocks, and nested sections, and naive token-based splitting breaks across section boundaries, separates tables from their context, and strips the heading provenance that makes retrieval useful. EnConvert’s Ingest endpoint accepts a DOCX or PDF upload and returns JSONL chunks split at H1, H2, and H3 heading boundaries, with a 512 word soft cap and tables and code blocks kept whole even when they exceed the cap. Each chunk carries headings_path, section, chunk_index, word_count, and source_url metadata, ready to pass into a Claude Code agent or vector store.

Example

python
import json
import os
import time
import requests
import anthropic

# Step 1: Upload the product specification document to the Ingest endpoint
with open("product-specification.docx", "rb") as f:
    upload_response = requests.post(
        "https://api.enconvert.com/v2/ingest/files",
        headers={"X-API-Key": os.environ["ENCONVERT_API_KEY"]},
        files={"files": f},
    )

if upload_response.status_code == 402:
    raise RuntimeError("Ingest is not on your plan, or the monthly ops allowance is exhausted")
upload_response.raise_for_status()
job_id = upload_response.json()["job_id"]

# Step 2: Poll until the job reaches a terminal state
while True:
    status_response = requests.get(
        f"https://api.enconvert.com/v2/ingest/{job_id}",
        headers={"X-API-Key": os.environ["ENCONVERT_API_KEY"]},
    )
    status_response.raise_for_status()
    job = status_response.json()

    if job["status"] == "completed":
        break
    if job["status"] in {"failed", "canceled"}:
        raise RuntimeError(
            job.get("error_message") or f"Ingest job {job_id} ended with status {job['status']}"
        )
    time.sleep(5)

# Step 3: Download JSONL output from signed output_url (expires after 15 minutes)
jsonl_response = requests.get(job["output_url"])
jsonl_response.raise_for_status()
chunks = [json.loads(line) for line in jsonl_response.text.splitlines() if line.strip()]

# Tables and code blocks are kept whole even when they exceed the 512 word soft cap
# Step 4: Build context block from chunks, preserving heading path and section metadata
context = "\n\n".join(
    f"[section: {c['metadata']['section']} | headings: {' > '.join(c['metadata']['headings_path'])} | chunk: {c['metadata']['chunk_index']} | source: {c['metadata']['source_url']}]\n"
    f"{c.get('content')}"
    for c in chunks[:10]
)

# Step 5: Pass context into a Claude Code agent using the Anthropic SDK messages API
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=4096,
    system=(
        "Answer questions about the product specification using only the context provided. "
        "Cite the section path and chunk index for every claim. "
        "Quote tables and code blocks exactly as they appear in the context."
    ),
    messages=[
        {
            "role": "user",
            "content": f"Context:\n\n{context}\n\nWhat are the required fields for the create endpoint, and which section defines them?"
        }
    ],
)

for block in message.content:
    if block.type == "text":
        print(block.text)

What you get back

The Ingest endpoint returns JSONL chunks split at H1, H2, and H3 heading boundaries with a 512 word soft cap, tables and code blocks kept whole even when they exceed the cap, and each chunk carrying headings_path, section, chunk_index, word_count, and source_url. Naive token-based splitting breaks tables mid-row and severs code blocks from their context, and passing the raw document into an agent context inflates token usage without section provenance.

Get free API key — 500 ops per month, no card required.