https://txtfetch.com/migrate/unstructured/
Migrate from Unstructured.io to txtfetch
Its own Partition endpoint is now legacy. That's a migration moment either way. Here's what your code looks like before and after.
Where you are today
The call below is the legacy Partition endpoint, checked against Unstructured.io's current docs.
import os
import unstructured_client
from unstructured_client.models import operations, shared
client = unstructured_client.UnstructuredClient(
api_key_auth=os.environ["UNSTRUCTURED_API_KEY"],
)
req = operations.PartitionRequest(
partition_parameters=shared.PartitionParameters(
files=shared.Files(content=open("report.pdf", "rb"), file_name="report.pdf"),
strategy=shared.Strategy.AUTO,
),
)
res = client.general.partition(request=req)
elements = res.elementsChecked against Unstructured.io's docs on 2026-09-10, using unstructured-client 0.46.2.
the call that replaces it
One HTTP call. No SDK to install, and no job to poll.
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@report.pdfimport os
import requests
with open("report.pdf", "rb") as f:
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
files={"file": f},
)
print(r.json()["extracted_text"])import { readFile } from "node:fs/promises";
const file = new Blob([await readFile("report.pdf")]);
const form = new FormData();
form.append("file", file, "report.pdf");
const res = await fetch("https://api.txtfetch.com/v1/extract", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
body: form,
});
const { extracted_text } = await res.json();
console.log(extracted_text);drop-in adapter
A short function standing in for client.general.partition(request).elements. Change one function, not your whole pipeline.
import os
import requests
def partition_like_elements(file_path: str) -> list[dict]:
"""Drop-in swap for client.general.partition(request).elements.
Downstream code that reads el["text"] per element keeps working
unmodified. txtfetch returns one element for the whole document, not
one per paragraph or table.
"""
with open(file_path, "rb") as f:
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
files={"file": f},
)
r.raise_for_status()
text = r.json()["extracted_text"]
return [{"type": "NarrativeText", "text": text, "metadata": {}}]import { readFile } from "node:fs/promises";
// Drop-in swap for client.general.partition(request).elements. Downstream code
// that reads el.text per element keeps working unmodified.
async function partitionLikeElements(filePath) {
const file = new Blob([await readFile(filePath)]);
const form = new FormData();
form.append("file", file, filePath);
const res = await fetch("https://api.txtfetch.com/v1/extract", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
body: form,
});
const { extracted_text } = await res.json();
return [{ type: "NarrativeText", text: extracted_text, metadata: {} }];
}field mapping
| Unstructured.io field | txtfetch equivalent | Note |
|---|---|---|
| element.text (many per document) | extracted_text (one string) | Unstructured splits a document into many elements. txtfetch returns one string for the whole document. |
| element.type (Title, NarrativeText, Table, Image) | not returned | The plain-text response carries no per-element structural tag. |
| element.metadata.page_number | not returned | No per-element page numbers come back in the plain-text response. |
| element.metadata.text_as_html (Table elements) | ?format=markdown table rows | Ask for markdown output and read the GFM pipe table instead of the HTML fragment. |
what you lose
- Typed element types: Title, NarrativeText, Table, Image, ListItem. txtfetch returns one string, not a tagged list.
- Per-element page numbers and other layout metadata.
- Unstructured's own chunking strategies (by_title, by_page, by_similarity) built into the parse step.
- The hi_res and vlm partitioning strategies tuned for hard scans.
- A self-hostable open-source core.
See the full capability table on txtfetch vs Unstructured.io.
what you gain
- Hundreds of formats through one endpoint (see /formats/coverage), not a curated subset.
- One HTTP call. No SDK, no async job, no strategy parameter to tune.
- Billing per document, not per page. A 300-page report costs the same as a one-pager.
The pricing math behind per-document billing lives on why per-page pricing punishes long documents.
Cutover
Run both calls on the same file while you switch traffic over. Compare the two outputs side by side, or clean up formatting differences first if the two extractions disagree on whitespace.
- Compare outputs side by side, to dual-run both calls on the same file.
- Clean up extracted text, if the two outputs disagree on formatting.
frequently asked questions
- Is Unstructured's Partition endpoint really deprecated?
- Unstructured's own docs call it the legacy endpoint and point new work at the Pipeline API instead. The Python call above still works today, but plan the switch either way.
- Does txtfetch return typed elements like Unstructured does?
- No. txtfetch returns one plain-text string per document. If your code reads el.type or el.metadata.page_number, that code needs the adapter above or a rewrite.
- Can I keep Unstructured for chunking and use txtfetch for extraction?
- Yes. Some teams run both: txtfetch for format breadth and flat pricing, Unstructured (or your own chunker) for the by_title or by_similarity chunking strategy.
Swap the call. Keep the pipeline.
Point your Unstructured.io code at txtfetch, then diff the output before you cut over.
Get an API key →