Quickstart
Three steps to your first PDF.
1. Create an account and get your API key
Sign up at getpapyr.dev/signup. Your first API key is created automatically and shown on the dashboard. Free tier includes 100 docs/month — no card required.
2. Generate your first PDF
curl -X POST https://api.getpapyr.dev/v1/render/pdf \
-H "Authorization: Bearer pk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello, Papyr</h1>"}' \
--output hello.pdf3. Use a template for common documents
curl -X POST https://api.getpapyr.dev/v1/render/pdf \
-H "Authorization: Bearer pk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"template": "invoice",
"data": {
"company_name": "Acme Inc",
"invoice_number": "INV-1001",
"date": "2026-04-25",
"due_date": "2026-05-25",
"bill_to": "Jane Smith",
"line_items": [
{ "description": "API work", "quantity": 1, "rate": "$500", "amount": "$500" }
],
"total": "$500.00",
"payment_terms": "Net 30"
}
}' --output invoice.pdfAuthentication
All API requests are authenticated with an API key. Pass it in the Authorization header:
Authorization: Bearer pk_live_your_api_key
Keys look like pk_live_.... You can create and revoke keys anytime from the dashboard or via the Keys API.
Account signup
/v1/auth/signup| Field | Type | Description |
|---|---|---|
| emailrequired | string | Your email address |
| passwordrequired | string | Minimum 8 characters |
| name | string | Display name (optional) |
Response
{
"token": "eyJ...", // JWT for dashboard API calls
"user": {
"id": "clxyz...",
"email": "you@example.com",
"name": "Jane",
"plan": "free"
},
"apiKey": "pk_live_..." // use this to generate PDFs
}Login
/v1/auth/login| Field | Type | Description |
|---|---|---|
| emailrequired | string | |
| passwordrequired | string |
{
"token": "eyJ...",
"user": { "id": "...", "email": "...", "name": "...", "plan": "free" }
}Password reset
Send a reset link to an email address. Always returns 200 to prevent email enumeration.
/v1/auth/forgot-password{ "email": "you@example.com" }Apply the token from the email link:
/v1/auth/reset-password{ "token": "abc123...", "password": "newpassword" }Generate PDF
/v1/render/pdfConverts HTML (or a template + data) to a PDF. Returns binary PDF data. Authenticated with your API key.
Request body
| Field | Type | Description |
|---|---|---|
| html | string | Raw HTML to render. Required if "template" is not set. |
| template | string | Template name. Required if "html" is not set. See Templates. |
| data | object | Template variables. Required when using a template. |
| options.format | string | Paper size: A4, Letter, Legal, A3, A5, Tabloid (default: A4) |
| options.landscape | boolean | Landscape orientation (default: false) |
| options.margin | object | Margins with top, right, bottom, left keys (default: 20mm each) |
| options.printBackground | boolean | Include CSS background colors and images (default: true) |
Code examples
curl -X POST https://api.getpapyr.dev/v1/render/pdf \
-H "Authorization: Bearer pk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Report Q1</h1><p>Revenue up 32%.</p>",
"options": {
"format": "Letter",
"landscape": false,
"margin": { "top": "25mm", "right": "20mm", "bottom": "25mm", "left": "20mm" },
"printBackground": true
}
}' --output report.pdfResponse
Returns the PDF as binary with Content-Type: application/pdf. Response headers also include your current quota:
Content-Type: application/pdf Content-Disposition: inline; filename="document.pdf" X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 847
Templates
Pre-built Handlebars templates for common documents. Pass a template name and a data object instead of raw HTML. Any field not supplied is simply omitted from the output.
List available templates
/v1/templatesNo authentication required.
{ "templates": ["invoice", "receipt", "report"] }invoice
Line-item invoice with total and payment terms.
| Field | Type | Description |
|---|---|---|
| company_namerequired | string | Your company name |
| invoice_numberrequired | string | e.g. "INV-1042" |
| daterequired | string | Issue date |
| due_daterequired | string | Payment due date |
| bill_torequired | string | Customer name / address |
| line_itemsrequired | array | Each: { description, quantity, rate, amount } |
| totalrequired | string | Formatted total, e.g. "$3,000.00" |
| payment_terms | string | e.g. Net 30 |
{
"template": "invoice",
"data": {
"company_name": "Acme Inc",
"invoice_number": "INV-1042",
"date": "2026-04-25",
"due_date": "2026-05-25",
"bill_to": "Jane Smith, Widget Corp",
"line_items": [
{ "description": "API Integration", "quantity": 1, "rate": "$2,500", "amount": "$2,500" },
{ "description": "Support (monthly)", "quantity": 1, "rate": "$500", "amount": "$500" }
],
"total": "$3,000.00",
"payment_terms": "Net 30 — bank transfer"
}
}receipt
Single-item payment receipt.
| Field | Type | Description |
|---|---|---|
| company_namerequired | string | |
| receipt_numberrequired | string | |
| daterequired | string | |
| descriptionrequired | string | What was purchased |
| amountrequired | string | Formatted total, e.g. "$99.00" |
| payment_method | string | e.g. "Visa ending 4242" |
| company_address | string | Your address |
| customer_email | string | Customer email (shown if provided) |
{
"template": "receipt",
"data": {
"company_name": "Acme Inc",
"receipt_number": "RCT-0091",
"date": "2026-04-25",
"description": "Pro Plan — April 2026",
"amount": "$49.00",
"payment_method": "Visa ending 4242",
"customer_email": "jane@widgetcorp.com"
}
}report
Multi-section document with a title page.
| Field | Type | Description |
|---|---|---|
| titlerequired | string | Document title |
| company_namerequired | string | |
| author | string | Author name |
| date | string | Report date |
| sectionsrequired | array | Each: { title, content } — content supports HTML |
{
"template": "report",
"data": {
"title": "Q1 2026 Summary",
"company_name": "Acme Inc",
"author": "Finance Team",
"date": "2026-04-25",
"sections": [
{ "title": "Revenue", "content": "<p>Total revenue: <strong>$1.2M</strong>, up 32% YoY.</p>" },
{ "title": "Expenses", "content": "<p>Operating expenses held flat at $800K.</p>" }
]
}
}API Keys
Manage your API keys programmatically. These endpoints use your JWT session token (returned by login/signup), not an API key — they are dashboard operations, not document generation.
Authorization: Bearer eyJ... for these endpoints.List keys
/v1/keysReturns all keys for your account. Key values are masked — only the last 8 characters are shown.
{
"keys": [
{
"id": "clxyz...",
"key": "pk_live_...abc12345",
"name": "Default",
"active": true,
"createdAt": "2026-04-01T00:00:00.000Z"
}
]
}Create a key
/v1/keysMaximum 5 keys per account. The full key value is only returned once — at creation time.
// Request
{ "name": "Production" }
// Response — save the key, it won't be shown again
{
"id": "clxyz...",
"key": "pk_live_FULL_KEY_HERE",
"name": "Production"
}Revoke a key
/v1/keys/:idImmediately deactivates the key. Any in-flight requests using it will fail with 401.
{ "message": "API key revoked" }Usage
/v1/usageReturns your current month's document usage, limit, and a daily breakdown. Authenticated with your JWT session token.
{
"plan": "starter",
"used": 153,
"limit": 1000,
"remaining": 847,
"daily": {
"2026-04-01": 12,
"2026-04-02": 30,
"2026-04-03": 111
}
}Usage resets on the 1st of each month (UTC). The same quota is also reflected in X-RateLimit-Remaining response headers on each render call.
Errors
Errors always return JSON with an error field. HTTP status codes follow standard conventions.
| Status | Meaning | Common cause |
|---|---|---|
| 400 | Bad Request | Missing required field, invalid plan, or malformed JSON |
| 401 | Unauthorized | Missing, invalid, or revoked API key or JWT |
| 404 | Not Found | Resource (e.g. API key ID) does not exist or belongs to another user |
| 409 | Conflict | Email already registered |
| 429 | Quota Exceeded | Monthly document limit reached — upgrade your plan |
| 500 | Server Error | Something went wrong on our end |
// Example error response
{
"error": "Monthly document limit reached",
"limit": 1000,
"used": 1000,
"plan": "starter"
}Rate Limits
There are two kinds of limits:
| Plan | Monthly docs | Price |
|---|---|---|
| Free | 100 | $0 |
| Starter | 1,000 | $19/mo |
| Pro | 10,000 | $49/mo |
| Scale | 100,000 | $149/mo |
Burst limit: there is also a per-IP request rate limit on the render endpoint to prevent abuse. If you hit it you will receive a 429 with a Retry-After header. This limit is generous and is not normally reached in production workloads.
Every render response includes quota headers:
X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 847
Quota resets on the 1st of each calendar month (UTC). Need more? Upgrade from your dashboard.