Skip to content
TryDocify

Tutorial

How to generate a PDF from JSON with a REST API

A step-by-step tutorial: send a JSON document to a REST API and get back a paginated PDF with tables, totals, headers and page numbers. Copy-paste cURL examples.

Updated · Every code sample on this page is run against the API before publishing.

How JSON to PDF works

Instead of designing a page in HTML and printing it, you describe the document as data: a title, a running header and footer, and an ordered list of blocks such as headings, paragraphs, key/value grids, tables and notes. You POST that JSON to /api/v1/generate, and a layout engine typesets it: it wraps text, breaks pages, repeats table headers on every page and numbers the pages. The response body is the PDF.

This tutorial uses cURL so it works from any terminal. The same requests in Node.js, Python and C# are in the language guides.

1. Your first PDF

Put your API key in an environment variable (export TRYDOCIFY_API_KEY=dfy_live_…) and send the smallest useful document: a title and one paragraph.

bash · terminal
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": "Hello, PDF",
    "blocks": [
      { "type": "paragraph", "text": "This PDF was generated from JSON." }
    ]
  }' \
  --output hello.pdf

"test": true renders a free, watermarked PDF, so you can iterate on a layout without spending credits. Every plan includes 200 test renders a day. Open hello.pdf: the title, the paragraph, and page numbers in the footer, which are on by default.

2. A real document

Here is a complete invoice. It uses four ideas you will reuse in most documents:

  • header and footer repeat on every page, with showPageNumbers for "Page X of Y".
  • A keyValue block lays out label/value pairs in up to three columns.
  • A table with relative column widths, right-aligned numbers, and footer rows for totals; the last footer row is styled as the grand total.
  • A note with a tone (info, success, warning or danger) for payment instructions.
json · invoice.json
{
  "test": true,
  "fileName": "invoice-1042.pdf",
  "title": "Invoice #1042",
  "subtitle": "Issued 3 November 2026 · Due 3 December 2026",
  "header": { "left": "Acme Studio", "right": "INV-1042" },
  "footer": { "text": "Acme Studio · 221B Baker Street, London", "showPageNumbers": true },
  "theme": { "accentColor": "#1D4ED8" },
  "blocks": [
    {
      "type": "keyValue",
      "columns": 2,
      "items": [
        { "key": "Billed to", "value": "Globex Corporation" },
        { "key": "Invoice no.", "value": "INV-1042" },
        { "key": "Email", "value": "[email protected]" },
        { "key": "Terms", "value": "Net 30" }
      ]
    },
    {
      "type": "table",
      "columns": [
        { "header": "Description", "width": 4 },
        { "header": "Qty", "align": "right" },
        { "header": "Unit price", "align": "right", "width": 2 },
        { "header": "Amount", "align": "right", "width": 2 }
      ],
      "rows": [
        ["Design system audit", 1, "$1,200.00", "$1,200.00"],
        ["Component library (React)", 3, "$850.00", "$2,550.00"]
      ],
      "footer": [
        ["Subtotal", "", "", "$3,750.00"],
        ["VAT (20%)", "", "", "$750.00"],
        ["Total due", "", "", "$4,500.00"]
      ]
    },
    {
      "type": "note",
      "tone": "info",
      "title": "Payment",
      "text": "Bank transfer to IBAN GB33 BUKB 2020 1555 5555 55. Reference INV-1042."
    }
  ]
}

Numbers and booleans are accepted wherever text is expected and are printed exactly as written, so ["Component library", 3, "$850.00"] is a valid row. Format money yourself before sending it; the API never rounds or converts values.

3. Send it from a file

Keep the document in invoice.json and send it with --data-binary, which preserves the file exactly. The response headers tell you about the result:

bash · terminal
# Send the document from a file and keep the response headers.

curl -X POST https://api.trydocify.com/api/v1/generate \
  -H "X-API-Key: $TRYDOCIFY_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @invoice.json \
  --dump-header headers.txt \
  --output invoice-1042.pdf

# X-Page-Count, X-Render-Time-Ms, X-Credits-Remaining (and in test mode X-Test-Renders-Remaining)

cat headers.txt
HeaderMeaning
X-Page-CountPages in the generated PDF.
X-Render-Time-MsTime spent typesetting, in milliseconds.
X-Credits-RemainingCredits left in the current billing period.
X-Test-Renders-RemainingFree test renders left today (test mode only).

4. Handle errors

Every error is an RFC 9457 problem document with a stable code. Validation errors point to the exact JSON path, and they are never charged: a credit is only used for a PDF you actually receive.

bash · terminal
# A table row with the wrong number of cells is rejected before rendering, and costs nothing.

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,
    "blocks": [
      { "type": "table", "columns": [{ "header": "Item" }, { "header": "Amount" }], "rows": [["Only one cell"]] }
    ]
  }'
json · response (abridged)
{
  "title": "The document payload is invalid.",
  "status": 400,
  "code": "validation_failed",
  "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
  "errors": {
    "blocks[0].rows[0]": ["Each row must have exactly 2 cells (one per column)."]
  }
}

Branch on code, not on the message. The ones to handle in an integration are validation_failed (fix the document), quota_exceeded (upgrade or wait for the reset), and rate_limited / renderer_busy (retry after the Retry-After header). The full list is in the docs.

5. Get JSON instead of bytes

Some tools (queues, no-code platforms, webhooks) can't handle binary responses. Send Accept: application/json and the PDF comes back base64-encoded in data, together with fileName, pageCount, sizeBytes, creditsRemaining and renderTimeMs.

Going to production

  • Remove "test": true. Nothing else changes: same key, same endpoint.
  • Call the API from your server only. The key must never reach a browser or mobile app.
  • Respect Retry-After on 429 and 503. Each key may sustain 120 requests a minute with bursts of 30.
  • Generate on demand rather than storing PDFs: TryDocify keeps no copy of your documents, and rendering is fast.

Try it with your own data

Every plan includes 200 free test renders a day. Add "test": true while you build, and remove it when you go live.