Shablonix
POST /v1/generate

Generate PDF

Generate a PDF document from a template with dynamic data.

Overview

The generate endpoint creates a PDF document by merging a template with the provided data. The generated PDF is stored temporarily and a download URL is returned in the response.

A request with Accept: application/pdf returns the file in the response and does not store it (see Direct file response). Other modes store the file for 72 hours by default. After that, regenerate the document or keep a copy in your own storage.

Endpoint

POST https://api.shablonix.online/v1/generate
Content-Type: application/json
Authorization: Bearer tf_live_your_api_key

Request Body

ParameterTypeRequiredDescription
template_idstringYesThe ID of the template to use for generation
dataobjectYesKey-value pairs to populate template variables
optionsobjectNoPDF generation options (page size, margins, etc.)
asyncbooleanNotrue returns 202 Accepted with status_url. Poll or wait for a webhook. Direct file streaming is disabled.
webhook_urlstringNoOptional unsigned callback for this job only. Prefer account webhooks for HMAC-signed document.completed events.
filenamestringNoCustom filename for the generated PDF

Options

The options object allows you to customize PDF generation:

Page Size

ValueDimensionsUse Case
letter8.5 x 11 inchesUS standard (default)
a4210 x 297 mmInternational standard
legal8.5 x 14 inchesLegal documents
customCustom width/heightRequires width & height

Orientation

ValueDescription
portraitVertical orientation (default)
landscapeHorizontal orientation

Margins

Margins can be specified in inches, millimeters, or pixels:

{
  "options": {
    "margins": {
      "top": "1in",
      "right": "0.75in",
      "bottom": "1in",
      "left": "0.75in"
    }
  }
}
You can also use a single value to set all margins: "margins": "1in"

Full Options Example

{
  "template_id": "invoice_pro",
  "data": { ... },
  "options": {
    "page_size": "a4",
    "orientation": "portrait",
    "margins": {
      "top": "20mm",
      "right": "15mm",
      "bottom": "20mm",
      "left": "15mm"
    },
    "header": {
      "enabled": true,
      "height": "50px"
    },
    "footer": {
      "enabled": true,
      "height": "30px",
      "content": "Page {{page}} of {{pages}}"
    },
    "print_background": true,
    "scale": 1.0
  }
}

Response

A successful request returns a JSON object with the generated document details:

{
  "id": "a1b2c3d4",
  "status": "completed",
  "template_id": "meridian-invoice",
  "file_url": "/v1/generate/a1b2c3d4/download",
  "status_url": "/v1/generate/a1b2c3d4",
  "file_size": 142857,
  "created_at": "2026-08-25T10:30:00Z",
  "expires_at": "2026-08-28T10:30:00Z",
  "render_time_ms": 234
}
FieldTypeDescription
idstringUnique document identifier
statusstringcompleted, processing, or failed
file_urlstringAuthenticated download URL when the job has completed
file_sizeintegerFile size in bytes
status_urlstringStable polling URL for this generation
expires_atstringISO 8601 timestamp when URL expires

Slow renders

A synchronous request waits up to 60 seconds for a render worker. If the document is not ready by then, the API returns 202 Accepted with status: "pending"; poll status_url for the result. @shablonix/sdk 0.3+ polls for you, so generatePdf() still resolves with a completed document.

Code Examples

curl -X POST https://api.shablonix.online/v1/generate \
  -H "Authorization: Bearer tf_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "meridian-invoice",
    "data": {
      "company_name": "Acme Corp",
      "invoice_number": "INV-2024-001",
      "date": "January 15, 2024",
      "due_date": "February 15, 2024",
      "customer": {
        "name": "John Smith",
        "email": "john@example.com",
        "address": "123 Main St, New York, NY 10001"
      },
      "items": [
        {
          "description": "Web Development Services",
          "quantity": 40,
          "unit_price": 150,
          "total": 6000
        },
        {
          "description": "UI/UX Design",
          "quantity": 20,
          "unit_price": 125,
          "total": 2500
        }
      ],
      "subtotal": 8500,
      "tax_rate": 8.5,
      "tax_amount": 722.50,
      "total": 9222.50
    },
    "options": {
      "page_size": "letter",
      "orientation": "portrait"
    }
  }'

Direct file response

Send an Accept header matching the output format and the file comes back as the response body instead of JSON. There is no second download request and, without an idempotency key, Shablonix keeps no copy: the file is deleted as soon as it has been sent.

formatAccept
pdfapplication/pdf
htmltext/html
docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
curl -X POST https://api.shablonix.online/v1/generate \
  -H "Authorization: Bearer tf_live_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/pdf" \
  -d '{ "template_id": "meridian-invoice", "data": { "invoice_number": "INV-2024-001" } }' \
  -o invoice.pdf

Response headers

X-Shablonix-Generation-IDGeneration ID, for support and usage records
X-Shablonix-Output-Retentionnone, or temporary when an idempotency key was sent
X-Shablonix-Pdf-RendererRenderer that produced a PDF
Content-LengthFile size in bytes
Content-Dispositionattachment; filename="<id>.pdf"
  • The whole document is rendered before the first byte is sent. Direct responses save the download round trip and storage; they do not start sooner.
  • The generation is counted once rendering finishes. A client that disconnects while the file is being sent is still charged, and without a key the file cannot be fetched again.
  • If rendering takes longer than 60 seconds, a request without an idempotency key gets 504; the render is cancelled, nothing is stored, and it is not charged. With a key, the API answers 409; retry with the same key to receive the file once it is ready. For large documents, send a key or use async generation.
  • Ignored when async or webhook_url is set; those requests always return JSON.

Idempotency

