Skip to main content

PDF4me .NET SDK: C# PDF Library and API Client

Pdf4me.Client is a .NET client library for the PDF4me API that exposes 106 document actions as typed async methods. Install it from NuGet, create one Pdf4meClient with your API key, and convert, optimize, merge, split, OCR or extract from documents in C# without shipping a rendering engine.

Package facts​

FieldValue
Package namePdf4me.Client
Current version2.0.0 (last updated 2026-10-13)
LicenseMIT
.NET support.NET 8, 9 and 10, plus .NET Standard 2.0 for .NET Framework 4.6.2 and later
Total downloads246.4K across all versions
Package size1.34 MB
Actions106 across 18 sections
Runtime dependenciesMicrosoft.Kiota.Bundle (>= 2.1.2) and Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
Base URLhttps://api.pdf4me.com
Sourcegithub.com/pdf4me/pdf4me-clientapi-dotnet
NuGetnuget.org/packages/Pdf4me.Client

Prerequisites​

  • .NET 8 or newer, or .NET Framework 4.6.2 or newer through .NET Standard 2.0.
  • A PDF4me account and API key.

Install​

dotnet add package Pdf4me.Client

Authenticate​

Set your API key in the environment:

export PDF4ME_API_KEY="your-api-key"

On Windows, set lasts only for the current CMD session. Use setx PDF4ME_API_KEY "your-api-key" for a value that survives a reboot, then open a new terminal so the process picks it up.

The client takes the key in its constructor. Nothing it logs carries the API key or a document's content, and an empty or whitespace-only key throws ArgumentException before any request goes out.

Quick start​

This program uploads input.pdf for optimization and saves the result as optimized.pdf:

using Pdf4me.Client;

var apiKey = Environment.GetEnvironmentVariable("PDF4ME_API_KEY")
?? throw new InvalidOperationException("Set PDF4ME_API_KEY first.");

using var client = new Pdf4meClient(apiKey);
var result = await client.OptimizeAsync(
File.ReadAllBytes("input.pdf"), docName: "input.pdf", profile: OptimizeProfile.Max);
File.WriteAllBytes("optimized.pdf", result);

Create one client and reuse it for every call. It keeps a pooled connection, so a client per call would negotiate TLS every time. Dispose it when you are done.

What is the PDF4me .NET SDK?​

It is a single NuGet package, Pdf4me.Client, that wraps the PDF4me API v2 behind one class. Every action is an async method on Pdf4meClient named after its endpoint, so /api/v2/ConvertOcrPdf is ConvertOcrPdfAsync. The first parameters are what the action works on, and everything else is optional, so pass it by name:

var searchable = await client.ConvertOcrPdfAsync(
scan, language: "English", qualityType: QualityType.Draft);

var numbered = await client.AddPageNumberAsync(
pdf, pageNumberFormat: "Page # of {1}", alignX: AddPageNumberAlignX.Center);

The Generated tree inside the package is produced by Microsoft Kiota from the PDF4me OpenAPI description, which is why the option and result models are consistent with the REST API and with the Python and JavaScript clients: the same actions, with the same names and defaults.

How does a C# PDF library differ from an API client?​

Most results for "c# pdf library" are self-hosted rendering libraries: you add the package, it does the work in your process, and you own the rendering engine. Pdf4me.Client is the other shape. It sends the document to PDF4me and returns the finished bytes. The trade is real in both directions, so it is worth being plain about it:

Local library vs hosted APISelf-hosted libraryPdf4me.Client
Where work happensIn your processOn the PDF4me service
Network requiredNoYes, every call
Cost shapePackage license, sometimes per-developer or per-deploymentMIT client, API credits on a free or paid plan
Works offline or air-gappedYesNo
Documents leave your infrastructureNoYes
OCR, AI extraction, ZUGFeRDUsually a separate product, if availableIncluded in the same 106 actions
You maintainThe engine, its fonts and its CVEsAn API key

Pick a local library when the documents cannot leave your network, when you need to work offline, or when per-document cost at your volume beats a subscription. Pick this client when you would otherwise be integrating several products to cover conversion, OCR, barcodes, e-invoicing and AI extraction, and you would rather not ship or patch a rendering engine at all.

