API Reference
Everything you need to integrate PDFForge into your application. Fill PDF forms, generate documents from HTML, and manage your templates programmatically.
Introduction
Overview of the PDFForge REST API.
PDFForge is a REST API for filling PDF forms, generating PDFs from HTML templates, and filling DOCX templates. All requests are made to:
https://api.pdfforge.dev/v1The API accepts JSON request bodies (except file uploads which use multipart/form-data) and returns JSON responses. All timestamps are in ISO 8601 UTC format.
Key concepts:
- Templates — Reusable PDF forms, HTML/Handlebars templates, or DOCX templates stored in your account
- Documents — Generated PDFs or DOCX files created by filling a template with data
- API Keys — Bearer tokens used to authenticate every request (
pk_live_for production,pk_test_for testing)
Authentication
Authenticating your API requests.
Every API request must include a valid API key in the Authorization header:
Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxAPI keys use the format pk_live_ (production) or pk_test_ (testing) followed by 32 random characters. Keys are hashed with SHA-256 before storage — they are shown exactly once at creation time and can never be retrieved again.
You can create and manage API keys from the Dashboard or via the API Keys endpoints below.
Requests with a missing or invalid key receive a 401 Unauthorized response.
Sandbox Mode
Test your integration safely with sandbox API keys.
API keys come in two modes: Live (pk_live_) and Sandbox (pk_test_). Sandbox keys let you test your integration without affecting production quotas or triggering webhooks.
How sandbox mode differs from production:
| Aspect | Live Keys | Sandbox Keys |
|---|---|---|
| Documents | Real output | Watermarked "SANDBOX — NOT FOR PRODUCTION" |
| Usage quota | Plan limit/month | 60 documents/day |
| Rate limit | Plan-based | Fixed 60 req/min |
| Retention | 7 days – 1 year | 1 hour |
| Webhooks | Triggered | Disabled |
Key behaviors:
- Sandbox keys access the same templates as live keys (shared data, no isolation)
- All three document types (PDF fill, HTML generate, DOCX fill) are watermarked
- Usage events are tracked separately with
mode: "test"— they never count against your monthly quota - The daily sandbox cap resets at midnight UTC
- Documents created in sandbox mode expire after 1 hour regardless of your plan
Creating sandbox keys:
Create a sandbox key from the Dashboard by selecting "Test" mode, or via the API:
POST /v1/api-keys
{ "name": "My Test Key", "mode": "test" }Use sandbox keys exactly like live keys — just swap the Authorization header. The API automatically applies sandbox rules based on the key prefix.
Quick Start
Generate your first PDF in three steps.
Step 1 — Create an API key
Sign in to the Dashboard and create a new API key. Copy the raw key — you will not see it again.
Step 2 — Upload a template
Upload a PDF form, HTML/Handlebars template, or DOCX template via POST /v1/templates. The API detects fillable fields and template tags automatically.
Step 3 — Generate a document
Call POST /v1/documents/fill (for PDF forms and DOCX templates) or POST /v1/documents/generate (for HTML templates) with your template ID and data. You will receive a signed download URL in the response.
That's it. Three API calls to go from zero to a production document.
Templates
Upload, manage, and inspect your PDF, HTML, and DOCX templates.
/v1/templatesUpload a PDF form, HTML/Handlebars template, or DOCX template. The API auto-detects fillable fields and template tags.
| Name | Type | Required | Description |
|---|---|---|---|
| file | File | PDF (.pdf), HTML (.html, .hbs, .handlebars), or DOCX (.docx) file | |
| name | string | Human-readable template name |
Response 201
{
"id": "tpl_a1b2c3d4e5f6g7h8",
"name": "Invoice Template",
"type": "html",
"fields": [
{
"name": "company",
"type": "string"
},
{
"name": "amount",
"type": "number"
}
],
"created_at": "2026-02-08T10:00:00.000Z"
}/v1/templates/from-pdfAnalyze a static PDF with AI and generate a matching HTML/Handlebars template with detected fields and sample data.
| Name | Type | Required | Description |
|---|---|---|---|
| file | File | A PDF file to analyze (max 32 MB) | |
| name | string | Template name (defaults to filename without .pdf extension) |
Response 201
{
"id": "tpl_x9y8z7w6v5u4t3s2",
"name": "Contract Agreement",
"type": "html",
"fields": [
{
"name": "client_name",
"type": "text"
},
{
"name": "contract_date",
"type": "date"
},
{
"name": "total_amount",
"type": "currency"
}
],
"analysis": {
"document_type": "contract",
"language": "en",
"page_count": 3,
"structure_summary": "A service agreement with client details, terms, and signature block.",
"fields": [
{
"variable_name": "client_name",
"field_type": "text"
},
{
"variable_name": "contract_date",
"field_type": "date"
},
{
"variable_name": "total_amount",
"field_type": "currency"
}
],
"sample_data": {
"client_name": "Acme Corp",
"contract_date": "2026-03-15",
"total_amount": "12,500.00"
}
},
"created_at": "2026-02-08T10:05:00.000Z"
}This endpoint uses AI-powered analysis to convert your PDF into an HTML template.
PDF must be a valid file and cannot exceed 32 MB.
The generated template is an AI approximation (80–90% fidelity). Review and refine it in the editor before production use.
Embedded JPEG and PNG images are extracted and included. Vector-only logos (PDF drawing operators) cannot be extracted and will be approximated or omitted.
Custom fonts are replaced with web-safe alternatives (Helvetica, Georgia, Courier). Exact font matching is not possible.
Complex layouts (overlapping elements, multi-column text flows, intricate tables) may require manual adjustment.
Checkboxes and form fields are converted to styled HTML elements with Handlebars conditionals — native PDF form widgets are not preserved.
/v1/templatesRetrieve all templates belonging to your account, ordered by creation date.
Response 200
{
"data": [
{
"id": "tpl_a1b2c3d4e5f6g7h8",
"name": "Invoice Template",
"type": "html",
"fields_count": 5,
"created_at": "2026-02-08T10:00:00.000Z"
},
{
"id": "tpl_r4s3t2u1v0w9x8y7",
"name": "Application Form",
"type": "pdf_form",
"fields_count": 12,
"created_at": "2026-02-07T14:30:00.000Z"
}
]
}/v1/templates/:idRetrieve full details for a single template, including its field definitions.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Template ID (e.g. tpl_a1b2c3d4e5f6g7h8) |
Response 200
{
"id": "tpl_a1b2c3d4e5f6g7h8",
"name": "Invoice Template",
"type": "html",
"fields": [
{
"name": "company",
"type": "string"
},
{
"name": "amount",
"type": "number"
},
{
"name": "date",
"type": "string"
}
],
"created_at": "2026-02-08T10:00:00.000Z"
}/v1/templates/:id/fieldsRetrieve only the fillable fields for a template. Useful for building dynamic forms.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Template ID |
Response 200
{
"fields": [
{
"name": "full_name",
"type": "text"
},
{
"name": "email",
"type": "email"
},
{
"name": "agree_terms",
"type": "checkbox"
}
]
}/v1/templates/:id/fieldsUpdate the type metadata for template fields. Field names cannot be changed — only their types.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Template ID |
| Name | Type | Required | Description |
|---|---|---|---|
| fields | array | Array of { name, type } objects to update |
Request Body
{
"fields": [
{
"name": "full_name",
"type": "text"
},
{
"name": "email",
"type": "email"
}
]
}Response 200
{
"fields": [
{
"name": "full_name",
"type": "text"
},
{
"name": "email",
"type": "email"
},
{
"name": "agree_terms",
"type": "checkbox"
}
]
}/v1/templates/:id/contentRetrieve the raw HTML source of an HTML template. Only available for templates with type "html".
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Template ID |
Response 200
{
"content": "<div class=\"invoice\">{{company}} — {{amount}}</div>"
}Returns 400 INVALID_TEMPLATE_TYPE for PDF form templates.
/v1/templates/:idUpdate a template's name and/or HTML content. When content is updated, fields are automatically re-extracted.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Template ID |
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | New template name | |
| content | string | New HTML content (html templates only) |
Request Body
{
"name": "Invoice Template v2",
"content": "<div class=\"invoice\">{{company}} — {{total}}</div>"
}Response 200
{
"id": "tpl_a1b2c3d4e5f6g7h8",
"name": "Invoice Template v2",
"type": "html",
"fields": [
{
"name": "company",
"type": "string"
},
{
"name": "total",
"type": "string"
}
],
"created_at": "2026-02-08T10:00:00.000Z"
}At least one of name or content is required.
Content update is only available for HTML templates.
/v1/templates/:idPermanently delete a template and its file from storage.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Template ID |
Response 200
{
"deleted": true
}Documents
Fill PDF forms and DOCX templates, generate PDFs from HTML, and manage your documents.
/v1/documents/fillFill a PDF form or DOCX template with the provided data and receive a download URL for the completed document. Output format matches template type: PDF for pdf_form, DOCX for docx.
| Name | Type | Required | Description |
|---|---|---|---|
| template_id | string | ID of a pdf_form or docx template | |
| data | object | Key-value map of field names to values (string, number, or boolean) | |
| options.flatten | boolean | Flatten form fields after filling (PDF only)(default: false) | |
| options.filename | string | Output filename | |
| options.webhook_url | string | URL to receive a one-time POST with the document response (fire-and-forget, unsigned) |
Request Body
{
"template_id": "tpl_r4s3t2u1v0w9x8y7",
"data": {
"full_name": "Jane Smith",
"email": "jane@example.com",
"agree_terms": true
},
"options": {
"flatten": true,
"filename": "application-jane.pdf"
}
}Response 201
{
"id": "doc_m3n4o5p6q7r8s9t0",
"status": "completed",
"download_url": "https://storage.pdfforge.dev/documents/...",
"expires_at": "2026-02-15T10:00:00.000Z",
"filename": "application-jane.pdf",
"pages": 2,
"size_bytes": 45230
}For DOCX templates, the pages field in the response is null.
Output format matches template type: PDF for pdf_form, DOCX for docx.
/v1/documents/generateRender an HTML/Handlebars template with data and convert to PDF using a headless browser.
| Name | Type | Required | Description |
|---|---|---|---|
| template_id | string | ID of an html template | |
| data | object | Template variables passed to Handlebars | |
| options.format | string | Page size: A4, A3, Letter, Legal(default: A4) | |
| options.margin | object | Page margins: { top, right, bottom, left } | |
| options.landscape | boolean | Landscape orientation(default: false) | |
| options.filename | string | Output filename | |
| options.webhook_url | string | URL to receive a one-time POST with the document response (fire-and-forget, unsigned) |
Request Body
{
"template_id": "tpl_a1b2c3d4e5f6g7h8",
"data": {
"company": "Acme Corp",
"items": [
{
"name": "Widget",
"qty": 3,
"price": 9.99
},
{
"name": "Gadget",
"qty": 1,
"price": 24.5
}
]
},
"options": {
"format": "A4",
"margin": {
"top": "20mm",
"bottom": "20mm"
},
"filename": "invoice-acme.pdf"
}
}Response 201
{
"id": "doc_k1l2m3n4o5p6q7r8",
"status": "completed",
"download_url": "https://storage.pdfforge.dev/documents/...",
"expires_at": "2026-03-10T10:00:00.000Z",
"filename": "invoice-acme.pdf",
"pages": 1,
"size_bytes": 82104
}/v1/documentsRetrieve a paginated list of documents for your account with optional filters.
| Name | Type | Required | Description |
|---|---|---|---|
| limit | integer | Maximum number of results(default: 50) | |
| offset | integer | Pagination offset(default: 0) | |
| status | string | Filter by status: completed, expired, failed | |
| template_id | string | Filter by template ID |
Response 200
{
"data": [
{
"id": "doc_k1l2m3n4o5p6q7r8",
"template_id": "tpl_a1b2c3d4e5f6g7h8",
"template_name": "Invoice Template",
"status": "completed",
"filename": "invoice-acme.pdf",
"size_bytes": 82104,
"created_at": "2026-02-08T12:00:00.000Z",
"expires_at": "2026-03-10T12:00:00.000Z"
}
],
"total": 42,
"limit": 50,
"offset": 0
}/v1/documents/:idRetrieve details for a single document.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Document ID (e.g. doc_k1l2m3n4o5p6q7r8) |
Response 200
{
"id": "doc_k1l2m3n4o5p6q7r8",
"template_id": "tpl_a1b2c3d4e5f6g7h8",
"template_name": "Invoice Template",
"status": "completed",
"filename": "invoice-acme.pdf",
"size_bytes": 82104,
"created_at": "2026-02-08T12:00:00.000Z",
"expires_at": "2026-03-10T12:00:00.000Z"
}/v1/documents/:id/downloadGet a fresh signed download URL for a document. The URL is valid for 24 hours.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Document ID |
Response 200
{
"download_url": "https://storage.pdfforge.dev/documents/...",
"expires_in": 86400
}Returns 410 Gone if the document has expired based on your plan's retention period.
/v1/documents/:idPermanently delete a document from storage and the database.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Document ID |
Response 200
{
"deleted": true
}Usage
Track your document generation usage for the current billing period.
/v1/usageRetrieve fill and generate counts for the current calendar month.
| Name | Type | Required | Description |
|---|---|---|---|
| mode | string | Filter by key mode: "live", "test", or "all"(default: all) |
Response 200
{
"mode": "all",
"fill": 12,
"generate": 38,
"total": 50,
"period": {
"start": "2026-02-01T00:00:00.000Z",
"end": "2026-02-28T23:59:59.000Z"
}
}Billing
Manage subscriptions and check billing status via Stripe integration.
/v1/billing/checkoutCreate a Stripe Checkout session to subscribe to a paid plan. Redirects the user to Stripe's hosted payment page.
| Name | Type | Required | Description |
|---|---|---|---|
| plan | string | Target plan: "starter", "pro", or "business" |
Request Body
{
"plan": "pro"
}Response 200
{
"url": "https://checkout.stripe.com/c/pay/cs_live_..."
}/v1/billing/portalCreate a Stripe Customer Portal session for managing the existing subscription, invoices, and payment methods.
Response 200
{
"url": "https://billing.stripe.com/p/session/..."
}Returns 400 NO_SUBSCRIPTION if the account has no active Stripe customer.
/v1/billing/statusRetrieve current plan, usage limits, and subscription details.
Response 200
{
"plan": "pro",
"limit": 2500,
"used": 127,
"remaining": 2373,
"subscriptionStatus": "active",
"planPeriodEnd": "2026-03-08T00:00:00.000Z"
}The limit and remaining fields are null for the Enterprise plan (unlimited).
Account
View and update your account details.
/v1/accountRetrieve details for the currently authenticated account.
Response 200
{
"id": "acc_f1e2d3c4b5a6",
"email": "jane@example.com",
"name": "Jane Smith",
"plan": "pro",
"createdAt": "2026-01-15T09:30:00.000Z"
}/v1/accountUpdate the account display name.
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | New display name (1-255 characters) |
Request Body
{
"name": "Jane Smith-Williams"
}Response 200
{
"id": "acc_f1e2d3c4b5a6",
"email": "jane@example.com",
"name": "Jane Smith-Williams",
"plan": "pro",
"createdAt": "2026-01-15T09:30:00.000Z"
}API Keys
Create, list, and revoke API keys. These endpoints require session authentication (dashboard cookie).
/v1/api-keysList all API keys for your account. The raw key value is never returned — only the prefix is shown.
Response 200
[
{
"id": "ak_9a8b7c6d5e4f",
"prefix": "pk_live_a1b2c3d4",
"name": "Production Key",
"lastUsed": "2026-02-08T08:15:00.000Z",
"createdAt": "2026-01-20T12:00:00.000Z"
},
{
"id": "ak_3f2e1d0c9b8a",
"prefix": "pk_live_x9y8z7w6",
"name": "Staging Key",
"lastUsed": null,
"createdAt": "2026-02-01T16:45:00.000Z"
}
]/v1/api-keysGenerate a new API key. The raw key is returned in the response and can never be retrieved again.
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | A human-readable label for the key (1-255 characters) | |
| mode | string | Key mode: "live" (production) or "test" (sandbox)(default: live) |
Request Body
{
"name": "CI/CD Pipeline",
"mode": "live"
}Response 201
{
"id": "ak_h7g6f5e4d3c2",
"prefix": "pk_live_q1w2e3r4",
"name": "CI/CD Pipeline",
"createdAt": "2026-02-08T14:00:00.000Z",
"key": "pk_live_q1w2e3r4t5y6u7i8o9p0a1s2d3f4g5h6"
}The key field is only returned once at creation time. Store it securely.
/v1/api-keys/:idPermanently revoke an API key. Any request using this key will immediately receive 401 Unauthorized.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | API key ID |
Response 200
{
"message": "API key revoked"
}Webhooks
Receive real-time notifications when documents are generated, fail, or expire.
Webhooks notify your application when events occur in your PDFForge account. Each delivery includes an HMAC-SHA256 signature for verification.
Event types:
| Event | Trigger |
|---|---|
| `document.completed` | Document successfully generated |
| `document.failed` | Document generation failed |
| `document.expired` | Document expired and was cleaned up |
| `document.deleted` | Document was manually deleted |
Signature verification:
Every webhook delivery includes an X-PDFForge-Signature header containing an HMAC-SHA256 signature of the request body, signed with your endpoint's secret:
X-PDFForge-Signature: sha256=<hex-digest>Verify the signature in your handler to ensure the request came from PDFForge. The endpoint secret is returned when you create or retrieve a webhook endpoint.
Per-request webhooks:
In addition to global webhook endpoints, you can pass a webhook_url in the options of any fill or generate request. This fires a one-time, unsigned POST to your URL with the document response payload. Per-request webhooks are useful for async workflows where you want notification for a specific document.
{
"template_id": "tpl_abc123",
"data": { "name": "Jane" },
"options": {
"webhook_url": "https://your-app.com/hooks/pdfforge"
}
}Note: Webhooks are available on Pro plans and above. Sandbox (test) keys do not trigger webhooks.
/v1/webhooksRegister a new webhook endpoint to receive event notifications.
| Name | Type | Required | Description |
|---|---|---|---|
| url | string | HTTPS URL to receive webhook deliveries | |
| events | string[] | Event types to subscribe to |
Request Body
{
"url": "https://your-app.com/webhooks/pdfforge",
"events": [
"document.completed",
"document.failed"
]
}Response 201
{
"id": "wh_a1b2c3d4e5f6g7h8",
"url": "https://your-app.com/webhooks/pdfforge",
"events": [
"document.completed",
"document.failed"
],
"secret": "whsec_k9j8h7g6f5e4d3c2b1a0...",
"active": true,
"created_at": "2026-02-16T10:00:00.000Z"
}The secret is only returned at creation time. Store it securely for signature verification.
URL must use HTTPS.
Maximum 5 webhook endpoints per account.
/v1/webhooksList all webhook endpoints for your account.
Response 200
[
{
"id": "wh_a1b2c3d4e5f6g7h8",
"url": "https://your-app.com/webhooks/pdfforge",
"events": [
"document.completed",
"document.failed"
],
"active": true,
"created_at": "2026-02-16T10:00:00.000Z"
}
]/v1/webhooks/:idRetrieve details for a single webhook endpoint.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Webhook endpoint ID |
Response 200
{
"id": "wh_a1b2c3d4e5f6g7h8",
"url": "https://your-app.com/webhooks/pdfforge",
"events": [
"document.completed",
"document.failed"
],
"active": true,
"created_at": "2026-02-16T10:00:00.000Z"
}/v1/webhooks/:idUpdate an existing webhook endpoint's URL, events, or active status.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Webhook endpoint ID |
| Name | Type | Required | Description |
|---|---|---|---|
| url | string | New HTTPS URL | |
| events | string[] | New event subscriptions | |
| active | boolean | Enable or disable the endpoint |
Request Body
{
"events": [
"document.completed",
"document.failed",
"document.expired"
],
"active": true
}Response 200
{
"id": "wh_a1b2c3d4e5f6g7h8",
"url": "https://your-app.com/webhooks/pdfforge",
"events": [
"document.completed",
"document.failed",
"document.expired"
],
"active": true,
"created_at": "2026-02-16T10:00:00.000Z"
}/v1/webhooks/:idPermanently delete a webhook endpoint. Pending deliveries will be cancelled.
| Name | Type | Required | Description |
|---|---|---|---|
| id | string | Webhook endpoint ID |
Response 200
{
"deleted": true
}Errors
Error format, codes, and document retention policies.
All API errors return a consistent JSON envelope:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description of what went wrong"
}
}Error Codes
| Code | HTTP | Description |
|---|---|---|
| VALIDATION_ERROR | 400 | Request body validation failed |
| INVALID_INPUT | 400 | Missing or malformed input |
| INVALID_TEMPLATE_TYPE | 400 | Wrong template type for the operation |
| PDF_TOO_LARGE | 400 | Uploaded PDF exceeds the 32 MB limit |
| NO_SUBSCRIPTION | 400 | No active Stripe subscription found |
| UNAUTHORIZED | 401 | Missing or invalid API key |
| TEMPLATE_NOT_FOUND | 404 | Template does not exist or is not owned by your account |
| DOCUMENT_NOT_FOUND | 404 | Document does not exist or is not owned by your account |
| API_KEY_NOT_FOUND | 404 | API key does not exist |
| DOCUMENT_EXPIRED | 410 | Document has expired and been cleaned up |
| USAGE_LIMIT_EXCEEDED | 429 | Monthly document generation limit reached |
| SANDBOX_DAILY_LIMIT | 429 | Sandbox daily limit of 60 documents reached |
Document Retention
Generated documents are retained based on your plan. After expiration, files are removed from storage and the document status changes to expired.
| Plan | Retention |
|---|---|
| Free | 7 days |
| Starter | 30 days |
| Pro | 90 days |
| Business | 365 days |
| Enterprise | Unlimited |
Sandbox documents (created with pk_test_ keys) always expire after 1 hour regardless of plan.