twilldocs / sdk
Official PHP SDK for the Twill Docs document generation API.
Requires
- php: >=8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
- phpstan/phpstan: ^1.11
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
The official PHP SDK for Twill Docs, the document infrastructure API. Turn structured data into production-ready PDFs — invoices, receipts, payslips, and more.
- Zero dependencies — needs only
ext-curlandext-json. - Typed errors — catch
RateLimitException,ValidationException, and friends. - PSR-4, PHP 8.1+.
Install
composer require twilldocs/sdk
Quickstart
<?php require 'vendor/autoload.php'; use TwillDocs\Client; $twill = new Client('twdc_...'); // Create an invoice, wait for it to render, then download the PDF. $doc = $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', 'tax_id' => 'US123456789'], 'buyer' => ['name' => 'Acme Corp', 'address' => '1 Infinite Loop'], 'line_items' => [ ['description' => 'Consulting', 'quantity' => 3, 'unit_price' => 1200], ['description' => 'Travel expenses', 'quantity' => 1, 'unit_price' => 340], ], 'tax_rate' => 0.085, ]); $pdf = $twill->documents->download($doc['id']); file_put_contents("invoice-{$doc['id']}.pdf", $pdf);
You supply line items and the tax rate; Twill computes the totals and renders the document.
Configuration
$twill = new Client( apiKey: 'twdc_...', // required baseUrl: 'https://api.twilldocs.com', // default; use http://localhost:8080 for local dev timeout: 30, // per-request timeout in seconds );
Documents
$doc = $twill->documents->create('invoice', [...]); // returns immediately, status "pending" $twill->documents->retrieve($doc['id']); // check status $twill->documents->waitUntilReady($doc['id']); // poll until succeeded / failed $pdf = $twill->documents->download($doc['id']); // string (bytes) $twill->documents->generate('invoice', [...]); // create + wait, in one call
Every create/generate sends an idempotency key automatically (override with
the $idempotencyKey argument), so a retried request never produces a duplicate.
Templates
Pass a template name (or a TwillDocs\Template constant) as the first argument:
use TwillDocs\Template; $twill->documents->generate(Template::RECEIPT, [...]);
Available: invoice, quote, receipt, purchase_order, delivery_note,
payslip, offer_letter, nda, service_agreement. Each template's input
shape is documented at twilldocs.com/docs.
API keys
$keys = $twill->apiKeys->list(); $twill->apiKeys->revoke($keys[0]['id']);
Brand
$twill->brand->retrieve(); $twill->brand->update(theme: 'modern'); $twill->brand->update(logo: [ 'filename' => 'logo.png', 'contents' => file_get_contents('logo.png'), 'contentType' => 'image/png', ]); $twill->brand->deleteLogo();
Errors
Every failure throws a subclass of TwillDocs\Exceptions\TwillException:
use TwillDocs\Exceptions\ValidationException; use TwillDocs\Exceptions\RateLimitException; use TwillDocs\Exceptions\TwillException; try { $twill->documents->generate('invoice', $data); } catch (ValidationException $e) { print_r($e->errors); // per-field messages } catch (RateLimitException $e) { sleep($e->retryAfter ?? 1); } catch (TwillException $e) { echo "{$e->status} {$e->type} {$e->getMessage()}"; }
| Class | When |
|---|---|
ValidationException |
400 / 422 (->errors) |
AuthenticationException |
401 |
PermissionException |
403 |
NotFoundException |
404 |
ConflictException |
409 |
RateLimitException |
429 (->retryAfter) |
ServerException |
5xx |
ConnectionException |
network failure before a response |
TimeoutException |
request exceeded timeout |
Health
$twill->health(); // ['status' => 'ok' | 'degraded', 'checks' => [...]]