Report Generation API

Report generation API for automated PDF reports

Charts, long tables and page numbers, rendered from your data on demand or at the end of every month. One call returns a finished multi-page PDF your customer can keep.

  • 50 free renders/month
  • No credit card
  • Charts, bookmarks, page numbers

What is a report generation API?

A report generation API turns structured data into a finished PDF report. Your application sends figures, table rows and chart data as JSON; the API fills them into an HTML template, waits until every chart has finished drawing, paginates the result and returns a multi-page PDF with running headers, bookmarks and page numbers.

Report generation API key facts
EndpointPOST https://api.dynamicdocumentapi.com/v1/pdf/from-template, or /v1/pdf/from-html and /v1/pdf/from-url for a page you already render
InputYour report data as JSON, plus an HTML/CSS template with Jinja syntax
ChartsAny JavaScript chart library — Chart.js, ECharts, D3, Highcharts — drawn in Chromium 153 before the capture
Waitingload, networkidle, a CSS selector, a ready_flag or a fixed delay; timeout 100 ms to 300 s
PaginationRepeating table headers, controlled page breaks, running header and footer, “Page 3 of 12”
NavigationPDF bookmarks with outline, tagged PDF with tagged, single sections with page_ranges
BulkBatches from a JSON list or a CSV export, one PDF per client or merged into one file (paid plans)
Render timeMedian 216 ms, p95 516 ms1, plus whatever your charts need to draw
PriceFree: 50 renders/month. Paid from €15/month (billed annually) for 3,000 renders, auto top-ups from €6 per 1,000

One note on the name. Searching for “reporting API” also turns up the browser feature of that name, which sends CSP violations and deprecation warnings to a collection endpoint. That is a different thing. This page is about the other meaning — also searched as PDF report API — where your data goes in and a finished, paginated report comes back.

1. Measured over 1,664 successful renders on engine 2026.4. total_ms = time in the queue plus processing in the worker, excluding API overhead and network transfer. A report with heavy client-side charts takes longer, because the engine waits for the condition you set before it captures the page.

From data to a finished PDF report

Store the report layout as a template once — cover page, chart section, table section — then send one call per report. The example asks for bookmarks, a running footer with page numbers and a tagged PDF, and waits for the charts to finish before capturing. If your reporting page already exists in your app, you can render HTML reports to PDF instead.

curl https://api.dynamicdocumentapi.com/v1/pdf/from-template \
  -H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
  -H "Idempotency-Key: acme-2026-Q3" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "tpl_quarterly_report",
    "data": {
      "client": "Acme Co.",
      "period": "Q3 2026",
      "locale": "en-US",
      "currency": "USD",
      "kpis": { "revenue": 482150.00, "orders": 1284, "growth": 0.18 },
      "series": [82000, 97400, 113200, 140550],
      "rows": [ { "channel": "Search", "sessions": 48120, "revenue": 190400 } ]
    },
    "pdf": {
      "paper": { "format": "A4" },
      "margin": { "top": "22mm", "bottom": "18mm" },
      "outline": true,
      "tagged": true,
      "footer": { "enabled": true,
                  "left": "Acme Co. · Q3 2026",
                  "right": "Page {{page}} of {{pages}}" },
      "wait": { "until": "selector", "selector": "#charts[data-ready]",
                "timeout_ms": 20000, "strict": false }
    },
    "filename": "acme-q3-2026.pdf"
  }'
