A developer building a RAG pipeline that ingests PDFs needs clean, heading-aware chunks from a simple API call, not a platform with several API surfaces, an open-source runtime to manage, or extraction options to tune. Unstructured is a document parsing platform with an open-source library and a managed API, and its extraction is element-level, returning typed document elements by default, with chunking as a strategy you opt into and configure. For a developer who needs PDF content as heading-aware chunks with metadata intact, that means choosing a chunking strategy, setting size limits, and mapping element metadata to the fields a vector store needs. EnConvert is a REST API that accepts a PDF upload and returns heading-aware JSONL chunks with section metadata, with no library to install and no element types to map, so you get RAG-ready chunks from any PDF in one API call.
Best Unstructured Alternative for PDF Ingestion
EnConvert is an Unstructured alternative for PDF ingestion that returns heading-aware JSONL chunks with section metadata from one upload and no library to run
Get API keyExample
import os
import json
import time
import requests
# Step 1: The Unstructured Partition Endpoint partitions the PDF and returns typed elements by default
# Chunking is opt-in: add chunking_strategy "by_title" and tune size limits
with open("/path/to/your/report.pdf", "rb") as f:
partition = requests.post(
"https://api.unstructuredapp.io/general/v0/general",
headers={"unstructured-api-key": os.environ["UNSTRUCTURED_API_KEY"]},
files={"files": ("report.pdf", f, "application/pdf")},
data={
"strategy": "hi_res",
# Add "chunking_strategy": "by_title" to receive chunks instead of elements
# Add "max_characters" and "overlap" to tune chunk size
},
timeout=300,
)
partition.raise_for_status()
elements = partition.json()
for element in elements:
print(element["type"], element["text"])
# Output: typed elements such as Title, NarrativeText, Table, ListItem
# Map element types and metadata to your vector store schema before loading
# Step 2: EnConvert uploads the PDF once and queues an ingest job
with open("/path/to/your/report.pdf", "rb") as f:
upload = requests.post(
"https://api.enconvert.com/v2/ingest/files",
headers={"X-API-Key": os.environ["ENCONVERT_API_KEY"]},
files={"files": ("report.pdf", f, "application/pdf")},
timeout=120,
)
upload.raise_for_status()
job_id = upload.json()["job_id"]
# Step 3: Poll until the job completes (queued, processing, then completed)
while True:
status_response = requests.get(
f"https://api.enconvert.com/v2/ingest/{job_id}",
headers={"X-API-Key": os.environ["ENCONVERT_API_KEY"]},
timeout=30,
)
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['status']}")
time.sleep(2)
# Step 4: Download the JSONL, one chunk per line
# The output_url is signed (valid 15 minutes). Plain GET, no API key.
jsonl_response = requests.get(job["output_url"], timeout=60)
jsonl_response.raise_for_status()
# Split on "\n" only: str.splitlines() would also split on unicode line separators inside chunk text
chunks = [
json.loads(line)
for line in jsonl_response.content.decode("utf-8").split("\n")
if line.strip()
]
print(f"Loaded {len(chunks)} chunks ready for embedding")
# Each chunk has "content" (the text to embed) plus "metadata" with source_url,
# title, headings_path (a list), section, chunk_index and word_count.
# Embed chunk["content"] and store chunk["metadata"] alongside it. If your vector
# store only accepts scalar metadata, join the list: " > ".join(headings_path)
for chunk in chunks[:3]:
print(" > ".join(chunk["metadata"]["headings_path"]), "|", chunk["content"][:80])
What you get back
Capability | EnConvert | Unstructured |
|---|---|---|
Output format | JSONL chunks split at H1 to H3 boundaries, 512 word soft cap, tables and code blocks kept whole; each chunk carries headings_path, section, chunk_index, and word_count | Typed document elements as JSON by default; chunked output available through opt-in strategies |
RAG-ready chunked output | Confirmed: chunks split at H1 to H3 heading boundaries, 512 word default soft cap, tables and code blocks kept whole even when they exceed the cap; each chunk carries headings_path, section, chunk_index, and word_count | Confirmed: chunking strategies including by_title available; requires opt-in configuration |
Heading structure preserved | Confirmed: chunks split at H1 to H3 boundaries, 512 word soft cap, tables and code blocks kept whole; each chunk carries headings_path, section, chunk_index, and word_count | Section boundaries detected via Title elements; no equivalent breadcrumb path documented |
Web page ingestion | Perceive: any URL to clean Markdown, including JavaScript-rendered pages; Ingest also accepts URL lists, sitemaps, and website crawls returning the same JSONL chunk schema as file ingestion | Not documented for JavaScript-rendered pages |
Document conversion | Convert: 45+ conversion routes across documents, images, and data formats | Not documented as a general-purpose file format conversion endpoint |
MCP server | Native, exposes full ingestion toolkit as MCP tools | Available through its SDK’s MCP server |
Open-source option | No; managed API only | Confirmed: open-source library alongside the managed API, Apache 2.0 licensed |
Free tier | 500 ops per month, no card required | Open-source library free to run; managed API trial documented as 1,000 pages per day for 14 days |
WHY ENCONVERT FOR PDF INGESTION
Heading-aware JSONL chunks split at document heading boundaries so chunking respects document structure, and each chunk carries a full headings_path breadcrumb so retrieval knows exactly where in the PDF a result came from. You do not write element type mapping, configure a chunking strategy, or transform the output schema before loading into a vector store. When the pipeline grows beyond PDFs, the same API handles DOCX and XLSX with the same output schema, and web pages through Perceive with no integration changes.