HTML to PDF in C#
One POST, a PDF back. No headless browser to install, no Chromium to keep patched, no fonts to install on the box — the render happens on a warm browser that is already running.
Render HTML to a PDF
using System.Net.Http.Json;
var client = new HttpClient { Timeout = TimeSpan.FromSeconds(60) };
client.DefaultRequestHeaders.Authorization =
new("Bearer", Environment.GetEnvironmentVariable("PDFCRAFT_API_KEY"));
var response = await client.PostAsJsonAsync("https://api.pdfcraft.dev/v1/render", new
{
html = "<h1>Invoice 1042</h1>",
options = new { printBackground = true, margin = new { top = "20mm" } }
});
response.EnsureSuccessStatusCode();
await using var file = File.Create("invoice.pdf");
await response.Content.CopyToAsync(file);Read a PDF back as JSON
The same key works for extraction. Send a PDF, name the fields you want by the label printed on the page, and get them back with the raw text, a coerced value, a confidence and a bounding box.
var encoded = Convert.ToBase64String(await File.ReadAllBytesAsync("statement.pdf"));
var response = await client.PostAsJsonAsync("https://api.pdfcraft.dev/v1/extract", new
{
file = encoded,
schema = new Dictionary<string, string>
{
["account_number"] = "string",
["closing_balance"] = "currency"
},
options = new { rows_as_objects = true }
});
var body = await response.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine(body.GetProperty("fields").GetProperty("closing_balance").GetProperty("value"));What bites in C#
- Reuse one HttpClient for the life of the process. A `using var client = new HttpClient()` per call exhausts sockets under load — the classic .NET failure mode, and it only appears in production.
- `CopyToAsync` to a FileStream rather than `ReadAsByteArrayAsync`, so a large PDF never lands on the large object heap.
- System.Text.Json serialises anonymous objects camelCase by default, which matches the API. If you have configured a different policy globally, the option names will not match and you will get a 400.
Errors are one shape
Every failure is {"error":{"code","message","docs_url"}} with a stable code, so you can switch on error.code rather than parsing prose. The two worth handling explicitly are rate_limited — honour Retry-After — and render_failed, which means your HTML broke rather than ours did.
The same thing in another language
- HTML to PDF in Node.js
- HTML to PDF in Python
- HTML to PDF in Go
- HTML to PDF in PHP
- HTML to PDF in Ruby
- HTML to PDF in Java
- HTML to PDF in Rust
- HTML to PDF in curl
- HTML to PDF in Deno
- HTML to PDF in Bun
- HTML to PDF in TypeScript
Or try it with no code at all in the playground. A free key is 100 renders a month.