Note that the two questions autocomplete asks most about this category are answered differently here. The client itself is MIT licensed and open source, so the "free" and "open source" boxes are ticked for the code you link against. The processing behind it is a hosted service, which is not free beyond its free tier.

Passing documents and reading results​

Documents are a DocumentSource: either the file's bytes, or a string the API fetches itself, which may be a pdf4meblobid://<id> blob reference or an https URL. Both convert implicitly, so either can be passed straight in. A string is never read as a local file path, so read the file and pass its bytes.

byte[] fromBytes = await client.OptimizeAsync(File.ReadAllBytes("report.pdf"));
byte[] fromUrl = await client.OptimizeAsync("https://example.com/report.pdf");
byte[] fromBlob = await client.OptimizeAsync("pdf4meblobid://<id>");

Results come back one of two ways. Actions that produce a document return its byte[], so File.WriteAllBytes takes it directly. Actions that answer with data return a typed model instead:

DocMetadata metadata = await client.GetPdfMetadataAsync(pdf);
Console.WriteLine($"{metadata.PageCount} pages, PDF {metadata.PdfVersion}");

ReadBarcodesRes scan = await client.ReadBarcodesAsync(pdf);
foreach (var barcode in scan.Barcodes ?? [])
Console.WriteLine($"page {barcode.Page}: {barcode.Value}");

The AI document parser decides its own response shape, so everything it returns arrives in the model's AdditionalData:

var parsed = await client.AiDocumentParserAsync(File.ReadAllBytes("invoice.pdf"), "invoice parser");
foreach (var (field, value) in parsed.AdditionalData)
Console.WriteLine($"{field}: {value}");

Several documents merge in the order given, and some actions take no document at all because the input is text or a URL:

byte[] merged = await client.MergeAsync([File.ReadAllBytes("a.pdf"), File.ReadAllBytes("b.pdf")]);
byte[] qrCode = await client.CreateBarcodeAsync("https://pdf4me.com", barcodeType: CreateBarcodeBarcodeType.QRCode);
byte[] webPage = await client.ConvertUrlToPdfAsync("https://example.com");

Options that are objects, such as ConvertToPdfAction and ImageAction, and all the result models, live in Pdf4me.Client.Generated.Models. Enums an action takes have short names in Pdf4me.Client (QualityType, AlignX, SplitAction), while the few whose generated name is already short (DocType, ImageTypeExtract) are used straight from the generated namespace:

using Pdf4me.Client;
using Pdf4me.Client.Generated.Models;

var pages = await client.CreateImagesAsync(
pdf, new ImageAction { ImageExtension = ImageExtension.Png }, pageNrs: "1");

Every action also takes three optional arguments: extra, a dictionary of additional request fields for options the API description does not list yet, sent as they are alongside the action's own; maxWait, which caps the wait for that call's queued job; and cancellationToken, which cancels the request and any polling.

Client configuration​

using var client = new Pdf4meClient(apiKey, new Pdf4meClientOptions
{
BaseUrl = Pdf4meClient.DefaultBaseUrl, // https://api.pdf4me.com
Timeout = TimeSpan.FromSeconds(100), // each HTTP request
MaxWait = TimeSpan.FromMinutes(5), // waiting for one queued job
Logger = loggerFactory.CreateLogger<Pdf4meClient>(),
});

In ASP.NET Core, or any app using dependency injection, register one client for the whole app and let the logger come from the app's own logging setup:

builder.Services.AddSingleton(services => new Pdf4meClient(
builder.Configuration["Pdf4me:ApiKey"]!,
new Pdf4meClientOptions { Logger = services.GetRequiredService<ILogger<Pdf4meClient>>() }));

Give the client an ILogger from Microsoft.Extensions.Logging to see every request, response, queued job and poll at Debug level.

How do you handle long jobs and OCR in C#?​

