Three NuGet packages, one HttpClient call.

PdfPig or iText, the Open XML SDK, and a Tesseract wrapper each do one job well. None of the three talk to each other, and none of them do OCR without native binaries.

the-parser-zoo

.NET's document story means picking a library per format, and living with each one's specific trade-off. On PDF, PdfPig is MIT-licensed and capable for text extraction, but it has no OCR of its own. iText is more full-featured, but it is dual-licensed. It is free under AGPLv3, which requires open-sourcing anything that links it. In most commercial contexts, that is a non-starter. The alternative is a paid commercial license. The Open XML SDK reads .docx, .xlsx, and .pptx directly against the OOXML schema. But its object model is low-level by design. Pulling plain text out of a paragraph means walking Run and Text elements manually. It also has nothing at all for the legacy .doc, .xls, and .ppt binary formats, which need a separate library entirely. OCR means a native Tesseract wrapper, commonly the Tesseract NuGet package, itself a wrapper over the C++ library. It ships platform-specific native binaries (win-x64, linux-x64, osx-arm64, and more) as part of your deployment. Picking the wrong runtime identifier for a target container is a common source of a DllNotFoundException. That error only shows up once deployed, not at build time.

librarycoversstops at
PdfPigPDF text extraction, MIT-licensedNo OCR of its own — scanned pages with no text layer return nothing
iTextFull-featured PDF reading and writingFree under AGPLv3 (viral copyleft) or a paid commercial license — a real decision for closed-source products
Open XML SDKDirect, schema-accurate .docx/.xlsx/.pptx accessLow-level object model (manual Run/Text traversal); no legacy .doc/.xls/.ppt support at all
Tesseract (NuGet wrapper)OCR via a native Tesseract binary per platformShips platform-specific native binaries; a wrong runtime identifier fails only after deployment

one-request

txtfetch replaces the three-library split with one call. POST a file, or pass ?url=, to https://api.txtfetch.com/v1/extract using System.Net.Http and System.Text.Json. Both are already in the BCL, so this needs zero NuGet packages. There is no AGPL-vs-commercial licensing decision for PDF, and no manual Run/Text traversal for Office files. There is also no native Tesseract runtime identifier to get right for OCR. The response is the same { "status": "success", "extracted_text": "..." } across every format.

C#
using System.Net.Http.Headers;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("TXTFETCH_KEY"));

using var content = new MultipartFormDataContent();
var fileBytes = await File.ReadAllBytesAsync("report.pdf");
content.Add(new ByteArrayContent(fileBytes), "file", "report.pdf");

var response = await client.PostAsync("https://api.txtfetch.com/v1/extract", content);
var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;

Console.WriteLine(json.GetProperty("extracted_text").GetString());
{
  "status": "success",
  "extracted_text": "..."
}

from-a-url

Skip the download entirely. Pass a url parameter and txtfetch fetches the document server-side:

C#
using System.Net.Http.Headers;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("TXTFETCH_KEY"));

var url = "https://api.txtfetch.com/v1/extract?url=" + Uri.EscapeDataString("https://example.com/report.pdf");
var response = await client.PostAsync(url, null);
var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;

Console.WriteLine(json.GetProperty("extracted_text").GetString());

errors

Every non-success response carries a stable error.code. Match on that, not on error.message. See the full error reference for every code and HTTP status txtfetch can return.

C#
using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("TXTFETCH_KEY"));

using var content = new MultipartFormDataContent();
var fileBytes = await File.ReadAllBytesAsync("report.pdf");
content.Add(new ByteArrayContent(fileBytes), "file", "report.pdf");

var response = await client.PostAsync("https://api.txtfetch.com/v1/extract", content);
var body = await response.Content.ReadAsStringAsync();
var json = JsonDocument.Parse(body).RootElement;

