npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dynamic-document-api/sdk

v0.2.0

Published

Official Dynamic Document API SDK — turn templates and JSON into PDFs and images.

Readme

Dynamic Document API for TypeScript and JavaScript

The official SDK for Dynamic Document API: turn templates and JSON into PDFs, images and e-invoices, from EU data centres.

npm install @dynamic-document-api/sdk

It runs on Node.js 18 and later, Deno, Bun and edge runtimes. It has no dependencies: it uses fetch, AbortController and Web Crypto. Keep your API key on the server; don't ship it to browsers.

Quickstart

import { DynamicDocumentApi } from "@dynamic-document-api/sdk";

const dda = new DynamicDocumentApi(); // reads DYNAMIC_DOCUMENT_API_KEY

const render = await dda.pdf.fromTemplate({
  templateId: "tpl_…",
  data: { number: "2026-0042", customer: { name: "Ada Lovelace" } },
});

console.log(render.files?.[0]?.url); // a signed download link

Create a key in the dashboard under API keys. A test key (dda_test_…) renders for free, with a watermark; a live key (dda_live_…) counts against your plan.

Renders

// From HTML, a URL or Markdown
const fromHtml = await dda.pdf.fromHtml({ html: "<h1>Hello</h1>", pdf: { paper: { size: "A4" } } });
const screenshot = await dda.images.fromUrl({ url: "https://example.com", format: "png" });

// The unified endpoint takes every input type and output format
const render = await dda.renders.create({
  input: { type: "template", templateId: "tpl_…" },
  data: { number: 42 },
  output: { format: "pdf" },
  mode: "async", // answer at once and render in the background
});
const done = await dda.waitForRender(render.id); // polls until it is finished

// The file in the response body instead of a link
const binary = await dda.renders.createBinary({ input: { type: "html", html: "<h1>Hi</h1>" } });
const bytes = await binary.bytes();

// Download a hosted file
const pdf = await dda.files.download(done.files![0]!);

Batches

const batch = await dda.batches.create({
  templateId: "tpl_…",
  items: invoices.map((invoice) => ({ data: invoice, reference: invoice.number })),
  combine: { zip: true },
});
const finished = await dda.waitForBatch(batch.id);
for await (const item of dda.batches.listItems(batch.id, { status: "failed" })) console.log(item.index, item.error);

PDF tools and e-invoices

const merged = await dda.pdfTools.merge({ sources: [{ renderId: a.id }, { renderId: b.id }] });
const archived = await dda.pdfTools.pdfa({ source: { renderId: render.id }, level: "2b" });

// Standalone XRechnung or Factur-X XML
const xml = await dda.einvoices.create({ profile: "XRECHNUNG", invoice: { /* EN 16931 fields */ } });

Files for the PDF tools, attachments and batch CSVs go in with dda.uploads.create({ file, filename }); uploads are kept for 24 hours.

Templates

for await (const template of dda.templates.list()) console.log(template.id, template.name);

const created = await dda.templates.create({ name: "Invoice", type: "pdf_code", html: "<h1>Invoice {{ number }}</h1>" });
await dda.templates.updateDraft(created.id, { html: "<h1>Invoice {{ number }}</h1><p>{{ customer.name }}</p>" });
await dda.templates.publish(created.id, { note: "First version" });

Email delivery and storage

Send rendered documents through your own email provider, or upload them to your own bucket or SFTP server.

const connection = await dda.emailConnections.create({
  name: "Billing",
  provider: "brevo",
  defaultFrom: { email: "[email protected]", name: "Example Ltd" },
  credentials: { api_key: process.env.BREVO_API_KEY },
});
await dda.emailRules.create("tpl_…", {
  name: "Send the invoice",
  connectionId: connection.id,
  to: "{{ customer.email }}",
  subject: "Invoice {{ number }}",
});

for await (const send of dda.emailSends.list({ status: "failed" })) console.log(send.id);

await dda.storageDestinations.create({
  name: "Archive",
  provider: "s3",
  config: { bucket: "acme-invoices", region: "eu-central-1" },
  credentials: { access_key_id: "…", secret_access_key: "…" },
});

Signed links

A signed link renders an image or PDF from a template on request, for <img> tags, emails and social cards. sign() builds the URL locally with the link's secret, without an API call:

const link = await dda.signedLinks.create({
  templateId: "tpl_…",
  name: "Social card",
  formats: ["png"],
  allowedParams: ["title.text"],
});
// link.secret is shown once; store it

const url = await dda.signedLinks.sign(link.id, process.env.DDA_LINK_SECRET!, {
  params: { "title.text": "Summer Sale" },
  expiresIn: 3600,
});

Webhooks

await dda.webhookEndpoints.create({ url: "https://example.com/hooks/dda", events: ["render.succeeded", "batch.completed"] });

// In your handler: verify with the raw body, before parsing it
const event = await dda.webhooks.verify(rawBody, request.headers, process.env.DDA_WEBHOOK_SECRET!);
if (event.type === "render.succeeded") console.log(event.data.object.id);

Pass an array of secrets to verify() while you roll one with dda.webhookEndpoints.rollSecret().

Configuration

const dda = new DynamicDocumentApi({
  apiKey: "dda_live_…",          // default: DYNAMIC_DOCUMENT_API_KEY
  region: "eu",                  // the EU (Germany) is the only region today
  timeoutMs: 130_000,            // per request; above the longest synchronous wait (120 s)
  retry: { maxAttempts: 3 },     // or false
  autoIdempotencyKey: true,      // every POST gets an Idempotency-Key
  telemetry: true,               // the SDK's name and version in User-Agent
  fetch: customFetch,            // default: the global fetch
});

// Per call: your own idempotency key, request id, timeout, retries, headers or abort signal
await dda.pdf.fromHtml({ html }, { idempotencyKey: `invoice-${id}`, timeoutMs: 120_000 });

Retries cover network errors, 429 and 5xx, with exponential backoff and jitter, and respect Retry-After. A retried POST reuses its idempotency key, so a render is never billed twice. Request bodies can be written in camelCase (templateId) or as in the API reference (template_id). Values you own are sent exactly as written: data, metadata, HTTP headers, signed-link params, and the config and credentials of connections and destinations, which use the API's own spelling.

Lists

Every list is an async iterable that fetches pages as you go:

for await (const render of dda.renders.list({ status: "failed" })) console.log(render.id);
const page = await dda.renders.list({ limit: 20 }).page();
const recent = await dda.renders.list().all(200); // pass a limit: a workspace can hold millions

Errors

Every failure is a DynamicDocumentApiError with status, code, message and requestId, and a subclass per kind of failure:

import { RateLimitError, RenderFailedError } from "@dynamic-document-api/sdk";

try {
  await dda.pdf.fromTemplate({ templateId, data });
} catch (error) {
  if (error instanceof RenderFailedError) console.error(error.code, error.message); // e.g. template_runtime_error
  else if (error instanceof RateLimitError) console.error("slow down");
  else throw error;
}

Every error code is listed at https://dynamicdocumentapi.com/docs/errors.

Anything else

dda.request() calls any endpoint with the same authentication, retries and error handling:

const account = await dda.request("GET", "/account");

The full API is described at https://dynamicdocumentapi.com/docs/api-reference.

Versioning

The SDK follows semantic versioning. While it is 0.x, a minor version can change the API; see the changelog.

Licence

MIT. See LICENSE.