Every action is submitted as an asynchronous job. A call returns the finished result whether the server answered at once or queued the work, polling in between at the interval the server suggests. This matters for two reasons: a single await can block for minutes, and the thing that ends the wait is MaxWait, not Timeout.

  • Timeout applies to each HTTP request.
  • MaxWait applies to the whole queued job, and maxWait: on an action overrides it for that call.
  • The OCR actions, ConvertOcrPdfAsync and ImageExtractTextAsync, wait at least 10 minutes (DefaultOcrMaxWait), or longer when MaxWait is higher.
using var cancel = new CancellationTokenSource(TimeSpan.FromMinutes(20));
byte[] searchable = await client.ConvertOcrPdfAsync(
File.ReadAllBytes("scan.pdf"), language: "English",
maxWait: TimeSpan.FromMinutes(20), cancellationToken: cancel.Token);

Errors​

A refused or failed request throws Pdf4meException, from Pdf4me.Client.Generated.Models:

PropertyHolds
MessageThe API's message, else the response body cut to 500 characters, else the HTTP status
ResponseStatusCodeThe HTTP status code
TraceIdThe API's trace id. Quote it when reporting a problem
ResponseBodyThe whole response body as sent
ResponseHeadersThe response headers

A job still running when the wait runs out throws TimeoutException. Invalid arguments, such as an empty document, throw ArgumentException before any request is sent.

try
{
byte[] result = await client.OptimizeAsync(pdf);
}
catch (Pdf4meException e)
{
Console.Error.WriteLine($"PDF4me returned {e.ResponseStatusCode}: {e.Message} (trace {e.TraceId})");
}
catch (TimeoutException e)
{
Console.Error.WriteLine(e.Message);
}

The 18 sections and all 106 actions​

Actions are grouped by the sections of the PDF4me REST API reference, and each section's methods live in their own file in the source. A few sit in a section you might not guess from the name: OCR is in Find Search, PDF/A creation is in Convert, and MergeV3Async is in PDF rather than Merge & Split.

SectionActionsMethods on Pdf4meClient
AI Document Extraction14AiDocumentParserAsync, ProcessBankChequeAsync, ProcessBankStatementAsync, ProcessContractAsync, ProcessCreditCardAsync, ProcessHealthCardAsync, ProcessInvoiceAsync, ProcessMarriageCertificateAsync, ProcessMortgageDocumentAsync, ProcessOrderAsync, ProcessPayStubAsync, ProcessReceiptAsync, ProcessShippingLabelAsync, ProcessTaxDocumentAsync
Barcode7AddBarcodeAsync, CreateBarcodeAsync, CreateSwissQrBillAsync, ReadBarcodesAsync, ReadSwissQrBillAsync, CreateEpcQrCodeAsync, ReadBarcodesFromImageAsync
Convert13ConvertHtmlToPdfAsync, ConvertJsonToExcelAsync, ConvertMdToPdfAsync, ConvertPdfToExcelAsync, ConvertPdfToPowerpointAsync, ConvertPdfToWordAsync, ConvertToPdfAsync, ConvertUrlToPdfAsync, ConvertVisioAsync, ConvertWordToPdfFormAsync, CreatePdfAAsync, FlattenPdfAsync, LinearizePdfAsync
Edit7AddAttachmentToPdfAsync, AddHtmlHeaderFooterAsync, ImageStampAsync, AddMarginAsync, AddPageNumberAsync, StampAsync, SignPdfAsync
Excel1FindAndReplaceTextInExcelAsync
Extract8ClassifyDocumentAsync, ExtractAttachmentFromPdfAsync, ExtractPdfFormDataAsync, ExtractResourcesAsync, ExtractTableFromPdfAsync, ExtractTextByExpressionAsync, ExtractTextFromWordAsync, ParseDocumentAsync
Find Search2FindAndReplaceAsync, ConvertOcrPdfAsync
Forms2AddFormFieldAsync, FillPdfFormAsync
Generate6EnableTrackingChangesInWordAsync, GenerateDocumentSingleAsync, GenerateDocumentMultipleAsync, GetTrackingChangesInWordAsync, ReplaceTextWithImageInWordAsync, GenerateDocumentSingleV2Async
Image13AddImageWatermarkToImageAsync, AddTextWatermarkToImageAsync, CompressImageAsync, ConvertImageFormatAsync, CreateImagesAsync, CropImageAsync, FlipImageAsync, GetImageMetadataAsync, ImageExtractTextAsync, RemoveExifTagsFromImageAsync, ResizeImageAsync, RotateImageAsync, RotateImageByExifDataAsync
Merge & Split5MergeAsync, MergeOverlayAsync, SplitPdfAsync, SplitPdfByBarcodeAsync, SplitByTextAsync
Optimize1OptimizeAsync
Organize5DeleteBlankPagesAsync, DeletePagesAsync, ExtractPagesAsync, RotateAsync, RotatePageAsync
PDF16MergeV3Async, GetPdfMetadataAsync, RepairPdfAsync, CreateHyperlinkAnnotationAsync, DeleteHyperlinkAnnotationAsync, DigitalSignPdfAsync, ExtractHyperlinkAnnotationAsync, GetDocumentByTextAsync, GetTextAndCoordinatesAsync, GetTextByPositionAsync, HighlightTextAsync, PrepareForPrintAsync, ReplaceTextWithImageAsync, ResizePdfAsync, SetPdfMetadataAsync, SignDocumentAsync
PDF4me1UpdateHyperlinkAnnotationAsync
Security2ProtectAsync, UnlockAsync
Word2DisableTrackingChangesInWordAsync, FindAndReplaceTextInWordAsync
ZUGFeRD1CreateZugferdInvoiceAsync