Send an Idempotency-Key header (8–200 visible ASCII characters, e.g. your order or invoice ID) to make a generate request safe to retry. Repeating the same key with the same body within 24 hours returns the original generation without rendering or charging again. This works for synchronous, async, and direct file requests, and for POST /v1/emails/compile.

curl -X POST https://api.shablonix.online/v1/generate \
  -H "Authorization: Bearer tf_live_your_api_key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/pdf" \
  -H "Idempotency-Key: invoice-INV-2024-001" \
  -d '{ "template_id": "meridian-invoice", "data": { "invoice_number": "INV-2024-001" } }' \
  -o invoice.pdf
StatusMeaning
409The key was used with a different body, or the first request is still rendering. A still-rendering response includes id, status_url, and retryAfter (seconds); retry with the same key.
410The key is older than 24 hours. Check the original result before submitting new work.

A keyed direct response keeps the file for the standard retention window so that the replay can be served (X-Shablonix-Output-Retention: temporary). Omit the key if the document must never be stored.

Async generation

Synchronous generate waits until the PDF is stored, then returns JSON with file_url. Set async: true when you do not want to hold the HTTP connection: the API returns 202 Accepted and you learn the result by polling, by webhook, or both.

The same REST paths work from cURL, any HTTP client, or @shablonix/sdk. You do not need the SDK.

  • Poll — GET the status_url until completed or failed.
  • Account webhooks — configure once. Shablonix POSTs signed document.completed / document.failed when the file is ready, including after a synchronous generate.
  • Per-request webhook_url — extra unsigned callback for that job only.
Accept: application/pdf streaming is disabled when async or webhook_url is set. Download from file_url after the job completes.

Enqueue

POST the same body as a sync generate, plus "async": true. Store id and status_url from the 202 body.

{
  "id": "a1b2c3d4",
  "status": "pending",
  "template_id": "meridian-invoice",
  "created_at": "2026-08-26T10:30:00Z",
  "expires_at": "2026-08-29T10:30:00Z",
  "status_url": "/v1/generate/a1b2c3d4"
}
curl -sS -X POST https://api.shablonix.online/v1/generate \
  -H "Authorization: Bearer tf_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "meridian-invoice",
    "data": { "invoice": { "number": "INV-2026-001" } },
    "async": true
  }'

Poll status

GET /v1/generate/:id (the status_url). Status is pending, processing, completed, or failed. Prefix relative URLs with https://api.shablonix.online.

ID=a1b2c3d4

while true; do
  BODY=$(curl -sS https://api.shablonix.online/v1/generate/$ID \
    -H "Authorization: Bearer tf_live_your_api_key")
  STATUS=$(printf '%s' "$BODY" | python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])')
  echo "$STATUS"
  case "$STATUS" in
    completed|failed) printf '%s\n' "$BODY"; break ;;
  esac
  sleep 2
done

Download

When status is completed, GET the file_url with the same API key. Copy the bytes into your own storage before expires_at.

curl -sS https://api.shablonix.online/v1/generate/$ID/download \
  -H "Authorization: Bearer tf_live_your_api_key" \
  --output invoice.pdf

Account webhooks

Configure one HTTPS URL. Every later generate — sync or async — delivers document.completed or document.failed when those events are selected. Payloads are HMAC-signed. Requires webhooks:write. Store the secret; it is shown once if Shablonix generated it. Zapier and Make use POST /v1/hooks instead — extra destinations that do not replace this account URL. See Webhooks → REST hooks.

curl -sS -X POST https://api.shablonix.online/v1/webhooks/settings \
  -H "Authorization: Bearer tf_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example/webhooks/shablonix",
    "events": ["document.completed", "document.failed"]
  }'

Receive the POST, verify the signature, then download with the generation id. file_url and status_url in the payload are API-relative.

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const API = 'https://api.shablonix.online';

app.post(
  '/webhooks/shablonix',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const signature = req.get('X-Shablonix-Signature');
    const timestamp = req.get('X-Shablonix-Timestamp');
    const secret = process.env.SHABLONIX_WEBHOOK_SECRET;
    const signed = `${timestamp}.${req.body.toString('utf8')}`;
    const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
    if (
      !signature ||
      !timestamp ||
      !crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'))
    ) {
      return res.status(400).json({ error: 'invalid signature' });
    }

    const event = JSON.parse(req.body.toString('utf8'));
    if (event.type === 'document.completed') {
      const pdf = await fetch(`${API}${event.data.file_url}`, {
        headers: { Authorization: `Bearer ${process.env.SHABLONIX_API_KEY}` },
      });
      // store Buffer.from(await pdf.arrayBuffer())
    }

    res.status(200).json({ received: true });
  },
);

Headers, retry schedule, and replay protection are on the Webhooks page.

Per-request webhook

Pass webhook_url on one generate call for an extra POST when that job finishes. These callbacks are not HMAC-signed. Account webhooks still fire if configured.

curl -sS -X POST https://api.shablonix.online/v1/generate \
  -H "Authorization: Bearer tf_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "meridian-invoice",
    "data": { "invoice": { "number": "INV-2026-001" } },
    "async": true,
    "webhook_url": "https://your-app.example/webhooks/generation"
  }'

Batch

POST /v1/generate/batch is always asynchronous (202 Accepted, 1–100 items). Poll GET /v1/generate/batch/:id or subscribe to batch.progress, batch.completed, and batch.failed.

curl -sS -X POST https://api.shablonix.online/v1/generate/batch \
  -H "Authorization: Bearer tf_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "meridian-invoice",
    "format": "pdf",
    "items": [
      { "data": { "invoice": { "number": "INV-001" } } },
      { "data": { "invoice": { "number": "INV-002" } } }
    ]
  }'