.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
| Phase | Scope | Target |
|---|---|---|
| 1 — Core render | RenderAsync + RenderInlineAsync, AddPaperwright DI extension, basic error types | First public release |
| 2 — Template CRUD | Templates.ListAsync, GetAsync, CreateAsync, DeleteAsync | Shortly after v1 |
| 3 — API-key management | ApiKeys.CreateAsync, ListAsync, RevokeAsync | After Phase 2 |
| 4 — Resilience | Built-in retry (Polly), HttpClient factory integration, timeout config | After Phase 2 |
| 5 — Streaming | RenderStreamAsync for large PDFs without buffering | Later |
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.
- Minimal
- Inline render
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);
var body = JsonContent.Create(new
{
content = "<h1>Hello {{name}}</h1>",
data = new { name = "World" },
});
var response = await http.PostAsync("/api/render", body, ct);
response.EnsureSuccessStatusCode();
Token grammar
Tokens in your template content follow this format:
| Token | Resolves 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.