Search by

invoicexml / facturx

InvoiceXML

PHP client for Factur-X e-invoices: create, validate, embed and extract EN 16931 invoices with the InvoiceXML API.

v1.0.1 2026-10-12 19:27 UTC

This package is auto-updated.

Last update: 2026-10-12 19:48:26 UTC


README

tests

A thin PHP client for the InvoiceXML API that creates, validates, embeds and reads Factur-X e-invoices. A Factur-X invoice is a PDF/A-3 with embedded EN 16931 CII XML, the hybrid format of the French e-invoicing mandate.

You send an invoice as a PHP array, and the API computes totals and the VAT breakdown, builds the XML and the PDF/A-3, and validates the result before returning it. Your project needs no PDF library, no XML builder and no Schematron tooling.

Installation

composer require invoicexml/facturx

Requires PHP 8.1 or newer. Guzzle is used by default, and any PSR-18 HTTP client works.

Quick start

use InvoiceXml\FacturX\Client;

$client = new Client(getenv('INVOICEXML_API_KEY'));

$pdf = $client->createFacturx([
    'invoiceNumber'  => 'FA-2026-0142',
    'issueDate'      => '2026-05-17',
    'currency'       => 'EUR',
    'seller'         => ['name' => 'Atelier Lumière SARL', 'vatIdentifier' => 'FR12345678901', 'postalAddress' => ['line1' => '8 rue des Arts', 'city' => 'Lyon', 'postCode' => '69002', 'country' => 'FR']],
    'buyer'          => ['name' => 'Globex SAS', 'postalAddress' => ['line1' => '15 rue de Rivoli', 'city' => 'Paris', 'postCode' => '75001', 'country' => 'FR']],
    'paymentDetails' => ['paymentAccountIdentifier' => 'FR7630006000011234567890189'],
    'lines'          => [['quantity' => 2, 'priceDetails' => ['netPrice' => 450], 'vatInformation' => ['rate' => 20], 'item' => ['name' => 'Conseil en design']]],
], ['language' => 'fr']);

$pdf->save('facture.pdf');

The result is a Factur-X PDF/A-3 in the EN 16931 profile, already validated against the official rules.

Why InvoiceXML

  • Rule sets updated on their legal effective dates. New Factur-X and BR-FR releases go live server-side on the day they take effect. You never update a package or redeploy to stay compliant.
  • Validated before delivery. Every created or embedded invoice passes the full rule set before it is returned. Data that cannot produce a compliant invoice is refused with the violated rules, so nothing broken reaches a customer.
  • Zero data retention. Documents are processed in memory and discarded when the response is returned. Your data is never used for AI training.
  • Processing in Frankfurt. All processing runs in Frankfurt, Germany.
  • Integration assistance. When a rule finding needs a human explanation, a person who knows the rule sets answers your support request.

Get an API key

Sign up at invoicexml.com to start a 30-day free trial with 100 credits. No credit card is needed. Plans are listed on the pricing page.

Usage

Invoices use the field names of the EN 16931 InvoiceDocument model from the OpenAPI specification. The API explorer documents every field. Every method that takes a file accepts a path on disk or a File object.

Create a Factur-X PDF

$pdf = $client->createFacturx($invoice, [
    'language'   => 'fr',          // labels of the rendered PDF: en, de or fr
    'brandColor' => '#0F766E',
    'logoUrl'    => 'https://example.com/logo.png',
]);

$pdf->save($pdf->filename);       // for example invoice-facturx-FA-2026-0142.pdf

To keep your own invoice layout, pass 'pdfUrl' => 'https://...' and the API embeds the XML into that PDF instead of rendering one.

French mandate rules (BR-FR)

For invoices bound for the French exchange network, add br-fr. The invoice is then also checked against the BR-FR Flux 2 rules, on top of the rules of its declared profile.

$pdf    = $client->createFacturx($invoice, ['rules' => ['br-fr']]);
$result = $client->validateFacturx('facture.pdf', rules: ['br-fr']);
$pdf    = $client->embedFacturx('layout.pdf', 'factur-x.xml', rules: ['br-fr']);

Validate

The API answers valid and invalid documents alike with a result, so check valid.

$result = $client->validateFacturx('incoming.pdf');

if (!$result->valid) {
    foreach ($result->errors as $error) {
        echo "{$error->rule} ({$error->layer}): {$error->message}\n";
    }
}

Each finding carries the following fields:

  • rule is the rule identifier, for example BR-CO-15.
  • layer is xsd, en16931 or cius.
  • message is a plain-language explanation.
  • fields names the invoice fields involved, for example seller.name.
  • line and raw give the XML line and the verbatim validator output.

$result->data holds the verdict details, such as schemaValid, schematronValid and profile. $result->report is the field report: one row per invoice field with its value and its findings.

Embed XML into an existing PDF

$facturx = $client->embedFacturx('layout.pdf', 'factur-x.xml');

The XML is validated before embedding. Pass skipValidation: true to embed it as is.

Read incoming invoices

$data = $client->extractJson('incoming.pdf');             // Factur-X PDF or CII XML
$invoice = $data['invoice'];                              // same shape createFacturx() accepts

$zip = $client->extractAttachments('incoming.pdf');       // supporting documents as a ZIP

Extraction parses the embedded XML deterministically, with no AI involved. The returned invoice can go straight back into createFacturx().

Errors and rate limits

Any error status throws InvoiceXml\FacturX\ApiException. When a create or embed request is refused because the invoice would not be compliant, $e->errors lists the violated rules as findings.

use InvoiceXml\FacturX\ApiException;

try {
    $client->createFacturx($invoice);
} catch (ApiException $e) {
    echo $e->statusCode, ' ', $e->errorCode, ': ', $e->detail, "\n";
    foreach ($e->errors as $error) {
        echo "{$error->rule}: {$error->message}\n";
    }
}

errorCode is a stable numeric code, documented under error handling.

When too many requests for one API key run at the same time, the API answers 429 with a Retry-After header. The client waits that long and resends the request, up to 3 times. A request answered with 429 was not processed and not billed, so resending never creates duplicates. If the retries are used up, a TooManyRequestsException carries retryAfter in seconds. Set maxRetries: 0 to handle 429 yourself.

Configuration

$client = new Client(
    apiKey: getenv('INVOICEXML_API_KEY'),
    httpClient: $psr18Client,          // any PSR-18 client, Guzzle by default
    requestFactory: $psr17Factory,     // PSR-17 factories, Guzzle's by default
    streamFactory: $psr17Factory,
    maxRetries: 3,
);

Links

License

MIT