eCom Learning Solutions / Developer toolsOpen source
Documentation library/Complete guide
DOCUMENTATION / API v1

Build documents
into your application.

A practical guide to generating PDFs, combining pages, and connecting the API to your own platform.

Try it before you integrate.
The template studio and merge workspace use the same API endpoints as your application.

GET/api/v1/templatesDiscover preset templates and request examples (X-Api-Key required)
POST/api/v1/pdfs/{id}Populate a preset template
POST/api/v1/pdfs/custom-templateSupply a template definition and data
POST/api/v1/pdfs/mergeCombine supplied PDF pages
POST/api/v1/pdfs/page-numbersNumber an existing PDF

Send Content-Type: application/json. A successful POST returns 200 OK, Content-Type: application/pdf, and a download filename in Content-Disposition. Save the response as bytes. Error responses are JSON.

01 / QUICKSTART

Your first PDF

This example creates a complete document with caller supplied wording. Choose your preferred client. These examples use the address of the API you are viewing; change it to your deployment when integrating.

Bash / macOS / Linux
curl --fail-with-body \
  -X POST __API_ORIGIN__/api/v1/pdfs/custom-template \
  -H 'Content-Type: application/json' \
  --data '{"template":{"title":"{{data.title}}","pages":[{"layout":"flow","blocks":[{"type":"heading","text":"{{labels.heading}}"},{"type":"paragraph","text":"{{data.summary}}"}]}]},"labels":{"heading":"Overview"},"data":{"title":"Project brief","summary":"Made for your app."}}' \
  --output project-brief.pdf

On deployments that require a key, add -H 'X-Api-Key: YOUR_KEY' to cURL. Use a backend for calls from your platform so its API key stays private.

02 / GENERATE

Templates. Your wording.

A preset template is a layout registered by this deployment. Its catalog entry includes a complete, editable request example; in the API response the existing example property means a ready-to-submit request body, not a separate template definition. The production deployment exposes its production catalog. The downloadable application defaults to the smaller generic catalog. Preset catalog reads, detail pages, and request-example downloads require a valid X-Api-Key. Browse the preset references or use GET /api/v1/templates/{id} with the key in a request header.

Each PDF request can provide labels and data maps. Text fields in a layout can refer to them as {{labels.heading}} and {{data.name}}. You provide the printable labels and values on each call. Every referenced key must be present.

JSON · /api/v1/pdfs/custom-template
{
  "template": {
    "title": "{{data.title}}",
    "pages": [{ "layout": "flow", "blocks": [
      { "type": "heading", "text": "{{labels.heading}}" },
      { "type": "paragraph", "text": "{{data.summary}}" }
    ] }]
  },
  "labels": { "heading": "Project overview" },
  "data": { "title": "October update", "summary": "Prepared by Alex Example." }
}

Labels must be strings. Data may be strings, numbers, or booleans. Every referenced key is required. Legacy preset templates also accept a compact body containing only labels, data, and optional options; the selected preset supplies the layout, and your maps replace its sample values. For other request types, send the full request example. Images and fonts are supplied as base64; use each catalog entry and the OpenAPI schema for supported fields.

Explore this catalog ↗
CUSTOM / LAYOUT

Bring your own template JSON.

Use POST /api/v1/pdfs/custom-template when the layout is not in this deployment's preset catalog. Put the complete supported layout under template and send printable wording and populated values in separate labels and data maps. This route uses the shared PDF renderer without registering or changing a preset.

JSON · custom template request
{
  "template": {
    "title": "{{data.documentTitle}}",
    "pages": [{
      "layout": "flow",
      "blocks": [
        { "type": "heading", "text": "{{labels.heading}}" },
        { "type": "paragraph", "text": "{{data.summary}}" },
        { "type": "field", "label": "{{labels.owner}}", "value": "{{data.owner}}" }
      ]
    }]
  },
  "labels": { "heading": "Project overview", "owner": "Prepared by:" },
  "data": {
    "documentTitle": "Project overview",
    "summary": "A caller-defined layout populated by this request.",
    "owner": "Alex Example"
  }
}
cURL · submit your custom-template.json
curl --fail-with-body \
  -X POST '__API_ORIGIN__/api/v1/pdfs/custom-template' \
  -H 'Content-Type: application/json' \
  --data-binary '@custom-template.json' \
  --output custom-document.pdf
# Add -H 'X-Api-Key: YOUR_KEY' when this deployment requires authentication.

