Recipe · PHP

Generate an invoice PDF at billing-cycle close with PHP

This recipe shows the composition step around the same endpoint documented on the main API page — read that first for the full field reference. It builds an invoice from your own subscription record and calls the API with plain PHP's curl extension — no framework or Composer package required.

What you'll need

  • An InvoiceCraftly API key (see the API reference).
  • PHP 8 or later with the curl extension enabled (enabled by default in most installs).
  • Your own subscription/billing record — the example below reads name, address, and plan price from an array shaped like the row your billing table already stores.

Generate the invoice when a billing period closes

<?php
// Plain PHP, curl extension only — no Composer package required
function build_invoice(array $subscription): array {
    return [
        'type' => 'invoice',
        'number' => $subscription['invoice_number'],
        'issueDate' => $subscription['period_end'],
        'currency' => $subscription['currency'],
        'seller' => ['name' => 'Your SaaS Company', 'addressLines' => ['Your Company Address']],
        'buyer' => ['name' => $subscription['customer_name'], 'addressLines' => [$subscription['customer_address']]],
        'items' => [[
            'description' => $subscription['plan_name'] . ' subscription',
            'quantity' => 1,
            'unitPrice' => $subscription['plan_price'],
            'taxRate' => $subscription['tax_rate'] ?? 0,
        ]],
    ];
}

function generate_invoice_pdf(array $subscription): string {
    $invoice = build_invoice($subscription);
    $apiKey = getenv('INVOICECRAFTLY_API_KEY');

    $ch = curl_init('https://invoicecraftly.com/api/v1/documents/pdf');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            "Authorization: Bearer {$apiKey}",
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode($invoice),
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $pdf = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

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

    if (!is_dir('invoices')) {
        mkdir('invoices', 0755, true);
    }
    $path = "invoices/{$invoice['number']}.pdf";
    file_put_contents($path, $pdf);
    return $path;
}
What happens on error

A billing cycle should not silently skip its invoice

generate_invoice_pdf throws on any non-200 status rather than writing a corrupt file, so a failed render surfaces in your own billing job's error handling instead of leaving a customer without an invoice. Check the HTTP status against the error table — a 429 RATE_LIMITED at billing-run time usually means too many subscriptions renewed in the same batch; stagger the run or wait for the Retry-After delay.

Start building

Get an API key and try this recipe.

Free during the beta, 100 renders included per month.

Get your API key