Easy PDF Server home

Easy PDF Server A Simple API to Convert HTML to PDF

API documentation

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

Quick start

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.

Authentication

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.

Request

POST /make-pdf takes exactly one of html or url. Send both or neither and the API returns 400.

FieldTypeNotes
htmlstringA complete document or a fragment. Maximum request body is 10 MB.
urlstringAbsolute http or https URL. Must be publicly reachable.
keystringOptional 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.

Rendering

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.

Limits

LimitValue
Request body10 MB
Render time30 seconds, then the render is killed
Queue20 requests waiting, 10 seconds of waiting before a 503
Concurrencyone render at a time per server
Demo key100 renders a month, watermarked

Plans are unlimited with email support and start with a 7-day free trial. See pricing.

Errors

Errors are JSON, with a human readable error and a stable code:

{"error":"Incorrect API key.","code":"invalid_api_key"}
StatusCodeWhat to do
400invalid_jsonBody is not valid JSON.
400invalid_inputSend exactly one of html or url.
400invalid_urlOnly public http and https URLs are allowed.
401missing_api_key, invalid_api_keyCheck the key.
403inactive_api_keyThe subscription was cancelled. Resubscribe or contact us.
413body_too_largeKeep the body under 10 MB.
429quota_exceededMonthly quota used. Resets on the 1st.
502render_failed, render_incompleteThe page could not be rendered. Retry once, then check the HTML.
503busy, timeoutThe 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.

Customer portal

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/..."}

MCP server

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.

Self-hosted

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.

Machine readable

The OpenAPI 3.0 spec is at /openapi.json. Point a client generator or an agent at it directly.