Skip to content
CastPDF
Documentation menu
API

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

Conventions

  • Base URL: https://api.castpdf.com/v1.
  • Authentication: Authorization: Bearer YOUR_API_KEY on every request except the signed file download. See Authentication and keys.
  • Requests: JSON with Content-Type: application/json (any other content type gets 415), at most 5 MB per body. Unknown fields are rejected with invalid_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-id header, such as req_4b8e0f6c2d7a41e9b3c5a1f0e2d4c6b8.
  • Rate limits: every authenticated /v1 request counts against your workspace's per-minute budget for the key's mode, reported in x-ratelimit-limit, x-ratelimit-remaining and x-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):

JSON
{
  "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 pathWhat it does
POST /v1/pdfGenerate a PDF from a template or from HTML
GET /v1/pdf/:idA document's status and a fresh download link
GET /v1/documentsThe document log, newest first
GET /v1/files/:idDownload a stored PDF through a signed link (no API key)
POST /v1/templatesCreate a template
GET /v1/templatesList templates
GET /v1/templates/:idGet a template (current or a given version)
PUT /v1/templates/:idUpdate a template (content changes create a new version)
DELETE /v1/templates/:idDelete a template
GET /v1/templates/:id/versionsList a template's versions
POST /v1/templates/:id/pinPin or unpin a version
POST /v1/templates/:id/previewRender a watermarked preview
GET /v1/accountThe key's team and mode
GET /v1/account/usageThe 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.

FieldTypeDescription
template_idstring (UUID)A stored template to render.
template_versioninteger, 1 or moreWith template_id: render this version. Default: the pinned version, or else the current one.
htmlstring, not emptyHTML 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).
cssstringWith 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.
dataobjectThe 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 heightPaper 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.
marginsstringOne 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.
filenamestring, up to 100 charactersThe 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.
metadataobjectYour 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:

HeaderValue
content-typeapplication/pdf
content-dispositionattachment; filename="invoice-042.pdf"
x-document-idThe document's id, for GET /v1/pdf/:id
x-pagesThe page count
x-blocked-resourcesHow many blocked external requests are reported (0 to 10; see external resources)
cache-controlno-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:

JSON
{
  "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
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"
  }'
Node.js 20+
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);
Python (requests)
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
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.pdf
Node.js 20+
import { 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()));
Python (requests)
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

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

Coming soon

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
curl https://api.castpdf.com/v1/pdf/3f0c9a4e-6b1d-4c2a-9a57-0d8e4b7f21c3 \
  -H "Authorization: Bearer YOUR_API_KEY"
Node.js 20+
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);
Python (requests)
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:

JSON
{
  "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…"
}
FieldDescription
statussucceeded, 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.
modefast or print.
testtrue for documents made with a test key.
template_id, template_versionThe template and version rendered, or null for HTML.
expires_atWhen the stored PDF is deleted, or null if it was never stored.
urlA 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 parameterDescription
limit1 to 100. Default 20.
beforeThe previous page's next_before, to get the next page.
curl
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:

JSON
{
  "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:

HeaderValue
content-typeapplication/pdf
content-dispositioninline; filename="invoice-042.pdf" (opens in the browser)
cache-controlprivate, with a max-age of 300 seconds
content-security-policysandbox
x-content-type-optionsnosniff

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:

JSON
{
  "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

FieldTypeDescription
namestring, 1 to 100 charactersRequired.
htmlstring, not emptyRequired. 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.
cssstringAt most 500 KB.
sample_dataobjectDefault data for renders without data and for previews. At most 1 MB as JSON.
settingsobjectPage defaults: mode, format, orientation and margin (singular), with the same values as the POST /v1/pdf fields.
curl
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"}
  }'
Node.js 20+
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); // …, 1
Python (requests)
import 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"])  # ..., 1

201 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 parameterDescription
limit1 to 100. Default 50.
offsetHow many to skip. Default 0.
curl
curl "https://api.castpdf.com/v1/templates?limit=20&offset=0" \
  -H "Authorization: Bearer YOUR_API_KEY"
Node.js 20+
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();
Python (requests)
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:

JSON
{
  "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
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_data or settings creates a new version with the next number; fields you leave out are copied from the current version. settings replaces the stored settings object as a whole. css can be set to null to remove it.
  • A change of name alone creates no version.
  • The latest 50 versions are kept, plus the pinned one; older ones are deleted.
curl
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; }"}'
Node.js 20+
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 number
Python (requests)
import 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
curl -X DELETE https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b \
  -H "Authorization: Bearer YOUR_API_KEY"
Node.js 20+
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); // 204
Python (requests)
import 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)  # 204

204 with no body. Errors: template_not_found (404).

GET /v1/templates/:id/versions

Lists the versions that still exist, newest first.

curl
curl https://api.castpdf.com/v1/templates/8d1f2c3b-4a5e-4f60-9b7a-1c2d3e4f5a6b/versions \
  -H "Authorization: Bearer YOUR_API_KEY"
JSON
{
  "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.

FieldTypeDescription
versioninteger, 1 or more, or nullRequired.
curl
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.

FieldTypeDescription
dataobjectThe Liquid data. Default: the version's sample_data.
versioninteger, 1 or moreDefault: 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
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.pdf

200 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
curl https://api.castpdf.com/v1/account \
  -H "Authorization: Bearer YOUR_API_KEY"
JSON
{
  "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
curl https://api.castpdf.com/v1/account/usage \
  -H "Authorization: Bearer YOUR_API_KEY"
Node.js 20+
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`);
Python (requests)
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:

JSON
{
  "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
}
FieldDescription
planThe plan of the current period.
period_start, period_endThe monthly period, in UTC. Annual plans have monthly periods too.
docs_usedSuccessful live documents in this period, included and overage together.
docs_includedThe documents included in this period: the plan's monthly documents plus rollover_docs (after an upgrade during the period, a prorated amount).
rollover_docsUnused documents carried over from the previous period (paid plans).
overage_docs, overage_cost_centsDocuments beyond the included ones, and their cost so far in US cents.
spending_cap_centsThe cap on this period's overage cost, in US cents; null on the Free plan, which has no overage.
overage_blocked_reasonnull 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.