PDFCraft
PHP

HTML to PDF in PHP

One POST, a PDF back. No headless browser to install, no Chromium to keep patched, no fonts to install on the box — the render happens on a warm browser that is already running.

Render HTML to a PDF

<?php
$payload = json_encode([
    'html' => '<h1>Invoice 1042</h1>',
    'options' => ['printBackground' => true, 'margin' => ['top' => '20mm']],
]);

$ch = curl_init('https://api.pdfcraft.dev/v1/render');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 60,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PDFCRAFT_API_KEY'),
        'Content-Type: application/json',
    ],
]);

$pdf = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new RuntimeException("render failed: {$status} {$pdf}");
}

file_put_contents('invoice.pdf', $pdf);

Read a PDF back as JSON

The same key works for extraction. Send a PDF, name the fields you want by the label printed on the page, and get them back with the raw text, a coerced value, a confidence and a bounding box.

<?php
$payload = json_encode([
    'file' => base64_encode(file_get_contents('statement.pdf')),
    'schema' => ['account_number' => 'string', 'closing_balance' => 'currency'],
    'options' => ['rows_as_objects' => true],
]);

$ch = curl_init('https://api.pdfcraft.dev/v1/extract');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('PDFCRAFT_API_KEY'),
        'Content-Type: application/json',
    ],
]);

$body = json_decode(curl_exec($ch), true);
echo $body['fields']['closing_balance']['value'];

What bites in PHP

Errors are one shape

Every failure is {"error":{"code","message","docs_url"}} with a stable code, so you can switch on error.code rather than parsing prose. The two worth handling explicitly are rate_limited — honour Retry-After — and render_failed, which means your HTML broke rather than ours did.

The same thing in another language

Or try it with no code at all in the playground. A free key is 100 renders a month.