For each action's parameters and response shape, see the PDF4me REST API reference, or hover the method in your editor: every method carries a documentation summary that matches its docs page.

Migrating from 1.x​

Version 2.0 is a rewrite on the PDF4me API v2 and nothing from 1.x keeps working. The headline changes:

  • It talks to API v2. 1.x called the v1 endpoints such as /Optimize/OptimizeByProfile; 2.0 calls /api/v2/....
  • The singleton and the per-area clients are gone. Pdf4me.Instance.Init(token) and Pdf4me.Instance.OptimizeClient become one new Pdf4meClient(apiKey) with every action as a method on it.
  • The namespace is Pdf4me.Client, not Pdf4meClient.
  • Newtonsoft.Json is no longer a dependency, and the vulnerable 12.0.3 that 1.x pulled in goes with it.
  • Errors are one type. Pdf4meApiException and Pdf4meBackendException become Pdf4meException.
  • .NET Framework 4.5.2 and 4.6.1 are no longer supported. The minimum is 4.6.2, through .NET Standard 2.0.
1.x2.0
Pdf4me.Instance.Init(token)using var client = new Pdf4meClient(apiKey);
OptimizeClient.OptimizeByProfileAsync(file, profile)client.OptimizeAsync(file, profile: ...)
MergeClient.Merge2PdfsAsync(file1, file2)client.MergeAsync([file1, file2])
SplitClient.SplitByPageNrAsync(file, pageNr)client.SplitPdfAsync(file, splitActionNumber: pageNr)
ExtractClient.ExtractPagesAsync(file, pageNrs)client.ExtractPagesAsync(file, pageNumbers: pageNrs)
ConvertClient.ConvertFileToPdfAsync(file, fileName)client.ConvertToPdfAsync(file, docName: fileName)
PdfAClient.CreatePdfAAsync(file, compliance)client.CreatePdfAAsync(file, compliance: ...)
PdfAClient.RotateDocumentAsync(file, rotate)client.RotateAsync(file, rotationType: ...)
PdfAClient.ProtectDocumentAsync(file, password, perms)client.ProtectAsync(file, password: ..., pdfPermission: ...)
PdfAClient.UnlockDocumentAsync(file, password)client.UnlockAsync(file, password: ...)
PdfAClient.RepairDocumentAsync(file)client.RepairPdfAsync(file)
ImageClient.CreateThumbnailAsync(file, width, pageNr, fmt)client.CreateImagesAsync(file, new ImageAction { ... }, pageNrs: ...)

