.NET client
Typefold.Client is the NuGet package for the Typefold API. It renders reports by name
through the Reports API; HTML, template versions and batches through the
Renders API; and email and SMS text. It also reads render status, cancels
renders and downloads PDFs. The .NET, Python and
TypeScript clients have the same features. For templates, assets,
webhooks and administration, call the HTTP API.
Before you start
Section titled “Before you start”- .NET Framework 4.6.2 or later, .NET Core 2.0 or later, or .NET 5 or later.
- An API key with the
render:create,render:readandtemplate:readscopes; see API keys and scopes. - A published template to render, such as one named
Invoice. See Build your first template.
1. Install the package
Section titled “1. Install the package”dotnet add package Typefold.ClientOn net8.0 it has no dependencies; on netstandard2.0 it uses System.Text.Json 8.0.6. The
package version matches the Typefold release it came with. Use a client no newer than your
server; see versioning.
2. Create a client
Section titled “2. Create a client”using Typefold.Client;
var client = new TypefoldClient( new Uri(Environment.GetEnvironmentVariable("PDF_URL")!), Environment.GetEnvironmentVariable("API_KEY")!);PDF_URL is your installation’s address, the URL you open Studio at, such as
https://pdf.example.com. Create one client per API key and reuse it; it is safe to share between
threads.
3. Render a report
Section titled “3. Render a report”RenderReportAsync renders the published version of a template, found by name, and returns the
file’s bytes. It waits up to 95 seconds for a PDF:
byte[] pdf = await client.RenderReportAsync(new RenderReportRequest{ Template = TemplateRef.ByName("Invoice"), Data = new { number = "2026-0042", customer = new { name = "Ada Lovelace" } },});
await File.WriteAllBytesAsync("invoice.pdf", pdf);Open invoice.pdf: you should see the template filled with your data, so {{customer.name}}
prints Ada Lovelace.
For an Excel file from a spreadsheet template, set Format = ReportFormat.Xlsx. To change the
paper, margins or PDF standard for one render, set Pdf.
Use dependency injection
Section titled “Use dependency injection”Register the client as a typed HttpClient, so it shares pooled connections:
builder.Services .AddHttpClient<TypefoldClient>((http, services) => { var config = services.GetRequiredService<IConfiguration>();
return new TypefoldClient( new Uri(config["Typefold:BaseUrl"]!), config["Typefold:ApiKey"]!, http); }) .ConfigureHttpClient(http => http.Timeout = TimeSpan.FromSeconds(120));Then inject TypefoldClient where you need it. Keep the API key in user secrets or environment
variables, not in appsettings.json.
Render HTML or a template version
Section titled “Render HTML or a template version”RenderAsync renders HTML you send, or a published template version, through the
Renders API. It creates the render, waits for it and downloads the PDF.
byte[] pdf = await client.RenderAsync(new DirectRenderRequest{ Document = new HtmlDocument { Html = "<h1>Invoice 42</h1><p>Total: 1,250.00 EUR</p>", Css = new[] { "h1 { color: #0b5394; }" }, }, Settings = new RenderSettings { PageSize = PageSize.Letter },});To render a published template version, pass its ID and the data:
byte[] ticket = await client.RenderAsync(new TemplateVersionRenderRequest{ TemplateVersionId = versionId, Data = new { ticket = new { number = "TK-1" } },});RenderAsync waits two minutes by default; pass timeout: TimeSpan.FromMinutes(5) to wait five.
Without Settings, the page is A4 with 10 mm margins.
Render in the background
Section titled “Render in the background”For long or numerous renders, create the render, store its ID, and pick up the PDF later:
// 1. Create. Returns as soon as the render is queued.Render render = await client.CreateRenderAsync(request);await SaveRenderIdAsync(orderId, render.Id);
// 2. Later, in a job or after a webhook arrives:render = await client.GetRenderAsync(renderId);
if (render.State == RenderState.Succeeded){ byte[] pdf = await client.DownloadAsync(render);}To wait in the same process instead, call WaitForRenderAsync(render), which polls until the
render finishes. To be called when it finishes, use webhooks. If you no
longer need a render, call CancelRenderAsync(render.Id).
Render a batch
Section titled “Render a batch”CreateBatchAsync creates up to 100 renders in one request; if any item is invalid, none is
created. WaitForBatchAsync waits until every item has finished, and DownloadBatchAsync
returns one PDF of every item in order:
RenderBatch batch = await client.CreateBatchAsync( tickets.Select(ticket => new TemplateVersionRenderRequest { TemplateVersionId = versionId, Data = ticket, }), idempotencyKey: "tickets-2026-10-12");
batch = await client.WaitForBatchAsync(batch);
if (batch.Counts.Succeeded == batch.Total){ byte[] pdf = await client.DownloadBatchAsync(batch.Id);}A failed item does not stop the others. batch.Items gives each item’s render Id and State,
to download or cancel it as a single render. See
Render many documents at once.
Retry safely
Section titled “Retry safely”RenderReportAsync, CreateRenderAsync, RenderAsync and CreateBatchAsync take an optional
idempotencyKey. Retrying with the same key returns what the first call created instead of
rendering again, so a network failure never produces a duplicate document:
var key = $"invoice-{invoice.Id}-v{invoice.Revision}";
byte[] pdf = await client.RenderReportAsync(request, idempotencyKey: key);Use a key that names the document. For reports, keys apply to PDF only. The rules are in idempotency keys.
Handle errors
Section titled “Handle errors”The client retries 429 RATE_LIMITED for you, up to MaxRateLimitRetries times. Any other API
error, or a 429 still refused after those retries, throws TypefoldException; its
Error.Code says what went wrong and Error.Retryable whether trying again can help:
try{ byte[] pdf = await client.RenderReportAsync(request, idempotencyKey: key);}catch (TypefoldException ex) when (ex.Error.Code == "RENDER_TIMEOUT"){ // Still rendering after 95 seconds. Follow it later with GetRenderAsync(ex.Error.RenderId). await QueueFollowUpAsync(ex.Error.RenderId!);}catch (TypefoldException ex) when (ex.Error.Retryable){ // RATE_LIMITED, a 5xx, and similar: back off and retry with the same idempotency key.}catch (TypefoldException ex){ // Not worth retrying as is: fix the request, template or key. logger.LogError(ex, "PDF render failed: {Code}", ex.Error.Code);}Every code is in the error reference.
Render email and SMS text
Section titled “Render email and SMS text”RenderStringAsync fills a template string and returns the text, with no PDF. See
Render email and SMS text.
string body = await client.RenderStringAsync(new RenderStringRequest{ Template = "{{#if (isEqual Status 'paid')}}Paid{{else}}Due{{/if}}: {{Booking.Code}}", Data = model, Engine = StringEngine.Handlebars, Helpers = "function isEqual(a, b) { return a === b; }", Format = StringFormat.Html,});StringFormat.Html escapes values and StringFormat.Text (the default) does not. Without
Engine, only {{path}} placeholders are filled.
Reference
Section titled “Reference”Everything is in the Typefold.Client namespace.
Client options
Section titled “Client options”new TypefoldClient(baseUrl, apiKey, httpClient):
| Option | Default | Meaning |
|---|---|---|
baseUrl |
Required | The API’s address as a Uri, such as https://pdf.example.com. A base path such as /pdf is kept. |
apiKey |
Required | Your API key. |
httpClient |
One with a 120 s timeout | A shared HttpClient. A report PDF can take up to 95 seconds, so keep its timeout above that. |
MaxRateLimitRetries |
3 |
How many times a 429 is sent again, after its Retry-After (else 1, 2, 4… seconds, at most 30). 0 throws the first one. |
MaxRateLimitRetries is a property: new TypefoldClient(baseUrl, apiKey) { MaxRateLimitRetries = 5 }.
Methods
Section titled “Methods”Every method also takes a CancellationToken.
| Method | Returns | Scopes |
|---|---|---|
RenderReportAsync(request, idempotencyKey) |
byte[] |
render:create, template:read, plus render:read for PDF |
RenderStringAsync(request) |
string |
render:create |
ListReportsAsync() |
IReadOnlyList<ReportSummary> |
template:read |
GetReportDataSchemaAsync(format, template) |
JsonElement (JSON Schema) |
template:read |
CreateRenderAsync(request, idempotencyKey) |
Render |
render:create |
GetRenderAsync(id) |
Render |
render:read |
WaitForRenderAsync(render, timeout) |
Render |
render:read |
DownloadAsync(render) |
byte[] |
render:read, only when it refreshes the link |
RenderAsync(request, idempotencyKey, timeout) |
byte[] |
render:create, render:read |
CancelRenderAsync(id) |
Nothing | render:cancel |
CreateBatchAsync(items, idempotencyKey) |
RenderBatch |
render:create |
GetBatchAsync(id) |
RenderBatch |
render:read |
WaitForBatchAsync(batch, timeout) |
RenderBatch |
render:read |
DownloadBatchAsync(id) |
byte[] |
render:read |
WaitForRenderAsync polls every 1 to 5 seconds and throws RENDER_TIMEOUT after timeout, two
minutes by default; a dropped connection or a retryable error such as RATE_LIMITED does not
stop it. WaitForBatchAsync polls the same way, for up to 10 minutes by default. DownloadAsync
re-reads the render first when its download link is about to expire, and throws
InvalidOperationException for a render that has no file. Cancelling a CancellationToken stops
your code waiting, not the render on the server; call CancelRenderAsync for that.
Request types
Section titled “Request types”Requests are classes with the API’s fields in PascalCase.
| Type | Properties |
|---|---|
RenderReportRequest |
Template, Data, Format (pdf), Locale (en-US), Pdf, Image, Test |
ReportPdfOptions |
PageSize, Orientation, Margins, PageRanges, Metadata, Pdfa, Pdfua, OpaqueFills, PreferCssPageSize, Attachments, Encryption, Signature, Operations, Watermark |
TemplateVersionRenderRequest |
TemplateVersionId, Data, Locale, Metadata, Attachments, Encryption, Signature, Watermark, Format, Image, Test, WaitMs |
DirectRenderRequest |
Document (Html, Css, AssetIds), Settings, Metadata, Attachments, Encryption, Signature, Watermark, Format, Image, Test, WaitMs |
RenderSettings |
PageSize (A4), Orientation, Margins (10 mm each, all four required), Locale, PageRanges, Metadata, Pdfa, Pdfua, OpaqueFills, PreferCssPageSize |
Margins |
TopMm, RightMm, BottomMm, LeftMm |
RenderStringRequest |
Template, Data, Format, Engine, Helpers |
PdfMetadata |
Title, Author, Subject, Creator, Keywords, Producer, Language, Xmp |
PdfEncryption |
OpenPassword, OwnerPassword, Permissions (PdfPermissions, with Printing from PdfPrinting) |
PdfSignature |
Certificate, Reason, Location, ContactInfo, Field (PdfSignatureField) |
PdfAttachment |
Name, MimeType, Description, Relationship, Content (text) or ContentBase64 (bytes) |
PdfOperation |
Type (PdfOperationType), Template or Pdf, MergeWholeDocument, MergeToFront, RenderForEveryPage |
PdfWatermark |
Text, Opacity, Rotation, FontSize, Color; see watermarks |
ImageOptions |
Page, Width, Quality, with Format set to RenderFormat.Png, Jpeg or Webp; see images |
TemplateRef.ByName, ById and ByShortid pick the template; a shortid exists only on
imported templates. PageSize has A3, A4, A5, Letter and
Legal, or use new PageSize(widthMm, heightMm). What each field does is in
the API reference and
PDF standards and security.
How data is sent
Section titled “How data is sent”Data is sent with its property names unchanged, so new { CustomerName = "Ada" } is
{{CustomerName}} in the template, and null values are kept. Anonymous types, records, classes,
dictionaries, JsonElement and JsonNode all work. A Newtonsoft JObject works only as the
whole Data value; convert a nested one first, for example with
JsonNode.Parse(jObject.ToString()). A template version or string render without Data sends
{}.
Exceptions
Section titled “Exceptions”TypefoldException has StatusCode (null when the error did not come from an HTTP response,
such as a failed render or a wait that ran out) and Error, the API’s error with Code,
Message, Retryable, Details, Diagnostics and RenderId. The client raises these codes
itself:
| Code | When |
|---|---|
RENDER_TIMEOUT |
A wait ran out of time. Retryable; follow RenderId, or the batch for WaitForBatchAsync. |
RENDER_FAILED |
A render was cancelled while RenderAsync waited. A failed render throws with the error it recorded instead. |
HTTP_ERROR |
A response that was not the API’s error format, such as a proxy’s error page or a failed download. Retryable for 429 and 5xx. |