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.

Documents are available for download for 24 hours. After that, you will need to regenerate the document or store it 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

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"
    }
  }'

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.

  • PollGET 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" } } }
    ]
  }'