rendoc

API Documentation

Everything you need to generate PDFs programmatically with rendoc.

REST APIJSONMCP Server

Quick Start

Get your first PDF generated in under 2 minutes.

1

Create an account

Sign up at rendoc.dev/register. The Free plan includes 100 documents per month.

2

Generate an API key

Go to your Dashboard > API Keys and create a new key. Store it securely — it will only be shown once.

3

Generate your first PDF

cURL
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

201 Created
{
  "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.

Header
Authorization: Bearer rd_live_your_key_here

Scopes

Each API key is created with specific permission scopes. Default scopes are applied when no scopes are specified during key creation.

ScopeDescriptionDefault
documents:writeGenerate and manage documentsYes
documents:readView and download documentsYes
templates:readList and view templatesYes
templates:writeCreate, update, and delete templatesNo
usage:readView usage statisticsNo

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

POST/api/v1/documents/generatedocuments:write

Generate a PDF document from a template and data.

Request Body

ParameterTypeRequiredDescription
template_idstringconditionalID of a saved template. Required if template is not provided.
templateobjectconditionalInline template definition. Required if template_id is not provided.
template.markupstringYesHTML markup for the template (max 500,000 characters).
template.paper_sizestringNoDefault: A4. Options: A4, LETTER, LEGAL, A3, A5.
template.orientationstringNoDefault: portrait. Options: portrait, landscape.
dataobjectYesKey-value pairs to populate template variables.
options.filenamestringNoCustom output filename (alphanumeric, dots, hyphens, underscores only, max 255 chars).
options.metadataobjectNoCustom metadata to attach to the document.
Info: Either 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

Request
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

Request
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

201 Created
{
  "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

GET/api/v1/documents/:iddocuments:read

Retrieve document metadata or download the PDF file.

JSON Metadata (default)

By default, this endpoint returns the document metadata as JSON.

Response (200 OK)
{
  "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
curl https://rendoc.dev/api/v1/documents/doc_abc123 \
  -H "Authorization: Bearer rd_live_your_key_here" \
  -H "Accept: application/pdf" \
  -o invoice.pdf
Tip: The download_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.

GET/api/v1/templatestemplates:read

List all templates (your own + public system templates).

Query Parameters

ParameterTypeDescription
categorystringFilter by category: INVOICE, RECEIPT, CONTRACT, REPORT, LETTER, CERTIFICATE, RESUME, PROPOSAL, CUSTOM.
Response (200 OK)
{
  "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"
      }
    ]
  }
}
POST/api/v1/templatestemplates:write

Create a new template.

ParameterTypeRequiredDescription
namestringYesDisplay name (1-100 characters).
slugstringYesURL-friendly identifier. Lowercase alphanumeric with hyphens (1-100 chars).
descriptionstringNoTemplate description (max 500 characters).
categorystringNoDefault: CUSTOM. Options: INVOICE, RECEIPT, CONTRACT, REPORT, LETTER, CERTIFICATE, RESUME, PROPOSAL, CUSTOM.
markupstringYesHTML markup with template variables (max 500,000 chars).
stylesstringNoCSS styles (max 100,000 chars).
schemaobjectYesData schema describing expected variables.
sample_dataobjectNoExample data for previewing the template.
paper_sizestringNoDefault: A4. Options: A4, LETTER, LEGAL, A3, A5.
orientationstringNoDefault: PORTRAIT. Options: PORTRAIT, LANDSCAPE.
is_publicbooleanNoDefault: false. Make template visible to other users.
Request
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"
}
GET/api/v1/templates/:idtemplates:read

Get full template details including markup and schema.

PUT/api/v1/templates/:idtemplates:write

Update an existing template. All fields are optional. Version is auto-incremented.

Request
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>"
}
DELETE/api/v1/templates/:idtemplates:write

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

SyntaxDescriptionExample
{{variable}}Simple variable{{company_name}}
{{object.property}}Nested property access{{address.city}}
{{items}}Array data (renders as table){{line_items}}

API Keys

Warning: The API Keys endpoints use session authentication (cookie-based), not API key authentication. Call them from your dashboard or authenticated browser session.
GET/api/v1/api-keys

List all your API keys with metadata (key hash is never returned).

Response (200 OK)
{
  "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"
      }
    ]
  }
}
POST/api/v1/api-keys

Create a new API key. The full key is returned only once.