if (response.IsSuccessStatusCode)
{
    Console.WriteLine(json.GetProperty("extracted_text").GetString());
}
else if (response.StatusCode == (HttpStatusCode)429)
{
    var code = json.GetProperty("error").GetProperty("code").GetString();
    var retryAfter = response.Headers.RetryAfter?.Delta?.TotalSeconds;
    Console.WriteLine($"back off: {code}, retry after {retryAfter}s");
}
else
{
    var error = json.GetProperty("error");
    Console.WriteLine($"extraction failed: {error.GetProperty("code").GetString()} — {error.GetProperty("message").GetString()}");
}

big-files-and-batches

Large uploads or slow documents are routed to an async job automatically. That returns a 202 plus a job_id to poll, and ?async=true forces that path for any request. Direct upload size ceilings by plan: Hobby 10 MB, Developer 50 MB, Scale 200 MB. Use ?url= for anything larger. Server-side fetches aren't held to the upload ceiling. See async jobs & webhooks for the full lifecycle, including webhook delivery instead of polling.

C#
using System.Net.Http.Headers;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("TXTFETCH_KEY"));

var submitUrl = "https://api.txtfetch.com/v1/extract?url="
    + Uri.EscapeDataString("https://example.com/report.pdf") + "&async=true";
var submit = await client.PostAsync(submitUrl, null);
var submitJson = JsonDocument.Parse(await submit.Content.ReadAsStringAsync()).RootElement;
var jobId = submitJson.GetProperty("job_id").GetString();

JsonElement result;
while (true)
{
    await Task.Delay(2000);
    var poll = await client.GetAsync($"https://api.txtfetch.com/v1/extract/{jobId}");
    result = JsonDocument.Parse(await poll.Content.ReadAsStringAsync()).RootElement;
    if (result.GetProperty("status").GetString() != "processing") break;
}

Console.WriteLine(result.GetProperty("extracted_text").GetString());

gotchas

  • iText's AGPLv3 free tier requires open-sourcing software that links it, in most distribution models. Many commercial .NET teams end up on the paid commercial license specifically to avoid that. That license cost is more than most PDF-reading tasks need to carry.
  • The Open XML SDK's object model mirrors the OOXML schema closely. That is powerful for editing, but it means even "give me the plain text" requires walking Paragraph → Run → Text nodes yourself. There's no ExtractText() convenience method.
  • Open XML SDK is OOXML-only. A legacy .doc or .xls, still common in older enterprise archives, needs an entirely separate library. The SDK doesn't parse the OLE2 binary format at all.
  • The Tesseract NuGet wrapper's native binaries are runtime-identifier-specific (win-x64, linux-x64, linux-arm64, and more). A container built for the wrong RID compiles fine, then throws DllNotFoundException the first time OCR actually runs.

formats

faq

How do I extract text from a PDF in C# without iText's AGPL license?
POST the PDF (or pass ?url=) to https://api.txtfetch.com/v1/extract with System.Net.Http. There is no PDF library to choose, and so no AGPLv3-vs-commercial-license decision to make at all. The response is { "status": "success", "extracted_text": "..." }.
Does the Open XML SDK have a simple way to get plain text from a .docx?
Not directly. Its object model mirrors the OOXML schema, so plain text means walking Paragraph, Run, and Text elements yourself. txtfetch returns extracted_text directly for .docx, .pptx, and .xlsx. It also reads the legacy .doc, .xls, and .ppt formats, which the Open XML SDK doesn't read at all.
How do I OCR a scanned document in .NET without shipping native Tesseract binaries?
POST the scan to txtfetch. OCR runs server-side, so there's no Tesseract NuGet package, no per-runtime-identifier native binary, and no DllNotFoundException risk in your deployment.
Is there an official .NET SDK for txtfetch?
Not yet. But there's nothing to install in the meantime. It's a single HttpClient POST using System.Net.Http and System.Text.Json, both already part of the .NET base class library, shown above.

go-further

Paste it into your project.

The C# / .NET snippet above runs as written. Add your key and it works.

Get an API key →

More SDK quickstarts →