Migrate from LlamaParse to txtfetch

Credit-metered tiers and a job to poll, for parsing you may not need. Here's what your code looks like before and after.

Where you are today

The call below is the current llama-cloud Parsing API, checked against LlamaParse's current docs.

Python
import os

from llama_cloud import LlamaCloud

client = LlamaCloud(api_key=os.environ["LLAMA_CLOUD_API_KEY"])

file = client.files.create(file="report.pdf", purpose="parse")
result = client.parsing.parse(
    file_id=file.id,
    tier="agentic",
    version="latest",
    expand=["markdown"],
)

text = result.markdown.pages[0].markdown

Checked against LlamaParse's docs on 2026-09-10, using llama-cloud 2.16.0.

the call that replaces it

One HTTP call. No SDK to install, and no job to poll.

curl
curl -X POST https://api.txtfetch.com/v1/extract \
  -H "Authorization: Bearer $TXTFETCH_KEY" \
  -F file=@report.pdf
Python
import 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"])
JavaScript
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 result.markdown.pages[n].markdown. Change one function, not your whole pipeline.

Python
import os

import requests


def parse_like_pages(file_path: str) -> list[str]:
    """Drop-in swap for result.markdown.pages[n].markdown.

    txtfetch has no per-page split, so this returns a one-item list.
    Downstream code that joins all pages into one string keeps working
    unmodified; code that indexes a specific page needs a rewrite.
    """
    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()
    return [r.json()["extracted_text"]]
JavaScript
import { readFile } from "node:fs/promises";

// Drop-in swap for result.markdown.pages[n].markdown. txtfetch has no
// per-page split, so this returns a one-item array.
async function parseLikePages(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 [extracted_text];
}

field mapping

LlamaParse's response fields, mapped to txtfetch's
LlamaParse fieldtxtfetch equivalentNote
result.markdown.pages[n].markdownextracted_text (?format=markdown)Ask for markdown output to keep heading and list structure. LlamaParse splits by page; txtfetch does not.
result.items (per-element JSON)?format=json element listtxtfetch's element JSON is coarser than LlamaParse's agentic-tier item breakdown.
tier (fast / cost_effective / agentic / agentic_plus)not applicabletxtfetch runs one extraction path. There is no fidelity dial to set per request.
take_screenshot=True (page images)not returnedtxtfetch returns text only, never a page image.

what you lose

  • The agentic and agentic-plus tiers' fidelity on multi-column layouts, embedded tables, and math notation.
  • Per-page screenshots and structured item JSON.
  • A pinnable parser version for reproducible output across runs.
  • output_tables_as_HTML and other tier-specific output options.

See the full capability table on txtfetch vs LlamaParse.

what you gain

  • Hundreds of formats through one endpoint (see /formats/coverage), not a PDF-first parser.
  • One flat call, no credits to track and no job tier to pick per request.
  • Billing per document, not per credit. See the pricing math.

The pricing math behind per-document billing lives on why per-page pricing punishes long documents.

Cutover

Run both calls on the same PDF while you switch over. Diff the two outputs, or clean up formatting differences first on the pages where LlamaParse's fidelity mattered most.

frequently asked questions

Do I need the agentic tier's fidelity, or is plain text enough?
Depends on the document. If your pipeline only ever reads plain text out of LlamaParse's markdown, txtfetch is a straight swap. If it reads structured items or table HTML, keep LlamaParse for those files.
Does txtfetch support LlamaParse's parsing tiers?
No. txtfetch runs one extraction path per document. There is no fast/agentic/agentic_plus tier to choose.
What happened to the llama-cloud-services package?
LlamaIndex deprecated it in favor of llama-cloud. The snippet above uses the current package, checked against LlamaIndex's own docs.

Swap the call. Keep the pipeline.

Point your LlamaParse code at txtfetch, then diff the output before you cut over.

Get an API key →

See the migration guide →