ParameterTypeRequiredDescription
namestringYesDescriptive name for the key (1-50 characters).
scopesstring[]NoDefault: ["documents:write", "documents:read", "templates:read"].
expires_in_daysnumberNoNumber of days until the key expires. Omit for no expiration.
Response (201 Created)
{
  "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

GET/api/v1/usageusage:read

Get usage statistics for the current billing period.

Query Parameters

ParameterTypeDescription
yearnumberYear to query. Defaults to current year.
monthnumberMonth to query (1-12). Defaults to current month.
Response (200 OK)
{
  "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.

SizeWidth (pt)Height (pt)Common Use
A4595.28841.89Standard international documents
Letter612792US standard documents
Legal6121008US legal documents
A3841.891190.55Large format, posters
A5419.53595.28Booklets, flyers

Orientation

ValueDescription
portraitDefault. Taller than wide (e.g., A4: 595 x 842 pt).
landscapeWider 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):

MCP Configuration
{
  "mcpServers": {
    "rendoc": {
      "command": "npx",
      "args": ["tsx", "node_modules/rendoc/src/mcp/server.ts"],
      "env": {
        "RENDOC_API_KEY": "rd_live_your_key_here"
      }
    }
  }
}

Available Tools

ToolDescription
generate_documentGenerate a PDF from a template and data
list_templatesBrowse available templates with optional category filter
get_documentGet details and download URL for a generated document
get_usageCheck current API usage and remaining quota
preview_templateInspect a template's markup, schema, and sample data

Example Prompt

“Generate an invoice PDF for Acme Inc with 3 line items: consulting (10h at $150/h), development (20h at $200/h), and design (5h at $120/h). Total $5,100.”

Rate Limits

Rate limits are applied per API key using a sliding window algorithm. Limits vary by plan.

PlanRequests / minuteDocuments / monthFile retention
Free101007 days
Starter ($19/mo)601,00030 days
Pro ($49/mo)20010,00090 days
Scale ($199/mo)1,000100,000365 days

Rate Limit Headers

Every API response includes rate limit information in the headers:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window.
X-RateLimit-RemainingRemaining requests in the current window.
X-RateLimit-ResetUnix 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.

429 Response
{
  "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 Response Format
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "details": { "issues": [...] },
    "doc_url": "https://rendoc.dev/docs/errors#validation-error"
  }
}
CodeHTTP StatusDescriptionCommon Cause
VALIDATION_ERROR400Request body failed validation.Missing required fields, invalid types, markup too long.
AUTH_REQUIRED401Authentication is required.Missing or invalid Authorization header.
FORBIDDEN403Insufficient permissions.API key lacks required scope for this operation.
NOT_FOUND404Resource not found.Invalid document ID, template ID, or resource doesn't belong to you.
RATE_LIMITED429Too many requests.Exceeded requests per minute for your plan.
USAGE_LIMIT_EXCEEDED429Monthly document limit reached.Reached monthly document quota. Upgrade plan or wait for reset.
INTERNAL_ERROR500Internal server error.Unexpected error. Contact support if persistent.

USAGE_LIMIT_EXCEEDED Details

When you exceed your monthly document quota, the error includes additional details:

429 Response
{
  "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.

FeatureFreeStarterProScale
Price$0/mo$19/mo$49/mo$199/mo
Documents / month1001,00010,000100,000
Templates320100Unlimited
API Keys1310Unlimited
Max file size5 MB20 MB50 MB100 MB
File retention7 days30 days90 days365 days
Rate limit10/min60/min200/min1,000/min
PDF enginesreact-pdfreact-pdf, chromiumreact-pdf, chromiumreact-pdf, chromium
SupportCommunityEmailPriorityDedicated

Code Examples

JavaScript / Node.js

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

generate_pdf.py
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:

Generate with 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:

Generate with 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:

Download PDF
curl https://rendoc.dev/api/v1/documents/doc_abc123 \
  -H "Authorization: Bearer rd_live_your_key_here" \
  -H "Accept: application/pdf" \
  -o document.pdf

List templates filtered by category:

List templates
curl "https://rendoc.dev/api/v1/templates?category=invoice" \
  -H "Authorization: Bearer rd_live_your_key_here"

Check usage for the current month:

Check usage
curl "https://rendoc.dev/api/v1/usage?year=2026&month=3" \
  -H "Authorization: Bearer rd_live_your_key_here"