Quickstart
Create a test key and render your first PDF with curl, Node.js or Python. Then switch to a live key when you are ready. Takes about five minutes.
On this page
Your first PDF in minutes. You sign up, pick a ready-made template, look at it in the editor, and then make the PDF with one copy-paste request. There is nothing to design and nothing to install.
1. Sign up free
Create your account at app.castpdf.com/signup. It is free, and you don't need a card. New accounts start on the Free plan, with 100 live PDFs a month.
We send you an email to verify your address. You can carry on straight away: you only need the verified address for clean PDFs, from the Generate PDF button (step 3) or a live key (step 6).
2. Pick a starter template
In the dashboard, open Templates and choose New template. Pick one of the six starter templates: invoice, receipt, certificate, sales report, event ticket or service agreement. The invoice is a good first choice.
CastPDF creates the template in your account and opens it in the editor, already filled with sample data. Prefer to start from nothing? Choose Blank instead.
3. Preview it
The editor shows the template's code on the left and a live PDF preview on the right. Open the Sample data tab and change a name or an amount: the preview follows as you type.
Previews are test renders. They are free, they never count towards your plan, and every page carries a diagonal "CASTPDF TEST" watermark.
For a clean PDF without the watermark, press Generate PDF in the editor. It renders your saved template (the editor offers to save unsaved changes first), gives you a download link, and counts as one PDF on your plan. It needs a verified email address. To make PDFs from your own app, use the API from step 4 on.
When you are happy with it, save your changes, then click Copy template ID. You need the ID in step 5. Requests render the saved template, not unsaved edits.
4. Copy your test key
Open API keys and create a key with the mode Test. Copy it straight away: the full key is shown only once, and CastPDF stores only a hash of it. Test keys start with cpdf_test_.
Test keys are free and never count towards your plan. Like previews, every page they render carries the watermark, and they are limited to 20 requests per minute. Authentication and keys has the details.
5. Make the PDF with one request
Pick one of the examples below and replace YOUR_API_KEY with your test key and YOUR_TEMPLATE_ID with the ID from step 3. The request sends only the template ID, so CastPDF fills the template with its own sample data.
curl https://api.castpdf.com/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template_id": "YOUR_TEMPLATE_ID"}' \
--output first.pdfimport { writeFile } from 'node:fs/promises';
const res = await fetch('https://api.castpdf.com/v1/pdf', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ template_id: 'YOUR_TEMPLATE_ID' }),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
await writeFile('first.pdf', Buffer.from(await res.arrayBuffer()));
console.log('pages:', res.headers.get('x-pages'), 'document:', res.headers.get('x-document-id'));import requests
res = requests.post(
"https://api.castpdf.com/v1/pdf",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"template_id": "YOUR_TEMPLATE_ID"},
timeout=60,
)
if not res.ok:
error = res.json()["error"]
raise RuntimeError(f"{error['code']}: {error['message']}")
with open("first.pdf", "wb") as f:
f.write(res.content)
print("pages:", res.headers["x-pages"], "document:", res.headers["x-document-id"])Now open first.pdf. It is your template, filled with the sample data, with the test watermark across every page. The response body is the PDF itself, and headers such as x-pages and x-document-id describe it.
If something is wrong, the API answers with a JSON error instead, for example 401 with "code": "invalid_api_key". Every code is explained on the errors page.
Send your own data
Add a data object with your own values. It replaces the sample data as a whole, so send every field the template uses: copy the object from the Sample data tab and change the values. Put the request in a file:
{
"template_id": "YOUR_TEMPLATE_ID",
"data": {
"customer": { "name": "Northwind Traders" }
}
}The data above is shortened to one field; yours holds the whole object. Then send the file:
curl https://api.castpdf.com/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d @request.json \
--output invoice.pdfThe template fills its {{ … }} fields from your data with Liquid, the template language CastPDF uses.
6. Switch to a live key
When your integration works:
- Verify your email address (live keys need a verified email).
- Create a key with the mode Live. Live keys start with
cpdf_live_. - Replace the test key in your configuration. Nothing else changes: the requests and responses are the same.
Live PDFs have no watermark and count towards your plan's monthly documents. Add an Idempotency-Key header to every POST /v1/pdf, so a retried request never produces, or bills, a second document. See Usage and billing.
Do it all from code
You can skip the dashboard entirely. These two requests work with the same test key.
Send HTML directly
Send HTML to POST /v1/pdf. When the request also has a data object, the HTML is rendered as a Liquid template with that data first.
curl https://api.castpdf.com/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello {{ name }}</h1>", "data": {"name": "Ada"}}' \
--output hello.pdfimport { writeFile } from 'node:fs/promises';
const res = await fetch('https://api.castpdf.com/v1/pdf', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ html: '<h1>Hello {{ name }}</h1>', data: { name: 'Ada' } }),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
await writeFile('hello.pdf', Buffer.from(await res.arrayBuffer()));import requests
res = requests.post(
"https://api.castpdf.com/v1/pdf",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"html": "<h1>Hello {{ name }}</h1>", "data": {"name": "Ada"}},
timeout=60,
)
res.raise_for_status()
with open("hello.pdf", "wb") as f:
f.write(res.content)hello.pdf says "Hello Ada". The response headers tell you about the document:
HTTP/1.1 200 OK
content-type: application/pdf
content-disposition: attachment; filename="document.pdf"
x-document-id: 3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3
x-pages: 1
x-blocked-resources: 0
cache-control: no-store
x-request-id: req_4b8e0f6c2d7a41e9b3c5a1f0e2d4c6b8
x-ratelimit-limit: 20
x-ratelimit-remaining: 19
x-ratelimit-reset: 60Create a template with the API
For documents you make again and again, store the HTML once as a template and send only the data. POST /v1/templates does the same as New template in the dashboard:
curl https://api.castpdf.com/v1/templates \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Invoice",
"html": "<h1>Invoice {{ number }}</h1><p>Total: {{ total | money: \"USD\" }}</p>",
"sample_data": {"number": "INV-001", "total": 99.5}
}'The response contains the template's id: use it as template_id, as in step 5. Templates render in print mode by default, with pagination by Paged.js.
Next steps
- Authentication and keys: test keys and live keys, and the limits of each.
- API reference: every endpoint, with request fields, responses and examples.
- Templates and versions: versions, pinning the one production uses, and previews.
- Liquid reference: loops, conditions and the
money,numberandformat_datefilters. - Print CSS guide: page size, page breaks, repeating table headers and page numbers.