The template object follows the LegacyPdfRequest JSON schema: a title, one to 25 pages, supported page layouts, and optional rendering options or fonts. Keep labels and data outside that object. Unknown properties, invalid JSON types, unsupported layouts, missing bindings, and failed field validation return HTTP 400 with a JSON validation response. A successful request returns the PDF bytes. Use the preset route when you want a server-provided layout; use this route when you want to supply the layout too.

03 / COMBINE

Merge supplied PDF pages

Send your PDF files to POST /api/v1/pdfs/merge. The API uses iTextSharp PdfCopy to copy PDF pages into one document. Files are processed in array order; page selections are processed in the order you provide.

JSON · merge request structure
{
  "documents": [
    { "pdfBase64": "BASE64_OF_FIRST_PDF", "pages": [3, 1, 2] },
    { "pdfBase64": "BASE64_OF_SECOND_PDF", "pages": [1] }
  ],
  "fileName": "combined.pdf",
  "pageNumbers": { "format": "{page} / {total}" }
}
FieldTypeBehaviour
documentsarray1–25 PDF objects, in output order.
pdfBase64stringBase64 encoded PDF bytes, without a data URL prefix. At most 10 MB decoded per file.
pagesinteger arrayOne-based page numbers. Omit or use null for all pages. An empty list is invalid. Repeats are allowed, e.g. [2, 1, 2].
fileNamestring, optionalDownload filename, up to 80 characters; sanitized by the API.
pageNumbersobject, optionalStamp your supplied format on the merged result.

To provide individual pages, supply one-page PDFs, or select pages from larger PDFs. The result may contain up to 500 pages. The 20 MiB request limit includes base64 and JSON overhead. Uploaded files must be valid, unencrypted PDFs; active content and attachments are rejected.

PowerShell + cURL · merge PDFs from your current folder
$baseUrl = '__API_ORIGIN__'
$first = [IO.Path]::GetFullPath('.\first.pdf')
$second = [IO.Path]::GetFullPath('.\second.pdf')
$request = @{
  documents = @(
    @{ pdfBase64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($first)); pages = @(1) }
    @{ pdfBase64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($second)) }
  )
  fileName = 'combined.pdf'
} | ConvertTo-Json -Depth 10
$requestPath = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName())
[IO.File]::WriteAllText($requestPath, $request, [Text.UTF8Encoding]::new($false))
try {
  curl.exe --fail-with-body -X POST "$baseUrl/api/v1/pdfs/merge" `
    -H 'Content-Type: application/json' `
    --data-binary "@$requestPath" --output '.\combined.pdf'
  if ($LASTEXITCODE -ne 0) { throw 'PDF merge failed.' }
} finally { Remove-Item -LiteralPath $requestPath }

Want a merge demo?
The hosted source archive includes examples/merge-pages.ps1 and its mock request files. The generic application download contains the five template request examples only.

Existing clients may keep using documentsBase64: ["BASE64_PDF_1", "BASE64_PDF_2"] to include every page. Send exactly one of documents or documentsBase64.

Page sizes, rotation, and content are copied without rasterizing. This is a page assembly operation: document-level features such as bookmarks, digital signatures, and interactive form behaviour are not guaranteed to carry over.

Open merge workspace ↗
04 / FINISH

Number an existing PDF

Use POST /api/v1/pdfs/page-numbers for an existing file, or attach the same pageNumbers object to a merge request. You provide the complete printed format.

JSON · page numbering
{
  "pdfBase64": "BASE64_OF_YOUR_PDF",
  "fileName": "numbered.pdf",
  "pageNumbers": {
    "format": "Page {page} of {total}",
    "fontSize": 8,
    "rightMargin": 50,
    "y": 50
  }
}

{page} is the one-based output page, and {total} is the total output count. Font size, right margin, and vertical position use PDF points (72 points = 1 inch). y is measured from the bottom. Default values are 8, 50, and 50. The position must be inside every page.

05 / ACCESS

Authentication & browser calls

Check GET /api/v1/security for this deployment's key requirement, body limit, output limit, and rendering timeout. Preset catalog reads, preset detail pages, and preset example downloads always require an X-Api-Key header. PDF POST endpoints require that header when the host enables authentication. The Studio validates the same header before loading templates. Never put keys in a URL.

cURL · authenticated request
curl --fail-with-body '__API_ORIGIN__/api/v1/pdfs/custom-template' \
  -H 'X-Api-Key: YOUR_KEY' \
  -H 'Content-Type: application/json' \
  --data '{"template":{"title":"Example","pages":[{"layout":"flow","blocks":[{"type":"paragraph","text":"Your content."}]}]},"labels":{},"data":{}}' \
  --output example.pdf

