Documentation
TryDocify API guide
Everything you need to turn JSON into production-quality PDFs. Base URL: https://api.trydocify.com
Quickstart
Send a document to POST /api/v1/generate with your key in the X-API-Key header. The response body is the PDF.
curl -X POST https://api.trydocify.com/api/v1/generate \
-H "X-API-Key: $TRYDOCIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Invoice #1042",
"header": { "left": "Acme Studio", "right": "INV-1042" },
"blocks": [
{ "type": "keyValue", "items": [{ "key": "Billed to", "value": "Globex Corp." }] },
{ "type": "table",
"columns": [{ "header": "Item" }, { "header": "Amount", "align": "right" }],
"rows": [["Design audit", "$1,200.00"], ["Component library", "$2,550.00"]],
"footer": [["Total", "$3,750.00"]] }
]
}' \
--output invoice.pdfAuthentication
Every request to /api/v1/generate, /api/v1/usage, /api/v1/keys/rotate and /api/v1/billing/portal needs your secret key in the X-API-Key header. Keys start with dfy_live_ and are shown exactly once, right after checkout.
- Keep keys server-side. Never ship them in browser or mobile bundles.
- We store only an HMAC-SHA256 hash of your key, so we can't show it to you again. Rotate it if it leaks.
- All traffic must use HTTPS.
Generate a PDF
POST/api/v1/generate
Success returns 200 with Content-Type: application/pdf and Content-Disposition: attachment. Useful response headers:
| Header | Meaning |
|---|---|
| X-Credits-Remaining | Credits left in the current billing period. |
| X-Page-Count | Number of pages in the generated PDF. |
| X-Render-Time-Ms | Time spent in the layout engine. |
Prefer JSON? Send Accept: application/json and the PDF comes back base64-encoded in data, alongside fileName, sizeBytes, pageCount, creditsRemaining and renderTimeMs. This is handy for no-code tools and queues.
Each successful PDF costs one credit. Validation errors, render failures and rejected requests are free: a credit is reserved atomically before rendering and refunded if anything goes wrong.
Test mode
Add "test": true to any document while you build your integration. The PDF is rendered exactly as it would be in production, with a TEST watermark on every page, and it doesn't use a credit.
curl -X POST https://api.trydocify.com/api/v1/generate \
-H "X-API-Key: $TRYDOCIFY_API_KEY" -H "Content-Type: application/json" \
-d '{ "test": true, "title": "Invoice #1042", "blocks": [{ "type": "paragraph", "text": "Layout check" }] }' \
-o test.pdf- Each key gets 200 test renders per day, reset at 00:00 UTC. After that, test requests return
429 test_limit_reached. - Test responses carry
X-Docify-Test: trueandX-Test-Renders-Remaining. WithAccept: application/json, the envelope hastestandtestRendersRemaining. - Test mode works while your subscription is active, even when this period's credits are used up.
- To go live, remove the flag. No key change is needed.
Document schema
The request body is a single document object:
| Field | Type | Description |
|---|---|---|
| blocks | Block[] | Required. Content rendered top to bottom (1–2,000 blocks). |
| title | string | Large title on the first page; also the PDF title metadata. |
| subtitle | string | Muted line under the title. |
| fileName | string | Download name. Sanitized; .pdf appended. Default document.pdf. |
| pageSize | enum | a3 · a4 (default) · a5 · letter · legal |
| orientation | enum | portrait (default) · landscape |
| header | { left, right } | Running header text on every page. |
| footer | { text, showPageNumbers } | Running footer. Page X of Y is on by default. |
| theme | object | accentColor and textColor (#RRGGBB), fontSize 7–16, margin 18–108 pt. |
| info | object | PDF metadata: author, subject, keywords. |
| test | boolean | Test mode: a free, watermarked render. 200 per key per day. Default false. |
Field names are camelCase; enum values are case-insensitive. Wherever a string is expected, numbers and booleans are accepted and rendered exactly as written, so ["Widget", 2, 19.90] is a valid table row.
Blocks
Each block has a type discriminator, which may appear anywhere in the object. Unknown types are rejected with invalid_json.
headingtext, level (1–3)
{ "type": "heading", "text": "Summary", "level": 2 }paragraphtext, align, bold, italic, muted
{ "type": "paragraph", "text": "Thanks for your order.\nSee you soon.", "align": "justify" }listitems, ordered
{ "type": "list", "ordered": true, "items": ["Sign", "Return"] }keyValueitems[{ key, value }], columns (1–3)
{ "type": "keyValue", "columns": 2, "items": [{ "key": "Order", "value": "#5531" }] }tablecolumns[{ header, align, width }], rows, footer, striped
{ "type": "table", "columns": [{ "header": "Item", "width": 4 }, { "header": "Qty", "align": "right" }], "rows": [["Widget", 2]], "footer": [["Total", 2]] }notetext, title, tone (info · success · warning · danger)
{ "type": "note", "tone": "warning", "title": "Due soon", "text": "Payment due in 7 days." }imagesrc (data URI), width (pt), align
{ "type": "image", "src": "data:image/png;base64,iVBORw0KG…", "width": 120 }divider—
{ "type": "divider" }spacerheight (1–400 pt)
{ "type": "spacer", "height": 24 }pageBreak—
{ "type": "pageBreak" }Tables flow across pages and repeat their header row. footer rows are emphasized and the last one is styled as a grand total. Images must be inline base64 PNG, JPEG or WebP data URIs (max 2 MB each). Remote URLs aren't fetched, which keeps renders fast and deterministic.
Usage & credits
GET/api/v1/usage
Returns your plan, remaining credits, the current period and request statistics. Statistics lag live traffic by a second or two; the credit counters are exact.
{
"apiKey": { "prefix": "dfy_live_Ab3x", "status": "active", "createdAt": "2026-10-01T09:12:44Z", "lastUsedAt": "2026-10-26T18:03:10Z" },
"plan": { "code": "pro", "name": "Pro", "monthlyQuota": 10000 },
"credits": { "limit": 10000, "used": 1342, "remaining": 8658 },
"period": { "start": "2026-10-01T09:12:44Z", "end": "2026-11-01T09:12:44Z" },
"testMode": { "dailyLimit": 200, "usedToday": 14, "remaining": 186, "resetsAt": "2026-10-27T00:00:00Z" },
"stats": {
"last24Hours": { "requests": 226, "succeeded": 223, "failed": 3, "creditsCharged": 209, "testRenders": 14 },
"currentPeriod": { "requests": 1471, "succeeded": 1453, "failed": 18, "creditsCharged": 1342, "testRenders": 111 },
"daily": [{ "date": "2026-10-26", "requests": 212, "creditsCharged": 209 }]
}
}Rotating keys
POST/api/v1/keys/rotate
Issues a new secret and invalidates the current one immediately. Plan, credits and history are kept. The response contains apiKey (shown once), prefix and rotatedAt.
Billing
POST/api/v1/billing/portal
Returns a short-lived Paddle customer portal url. Open it to switch between Starter and Pro, update your payment method, download invoices or cancel. Upgrades apply instantly. Credits reset at the start of each billing period.
Errors
Errors are RFC 9457 problem documents with a stable machine-readable code and a traceId for support.
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://trydocify.com/docs#error-validation-failed",
"title": "The document payload is invalid.",
"status": 400,
"detail": "One or more fields failed validation. See 'errors' for details.",
"code": "validation_failed",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": { "blocks[1].rows[3]": ["Each row must have exactly 4 cells (one per column)."] }
}| Code | Status | Meaning |
|---|---|---|
| missing_api_key | 401 | No X-API-Key header was sent. |
| invalid_api_key | 401 | The key is malformed, unknown, or was rotated. |
| revoked_api_key | 401 | The key was revoked by an operator. |
| subscription_inactive | 403 | The subscription is past due or canceled. Update billing to continue. |
| quota_exceeded | 402 | All credits for this period are used. Wait for the reset or upgrade. Test renders still work. |
| test_limit_reached | 429 | Today's free test renders are used. They reset at 00:00 UTC. |
| period_expired | 402 | The renewal hasn't been confirmed. Check your payment method. |
| validation_failed | 400 | The document is invalid. errors maps JSON paths to messages. |
| invalid_json | 400 | The body isn't valid JSON, or a value has the wrong type. |
| bad_request | 400 | The request couldn't be processed, for example an empty body. |
| payload_too_large | 413 | The body exceeds 10 MB. |
| unsupported_media_type | 415 | Send Content-Type: application/json. |
| rate_limited | 429 | Too many requests. Honor the Retry-After header. |
| render_failed | 422 | The layout engine rejected the document (e.g. an element taller than a page). Not charged. |
| renderer_busy | 503 | All renderers are busy. Retry with backoff. Not charged. |
| billing_not_configured | 503 | Billing isn't available on this server. |
| internal_error | 500 | Unexpected failure. Quote the traceId when contacting support. |
Limits
| Limit | Value |
|---|---|
| Requests per key | 120 / minute sustained, bursts of 30 |
| Request body | 10 MB |
| Blocks per document | 2,000 |
| Rows per table / cells per document | 20,000 / 200,000 |
| Columns per table | 24 |
| Images per document | 25 (2 MB each) |
| Paragraph text | 50,000 characters |
| Test renders per key | 200 / day (UTC) |
When you exceed the rate limit you get 429 with a Retry-After header. Back off and retry.
AI assistants
Using Claude Code, Cursor, Copilot or another AI coding assistant? Give it our instructions file. It covers the whole API (schema, every block, errors and limits) and the rules for a safe integration: key on the server, test mode while developing, retries on 429 and 503.
curl -o docs/TRYDOCIFY.md https://trydocify.com/trydocify.mdThen reference it from your assistant's instructions, for example a line in CLAUDE.md or AGENTS.md: For PDF generation, follow docs/TRYDOCIFY.md. The same content is available as llms-full.txt, and the machine-readable API is at openapi.json.