Your API key stays the same. Replace using Pdf4meClient; with using Pdf4me.Client;, add using Pdf4me.Client.Generated.Models; wherever you use option or result models or catch Pdf4meException, then work through the table above.

Troubleshooting​

TimeoutException on a large document. The job outlived the wait, which is MaxWait and defaults to 5 minutes, not Timeout. Raise it on the call with maxWait: TimeSpan.FromMinutes(20), or on the client for every call.

An OCR call seems to hang for ten minutes. That is the documented floor. ConvertOcrPdfAsync and ImageExtractTextAsync wait at least DefaultOcrMaxWait, which is 10 minutes, so give them their own cancellationToken if the caller needs to give up sooner.

A 401 in Pdf4meException.ResponseStatusCode. The key is present but rejected. Regenerate it in the PDF4me dashboard and confirm the variable is visible to the process. On Windows, set does not persist across sessions, so use setx and open a new terminal.

ArgumentException before anything hits the network. A local check failed, most often an empty document, which means the file read returned zero bytes. Verify the path and that the file is not locked by another process.

You passed a file path and nothing happened to that file. A string argument is treated as a blob reference or an https URL, never a local path. Read the file and pass its bytes.

CreateThumbnailAsync is not found. 1.x method names do not exist in 2.0. Thumbnails are now CreateImagesAsync with an ImageAction. See the migration table.

A compile error on a model or enum type. Option objects and result models are in Pdf4me.Client.Generated.Models, while most enums an action takes are in Pdf4me.Client. Import both.

Common questions​

Is the PDF4me .NET SDK free?​

The Pdf4me.Client package is free and MIT licensed, so there is no cost to install or redistribute it. The package is a client for the PDF4me API, which is a hosted service with a free tier and paid plans above it, so document processing consumes API credits on your account. The code you link against costs nothing; the processing is metered.

Is the PDF4me .NET client open source?​

Yes. The client is published under the MIT license and its source is at github.com/pdf4me/pdf4me-clientapi-dotnet. One caveat if you plan to contribute: src/Pdf4me.Client/Generated/ is produced by Kiota from the OpenAPI description and is replaced whole on every regeneration, so open an issue for anything wrong in that tree rather than patching it. The client and the section files around it are hand-written and are the right place for a pull request.

Which .NET versions does Pdf4me.Client support?​

Version 2.0.0 targets .NET 8, .NET 9 and .NET 10, plus .NET Standard 2.0, which means it also runs on .NET Framework 4.6.2 and later. .NET Framework 4.5.2 and 4.6.1 were supported by 1.x and are not supported by 2.0.

How do I convert HTML to PDF in C#?​

Call ConvertHtmlToPdfAsync with the HTML file's bytes, or ConvertUrlToPdfAsync with a URL string, and write the returned byte[] to disk:

byte[] fromHtml = await client.ConvertHtmlToPdfAsync(File.ReadAllBytes("page.html"));
byte[] fromUrl = await client.ConvertUrlToPdfAsync("https://example.com");
File.WriteAllBytes("page.pdf", fromUrl);

Rendering happens on the service, so there is no browser engine to install, containerise or patch, which is the usual cost of doing this with a local library.

How do I read a PDF in C#?​

It depends what "read" means. GetPdfMetadataAsync returns a DocMetadata with the page count and PDF version. ExtractTextByExpressionAsync, GetTextByPositionAsync and GetTextAndCoordinatesAsync pull text out, the last one with positions. ExtractTableFromPdfAsync handles tabular content, and ConvertOcrPdfAsync makes a scanned PDF searchable first. These actions return typed models rather than a byte[].

Can I create a PDF in C# without a library?​

You can write the PDF byte format by hand, and for a single page of plain text it is a reasonable exercise. It stops being reasonable quickly, because fonts, encodings, compression and the cross-reference table all have to be correct, and readers vary in how much malformedness they forgive. In practice the choice is between a local rendering library you ship and keep patched, and an API client like this one where the rendering happens server side. The PDF specification is published as ISO 32000-2 if you want to see the scale of it.

Where to go next​