Development and production settings require an API key for PDF calls. Studio always requires a valid configured key, even when anonymous generation is enabled. General guides and the deployed source download remain accessible; preset catalog and documentation details require a valid X-Api-Key. Browser workspaces hold a key only in page memory, do not save it to storage, and clear it when leaving the page.

CORS allows all origins by default, without cookies. Hosts can configure an allowlist or disable it. See CSP, CORS & response headers for configuration. Server-to-server HTTP calls do not need CORS.

BROWSER / SECURITY

CSP, CORS & response headers

These settings belong to the host. A PDF request cannot change them. Configure them in appsettings.json, environment variables, or your deployment's configuration provider, then restart the API.

CORS: all origins by default

Cors:Mode defaults to All. Browser clients receive Access-Control-Allow-Origin: *. Requests may use GET or POST and the Content-Type and X-Api-Key headers. Browsers can read Content-Disposition, Retry-After, and Link. Preflight OPTIONS requests do not require an API key.

ModeBehaviour
All · defaultAllow any browser origin, including the opaque null origin. No cookies or credentialed CORS.
AllowListAllow only the exact HTTP(S) origins in AllowedOrigins. Disallowed origins on PDF POSTs receive 403.
DisabledNo cross-origin response headers. PDF POSTs accept the API's own origin and server requests without an Origin header.
JSON · allow every origin (default)
{ "Cors": { "Mode": "All", "AllowedOrigins": [] } }
JSON · restrict browser clients
{
  "Cors": {
    "Mode": "AllowList",
    "AllowedOrigins": ["https://app.example.com", "https://admin.example.com"]
  }
}
PowerShell · configure an allowlist
$env:Cors__Mode = 'AllowList'
$env:Cors__AllowedOrigins__0 = 'https://app.example.com'
# To disable CORS instead: $env:Cors__Mode = 'Disabled'

Origins have no trailing slash or path; wildcards are not accepted in an allowlist. Set the mode explicitly when migrating a previous AllowedOrigins configuration. Malformed or multiple Origin values are rejected on PDF POSTs. CORS controls browser access; API keys, HTTPS, validation, and traffic limits still apply. Server-to-server HTTP calls do not need CORS.

Use credentials: 'omit' for cross-origin browser fetches. The API does not authenticate with cookies and never sends Access-Control-Allow-Credentials. Keep a shared deployment key on your application's backend; a key sent by browser JavaScript is visible to that browser's user.

cURL · check a browser preflight
curl -i -X OPTIONS '__API_ORIGIN__/api/v1/pdfs/custom-template' \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: Content-Type,X-Api-Key'

Content Security Policy

The frontend enforces a strict policy by default. Scripts, styles, fonts, connections, and workers are served from this deployment. Inline scripts and evaluation are blocked. Images may also use data/blob URLs for PDF tools; framing, plugins, and base URL changes are prohibited.

Default Content-Security-Policy
default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self'; worker-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'

To customize the policy, set SecurityHeaders:ContentSecurityPolicy to the complete policy string. To inspect violations without blocking resources, set SecurityHeaders:ContentSecurityPolicyReportOnly to true. This switches to the report-only header and disables CSP enforcement; keep the default false for enforcement. Review violations in browser developer tools. No report collection endpoint is included; configure your own collector and policy reporting directives if needed.

PowerShell · temporarily inspect a custom policy
$env:SecurityHeaders__ContentSecurityPolicyReportOnly = 'true'
# Return to enforcement after reviewing browser violations:
$env:SecurityHeaders__ContentSecurityPolicyReportOnly = 'false'

Suppress server and framework headers

Kestrel's Server header is disabled. Application responses remove Server, X-Powered-By, X-AspNet-Version, and X-AspNetMvc-Version, including validation and authentication errors. The supplied web.config also suppresses IIS server and framework headers on supported IIS 10 installations.

A reverse proxy, gateway, or HTTP.sys error response may add its own headers after the application runs. Configure that layer too, and inspect the public URL, including error responses. Header suppression reduces HTTP fingerprinting; it does not replace runtime updates. The source download and OpenAPI remain public, and iText's required PDF producer metadata is preserved.

cURL · inspect the public response
curl -I '__API_ORIGIN__/docs'
Additional headerDefault
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
Referrer-Policyno-referrer
Permissions-PolicyCamera, microphone, location, payment, and USB disabled
Cross-Origin-Opener-Policy / Resource-Policysame-origin; API calls use CORS
Cache-Controlno-store
Strict-Transport-Security180 days on applicable HTTPS responses when HTTPS is required

