REFRACT PDF is built for teams that need reliable, production-grade PDF generation on Microsoft's Power Platform. Whether you're generating sales orders, contracts, or compliance reports, REFRACT PDF handles the heavy lifting so your flows stay clean.
What You'll Build
In this guide, we'll walk through:
- Creating a template in the REFRACT PDF Designer
- Preparing a JSON payload
- Calling your REFRACT dispatcher
- Handling the response in TypeScript
- Finding the stored PDF
Prerequisites
Before you begin, make sure you have:
- A REFRACT Platform account (free tier works for this tutorial)
- A REFRACT dispatcher deployed in your Azure subscription, with its Entra ID app registration
- A second app registration for the caller, with a client secret
- A SharePoint site with the Templates and payloads libraries configured for the dispatcher
REFRACT PDF runs in your own Azure subscription. There is no shared hosted render endpoint: every request goes to the dispatcher you deployed, and rendering never sends document content outside your Azure subscription.
Step 1: Create a Template
Every PDF starts with a template. Templates are .rpkg packages, authored in the REFRACT PDF Designer, that define layout, fonts, and data bindings.
Open the REFRACT PDF Designer from your dashboard and create a new template. The Designer gives you a live preview as you build. When it is ready, place the .rpkg file in the Templates library of your SharePoint site. Render requests refer to it by path.
Step 2: Prepare a Payload
The data for a document is a plain JSON file in the payloads library. Its shape is whatever your template binds to. A payload for a sales order template might look like this:
{
"company": {
"name": "Contoso Ltd",
"address": "1 Microsoft Way, Redmond, WA 98052"
},
"lineItems": [
{ "sku": "PROD-001", "description": "Enterprise Licence", "qty": 5, "price": 29.99, "total": 149.95 },
{ "sku": "PROD-002", "description": "Support Add-on", "qty": 1, "price": 149.00, "total": 149.00 }
],
"totals": {
"subtotal": 298.95,
"tax": 29.90,
"grand": 328.85
}
}
Save it, for example, as Contoso/Invoices/SO-1001.json in the payloads library. The render route only reads it; it is not moved or deleted.
Step 3: Call the Dispatcher
Send a POST to /api/render on your dispatcher. The request carries an Entra ID access token as a bearer token. The dispatcher checks the token's issuer and audience, and that its tenant matches the tenant the dispatcher is deployed in. It also needs a valid REFRACT license.
POST https://<your-dispatcher-host>/api/render HTTP/1.1
Authorization: Bearer <Entra ID access token>
Content-Type: application/json
{
"templatePath": "Contoso/Invoices/SalesOrder.rpkg",
"payloadPath": "Contoso/Invoices/SO-1001.json",
"options": {
"documentId": "SO-1001",
"docType": "invoice"
}
}
templatePath and payloadPath are both required. A successful call returns 200 with the PDF itself as application/pdf, not a JSON wrapper. A 401 means the token was missing or rejected, a 403 means the token's tenant does not match the dispatcher or the license is not valid, and a 400 means the body was not usable.
The response headers identify the document:
X-VaultPDF-CorrelationIdis the identifier for this render. Quote it when you ask for support.X-VaultPDF-UploadStatusisok,skippedorfailed, for the stored copy described in Step 5.X-VaultPDF-BlobPathandX-VaultPDF-WebUrllocate the stored copy when it was uploaded.
The Power Platform connector's Generate Document action sends this same request, at /api/gateway/v1/documents/generate, and returns the same PDF.
Step 4: Handle the Response in TypeScript
A service-to-service caller gets its token with the client credentials flow, using the scope api://<dispatcher-app-client-id>/.default. Here is a minimal helper for a Node.js or Azure Function project:
const tenantId = "<your-tenant-id>";
const clientId = "<caller-app-registration-client-id>";
const clientSecret = process.env.CLIENT_SECRET!;
const dispatcherClientId = "<dispatcher-app-client-id>";
const dispatcherUrl = "https://<your-dispatcher-host>";
async function getToken(): Promise<string> {
const res = await fetch(
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
scope: `api://${dispatcherClientId}/.default`,
}),
}
);
if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
return (await res.json()).access_token;
}
async function renderPdf(
templatePath: string,
payloadPath: string,
documentId: string
) {
const res = await fetch(`${dispatcherUrl}/api/render`, {
method: "POST",
headers: {
Authorization: `Bearer ${await getToken()}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ templatePath, payloadPath, options: { documentId } }),
});
if (!res.ok) {
throw new Error(`Render failed ${res.status}: ${await res.text()}`);
}
return {
pdf: Buffer.from(await res.arrayBuffer()),
correlationId: res.headers.get("X-VaultPDF-CorrelationId"),
blobPath: res.headers.get("X-VaultPDF-BlobPath"),
};
}
Step 5: Find the Stored PDF
The dispatcher returns the PDF in the response and also stores a copy in your generated documents library when one is configured. X-VaultPDF-UploadStatus tells you whether that worked, and on success X-VaultPDF-WebUrl is the link to the stored file. In Power Automate, pass the blob path from Generate Document to Get Document URL to open it.
Next Steps
You've got the basics! Explore the template settings docs to customise page margins, headers, footers, and watermarks, or dive into hierarchical tables for complex nested data. The authentication guide covers the token flow in more detail.