L'API Recepto en PHP

Trois scripts : valider, lire, générer. Prérequis : PHP 8.1 ou plus récent, avec l'extension curl.

La validation se fait sans clé. La lecture et la génération lisent une clé d'API dans la variable d'environnement RECEPTO_API_KEY ; une clé gratuite s'obtient sur la page des tarifs.

Valider une facture

Le fichier part tel quel dans le champ file. La réponse donne le format, le profil, et chaque erreur avec l'identifiant et le texte de sa règle.

<?php
// Valider une facture avec Recepto : gratuit, sans clé.
//   php validate.php facture.pdf
$api = getenv('RECEPTO_API') ?: 'https://recepto.fr/v1';
$path = $argv[1];

$curl = curl_init("$api/validate");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => ['file' => new CURLFile($path, '', basename($path))],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 60,
]);
$report = json_decode(curl_exec($curl), true);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);

if ($status !== 200) {
    // 413 ou 422 : fichier illisible ; 429 : réessayer après Retry-After secondes.
    fwrite(STDERR, "Refusé ($status) : {$report['detail']}\n");
    exit(1);
}
printf("%s %s : %s\n", $report['format'], $report['profile'], $report['valid'] ? 'valide' : 'invalide');
foreach ($report['errors'] as $error) {
    printf("- %s : %s\n", $error['rule_id'], $error['message']);
}

Lire une facture reçue

/v1/parse renvoie la même structure JSON pour CII, UBL et Factur-X, avec le résumé de la validation dans source. Schéma : /v1/schemas/invoice-1.0.json.

<?php
// Lire une facture reçue en JSON (clé d'API requise).
//   RECEPTO_API_KEY=rcp_... php parse.php facture.pdf
$api = getenv('RECEPTO_API') ?: 'https://recepto.fr/v1';
$path = $argv[1];

$headers = [];
$curl = curl_init("$api/parse");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => ['file' => new CURLFile($path, '', basename($path))],
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('RECEPTO_API_KEY')],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 60,
    CURLOPT_HEADERFUNCTION => function ($curl, $line) use (&$headers) {
        $parts = explode(':', $line, 2);
        if (count($parts) === 2) {
            $headers[strtolower(trim($parts[0]))] = trim($parts[1]);
        }
        return strlen($line);
    },
]);
$result = json_decode(curl_exec($curl), true);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);

if ($status === 429) {
    fwrite(STDERR, "Limite atteinte : réessayer dans {$headers['retry-after']} s.\n");
    exit(1);
}
if ($status !== 200) {
    fwrite(STDERR, "Recepto a répondu $status : {$result['detail']}\n");
    exit(1);
}

$invoice = $result['invoice'];
echo "Facture {$invoice['number']} de {$invoice['seller']['name']}\n";
echo "Net à payer : {$invoice['totals']['due']} {$invoice['currency']}\n";
echo 'Valide selon EN 16931 : ' . ($result['source']['valid'] ? 'oui' : 'non') . "\n";
echo "Lectures restantes ce mois-ci : {$headers['x-quota-remaining']}\n";

Générer une facture Factur-X

L'entrée contient les parties et les lignes, sans aucun total. /v1/generate calcule totaux et TVA selon EN 16931, produit un PDF/A-3 Factur-X, et ne le renvoie que si son XML passe le XSD et le Schematron. Données de l'exemple :

{
  "number": "F2026-0042",
  "issue_date": "2026-10-15",
  "due_date": "2026-11-14",
  "delivery_date": "2026-10-10",
  "seller": {
    "name": "Atelier Numérique SAS",
    "legal_registration": {
      "value": "999999998",
      "scheme": "0002"
    },
    "vat_id": "FR11999999998",
    "address": {
      "line1": "12 rue des Lilas",
      "city": "Nantes",
      "postal_code": "44000"
    }
  },
  "buyer": {
    "name": "Boulangerie du Port SARL",
    "vat_id": "FR19787878784",
    "address": {
      "line1": "3 quai de la Fosse",
      "city": "Nantes",
      "postal_code": "44100"
    }
  },
  "payment": {
    "means_code": "30",
    "iban": "FR7630006000011234567890189"
  },
  "lines": [
    {
      "name": "Développement du site vitrine",
      "quantity": "12.5",
      "unit_code": "HUR",
      "unit_price": "65.00",
      "vat_rate": "20"
    }
  ]
}
<?php
// Générer une facture Factur-X à partir de ses données (clé d'API requise).
//   RECEPTO_API_KEY=rcp_... php generate.php facture.json facture.pdf
$api = getenv('RECEPTO_API') ?: 'https://recepto.fr/v1';
[, $source, $target] = $argv;

$curl = curl_init("$api/generate");
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => file_get_contents($source),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RECEPTO_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 60,
]);
$body = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);

if ($status === 422) {
    // La facture obtenue enfreint une règle officielle : le rapport dit laquelle.
    foreach (json_decode($body, true)['report']['errors'] ?? [] as $error) {
        printf("- %s : %s\n", $error['rule_id'], $error['message']);
    }
    exit(1);
}
if ($status !== 200) {
    fwrite(STDERR, "Recepto a répondu $status : $body\n");
    exit(1);
}

file_put_contents($target, $body);
echo "Facture Factur-X écrite dans $target\n";

Erreurs et limites

Référence complète de l'API · Erreurs de validation expliquées