API reference
Every CastPDF endpoint with request fields, limits, responses and errors, with examples in curl, Node.js and Python.
On this page
New here? The Quickstart makes your first PDF in a few minutes. This page lists every endpoint in detail.
Common tasks
- Render HTML to a PDF: send
htmltoPOST /v1/pdf. - Render a template with your data: send
template_idanddatatoPOST /v1/pdf. - Download a stored PDF:
GET /v1/pdf/:idreturns a fresh signedurl. Open it to download the file. - Check your usage this month:
GET /v1/account/usage.
Conventions
- Base URL:
https://api.castpdf.com/v1. - Authentication:
Authorization: Bearer YOUR_API_KEYon every request except the signed file download. See Authentication and keys. - Requests: JSON with
Content-Type: application/json(any other content type gets415), at most 5 MB per body. Unknown fields are rejected withinvalid_request, so a typo never passes silently. - Responses: JSON, except the endpoints that return a PDF (
application/pdf). Ids are UUIDs; timestamps are ISO 8601 in UTC. - Request ids: every response has an
x-request-idheader, such asreq_4b8e0f6c2d7a41e9b3c5a1f0e2d4c6b8. - Rate limits: every authenticated
/v1request counts against your workspace's per-minute budget for the key's mode, reported inx-ratelimit-limit,x-ratelimit-remainingandx-ratelimit-reset. See rate limits.
Errors
Every error has the same shape. docs_url links to the code on the errors page, and details appears only where noted (template errors carry the Liquid line and column):
{
"error": {
"code": "template_render_error",
"message": "Template error: undefined filter: moneyy",
"docs_url": "https://castpdf.com/docs/errors#template_render_error",
"details": { "line": 12, "column": 5 }
}
}Endpoints
| Method and path | What it does |
|---|---|
POST /v1/pdf | Generate a PDF from a template or from HTML |
GET /v1/pdf/:id | A document's status and a fresh download link |
GET /v1/documents | The document log, newest first |
GET /v1/files/:id | Download a stored PDF through a signed link (no API key) |
POST /v1/templates | Create a template |
GET /v1/templates | List templates |
GET /v1/templates/:id | Get a template (current or a given version) |
PUT /v1/templates/:id | Update a template (content changes create a new version) |
DELETE /v1/templates/:id | Delete a template |
GET /v1/templates/:id/versions | List a template's versions |
POST /v1/templates/:id/pin | Pin or unpin a version |
POST /v1/templates/:id/preview | Render a watermarked preview |
GET /v1/account | The key's team and mode |
GET /v1/account/usage | The current period's usage |
Documents
POST /v1/pdf
Generates a PDF and returns it, either as the response body (binary, the default) or as a signed link (url). The request returns when the PDF is ready; rendering is limited to 30 seconds and 50 pages.
Send exactly one of template_id or html.
| Field | Type | Description |
|---|---|---|
template_id | string (UUID) | A stored template to render. |
template_version | integer, 1 or more | With template_id: render this version. Default: the pinned version, or else the current one. |
html | string, not empty | HTML to render: a fragment or a full document. With data it is a Liquid template and may be at most 500 KB (larger is template_render_error). |
css | string | With html only: added as a stylesheet before the HTML. A template uses its own CSS, so css together with template_id is rejected with invalid_request; edit the template's CSS instead. |
data | object | The Liquid data. With template_id it defaults to the template's sample_data. With html, the HTML is rendered as a Liquid template only when data is present; without it, the HTML is used as it is. |
mode | "fast" or "print" | Default: the template's setting, else print for templates and fast for HTML. See fast and print mode. |
format | "A4", "A5", "Letter", "Legal", or an object with width and height | Paper size. Default: the template's setting, else A4. Custom sizes are lengths such as "210mm". |
orientation | "portrait" or "landscape" | Default: the template's setting, else portrait. Applies to the named formats only. |
margins | string | One to four lengths separated by spaces, like CSS margin: "20mm", "15mm 20mm", "1in 0.75in 1in 0.75in". Units: mm, cm, in, px, pt (or a bare 0). Default: the template's setting, else 20mm. |
filename | string, up to 100 characters | The name in content-disposition. Characters other than letters, digits, ., _ and - become -, and .pdf is added. Default: the template's name, or document. |
response | "binary" or "url" | binary (default) returns the PDF; url returns JSON with a signed download link. |
metadata | object | Your own key-value data (at most 20 keys and 8 KB as JSON), stored with the document for 30 days. The API does not return it. |
Optional header: Idempotency-Key, 1 to 255 printable ASCII characters without spaces. Repeating a request with the same key returns the first request's document instead of rendering (and counting) a second one. See idempotency.
The HTML after Liquid, with the CSS, may be at most 5 MB. Page options you set in the request override the template's settings.
Response: binary (default)
200 with the PDF as the body:
| Header | Value |
|---|---|
content-type | application/pdf |
content-disposition | attachment; filename="invoice-042.pdf" |
x-document-id | The document's id, for GET /v1/pdf/:id |
x-pages | The page count |
x-blocked-resources | How many blocked external requests are reported (0 to 10; see external resources) |
cache-control | no-store |
With a test key, a binary response without an Idempotency-Key is not stored: nothing could fetch it later. The document still appears in the log, with url: null.
Response: url
200 with JSON. The PDF is stored for your plan's retention period (test keys: 1 day), and url is a signed link that works without an API key until expires_at:
{
"id": "3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3",
"status": "succeeded",
"pages": 2,
"bytes": 48213,
"url": "https://api.castpdf.com/v1/files/3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3?exp=1791368102&sig=Jx0k…",
"expires_at": "2026-10-07T10:15:02.123Z",
"blocked_resources": []
}blocked_resources lists up to 10 external requests the renderer refused, each with its reason, for example "http://10.0.0.5/logo.png (address 10.0.0.5:80)". A replayed Idempotency-Key request reports none (x-blocked-resources: 0, blocked_resources: []), even if the first response had some.
Examples
From a template, returning a signed link:
curl https://api.castpdf.com/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-INV-042" \
-d '{
"template_id": "8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b",
"data": {"number": "INV-042", "customer": {"name": "Acme Ltd"}, "total": 1234.5},
"filename": "invoice-042",
"response": "url"
}'const res = await fetch('https://api.castpdf.com/v1/pdf', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'Idempotency-Key': 'invoice-INV-042',
},
body: JSON.stringify({
template_id: '8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b',
data: { number: 'INV-042', customer: { name: 'Acme Ltd' }, total: 1234.5 },
filename: 'invoice-042',
response: 'url',
}),
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
console.log(body.url, body.expires_at);import requests
res = requests.post(
"https://api.castpdf.com/v1/pdf",
headers={"Authorization": "Bearer YOUR_API_KEY", "Idempotency-Key": "invoice-INV-042"},
json={
"template_id": "8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b",
"data": {"number": "INV-042", "customer": {"name": "Acme Ltd"}, "total": 1234.5},
"filename": "invoice-042",
"response": "url",
},
timeout=60,
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(body["url"], body["expires_at"])From HTML and CSS, returning the PDF (A5, landscape, print mode):
curl https://api.castpdf.com/v1/pdf \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Certificate</h1><p>Awarded to {{ name }}</p>",
"css": "h1 { font-size: 32pt; text-align: center; }",
"data": {"name": "Ada Lovelace"},
"format": "A5",
"orientation": "landscape",
"margins": "15mm",
"mode": "print"
}' \
--output certificate.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>Certificate</h1><p>Awarded to {{ name }}</p>',
css: 'h1 { font-size: 32pt; text-align: center; }',
data: { name: 'Ada Lovelace' },
format: 'A5',
orientation: 'landscape',
margins: '15mm',
mode: 'print',
}),
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${error.code}: ${error.message}`);
}
await writeFile('certificate.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>Certificate</h1><p>Awarded to {{ name }}</p>",
"css": "h1 { font-size: 32pt; text-align: center; }",
"data": {"name": "Ada Lovelace"},
"format": "A5",
"orientation": "landscape",
"margins": "15mm",
"mode": "print",
},
timeout=60,
)
res.raise_for_status()
with open("certificate.pdf", "wb") as f:
f.write(res.content)Set your HTTP client's timeout to at least 60 seconds: a render may take up to 30 seconds, plus time in the queue when the service is busy.
Errors
| Code | When |
|---|---|
invalid_request (400) | Validation failed, both or neither of template_id and html, css together with template_id, metadata too large, or a malformed Idempotency-Key |
template_not_found (404) | The template or the requested version does not exist |
template_render_error (422) | Liquid failed, or html sent with data is over 500 KB; details has line and column when known |
document_too_large (413) | The HTML after Liquid is over 5 MB, the PDF would have more than 50 pages, or the PDF would be larger than 40 MB |
payload_too_large (413) | The request body is over 5 MB |
plan_limit_reached, spending_cap_reached (402) | Live keys only; see usage and billing |
idempotency_conflict (409), idempotency_mismatch (422), file_expired (410) | See idempotency |
render_failed (422), render_timeout (504), service_busy (503) | Rendering failed, took longer than 30 seconds, or the service is busy |
content_lost (422) | Print mode only: part of the document could not fit on a page and would be cut off, so no PDF was made |
All codes, including rate_limited and internal_error, are on the errors page.
Webhooks and background rendering. Today every POST /v1/pdf answers when the PDF is ready. Delivery to a webhook URL when rendering finishes is planned, and is not part of the API yet.
GET /v1/pdf/:id
Returns a document's record and, while the PDF is stored, a freshly signed download link. Test keys can read test documents only; a live document requested with a test key is document_not_found.
curl https://api.castpdf.com/v1/pdf/3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3 \
-H "Authorization: Bearer YOUR_API_KEY"const res = await fetch('https://api.castpdf.com/v1/pdf/3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3', {
headers: { Authorization: 'Bearer YOUR_API_KEY' },
});
const doc = await res.json();
console.log(doc.status, doc.url);import requests
res = requests.get(
"https://api.castpdf.com/v1/pdf/3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3",
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=30,
)
doc = res.json()
print(doc["status"], doc["url"])200, with cache-control: no-store:
{
"id": "3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3",
"status": "succeeded",
"pages": 2,
"bytes": 48213,
"mode": "print",
"test": false,
"template_id": "8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b",
"template_version": 4,
"error_code": null,
"created_at": "2026-09-30T10:15:02.123Z",
"expires_at": "2026-10-07T10:15:02.123Z",
"url": "https://api.castpdf.com/v1/files/3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3?exp=1791368102&sig=Jx0k…"
}| Field | Description |
|---|---|
status | succeeded, or failed when rendering failed; error_code then holds the error code, and pages and bytes are null. Requests rejected before rendering (validation, template errors, plan limits) create no document. |
mode | fast or print. |
test | true for documents made with a test key. |
template_id, template_version | The template and version rendered, or null for HTML. |
expires_at | When the stored PDF is deleted, or null if it was never stored. |
url | A signed link valid until expires_at, or null once the file expired or if it was never stored. |
Errors: document_not_found (404), invalid_request (400) for an id that is not a UUID.
GET /v1/documents
Your workspace's documents, newest first. Test keys see only test documents; live keys see all of them.
| Query parameter | Description |
|---|---|
limit | 1 to 100. Default 20. |
before | The previous page's next_before, to get the next page. |
curl "https://api.castpdf.com/v1/documents?limit=50" \
-H "Authorization: Bearer YOUR_API_KEY"200, with cache-control: no-store. Each item has the same fields as GET /v1/pdf/:id; next_before is null on the last page:
{
"data": [
{ "id": "3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3", "status": "succeeded", "pages": 2, "bytes": 48213, "mode": "print", "test": false, "template_id": "8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b", "template_version": 4, "error_code": null, "created_at": "2026-09-30T10:15:02.123Z", "expires_at": "2026-10-07T10:15:02.123Z", "url": "https://api.castpdf.com/v1/files/…" }
],
"next_before": "2026-09-30T10:15:02.123456Z_3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3"
}Treat next_before as opaque and pass it back unchanged. Document records are kept for 120 days.
GET /v1/files/:id
Downloads a stored PDF. You never build this URL yourself: it is the url returned by POST /v1/pdf (with response: "url"), GET /v1/pdf/:id and GET /v1/documents. The exp and sig query parameters sign it, so it needs no API key and can be handed to a browser or a customer. It stops working when the file expires.
200 with the PDF and these headers:
| Header | Value |
|---|---|
content-type | application/pdf |
content-disposition | inline; filename="invoice-042.pdf" (opens in the browser) |
cache-control | private, with a max-age of 300 seconds |
content-security-policy | sandbox |
x-content-type-options | nosniff |
Errors: forbidden (403) when the signature does not match, file_expired (410) when the link or the file expired.
Templates
A template stores HTML with Liquid placeholders, optional CSS, sample data and page settings. Every content change creates a new version. The concepts are explained in Templates and versions.
The full template object, returned by create, get and update:
{
"id": "8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b",
"name": "Invoice",
"current_version": 4,
"pinned_version": 3,
"updated_at": "2026-09-30T09:12:44.518Z",
"version": 4,
"html": "<h1>Invoice {{ number }}</h1>…",
"css": "h1 { color: #4338ca; }",
"sample_data": { "number": "INV-001", "total": 99.5 },
"settings": { "format": "A4", "margin": "20mm 15mm" }
}version, html, css, sample_data and settings belong to the version returned; the other fields to the template. css and sample_data may be null.
Test keys can read and preview templates, but not change them: create, update, pin and delete need a live key (or the dashboard), and a test key gets 403 forbidden. A leaked test key then cannot alter the templates your live documents use.
Templates per team: Free 5, Starter 25, Growth 100, Pro 2,000 and Scale 2,000 ("Unlimited" on the pricing page is this fair-use limit). Deleted templates do not count toward that number, but they are kept for 30 days, and all templates together, deleted ones included, are limited to that number plus 100. Template changes (create, update, pin and delete together) are limited to 60 a minute per team, shared with the dashboard.
POST /v1/templates
| Field | Type | Description |
|---|---|---|
name | string, 1 to 100 characters | Required. |
html | string, not empty | Required. The Liquid template, at most 500 KB. It is parsed on save: a syntax error or an unknown filter is rejected with template_render_error and its line and column. |
css | string | At most 500 KB. |
sample_data | object | Default data for renders without data and for previews. At most 1 MB as JSON. |
settings | object | Page defaults: mode, format, orientation and margin (singular), with the same values as the POST /v1/pdf fields. |
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>",
"css": "h1 { color: #4338ca; }",
"sample_data": {"number": "INV-001", "total": 99.5},
"settings": {"format": "A4", "margin": "20mm 15mm"}
}'const res = await fetch('https://api.castpdf.com/v1/templates', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Invoice',
html: '<h1>Invoice {{ number }}</h1><p>Total: {{ total | money: "USD" }}</p>',
css: 'h1 { color: #4338ca; }',
sample_data: { number: 'INV-001', total: 99.5 },
settings: { format: 'A4', margin: '20mm 15mm' },
}),
});
const template = await res.json();
console.log(template.id, template.current_version); // …, 1import requests
res = requests.post(
"https://api.castpdf.com/v1/templates",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"name": "Invoice",
"html": '<h1>Invoice {{ number }}</h1><p>Total: {{ total | money: "USD" }}</p>',
"css": "h1 { color: #4338ca; }",
"sample_data": {"number": "INV-001", "total": 99.5},
"settings": {"format": "A4", "margin": "20mm 15mm"},
},
timeout=30,
)
template = res.json()
print(template["id"], template["current_version"]) # ..., 1201 with the full template object (current_version: 1). Errors: invalid_request (400), plan_limit_reached (402, the plan's template limit, with details: { "limit": "templates", "max": N }, or the limit on all templates including recently deleted ones, with "limit": "templates_including_deleted"), forbidden (403, a test key), template_render_error (422), rate_limited (429).
GET /v1/templates
Lists templates, most recently updated first, without their content.
| Query parameter | Description |
|---|---|
limit | 1 to 100. Default 50. |
offset | How many to skip. Default 0. |
curl "https://api.castpdf.com/v1/templates?limit=20&offset=0" \
-H "Authorization: Bearer YOUR_API_KEY"const res = await fetch('https://api.castpdf.com/v1/templates?limit=20', {
headers: { Authorization: 'Bearer YOUR_API_KEY' },
});
const { data, has_more } = await res.json();import requests
res = requests.get(
"https://api.castpdf.com/v1/templates",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"limit": 20},
timeout=30,
)
page = res.json()200:
{
"data": [
{ "id": "8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b", "name": "Invoice", "current_version": 4, "pinned_version": 3, "updated_at": "2026-09-30T09:12:44.518Z" }
],
"has_more": false
}GET /v1/templates/:id
Returns the full template object with the current version's content, or with version n when you add ?version=n.
curl "https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b?version=3" \
-H "Authorization: Bearer YOUR_API_KEY"Errors: template_not_found (404) for an unknown template or a version that does not exist (any more).
PUT /v1/templates/:id
Updates a template. Send at least one field; the fields are those of POST /v1/templates, all optional.
- Any change to
html,css,sample_dataorsettingscreates a new version with the next number; fields you leave out are copied from the current version.settingsreplaces the stored settings object as a whole.csscan be set tonullto remove it. - A change of
namealone creates no version. - The latest 50 versions are kept, plus the pinned one; older ones are deleted.
curl -X PUT https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"css": "h1 { color: #0f172a; }"}'const res = await fetch('https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b', {
method: 'PUT',
headers: { Authorization: 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ css: 'h1 { color: #0f172a; }' }),
});
const template = await res.json();
console.log(template.current_version); // the new version numberimport requests
res = requests.put(
"https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"css": "h1 { color: #0f172a; }"},
timeout=30,
)
print(res.json()["current_version"])200 with the full template object at its (new) current version. Errors: invalid_request (400), template_not_found (404), template_render_error (422).
DELETE /v1/templates/:id
Deletes a template. Later renders with its id fail with template_not_found; documents already generated are not affected. The template and all its versions are erased for good 30 days later.
curl -X DELETE https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b \
-H "Authorization: Bearer YOUR_API_KEY"const res = await fetch('https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b', {
method: 'DELETE',
headers: { Authorization: 'Bearer YOUR_API_KEY' },
});
console.log(res.status); // 204import requests
res = requests.delete(
"https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b",
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=30,
)
print(res.status_code) # 204204 with no body. Errors: template_not_found (404).
GET /v1/templates/:id/versions
Lists the versions that still exist, newest first.
curl https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b/versions \
-H "Authorization: Bearer YOUR_API_KEY"{
"data": [
{ "version": 4, "created_at": "2026-09-30T09:12:44.518Z" },
{ "version": 3, "created_at": "2026-09-29T16:02:10.004Z" }
]
}POST /v1/templates/:id/pin
Pins a version: renders with this template_id and no template_version then use it, even after later updates. Send null to unpin, so renders follow the current version again.
| Field | Type | Description |
|---|---|---|
version | integer, 1 or more, or null | Required. |
curl https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b/pin \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"version": 3}'200 with the template without content (id, name, current_version, pinned_version, updated_at). Errors: template_not_found (404) for an unknown template or version.
POST /v1/templates/:id/preview
Renders a template as a watermarked PDF, for checking a template or a version before you use it. Previews are never counted, never stored and do not appear in the document log, with any key.
| Field | Type | Description |
|---|---|---|
data | object | The Liquid data. Default: the version's sample_data. |
version | integer, 1 or more | Default: the pinned version, or else the current one. |
The template's page settings apply; the page options of POST /v1/pdf are not accepted here.
curl https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b/preview \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"version": 4}' \
--output preview.pdf200 with content-type: application/pdf, content-disposition: inline; filename="Invoice.pdf", cache-control: no-store and x-pages. Previews have their own limit of 30 per minute per team and key mode, on top of the key's rate limit. Errors: as for POST /v1/pdf, without the plan limits.
Account
GET /v1/account
The team and key mode of the key you call with.
curl https://api.castpdf.com/v1/account \
-H "Authorization: Bearer YOUR_API_KEY"{
"team": { "id": "5e1b9c7a-2f4d-4a8b-9c0e-7d6f5a4b3c2d", "name": "Acme", "plan": "starter" },
"key": { "mode": "live", "prefix": "cpdf_live_0123" }
}GET /v1/account/usage
The team's live usage in the current monthly period. Test keys get the same numbers (test documents are never counted).
curl https://api.castpdf.com/v1/account/usage \
-H "Authorization: Bearer YOUR_API_KEY"const res = await fetch('https://api.castpdf.com/v1/account/usage', {
headers: { Authorization: 'Bearer YOUR_API_KEY' },
});
const usage = await res.json();
console.log(`${usage.docs_used} of ${usage.docs_included} documents used`);import requests
res = requests.get(
"https://api.castpdf.com/v1/account/usage",
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=30,
)
usage = res.json()
print(f"{usage['docs_used']} of {usage['docs_included']} documents used")200:
{
"plan": "starter",
"period_start": "2026-09-14T09:30:00.000Z",
"period_end": "2026-10-14T09:30:00.000Z",
"docs_used": 1250,
"docs_included": 3400,
"rollover_docs": 400,
"overage_docs": 0,
"overage_cost_cents": 0,
"spending_cap_cents": 3800,
"overage_blocked_reason": null
}| Field | Description |
|---|---|
plan | The plan of the current period. |
period_start, period_end | The monthly period, in UTC. Annual plans have monthly periods too. |
docs_used | Successful live documents in this period, included and overage together. |
docs_included | The documents included in this period: the plan's monthly documents plus rollover_docs (after an upgrade during the period, a prorated amount). |
rollover_docs | Unused documents carried over from the previous period (paid plans). |
overage_docs, overage_cost_cents | Documents beyond the included ones, and their cost so far in US cents. |
spending_cap_cents | The cap on this period's overage cost, in US cents; null on the Free plan, which has no overage. |
overage_blocked_reason | null when overage is available; otherwise why not: free, cancel_scheduled, past_due, no_subscription or billed_this_period. |
See Usage and billing for how these numbers work.