@twilldocs/sdk
v0.1.0
Published
Official TypeScript SDK for the Twill Docs document generation API.
Maintainers
Readme
Twill Docs — TypeScript SDK
The official TypeScript/JavaScript SDK for Twill Docs, the document infrastructure API. Turn structured data into production-ready PDFs — invoices, receipts, payslips, and more — with full type safety.
- Typed templates — the input for each document type is checked at compile time. You can't send a malformed invoice.
- Zero dependencies — uses the built-in
fetch. ESM and CommonJS. - Typed errors — catch
TwillRateLimitError,TwillValidationError, etc.
Install
npm install @twilldocs/sdkRequires Node.js 18+ (or any runtime with a global fetch).
Quickstart
import { TwillDocs } from "@twilldocs/sdk";
const twill = new TwillDocs({ apiKey: process.env.TWILL_API_KEY! });
// Create an invoice and wait for it to render, then download the PDF.
const doc = await twill.documents.generate("invoice", {
invoice_number: "INV-1001",
issue_date: "2026-07-22",
due_date: "2026-08-21",
currency: "USD",
seller: { name: "Northwind Studio", address: "500 Market St, San Francisco, CA", tax_id: "US123456789" },
buyer: { name: "Acme Corp", address: "1 Infinite Loop, Cupertino, CA" },
line_items: [
{ description: "Consulting", quantity: 3, unit_price: 1200 },
{ description: "Travel expenses", quantity: 1, unit_price: 340 },
],
tax_rate: 0.085,
});
const pdf = await twill.documents.download(doc.id); // Uint8ArrayYou supply line items and the tax rate; Twill computes the totals and renders the document.
Configuration
const twill = new TwillDocs({
apiKey: "twdc_...", // required
baseUrl: "https://api.twilldocs.com", // default; use http://localhost:8080 for local dev
timeout: 30_000, // per-request timeout in ms (default 30s)
});Documents
// Create — returns immediately with a pending document.
const doc = await twill.documents.create("receipt", { /* ReceiptInput */ });
// Check status.
const status = await twill.documents.retrieve(doc.id);
// Poll until rendered (or failed / timed out).
await twill.documents.waitUntilReady(doc.id, { intervalMs: 1000, timeoutMs: 60_000 });
// Download the finished PDF bytes.
const pdf = await twill.documents.download(doc.id);
// create + wait, in one call.
const ready = await twill.documents.generate("invoice", { /* InvoiceInput */ });Every create/generate sends an idempotency key automatically (override with
{ idempotencyKey }), so a retried request never produces a duplicate.
Templates
The first argument to create/generate narrows the input type to that
template's schema. Available templates and their input types:
| Template | Input type |
| -------- | ---------- |
| invoice | InvoiceInput |
| quote | QuoteInput |
| receipt | ReceiptInput |
| purchase_order | PurchaseOrderInput |
| delivery_note | DeliveryNoteInput |
| payslip | PayslipInput |
| offer_letter | OfferLetterInput |
| nda | NdaInput |
| service_agreement | ServiceAgreementInput |
All input types are exported, so you can build payloads elsewhere with full typing:
import type { InvoiceInput } from "@twilldocs/sdk";API keys
const keys = await twill.apiKeys.list();
await twill.apiKeys.revoke(keys[0].id);Brand
await twill.brand.retrieve();
await twill.brand.update({ theme: "modern" });
await twill.brand.update({ logo: { data: pngBytes, filename: "logo.png" } });
await twill.brand.deleteLogo();Errors
Every failure throws a subclass of TwillError. Catch the specific ones you
want to handle:
import {
TwillError,
TwillValidationError,
TwillRateLimitError,
TwillAuthenticationError,
} from "@twilldocs/sdk";
try {
await twill.documents.generate("invoice", input);
} catch (err) {
if (err instanceof TwillValidationError) {
console.error("Invalid input:", err.errors); // per-field messages
} else if (err instanceof TwillRateLimitError) {
await sleep((err.retryAfter ?? 1) * 1000);
} else if (err instanceof TwillAuthenticationError) {
// bad or revoked API key
} else if (err instanceof TwillError) {
console.error(err.status, err.type, err.message);
}
}| Class | When |
| ----- | ---- |
| TwillValidationError | 400 / 422 — bad request (.errors has field messages) |
| TwillAuthenticationError | 401 — missing/invalid/revoked key |
| TwillPermissionError | 403 |
| TwillNotFoundError | 404 |
| TwillConflictError | 409 |
| TwillRateLimitError | 429 (.retryAfter in seconds) |
| TwillServerError | 5xx |
| TwillConnectionError | network failure before a response |
| TwillTimeoutError | request exceeded timeout |
Health
const health = await twill.health(); // { status: "ok" | "degraded", checks: {...} }