API Reference

Base URL: https://api.getpapyr.dev

Open interactive API explorer

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.pdf

3. 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.pdf

Authentication

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.

Keep keys secret. They carry your full quota. If a key is compromised, revoke it immediately and create a new one.

Account signup

POST/v1/auth/signup
FieldTypeDescription
emailrequiredstringYour email address
passwordrequiredstringMinimum 8 characters
namestringDisplay 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

POST/v1/auth/login
FieldTypeDescription
emailrequiredstring
passwordrequiredstring
{
  "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.

POST/v1/auth/forgot-password
{ "email": "you@example.com" }

Apply the token from the email link:

POST/v1/auth/reset-password
{ "token": "abc123...", "password": "newpassword" }

Generate PDF

POST/v1/render/pdf

Converts HTML (or a template + data) to a PDF. Returns binary PDF data. Authenticated with your API key.

Request body

FieldTypeDescription
htmlstringRaw HTML to render. Required if "template" is not set.
templatestringTemplate name. Required if "html" is not set. See Templates.
dataobjectTemplate variables. Required when using a template.
options.formatstringPaper size: A4, Letter, Legal, A3, A5, Tabloid (default: A4)
options.landscapebooleanLandscape orientation (default: false)
options.marginobjectMargins with top, right, bottom, left keys (default: 20mm each)
options.printBackgroundbooleanInclude 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.pdf

Response

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

GET/v1/templates

No authentication required.

{ "templates": ["invoice", "receipt", "report"] }

invoice

Line-item invoice with total and payment terms.

FieldTypeDescription
company_namerequiredstringYour company name
invoice_numberrequiredstringe.g. "INV-1042"
daterequiredstringIssue date
due_daterequiredstringPayment due date
bill_torequiredstringCustomer name / address
line_itemsrequiredarrayEach: { description, quantity, rate, amount }
totalrequiredstringFormatted total, e.g. "$3,000.00"
payment_termsstringe.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.

FieldTypeDescription
company_namerequiredstring
receipt_numberrequiredstring
daterequiredstring
descriptionrequiredstringWhat was purchased
amountrequiredstringFormatted total, e.g. "$99.00"
payment_methodstringe.g. "Visa ending 4242"
company_addressstringYour address
customer_emailstringCustomer 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.

FieldTypeDescription
titlerequiredstringDocument title
company_namerequiredstring
authorstringAuthor name
datestringReport date
sectionsrequiredarrayEach: { 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.

Pass your JWT as Authorization: Bearer eyJ... for these endpoints.

List keys

GET/v1/keys

Returns 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

POST/v1/keys

Maximum 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

DELETE/v1/keys/:id

Immediately deactivates the key. Any in-flight requests using it will fail with 401.

{ "message": "API key revoked" }

Usage

GET/v1/usage

Returns 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.

StatusMeaningCommon cause
400Bad RequestMissing required field, invalid plan, or malformed JSON
401UnauthorizedMissing, invalid, or revoked API key or JWT
404Not FoundResource (e.g. API key ID) does not exist or belongs to another user
409ConflictEmail already registered
429Quota ExceededMonthly document limit reached — upgrade your plan
500Server ErrorSomething 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:

PlanMonthly docsPrice
Free100$0
Starter1,000$19/mo
Pro10,000$49/mo
Scale100,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.