API Documentation
Everything you need to generate PDFs programmatically with rendoc.
Quick Start
Get your first PDF generated in under 2 minutes.
Create an account
Sign up at rendoc.dev/register. The Free plan includes 100 documents per month.
Generate an API key
Go to your Dashboard > API Keys and create a new key. Store it securely — it will only be shown once.
Generate your first PDF
curl -X POST https://rendoc.dev/api/v1/documents/generate \
-H "Authorization: Bearer rd_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"template": {
"markup": "<h1>Hello {{name}}</h1><p>Welcome to rendoc!</p>",
"paper_size": "A4"
},
"data": {
"name": "World"
}
}'Response
{
"data": {
"id": "doc_abc123",
"status": "completed",
"download_url": "https://rendoc.dev/api/v1/documents/doc_abc123",
"file_name": "rendoc-1711234567890.pdf",
"file_size": 15234,
"page_count": 1,
"created_at": "2026-03-24T10:30:00.000Z",
"expires_at": "2026-03-31T10:30:00.000Z"
}
}Authentication
All API requests require a Bearer token in the Authorization header. API keys are generated from your dashboard and use the format rd_live_ followed by 64 hex characters.
Authorization: Bearer rd_live_your_key_hereScopes
Each API key is created with specific permission scopes. Default scopes are applied when no scopes are specified during key creation.
| Scope | Description | Default |
|---|---|---|
documents:write | Generate and manage documents | Yes |
documents:read | View and download documents | Yes |
templates:read | List and view templates | Yes |
templates:write | Create, update, and delete templates | No |
usage:read | View usage statistics | No |
Security Best Practices
- Keys are shown once at creation. Store them in an environment variable or secret manager.
- Keys are hashed with SHA-256 on our servers — we never store your full key.
- Use separate keys for production and development.
- Revoke compromised keys immediately from the dashboard.
- Use the minimum scopes required for each key.
Generate Document
/api/v1/documents/generatedocuments:writeGenerate a PDF document from a template and data.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
template_id | string | conditional | ID of a saved template. Required if template is not provided. |
template | object | conditional | Inline template definition. Required if template_id is not provided. |
template.markup | string | Yes | HTML markup for the template (max 500,000 characters). |
template.paper_size | string | No | Default: A4. Options: A4, LETTER, LEGAL, A3, A5. |
template.orientation | string | No | Default: portrait. Options: portrait, landscape. |
data | object | Yes | Key-value pairs to populate template variables. |
options.filename | string | No | Custom output filename (alphanumeric, dots, hyphens, underscores only, max 255 chars). |
options.metadata | object | No | Custom metadata to attach to the document. |
template_id or template must be provided, but not both. When using template_id, the paper size and orientation come from the saved template.Example: Inline Template
POST https://rendoc.dev/api/v1/documents/generate
Content-Type: application/json
Authorization: Bearer rd_live_your_key_here
{
"template": {
"markup": "<div style='font-family: sans-serif; padding: 40px;'><h1>Invoice #{{invoice_number}}</h1><p>Bill to: {{client_name}}</p><p>Amount: ${{amount}}</p><p>Due: {{due_date}}</p></div>",
"paper_size": "A4",
"orientation": "portrait"
},
"data": {
"invoice_number": "INV-001",
"client_name": "Acme Corp",
"amount": "1,250.00",
"due_date": "2026-04-15"
},
"options": {
"filename": "invoice-001.pdf",
"metadata": { "client_id": "acme-001" }
}
}Example: Saved Template
POST https://rendoc.dev/api/v1/documents/generate
Content-Type: application/json
Authorization: Bearer rd_live_your_key_here
{
"template_id": "clx1abc23def456",
"data": {
"company": "Acme Corp",
"items": [
{ "name": "Consulting", "hours": 10, "rate": 150 },
{ "name": "Development", "hours": 20, "rate": 200 }
],
"total": 5500
}
}Response
{
"data": {
"id": "doc_abc123",
"status": "completed",
"download_url": "https://rendoc.dev/api/v1/documents/doc_abc123",
"file_name": "invoice-001.pdf",
"file_size": 24512,
"page_count": 1,
"created_at": "2026-03-24T10:30:00.000Z",
"expires_at": "2026-03-31T10:30:00.000Z"
}
}Get Document
/api/v1/documents/:iddocuments:readRetrieve document metadata or download the PDF file.
JSON Metadata (default)
By default, this endpoint returns the document metadata as JSON.
{
"data": {
"id": "doc_abc123",
"status": "completed",
"download_url": "https://signed-url.blob.vercel-storage.com/...",
"file_name": "invoice-001.pdf",
"file_size": 24512,
"page_count": 1,
"template_id": "clx1abc23def456",
"metadata": { "client_id": "acme-001" },
"created_at": "2026-03-24T10:30:00.000Z",
"expires_at": "2026-03-31T10:30:00.000Z"
}
}Download PDF
To download the PDF file directly, set the Accept header to application/pdf:
curl https://rendoc.dev/api/v1/documents/doc_abc123 \
-H "Authorization: Bearer rd_live_your_key_here" \
-H "Accept: application/pdf" \
-o invoice.pdfdownload_urlin the JSON response is a signed URL that can be used for direct access. The URL expires based on your plan's retention period.Templates
Templates define the structure and layout of your PDFs. Create reusable templates with dynamic variables, then generate documents by passing different data.
/api/v1/templatestemplates:readList all templates (your own + public system templates).
Query Parameters
| Parameter | Type | Description |
|---|---|---|
category | string | Filter by category: INVOICE, RECEIPT, CONTRACT, REPORT, LETTER, CERTIFICATE, RESUME, PROPOSAL, CUSTOM. |
{
"data": {
"templates": [
{
"id": "clx1abc23def456",
"name": "Professional Invoice",
"slug": "professional-invoice",
"description": "Clean invoice template with itemized billing",
"category": "INVOICE",
"paper_size": "A4",
"orientation": "PORTRAIT",
"is_public": false,
"is_system": false,
"version": 3,
"created_at": "2026-03-20T08:00:00.000Z",
"updated_at": "2026-03-23T14:30:00.000Z"
}
]
}
}/api/v1/templatestemplates:writeCreate a new template.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name (1-100 characters). |
slug | string | Yes | URL-friendly identifier. Lowercase alphanumeric with hyphens (1-100 chars). |
description | string | No | Template description (max 500 characters). |
category | string | No | Default: CUSTOM. Options: INVOICE, RECEIPT, CONTRACT, REPORT, LETTER, CERTIFICATE, RESUME, PROPOSAL, CUSTOM. |
markup | string | Yes | HTML markup with template variables (max 500,000 chars). |
styles | string | No | CSS styles (max 100,000 chars). |
schema | object | Yes | Data schema describing expected variables. |
sample_data | object | No | Example data for previewing the template. |
paper_size | string | No | Default: A4. Options: A4, LETTER, LEGAL, A3, A5. |
orientation | string | No | Default: PORTRAIT. Options: PORTRAIT, LANDSCAPE. |
is_public | boolean | No | Default: false. Make template visible to other users. |
POST https://rendoc.dev/api/v1/templates
Content-Type: application/json
Authorization: Bearer rd_live_your_key_here
{
"name": "Monthly Report",
"slug": "monthly-report",
"description": "Monthly business performance report",
"category": "REPORT",
"markup": "<div style='padding: 40px; font-family: sans-serif;'><h1>{{title}}</h1><p>Period: {{period}}</p><h2>Summary</h2><p>Revenue: ${{revenue}}</p><p>Expenses: ${{expenses}}</p><p>Profit: ${{profit}}</p></div>",
"schema": {
"title": "string",
"period": "string",
"revenue": "number",
"expenses": "number",
"profit": "number"
},
"sample_data": {
"title": "March 2026 Report",
"period": "March 2026",
"revenue": "125,000",
"expenses": "80,000",
"profit": "45,000"
},
"paper_size": "A4",
"orientation": "PORTRAIT"
}/api/v1/templates/:idtemplates:readGet full template details including markup and schema.
/api/v1/templates/:idtemplates:writeUpdate an existing template. All fields are optional. Version is auto-incremented.
PUT https://rendoc.dev/api/v1/templates/clx1abc23def456
Content-Type: application/json
Authorization: Bearer rd_live_your_key_here
{
"name": "Monthly Report v2",
"markup": "<div style='padding: 40px;'><h1>{{title}}</h1><p>Updated layout</p></div>"
}/api/v1/templates/:idtemplates:writePermanently delete a template. This action cannot be undone.
Template Variables
Use double curly braces in your markup to define dynamic variables. Variables are replaced with values from the data object when generating a document.
| Syntax | Description | Example |
|---|---|---|
{{variable}} | Simple variable | {{company_name}} |
{{object.property}} | Nested property access | {{address.city}} |
{{items}} | Array data (renders as table) | {{line_items}} |
API Keys
/api/v1/api-keysList all your API keys with metadata (key hash is never returned).
{
"data": {
"api_keys": [
{
"id": "key_abc123",
"name": "Production",
"prefix": "rd_live_a1b2",
"scopes": ["documents:write", "documents:read", "templates:read"],
"is_active": true,
"last_used": "2026-03-24T09:15:00.000Z",
"expires_at": null,
"created_at": "2026-03-01T10:00:00.000Z"
}
]
}
}/api/v1/api-keysCreate a new API key. The full key is returned only once.
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Descriptive name for the key (1-50 characters). |
scopes | string[] | No | Default: ["documents:write", "documents:read", "templates:read"]. |
expires_in_days | number | No | Number of days until the key expires. Omit for no expiration. |
{
"data": {
"id": "key_abc123",
"name": "Production",
"key": "rd_live_a1b2c3d4e5f6...full64hexchars",
"prefix": "rd_live_a1b2",
"scopes": ["documents:write", "documents:read", "templates:read"],
"created_at": "2026-03-24T10:00:00.000Z",
"warning": "Store this key securely. It will not be shown again."
}
}Usage
/api/v1/usageusage:readGet usage statistics for the current billing period.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
year | number | Year to query. Defaults to current year. |
month | number | Month to query (1-12). Defaults to current month. |
{
"data": {
"plan": "starter",
"period": { "year": 2026, "month": 3 },
"usage": {
"documents": 342,
"pages": 1205,
"limit": 1000,
"remaining": 658,
"percentage": 34
},
"daily": [
{ "day": 1, "documents": 12, "pages": 45 },
{ "day": 2, "documents": 18, "pages": 62 }
]
}
}Paper Sizes & Orientation
rendoc supports the following standard paper sizes. All dimensions are in points (pt), where 1 pt = 1/72 inch.
| Size | Width (pt) | Height (pt) | Common Use |
|---|---|---|---|
| A4 | 595.28 | 841.89 | Standard international documents |
| Letter | 612 | 792 | US standard documents |
| Legal | 612 | 1008 | US legal documents |
| A3 | 841.89 | 1190.55 | Large format, posters |
| A5 | 419.53 | 595.28 | Booklets, flyers |
Orientation
| Value | Description |
|---|---|
portrait | Default. Taller than wide (e.g., A4: 595 x 842 pt). |
landscape | Wider than tall. Width and height are swapped. |
Default Margins
All documents are rendered with default margins of 40pt on all sides (top, right, bottom, left).
MCP Server
rendoc includes a Model Context Protocol (MCP) server that enables AI agents like Claude, ChatGPT, and Cursor to generate PDFs directly through natural language.
Configuration
Add this to your MCP client configuration (e.g., claude_desktop_config.json or .cursor/mcp.json):
{
"mcpServers": {
"rendoc": {
"command": "npx",
"args": ["tsx", "node_modules/rendoc/src/mcp/server.ts"],
"env": {
"RENDOC_API_KEY": "rd_live_your_key_here"
}
}
}
}Available Tools
| Tool | Description |
|---|---|
generate_document | Generate a PDF from a template and data |
list_templates | Browse available templates with optional category filter |
get_document | Get details and download URL for a generated document |
get_usage | Check current API usage and remaining quota |
preview_template | Inspect a template's markup, schema, and sample data |
Example Prompt
Rate Limits
Rate limits are applied per API key using a sliding window algorithm. Limits vary by plan.
| Plan | Requests / minute | Documents / month | File retention |
|---|---|---|---|
| Free | 10 | 100 | 7 days |
| Starter ($19/mo) | 60 | 1,000 | 30 days |
| Pro ($49/mo) | 200 | 10,000 | 90 days |
| Scale ($199/mo) | 1,000 | 100,000 | 365 days |
Rate Limit Headers
Every API response includes rate limit information in the headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window. |
X-RateLimit-Remaining | Remaining requests in the current window. |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets. |
Handling 429 Responses
When rate limited, you will receive a 429 Too Many Requests response. Use the X-RateLimit-Reset header to determine when to retry.
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests"
}
}Error Codes
All error responses follow a consistent format with an error code, human-readable message, optional details, and a link to the relevant documentation.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": { "issues": [...] },
"doc_url": "https://rendoc.dev/docs/errors#validation-error"
}
}| Code | HTTP Status | Description | Common Cause |
|---|---|---|---|
VALIDATION_ERROR | 400 | Request body failed validation. | Missing required fields, invalid types, markup too long. |
AUTH_REQUIRED | 401 | Authentication is required. | Missing or invalid Authorization header. |
FORBIDDEN | 403 | Insufficient permissions. | API key lacks required scope for this operation. |
NOT_FOUND | 404 | Resource not found. | Invalid document ID, template ID, or resource doesn't belong to you. |
RATE_LIMITED | 429 | Too many requests. | Exceeded requests per minute for your plan. |
USAGE_LIMIT_EXCEEDED | 429 | Monthly document limit reached. | Reached monthly document quota. Upgrade plan or wait for reset. |
INTERNAL_ERROR | 500 | Internal server error. | Unexpected error. Contact support if persistent. |
USAGE_LIMIT_EXCEEDED Details
When you exceed your monthly document quota, the error includes additional details:
{
"error": {
"code": "USAGE_LIMIT_EXCEEDED",
"message": "Monthly document limit reached (100). Upgrade your plan.",
"details": {
"current_usage": 100,
"limit": 100,
"reset_at": "2026-04-01T00:00:00.000Z"
},
"doc_url": "https://rendoc.dev/docs/errors#usage-limit-exceeded"
}
}Plans Comparison
Choose the plan that fits your document generation needs. All plans include access to the REST API and MCP server.
| Feature | Free | Starter | Pro | Scale |
|---|---|---|---|---|
| Price | $0/mo | $19/mo | $49/mo | $199/mo |
| Documents / month | 100 | 1,000 | 10,000 | 100,000 |
| Templates | 3 | 20 | 100 | Unlimited |
| API Keys | 1 | 3 | 10 | Unlimited |
| Max file size | 5 MB | 20 MB | 50 MB | 100 MB |
| File retention | 7 days | 30 days | 90 days | 365 days |
| Rate limit | 10/min | 60/min | 200/min | 1,000/min |
| PDF engines | react-pdf | react-pdf, chromium | react-pdf, chromium | react-pdf, chromium |
| Support | Community | Priority | Dedicated |
Code Examples
JavaScript / Node.js
const API_KEY = process.env.RENDOC_API_KEY;
const BASE_URL = "https://rendoc.dev/api/v1";
async function generatePdf(data) {
const response = await fetch(`${BASE_URL}/documents/generate`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
template_id: "your-template-id",
data,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`rendoc error: ${error.error.code} - ${error.error.message}`);
}
const { data: doc } = await response.json();
return doc;
}
async function downloadPdf(documentId, outputPath) {
const response = await fetch(`${BASE_URL}/documents/${documentId}`, {
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Accept": "application/pdf",
},
});
const buffer = Buffer.from(await response.arrayBuffer());
const { writeFile } = await import("node:fs/promises");
await writeFile(outputPath, buffer);
}
// Usage
const doc = await generatePdf({
company: "Acme Corp",
items: [{ name: "Widget", price: 49.99 }],
total: 49.99,
});
console.log("Document ID:", doc.id);
console.log("Download URL:", doc.download_url);
await downloadPdf(doc.id, "output.pdf");Python
import os
import httpx
API_KEY = os.environ["RENDOC_API_KEY"]
BASE_URL = "https://rendoc.dev/api/v1"
async def generate_pdf(data: dict, template_id: str) -> dict:
async with httpx.AsyncClient() as client:
response = await client.post(
f"{BASE_URL}/documents/generate",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
json={
"template_id": template_id,
"data": data,
},
)
response.raise_for_status()
return response.json()["data"]
async def download_pdf(document_id: str, output_path: str) -> None:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{BASE_URL}/documents/{document_id}",
headers={
"Authorization": f"Bearer {API_KEY}",
"Accept": "application/pdf",
},
)
response.raise_for_status()
with open(output_path, "wb") as f:
f.write(response.content)
# Usage
import asyncio
async def main():
doc = await generate_pdf(
data={
"company": "Acme Corp",
"items": [{"name": "Widget", "price": 49.99}],
"total": 49.99,
},
template_id="your-template-id",
)
print(f"Document ID: {doc['id']}")
print(f"Download URL: {doc['download_url']}")
await download_pdf(doc["id"], "output.pdf")
asyncio.run(main())cURL
Generate a document with an inline template:
curl -X POST https://rendoc.dev/api/v1/documents/generate \
-H "Authorization: Bearer rd_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"template": {
"markup": "<h1>Invoice #{{number}}</h1><p>Total: ${{total}}</p>",
"paper_size": "A4",
"orientation": "portrait"
},
"data": {
"number": "INV-001",
"total": "1,250.00"
},
"options": {
"filename": "invoice-001.pdf"
}
}'Generate a document with a saved template:
curl -X POST https://rendoc.dev/api/v1/documents/generate \
-H "Authorization: Bearer rd_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"template_id": "your-template-id",
"data": { "company": "Acme Corp", "total": 1250 }
}'Download a generated PDF:
curl https://rendoc.dev/api/v1/documents/doc_abc123 \
-H "Authorization: Bearer rd_live_your_key_here" \
-H "Accept: application/pdf" \
-o document.pdfList templates filtered by category:
curl "https://rendoc.dev/api/v1/templates?category=invoice" \
-H "Authorization: Bearer rd_live_your_key_here"Check usage for the current month:
curl "https://rendoc.dev/api/v1/usage?year=2026&month=3" \
-H "Authorization: Bearer rd_live_your_key_here"