References: Microsoft CORS guidance, OWASP CSP guidance, and IIS header filtering.

08 / SECURITY

OWASP guidance in practice

The application includes controls aligned with the OWASP API Security Top 10 (2023) and REST Security Cheat Sheet. The source download includes a detailed implementation mapping in docs/owasp.md.

AreaImplemented controls
Authentication & accessKey validation before Studio tools load, API keys on protected PDF POST routes, constant-time digest comparison, required-key startup checks, and HTTPS enforcement by default.
Resource consumptionRequest/output budgets, page and asset limits, IP rate limits, bounded concurrency/queue, and cooperative rendering deadlines.
SSRF & unsafe contentRequest-owned image/font/PDF bytes; XHTML external resources and entities prohibited; imported PDF scripts, attachments, remote actions, and external streams rejected.
Frontend & responsesStrict CSP, safe DOM text insertion, framing protection, no browser key storage, bounded PDF previews, safe filenames, and sanitized error responses.
Configuration & inventoryConfigurable CORS (all origins by default, without cookies), host restrictions, explicit proxy trust, suppressed server/framework headers, OpenAPI, versioned routes, and corresponding source downloads.
Dependencies & secretsNuGet vulnerability auditing, build failure for known vulnerability warnings, source-file allowlists, and credential removal from bundled configuration.

Understand the current boundaries

Every configured key grants the same PDF permissions. Key issuance, expiry, rotation, revocation, and per-client quotas are not automated. Current traffic quotas apply per IP and per application process. The service generates a result for each call and has no stored customer documents or document retrieval endpoints.

File validation is not antivirus or a hard sandbox. PDF, image, and font decoders run inside the API process, and cancellation is cooperative. Operators should use isolated rendering with hard resource limits, gateway protection, and monitored security events for public workloads. Keep the runtime, proxy, operating system, and vendored browser libraries patched.

OWASP advises stronger authentication for sensitive or high-value resources. Configure appropriate identities and permissions for those deployments. Framework logging is present; a dedicated audit trail and alerting system still need to be configured or integrated.

Verification status
Security regression tests and browser checks exercise the implemented controls. An independent penetration test and a full OWASP ASVS assessment have not been completed.

06 / RESPONSES

Limits & useful errors

ConstraintLimit
JSON request / generated response20 MiB / 32 MiB
Imported PDF / merged output10 MB per PDF / 500 pages total
PDFs in one merge call25
Default generation rate30 requests per minute per IP
Default concurrent generations2, with a queue of 8
Default rendering deadline60 seconds; cooperative cancellation
StatusAction
400 Invalid requestRead the JSON errors map and correct the indicated fields. Use the HTTPS URL for HTTPS required errors.
401 UnauthorizedSupply the deployment's API key.
403 ForbiddenCheck the allowed origins for this deployment.
404 Unknown templateRetrieve a valid ID from the template catalog.
408 Deadline exceededReduce the workload; rendering cancellation is cooperative.
413 / 415Reduce request size / send JSON with the correct content type.
429 Too many requestsRespect Retry-After when provided.
500 Server errorUse the trace identifier to investigate server logs.
JSON · example validation response
{
  "title": "Invalid PDF operation",
  "status": 400,
  "errors": { "documents[0].pages[0]": ["Use a page number between 1 and 3."] }
}
07 / SELF HOST

Run the API yourself

Download the application source, extract it, install the .NET 10 SDK, and open a terminal in the extracted folder containing PdfGenerator.slnx.

Terminal · local development
dotnet restore PdfGenerator.slnx
$apiKey = [Convert]::ToHexString([Security.Cryptography.RandomNumberGenerator]::GetBytes(32))
dotnet user-secrets set 'Security:ApiKeys:0' $apiKey --project src/PdfGenerator.Api
Set-Clipboard -Value $apiKey
dotnet run --project src/PdfGenerator.Api --urls http://localhost:5000

Open http://localhost:5000 and paste the copied key into Studio. The repository launch profile uses port 5149 if you omit --urls. For production, follow docs/security.md in the source download: configure separate API keys, allowed hosts, HTTPS, proxy trust, and workload limits. Keep credentials in environment variables or a secret store.

Get the source and examples ↓
09 / SOURCE

Source for this running API

The application is distributed under GNU AGPL-3.0 with its license and third party notices. Download the source bundled with this deployment. The download centre includes its SHA-256 checksum and build instructions.

Review LICENSE, COPYRIGHT, and THIRD_PARTY_NOTICES.md when using or redistributing the application. This software comes with no warranty.