One endpoint: send HTML or a URL, get a PDF back. This page covers authentication, what the renderer does with your HTML, the limits, and every error the API can return.
Quick start Authentication Request Rendering Limits Errors Portal MCP Self-hosted OpenAPI
Post JSON to https://api.easypdfserver.com/make-pdf and the response body is the PDF file
itself. The demo key works without an account, adds a watermark, and is limited to 100
renders a month, which is enough to test an integration.
curl -f https://api.easypdfserver.com/make-pdf \
-H "Authorization: Bearer demo" \
-H "Content-Type: application/json" \
-d '{"html":"<h1>Hello world</h1>"}' \
-o document.pdf
Everything below works the same in any language that can make an HTTP request. The samples are complete and runnable.
Send your API key in the Authorization header:
Authorization: Bearer eps_your_key_here
The key can also go in the JSON body as "key", which is what older integrations do. If both
are present the body wins. Keys look like eps_... and are shown once when issued. Treat one
like a password: anyone who has it can spend your quota.
Lost a key or need to change a card? The customer portal takes a key and opens the Stripe billing portal.
POST /make-pdf takes exactly one of html or url. Send both or
neither and the API returns 400.
| Field | Type | Notes |
|---|---|---|
html | string | A complete document or a fragment. Maximum request body is 10 MB. |
url | string | Absolute http or https URL. Must be publicly reachable. |
key | string | Optional if you send the Authorization header. |
Node.js 18 or newer
const response = await fetch('https://api.easypdfserver.com/make-pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.EASYPDF_API_KEY}`,
},
body: JSON.stringify({ html: '<h1>Hello world</h1>' }),
});
if (!response.ok) throw new Error(`API returned ${response.status}`);
const pdf = Buffer.from(await response.arrayBuffer());
require('fs').writeFileSync('document.pdf', pdf);
Python
import os, requests
response = requests.post(
"https://api.easypdfserver.com/make-pdf",
headers={"Authorization": f"Bearer {os.environ['EASYPDF_API_KEY']}"},
json={"html": "<h1>Hello world</h1>"},
)
response.raise_for_status()
open("document.pdf", "wb").write(response.content)
PHP
$ch = curl_init("https://api.easypdfserver.com/make-pdf");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer " . getenv("EASYPDF_API_KEY"),
],
CURLOPT_POSTFIELDS => json_encode(["html" => "<h1>Hello world</h1>"]),
]);
$pdf = curl_exec($ch);
curl_close($ch);
file_put_contents("document.pdf", $pdf);
Ruby
require "net/http"
require "json"
require "uri"
uri = URI("https://api.easypdfserver.com/make-pdf")
request = Net::HTTP::Post.new(uri,
"Content-Type" => "application/json",
"Authorization" => "Bearer #{ENV['EASYPDF_API_KEY']}")
request.body = { html: "<h1>Hello world</h1>" }.to_json
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
File.binwrite("document.pdf", response.body)
To render a web page instead, send "url" in place of "html". The renderer fetches
the page over the public internet, so anything behind a login or on a private network will not load.
Documents are rendered by headless Chromium 156, the same engine as current Chrome. That means flexbox, CSS grid, custom properties, and webfonts behave the way they do in a browser.
@page { size: A4; margin: 20mm } is respected, and so are
page-break-* and break-inside.<base href> pointing at your server.| Limit | Value |
|---|---|
| Request body | 10 MB |
| Render time | 30 seconds, then the render is killed |
| Queue | 20 requests waiting, 10 seconds of waiting before a 503 |
| Concurrency | one render at a time per server |
| Demo key | 100 renders a month, watermarked |
Plans are unlimited with email support and start with a 7-day free trial. See pricing.
Errors are JSON, with a human readable error and a stable code:
{"error":"Incorrect API key.","code":"invalid_api_key"}
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_json | Body is not valid JSON. |
| 400 | invalid_input | Send exactly one of html or url. |
| 400 | invalid_url | Only public http and https URLs are allowed. |
| 401 | missing_api_key, invalid_api_key | Check the key. |
| 403 | inactive_api_key | The subscription was cancelled. Resubscribe or contact us. |
| 413 | body_too_large | Keep the body under 10 MB. |
| 429 | quota_exceeded | Monthly quota used. Resets on the 1st. |
| 502 | render_failed, render_incomplete | The page could not be rendered. Retry once, then check the HTML. |
| 503 | busy, timeout | The renderer is saturated. Retry with backoff. |
Retry 429, 502, and 503 with exponential backoff. Do not retry 400, 401, or 403: the request will fail the same way.
POST /portal with your key returns a Stripe portal URL for invoices, cards, and cancellation.
curl -s https://api.easypdfserver.com/portal \
-H "Authorization: Bearer eps_your_key_here" \
-H "Content-Type: application/json" -d '{}'
{"url":"https://billing.stripe.com/p/session/..."}
The MCP server exposes the API to Claude Desktop, Cursor, and other MCP clients as two tools:
html_to_pdf and url_to_pdf. It is a single file with no dependencies, and it
writes the PDF to /.easypdfserver/.
Download it (Node 18 or newer):
curl -fsSL https://www.easypdfserver.com/mcp/easypdfserver-mcp.mjs -o /easypdfserver-mcp.mjs
Add it to your client config, using the absolute path to the file and your key:
{
"mcpServers": {
"easypdfserver": {
"command": "node",
"args": ["/Users/you/easypdfserver-mcp.mjs"],
"env": {
"EASYPDF_API_KEY": "eps_your_key_here"
}
}
}
}
The same block works in Claude Desktop's claude_desktop_config.json, Cursor's
mcp.json, and Claude Code. For a self-hosted server, add
"EASYPDF_API_URL": "https://your-instance" to env.
Each tool returns the absolute path of the PDF it wrote, so the model can hand the file to you or read it with another tool.
Easy PDF Server also ships as an AWS Marketplace AMI. On your own instance authentication is disabled, so requests look like this:
curl -f http://your-instance/make-pdf \
-H "Content-Type: application/json" \
-d '{"html":"<h1>Hello</h1>"}' \
-o document.pdf
Keep it behind your own network controls. There is no key to check.
The OpenAPI 3.0 spec is at /openapi.json. Point a client generator or an agent at it directly.