Skip to main content

.NET SDK — Roadmap

Paperwright.Sdk is the first-party .NET client for the Paperwright API. This page describes the planned shape of the SDK, what it will replace, and how to integrate today while it ships.

:::info Status: In design The SDK is not yet released. This page is a living design document — the API shapes shown here are our intended target, subject to change before the NuGet package lands. :::


Why an SDK?

Calling the render API over raw HTTP works, but it puts boilerplate on every team that integrates: constructing HttpClient, attaching the X-Api-Key header, deserialising errors, retrying transient failures. Paperwright.Sdk wraps all of that into a single, typed PaperwrightClient so a render stays a render in your code.


Target API shape

Installation

dotnet add package Paperwright.Sdk

Configuration

// Program.cs / DI setup
builder.Services.AddPaperwright(options =>
{
options.ApiKey = builder.Configuration["Paperwright:ApiKey"];
options.BaseUrl = "https://api.paperwright.io"; // default
});

Or construct the client directly:

var client = new PaperwrightClient(new PaperwrightOptions
{
ApiKey = Environment.GetEnvironmentVariable("PAPERWRIGHT_API_KEY"),
BaseUrl = "https://api.paperwright.io",
});

Rendering a stored template

// Inject IPaperwrightClient wherever you need it.
public class InvoiceService(IPaperwrightClient paperwright)
{
public async Task<Stream> GenerateAsync(Invoice invoice, CancellationToken ct)
{
return await paperwright.RenderAsync(
templateId: "tmpl_abc123",
data: new
{
invoiceNumber = invoice.Number,
customerName = invoice.CustomerName,
lineItems = invoice.Lines,
total = invoice.Total,
},
cancellationToken: ct
);
}
}

RenderAsync returns a Stream of the rendered PDF bytes. Write it straight to an HTTP response, a file, or blob storage — no intermediate copy.


Inline render (no stored template)

For one-off or dynamically composed documents:

var pdf = await paperwright.RenderInlineAsync(
content: "<h1>Hello {{name}}</h1>",
data: new { name = "World" },
cancellationToken: ct
);

Listing and managing templates

// List all templates in the active workspace
IReadOnlyList<TemplateSummary> templates = await paperwright.Templates.ListAsync(ct);

// Fetch a single template
Template template = await paperwright.Templates.GetAsync("tmpl_abc123", ct);

// Create a template (useful for CI pipelines)
Template created = await paperwright.Templates.CreateAsync(new CreateTemplateRequest
{
Name = "Shipping Label",
Content = "<p>Ship to: {{address}}</p>",
}, ct);

Error handling

All API errors surface as PaperwrightException (or its subclasses):

try
{
var pdf = await paperwright.RenderAsync(templateId, data, ct);
}
catch (PaperwrightNotFoundException)
{
// Template doesn't exist (or belongs to another workspace)
}
catch (PaperwrightValidationException ex)
{
// Bad request — ex.Errors contains field-level details
foreach (var (field, message) in ex.Errors)
logger.LogWarning("Validation: {Field} — {Message}", field, message);
}
catch (PaperwrightException ex)
{
// Unexpected API error — ex.StatusCode + ex.Message
}

Planned phases

PhaseScopeTarget
1 — Core renderRenderAsync + RenderInlineAsync, AddPaperwright DI extension, basic error typesFirst public release
2 — Template CRUDTemplates.ListAsync, GetAsync, CreateAsync, DeleteAsyncShortly after v1
3 — API-key managementApiKeys.CreateAsync, ListAsync, RevokeAsyncAfter Phase 2
4 — ResilienceBuilt-in retry (Polly), HttpClient factory integration, timeout configAfter Phase 2
5 — StreamingRenderStreamAsync for large PDFs without bufferingLater

Integrating today (raw HTTP)

Until the SDK ships, use HttpClient directly. The pattern below is copy-paste-ready and maps 1-to-1 to what the SDK will do internally.

using var http = new HttpClient { BaseAddress = new Uri("http://localhost:5110") };
http.DefaultRequestHeaders.Add("X-Api-Key", apiKey);

var body = JsonContent.Create(new { data = new { invoiceNumber = "INV-1024", total = "$4,200" } });
var response = await http.PostAsync($"/api/templates/{templateId}/render", body, ct);
response.EnsureSuccessStatusCode();

await using var pdf = await response.Content.ReadAsStreamAsync(ct);
await pdf.CopyToAsync(destination, ct);

Token grammar

Tokens in your template content follow this format:

TokenResolves to
{{field}}Value from data.field
{{field | date:"medium"}}Formatted date (preset)
{{field | date:"dd/MM/yyyy"}}Formatted date (custom .NET format)
{{$today | date:"long"}}Server date at render time
{{$now | datetime:"24h"}}Server datetime at render time
{{$page}} / {{$pages}}Page number / total pages

Full token reference → API Reference: Rendering.


Feedback

The SDK API is intentionally designed before the code is written — now is the right time to flag anything that doesn't fit your integration. Open an issue or start a discussion on GitHub.