// Node.js 18+: render one client report and return the signed link
export async function renderReport(client, period, data) {
  const res = await fetch("https://api.dynamicdocumentapi.com/v1/pdf/from-template", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.DYNAMIC_DOCUMENT_API_KEY}`,
      "Idempotency-Key": `${client}-${period}`, // one report per period
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      template_id: "tpl_quarterly_report",
      data,
      pdf: {
        outline: true,
        tagged: true,
        footer: { enabled: true, right: "Page {{page}} of {{pages}}" },
        wait: { until: "selector", selector: "#charts[data-ready]" },
      },
      filename: `${client}-${period}.pdf`,
    }),
  });
  if (!res.ok) throw new Error(await res.text());
  const render = await res.json();
  return render.files[0].url; // signed link, 1 hour by default
}
import os
import requests

def render_report(client: str, period: str, data: dict) -> bytes:
    res = requests.post(
        "https://api.dynamicdocumentapi.com/v1/pdf/from-template",
        headers={
            "Authorization": f"Bearer {os.environ['DYNAMIC_DOCUMENT_API_KEY']}",
            "Idempotency-Key": f"{client}-{period}",
        },
        json={
            "template_id": "tpl_quarterly_report",
            "data": data,
            "pdf": {
                "outline": True,          # PDF bookmarks per section
                "tagged": True,           # structure for screen readers
                "footer": {"enabled": True, "right": "Page {{page}} of {{pages}}"},
                "wait": {"until": "selector", "selector": "#charts[data-ready]"},
            },
            "delivery": "binary",   # PDF bytes, ready to attach
        },
        timeout=120,
    )
    res.raise_for_status()
    return res.content
<?php
function renderReport(string $client, string $period, array $data): string
{
    $ch = curl_init('https://api.dynamicdocumentapi.com/v1/pdf/from-template');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . getenv('DYNAMIC_DOCUMENT_API_KEY'),
            'Idempotency-Key: ' . $client . '-' . $period,
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS     => json_encode([
            'template_id' => 'tpl_quarterly_report',
            'data'        => $data,
            'pdf'         => [
                'outline' => true,
                'footer'  => ['enabled' => true, 'right' => 'Page {{page}} of {{pages}}'],
                'wait'    => ['until' => 'selector', 'selector' => '#charts[data-ready]'],
            ],
            'delivery'    => 'binary',
        ]),
    ]);
    return curl_exec($ch); // PDF bytes
}

The response is a render object with a signed link in files[0].url, valid for one hour by default and up to seven days with expires_in. Ask for "delivery": "binary" instead and you get the PDF bytes straight back, ready to attach to an email. The Idempotency-Key makes a retry harmless: the same key returns the report that was already rendered, for 24 hours.

Charts that are finished before the page is captured

Most broken PDF reports have the same cause: the page was captured while the charts were still animating. JavaScript runs by default, and the engine always waits for the load event, web fonts, image decoding and two rendered frames. For charts, that is usually not enough — so you say what “ready” means.

Let the chart announce itself

The most reliable signal comes from the chart library itself. Turn animation off, and in the library's completion callback set a marker the API can wait for.

// Chart.js: no animation, flag when drawn
new Chart(ctx, { options: { animation: {
  onComplete: () => charts.dataset.ready = "1"
} } });
"wait": { "until": "selector",
  "selector": "#charts[data-ready]",
  "timeout_ms": 20000, "strict": false }

Four other ways to wait

networkidle waits for a quiet network (a 500 ms window without requests) and suits dashboards that fetch their own data. ready_flag lets your script declare completion, delay adds a fixed pause, and load is the default. timeout_ms runs from 100 ms to 300 seconds.

Decide what a timeout means

With strict: false a timeout still returns the report, plus a wait_timeout warning you can log — a monthly statement usually has to go out, even with one missing widget. With strict: true the render fails instead, which is what you want for a report that must be complete.

Sharp charts, not screenshots

SVG charts stay vector in the PDF, so they print cleanly at any size. Canvas-based charts are raster: give the canvas a higher device pixel ratio before drawing, or render the same data as SVG for print. Either way, colors follow print_background, which is on by default.

Already have the report as a page in your product? Point the URL endpoint at it and export a dashboard URL with a session cookie or an Authorization header, instead of rebuilding the layout as a template.

Long tables, page breaks and navigation

A report is rarely one page. These are the CSS rules and PDF options that decide whether a 40-page export looks finished or improvised.

Tables that survive a page break

Line items are a normal HTML table, so the browser's own print rules apply. The header row repeats on every page, rows are never cut in half, and a section keeps at least two lines together.

thead            { display: table-header-group; }
tr, .totals      { break-inside: avoid; }
h2               { break-after: avoid; }
p                { orphans: 2; widows: 2; }
.section + .section { break-before: page; }

Bookmarks and structure

"outline": true builds the PDF bookmark tree from your headings, so a reader can jump to “Channels” or “Appendix” in the sidebar of any PDF viewer. "tagged": true adds the structure that screen readers and accessibility checks rely on. page_ranges returns just one section — useful when a customer asks for “only the summary”.

Cover page, running header and page numbers

The running header and footer are separate from the document body. Use the simple form with left, center and right, or pass your own HTML for a logo and a confidentiality note. {{page}} and {{pages}} are filled per page, and the top margin has to leave room for the header.

"header": { "enabled": true, "height": "16mm",
  "html": "<div class='hdr'><img src='logo.svg'>Confidential</div>" },
"footer": { "enabled": true,
  "left": "Acme Co. · Q3 2026",
  "right": "Page {{page}} of {{pages}}" },
"margin": { "top": "22mm", "bottom": "18mm" }

A cover page is simply the first section of the template with break-after: page. To keep the header off it, hide it for the first page in your header HTML with a {{page}} condition, or render the cover as its own render and merge the files.

Monthly runs: one report per client

Reporting work arrives in waves. On the first working day of the month an agency needs 200 client reports, and on every other day almost none. Three parts of the API exist for exactly that.

  1. Your schedule, our render

    The API renders when you call it; the calendar stays in your system — a cron job, a queue worker or a scheduled task that runs at 6:00 on the first of the month.

  2. One batch, many reports

    Send the whole run as a batch from a JSON list or a CSV export: one row per client, one PDF per row, or every report merged into a single file.

  3. Webhook when it is done

    Long runs go async. A signed webhook tells your system when each report — or the whole batch — is ready, so nothing sits blocked on an open connection.

Webhooks are signed with HMAC-SHA256 following the Standard Webhooks specification, and a delivery your server does not accept is retried up to eight times over about 28 hours. Batches are available on paid plans. Files live as long as you set with retention_days, up to your plan's maximum, and you can delete them at any time — or take the bytes directly and store nothing with us.

Report types teams automate

Agency client reports

Monthly performance reports with the client's logo on the cover, channel tables and a commentary section. One template, one batch, 200 branded PDFs — instead of 200 exports pasted into slides.

Analytics and usage exports

The “Export as PDF” button in your own product. The user picks a period, your backend renders the same view as a paginated document, and the download appears seconds later.

Statements and financial summaries

Account statements, portfolio summaries and payout reports, with amounts and dates formatted per locale from Unicode CLDR data and totals your system calculated.

Audit and compliance reports

Long, structured documents with bookmarks, a tagged structure and fixed metadata. Add a password and permissions with protect when a report leaves the building.

Report API, BI export or your own headless Chrome?

A PDF report API is not always the right tool. Here is where each option is genuinely better.

Report generation API compared with BI exports and self-hosted headless Chrome
Question BI tool export Self-hosted headless Chrome Dynamic Document API
Who designs the layoutThe tool, within its export themeYou, in HTML and CSSYou, in HTML and CSS
Your branding on every pageLimited to the themeFull controlFull control, plus versioned templates
Triggered from your appRarely, often a manual exportYesYes, one HTTP call
ChartsBuilt inYour chart libraryYour chart library, with wait conditions
OperationsNone beyond the toolQueue, workers, browser updates, storageNone — we run the engine
Data leaves your networkDepends on the toolNoYes: the payload is sent to the API
Best whenPeople explore data interactivelyData must never leave your network, or volume is very high and steadyYour product has to hand out finished, branded documents

Two honest limits. We return PDFs and images, not editable XLSX or DOCX files — if your readers need to keep working with the numbers, ship a spreadsheet next to the PDF. And a report that must never leave your infrastructure belongs on your own hardware, not on any API, ours included.

What a report costs to generate

Free

€0

50 reports per month. Enough to build the template and run a full month of testing.

Starter

€15 / month

3,000 renders per month, billed annually (€19 billed monthly). About €0.005 per report.

A render is one returned file of up to 50 pages, so a 40-page report costs the same as a one-page one. At 15,000 reports a month the Growth plan is €47 billed annually, about €0.0031 each. See the cost per report on every plan.

FAQ

Report generation API: frequently asked questions

Can I include charts in PDF reports?

Yes. Reports render in Chromium with JavaScript enabled, so Chart.js, ECharts, D3, Highcharts or a plain canvas all work. Charts come out as sharp vector or high-resolution raster graphics, exactly as the library draws them in a browser. There is no separate chart syntax to learn.

How do I keep table headers on every page?

Use a normal HTML table and let CSS do it: thead { display: table-header-group } repeats the header row on every page, and tr { break-inside: avoid } stops a row from being cut in half. The same rules keep a totals block together with the rows above it.

Can reports be generated on a schedule?

The API renders a report whenever you call it. The schedule stays in your system: a cron job, a queue worker or a scheduled task in your automation tool calls the endpoint at the end of each month or week. For long jobs use async mode and a webhook so nothing blocks.

Can I export a dashboard URL as a PDF report?

Yes. Point the URL endpoint at the dashboard and pass a session cookie or an Authorization header so the page loads signed in. Credentials are only sent to the same registrable domain. Then wait for a CSS selector or a ready flag so every widget is drawn before the capture.

What if a chart takes a long time to load?

Set pdf.wait to the condition that describes “done”: a CSS selector your chart adds, a ready flag your script sets, or networkidle. timeout_ms goes up to 300 seconds. With strict: false a timeout still returns the PDF plus a wait_timeout warning; with strict: true the render fails instead.

Can I generate one report per client in bulk?

Yes. Send a batch from a JSON list or a CSV export on paid plans: one row per client, one PDF per row, or merged into a single file. A signed webhook tells your system when the batch is finished, and each render can carry its own filename and metadata.

Build your first report free

50 renders a month on the free plan, no credit card, auto top-ups on paid plans.

Start free