API Reference
Generate beautiful, PDF/A-2A + PDF/UA-1 compliant documents from structured data. Designed for AI agents and API clients — no templates to learn, no design decisions to make.
Overview
The API is split into two stages — one creative, one mechanical:
| Endpoint | Purpose | Speed | Rate limit |
|---|---|---|---|
| POST /api/v1/templates | Create a reusable template (AI) | ~20s | 30/hour |
| GET /api/v1/templates | List your templates | fast | — |
| GET /api/v1/templates?id=... | Get a single template | fast | — |
| POST /api/v1/render | Render PDF from template | ~100ms | 100/hour |
| POST /api/v1/render | One-shot generate + render | ~20s | 50/hour |
| POST /api/v1/md | Markdown to PDF | ~100ms | 200/hour |
| POST /api/v1/carousel | Markdown to carousel PDF | ~100ms | 200/hour |
Recommended workflow: Create a template once, then render repeatedly with different data. This gives you consistent design, fast renders, and the ability to generate alternatives and pick the one you like.
Authentication
All endpoints require authentication. Two methods are supported:
- Bearer token — pass your API key in the Authorization header:
Authorization: Bearer your-api-key- Session cookie — if you're logged in to the web app, your session cookie is accepted automatically.
Unauthenticated requests receive a 401 response.
Device Authorization (for AI agents & CLIs)
If your agent or CLI needs to authenticate without pre-configured API keys, use the OAuth Device Authorization Grant flow. The agent opens the user's browser, the user approves, and the agent receives a persistent API key.
- Request a device code — POST /api/v1/device/code with an optional client_name. Returns device_code, user_code, verification_uri_complete, expires_in, and interval.
- Open the browser — direct the user to verification_uri_complete (code pre-filled) or display the user_code for manual entry.
- Poll for the token — POST /api/v1/device/token with device_code every 5 seconds.
- Handle responses — authorization_pending (keep polling), slow_down (increase interval by 5s), access_denied (abort), expired_token (restart).
- On success — the response includes an access_token. Store it and use as Authorization: Bearer <token> for all subsequent API calls. The token is a persistent API key.
# 1. Request device code
curl -X POST https://makespdf.com/api/v1/device/code \
-H "Content-Type: application/json" \
-d '{"client_name": "My AI Agent"}'
# 2. Open the verification_uri_complete in the user's browser
# 3. Poll for the token
curl -X POST https://makespdf.com/api/v1/device/token \
-H "Content-Type: application/json" \
-d '{"device_code": "<device_code from step 1>"}'Endpoints
Generate a reusable PDF template via AI. The template is a design tailored to your data shape, with {{variable}} placeholders that can be filled with different data on each render. Call multiple times with the same input to get alternative designs.
| Field | Type | Description |
|---|---|---|
| type required | string | Document type: "invoice", "receipt", "quote", "cv", "resume", "statement", "certificate", or "letter". |
| data required | object | The structured data for your document. The AI designs the template around this shape. Max 50KB. |
| style optional | string | Style hint: "modern", "classic", "minimal", "bold", or a freeform description. Max 200 chars. |
| brand optional | object | Brand customisation with optional logoUrl, primaryColor, secondaryColor, and fontPreference. |
POST /api/v1/templates
Content-Type: application/json
Authorization: Bearer your-api-key
{
"type": "invoice",
"style": "modern",
"brand": {
"primaryColor": "#2563eb",
"logoUrl": "https://example.com/logo.png"
},
"data": {
"company": "Acme Corp",
"invoiceNumber": "INV-001",
"date": "2025-03-15",
"items": [
{ "description": "Web design", "quantity": 1, "unitPrice": 2500 },
{ "description": "Hosting (annual)", "quantity": 1, "unitPrice": 300 }
],
"subtotal": 2800,
"tax": 280,
"total": 3080
}
}201 Created
{
"templateId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "invoice",
"style": "modern",
"model": "claude",
"version": 1,
"dataShape": "company,date,invoiceNumber,items[description,quantity,unitPrice],subtotal,tax,total",
"generationMs": 18420,
"costUsd": 0.032,
"createdAt": "2025-03-15T10:30:00.000Z"
}List your saved templates, newest first. Only returns templates owned by the authenticated caller.
| Field | Type | Description |
|---|---|---|
| id optional | string (UUID) | If provided, returns a single template by ID (including the full content/definition). |
| type optional | string | Filter by document type: "invoice", "receipt", "quote", "cv", "statement", "certificate", or "letter". |
| limit optional | number | Max results to return. Default 20, max 100. |
| offset optional | number | Pagination offset. Default 0. |
GET /api/v1/templates?type=invoice&limit=5
Authorization: Bearer your-api-key200 OK
{
"templates": [
{
"templateId": "a1b2c3d4-...",
"type": "invoice",
"style": "modern",
"dataShape": "company,date,items[...],total",
"model": "claude",
"version": 1,
"createdAt": "2025-03-15T10:30:00"
}
],
"total": 1,
"limit": 5,
"offset": 0
}GET /api/v1/templates?id=a1b2c3d4-e5f6-7890-abcd-ef1234567890
Authorization: Bearer your-api-keyReturns the full template including the content field (the DocumentDefinition JSON). The list endpoint omits content to keep responses compact.
Render a PDF. Supports two modes depending on the request body:
Pass a templateId from a previously created template, plus fresh data. No AI call — renders in ~100ms.
| Field | Type | Description |
|---|---|---|
| templateId required | string (UUID) | The ID of a template created via POST /api/v1/templates. |
| data required | object | Fresh data to populate the template. Should match the same shape as the original. Max 50KB. |
POST /api/v1/render
Content-Type: application/json
Authorization: Bearer your-api-key
{
"templateId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"data": {
"company": "Different Corp",
"invoiceNumber": "INV-002",
"date": "2025-03-20",
"items": [
{ "description": "Consulting", "quantity": 10, "unitPrice": 150 }
],
"subtotal": 1500,
"tax": 150,
"total": 1650
}
}Pass a type and data to generate a template and render in a single request. Convenient but slower (~20s) and the template is not saved for reuse.
| Field | Type | Description |
|---|---|---|
| type required | string | Document type: "invoice", "receipt", "quote", "cv", "resume", "statement", "certificate", or "letter". |
| data required | object | The structured data for your document. Max 50KB. |
| style optional | string | Style hint. Max 200 chars. |
| brand optional | object | Brand customisation (logoUrl, primaryColor, secondaryColor, fontPreference). |
POST /api/v1/render
Content-Type: application/json
Authorization: Bearer your-api-key
{
"type": "receipt",
"data": {
"store": "Corner Shop",
"date": "2025-03-15",
"items": [
{ "name": "Coffee", "quantity": 2, "price": 4.50 },
{ "name": "Croissant", "quantity": 1, "price": 3.00 }
],
"total": 12.00,
"paymentMethod": "Card ending 4242"
}
}By default, the response is the PDF binary with Content-Type: application/pdf. Metadata is returned in response headers:
| Field | Type | Description |
|---|---|---|
| X-Pages required | header | Number of pages in the PDF. |
| X-Render-Ms required | header | Total render time in milliseconds. |
| X-Model optional | header | AI model used (only present for one-shot mode). |
| X-Rate-Limit-Remaining required | header | Remaining requests in the current rate limit window. |
Convert GitHub Flavored Markdown to PDF. No AI call — pure computation, typically under 200ms.
| Field | Type | Description |
|---|---|---|
| markdown required | string | GitHub Flavored Markdown content. Max 200KB. |
| options.pageSize optional | string | "A3", "A4" (default), "A5", "Letter", or "Legal". |
| options.fontFamily optional | string | "Inter" (default) or "NotoSans". |
| options.fontSize optional | number | Base font size in points (6–24, default 10). |
| options.margins optional | array | [top, right, bottom, left] in points. Default [40, 40, 40, 40]. |
| options.title optional | string | PDF document title. Max 200 chars. |
POST /api/v1/md
Content-Type: application/json
Authorization: Bearer your-api-key
{
"markdown": "# Hello\n\nSome **bold** text.",
"options": {
"pageSize": "Letter",
"fontFamily": "NotoSans"
}
}Convert Markdown to a carousel-style PDF with one slide per page. Optimized for LinkedIn document uploads. Slides are separated by horizontal rules (---). The first slide is a title slide with larger headings and vertical centering. No AI call — pure computation, typically under 200ms.
| Field | Type | Description |
|---|---|---|
| markdown required | string | Markdown content with --- as slide separators. Requires blank lines before and after ---. Max 200KB. |
| options.format optional | string | "square" (default, 1080×1080px) or "portrait" (1080×1350px). |
| options.theme optional | string | "light" (default) or "dark". |
| options.fontFamily optional | string | "Inter" (default) or "NotoSans". |
| options.fontSize optional | number | Base font size in points (12–36, default 20). |
| options.title optional | string | PDF document title. Max 200 chars. |
POST /api/v1/carousel
Content-Type: application/json
Authorization: Bearer your-api-key
{
"markdown": "# My Talk\nSubtitle here\n\n---\n\n## Key Insight\nOne idea per slide keeps your audience engaged.\n\n---\n\n## Thanks!\nFollow me for more.",
"options": {
"format": "square",
"theme": "dark"
}
}Data formatting
Dates and monetary values in your data are automatically pre-formatted before rendering:
- Dates: 2025-03-15 becomes "15 March 2025". 2025-03 becomes "March 2025".
- Money: 12250 becomes "12,250.00" (for fields named amount, price, total, tax, etc.)
You can send raw values — the API handles the formatting. Currency symbols should be included in your data if needed (e.g. a currency field).
Supported document types
| Type | Description | Typical data |
|---|---|---|
| invoice | Business invoices | Line items, totals, payment details, addresses |
| receipt | Transaction confirmations | Items, totals, payment method, store info |
| quote | Quotes and estimates | Itemised pricing, validity, terms |
| cv / resume | CVs and resumes | Experience, education, skills, contact info |
| statement | Account statements | Transactions, balances, date ranges |
| certificate | Certificates | Recipient, issuer, date, details |
| letter | Formal letters | Sender, recipient, subject, body paragraphs |
Errors
All errors return JSON with an error field:
{
"error": "Invalid request",
"details": ["type: Required"]
}| Status | Meaning |
|---|---|
| 400 | Invalid request body. Check the details array for field-level errors. |
| 404 | Template not found (wrong ID or not owned by your API key). |
| 429 | Rate limit exceeded. Check the Retry-After header for seconds until reset. |
| 500 | Internal error (AI failure, rendering error, etc.) |
| 503 | Service unavailable — no AI providers configured. |