Vai al contenuto

API per la fattura elettronica

Invia le fatture allo SdI con una chiamata HTTP e ricevi notifiche e fatture dei fornitori direttamente sul tuo server. Nessun SDK da installare: bastano HTTP e JSON.

Come funziona

Il servizio è collegato direttamente al Sistema di Interscambio dell'Agenzia delle Entrate con il canale SDICoop. Il tuo gestionale parla con noi, noi parliamo con lo SdI:

  1. Invio: il gestionale manda il file XML della fattura con POST https://api.fattura-elettronica.cloud/api/inoltro e riceve subito l'identificativo SdI.
  2. Notifiche: quando lo SdI risponde (consegna, scarto, mancata consegna...), inoltriamo la notifica al tuo endpoint notifiche.
  3. Ricezione: le fatture dei fornitori indirizzate al tuo codice destinatario arrivano al tuo endpoint fatture, oltre che nel portale.

Gli endpoint sono semplici URL del tuo server che rispondono OK: li imposti nel profilo del portale. Ogni richiesta può essere firmata con un segreto che conosci solo tu.

Indirizzo base delle API: https://api.fattura-elettronica.cloud/api
Manda sempre l'header Accept: application/json, così anche gli errori arrivano in JSON.

Autenticazione

Le API usano le stesse credenziali del portale (l'e-mail deve essere confermata). Fai il login una volta, conserva il token e usalo in ogni richiesta con l'header Authorization: Bearer <token>. Il token vale a lungo: rifai il login solo se una chiamata risponde 401.

MetodoIndirizzoParametriRisposta
POST/api/loginemail, passwordaccess_token e dati dell'utente; senza access_token le credenziali non sono valide

Inviare una fattura

POST /api/inoltro in multipart/form-data, con il file XML nel campo fileFattura. Il nome del file deve rispettare le regole dello SdI, ad esempio IT01234567890_00001.xml (vedi formato XML e nome file).

<?php
// PHP 7.4+ con l'estensione curl
const API = 'https://api.fattura-elettronica.cloud/api';

function chiamaApi(string $percorso, array $campi = [], ?string $token = null): array
{
    $ch = curl_init(API . $percorso);
    $header = ['Accept: application/json'];
    if ($token) {
        $header[] = 'Authorization: Bearer ' . $token;
    }
    curl_setopt_array($ch, [
        CURLOPT_HTTPHEADER => $header,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 90,
    ]);
    if ($campi) {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $campi); // array = multipart/form-data (POST)
    }
    $risposta = curl_exec($ch);
    $http = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    return [$http, json_decode((string) $risposta, true)];
}

// 1. Login: il token vale a lungo, salvalo e rifai il login solo se ricevi 401
[$http, $login] = chiamaApi('/login', ['email' => 'tua@email.it', 'password' => 'LaTuaPassword']);
$token = $login['access_token'] ?? null;
if (!$token) {
    exit('Login non riuscito: ' . ($login['message'] ?? "HTTP $http"));
}

// 2. Invio della fattura XML allo SdI
$file = new CURLFile(__DIR__ . '/IT01234567890_00001.xml', 'application/xml');
[$http, $esito] = chiamaApi('/inoltro', ['fileFattura' => $file], $token);

if ($http === 200 && ($esito['esito'] ?? '') === 'ok') {
    echo 'Inviata, identificativo SdI: ' . $esito['inoltro']['id_sdi'] . PHP_EOL;
} else {
    echo "Errore $http: " . ($esito['messaggio'] ?? 'risposta non valida') . PHP_EOL;
}

// 3. Elenco delle fatture inviate (esito: 0 in attesa, 1 consegnata, 2 mancata consegna, 3 scartata, ...)
[$http, $elenco] = chiamaApi('/inoltro', [], $token);
foreach ($elenco['inoltri'] ?? [] as $inoltro) {
    echo $inoltro['id_sdi'] . ' ' . $inoltro['esito'] . PHP_EOL;
}

Se lo SdI accetta il file la risposta è 200:

{
  "esito": "ok",
  "messaggio": "Invio effettuato",
  "inoltro": {
    "id": 1532,
    "id_sdi": "8754310921",
    "piva_mittente": "IT01234567890",
    "piva_destinatario": "IT09876543210",
    "data_ora_ricezione_sdi": "2026-11-01 10:15:32",
    "data_ora_ricezione_esito": null,
    "esito": 0
  }
}

Conserva id_sdi: è l'identificativo con cui arriveranno tutte le notifiche di quella fattura. L'accettazione dello SdI vuol dire che il file è stato ricevuto; l'esito vero (consegna o scarto) arriva dopo, con le notifiche.

Stato delle fatture inviate

MetodoIndirizzoRisposta
GET/api/inoltroinoltri: tutte le tue fatture inviate, dalla più recente
GET/api/inoltro/{id}inoltro: una fattura (404 se non è tua)

Il campo esito vale:

ValoreSignificato
0In attesa della risposta dello SdI
1RC – ricevuta di consegna: la fattura è stata consegnata
2MC – mancata consegna: la fattura è nel cassetto fiscale del cliente
3NS – notifica di scarto: la fattura non è valida e va corretta e reinviata
4NE – esito del committente (PA): accettata o rifiutata
5DT – decorrenza termini (PA): nessuna risposta entro 15 giorni
6AT – attestazione di avvenuta trasmissione con impossibilità di recapito (PA)

Il dettaglio di ogni notifica è nella guida alle notifiche SdI. Invece di interrogare queste API, ti conviene ricevere le notifiche sul tuo endpoint.

Ricevere le fatture sul tuo server

Le fatture indirizzate al tuo codice destinatario sono sempre visibili nel portale. Se imposti l'endpoint ricezione fatture e attivi l'inoltro, te le mandiamo anche con una POST application/x-www-form-urlencoded con questi campi:

CampoContenuto
strxmlXML della fattura in base64, già estratto se il fornitore l'ha firmata (.p7m)
xmlFIl file così come è arrivato dallo SdI (.xml o .xml.p7m), in base64
xmlMIl file metadati dello SdI in base64, con IdentificativoSdI e NomeFile
formatoFPR12 (tra privati) o FPA12 (verso la PA)

Il tuo server deve rispondere HTTP 200 con il testo OK. Con qualsiasi altra risposta, o se non risponde, riproviamo in automatico con attese crescenti da 5 minuti a 6 ore, per circa 4 giorni. Per questo la stessa fattura può arrivare più volte: usa IdentificativoSdI per non duplicarla.

<?php
// ricevi-fatture.php - URL da indicare in "Endpoint ricezione fatture" (es. https://tuosito.it/ricevi-fatture.php)
const SEGRETO = 'IL_TUO_SEGRETO';  // pagina Integrazione del portale; lascia '' se non usi la firma

$corpo = file_get_contents('php://input');

// Verifica della firma: X-FE-Firma = HMAC-SHA256 di "timestamp\ncorpo" con il tuo segreto
if (SEGRETO !== '') {
    $timestamp = $_SERVER['HTTP_X_FE_TIMESTAMP'] ?? '';
    $attesa = hash_hmac('sha256', $timestamp . "\n" . $corpo, SEGRETO);
    if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300
            || !hash_equals($attesa, $_SERVER['HTTP_X_FE_FIRMA'] ?? '')) {
        http_response_code(403);
        exit('firma non valida');
    }
}

parse_str($corpo, $dati);
$xmlFattura   = base64_decode($dati['strxml'] ?? ''); // XML della fattura, già estratto dal .p7m
$fileOriginale = base64_decode($dati['xmlF'] ?? '');  // file così come è arrivato dallo SdI (.xml o .xml.p7m)
$metadati     = simplexml_load_string(base64_decode($dati['xmlM'] ?? '')); // file metadati dello SdI
$formato      = $dati['formato'] ?? '';               // FPR12 tra privati, FPA12 verso la PA

$idSdi = $metadati ? (string) $metadati->IdentificativoSdI : '';
if ($idSdi === '' || $xmlFattura === '') {
    http_response_code(400);
    exit('dati mancanti');
}

// Salva la fattura fuori dalla cartella pubblica. Stesso IdentificativoSdI = stessa fattura:
// se arriva di nuovo (ad esempio dopo un tuo errore) sovrascrivi, non duplicare.
$cartella = __DIR__ . '/../fatture_ricevute';
if (!is_dir($cartella)) {
    mkdir($cartella, 0750, true);
}
file_put_contents("$cartella/$idSdi.xml", $xmlFattura);
file_put_contents("$cartella/{$idSdi}_" . basename((string) $metadati->NomeFile), $fileOriginale);

// Leggi i dati che ti servono direttamente dall'XML
$fattura = simplexml_load_string($xmlFattura);
$fornitore = (string) $fattura->FatturaElettronicaHeader->CedentePrestatore->DatiAnagrafici->Anagrafica->Denominazione;
$numero = (string) $fattura->FatturaElettronicaBody->DatiGenerali->DatiGeneraliDocumento->Numero;

echo 'OK'; // rispondi esattamente OK: con qualsiasi altra risposta la fattura viene rimandata più tardi

Ricevere le notifiche dello SdI

Con l'endpoint ricezione notifiche ti inoltriamo ogni notifica appena arriva dallo SdI, nell'ordine in cui è arrivata, con una POST application/x-www-form-urlencoded:

CampoContenuto
tipoNotifica1 RC, 2 MC, 3 NS, 4 NE, 5 DT, 6 AT (gli stessi valori di esito)
esitojsonLa notifica dello SdI convertita in JSON: IdentificativoSdI, NomeFile, DataOraRicezione e i campi specifici (per uno scarto, ListaErrori)

Anche qui va risposto OK, con gli stessi tentativi automatici. Una notifica non viene mai consegnata prima di quelle precedenti della stessa fattura.

<?php
// ricevi-notifiche.php - URL da indicare in "Endpoint ricezione notifiche"
const SEGRETO = 'IL_TUO_SEGRETO';  // pagina Integrazione del portale; lascia '' se non usi la firma

$corpo = file_get_contents('php://input');
if (SEGRETO !== '') { // stessa verifica della firma dell'endpoint fatture
    $ts = $_SERVER['HTTP_X_FE_TIMESTAMP'] ?? '';
    $attesa = hash_hmac('sha256', $ts . "\n" . $corpo, SEGRETO);
    if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300 || !hash_equals($attesa, $_SERVER['HTTP_X_FE_FIRMA'] ?? '')) {
        http_response_code(403);
        exit('firma non valida');
    }
}

parse_str($corpo, $dati);
$tipi = [1 => 'RC', 2 => 'MC', 3 => 'NS', 4 => 'NE', 5 => 'DT', 6 => 'AT'];
$tipo = $tipi[(int) ($dati['tipoNotifica'] ?? 0)] ?? null;
$notifica = json_decode($dati['esitojson'] ?? '', true); // la notifica dello SdI convertita in JSON
if (!$tipo || !$notifica) {
    http_response_code(400);
    exit('dati mancanti');
}

$stati = [
    'RC' => 'consegnata',
    'MC' => 'mancata consegna: disponibile nel cassetto fiscale del cliente',
    'NS' => 'scartata',              // i motivi sono in $notifica['ListaErrori']
    'NE' => 'esito del committente', // solo PA: EsitoCommittente.Esito = EC01 accettata, EC02 rifiutata
    'DT' => 'decorrenza termini',    // solo PA: nessuna risposta entro 15 giorni
    'AT' => 'attestazione di avvenuta trasmissione',
];

// IdentificativoSdI è lo stesso restituito dall'API al momento dell'invio (inoltro.id_sdi).
// La stessa notifica può arrivare due volte: l'aggiornamento deve poter essere ripetuto.
$pdo = new PDO('mysql:host=localhost;dbname=gestionale;charset=utf8mb4', 'utente', 'password');
$pdo->prepare('UPDATE fatture SET stato_sdi = ?, notifica_sdi = ? WHERE id_sdi = ?')
    ->execute([$stati[$tipo], $dati['esitojson'], $notifica['IdentificativoSdI']]);

echo 'OK'; // rispondi esattamente OK: con qualsiasi altra risposta la notifica viene rimandata

Firma delle richieste

Per essere sicuro che fatture e notifiche arrivino davvero da noi, genera il tuo segreto personale nella pagina Integrazione del portale. Da quel momento ogni richiesta ai tuoi endpoint porta due header:

  • X-FE-Timestamp: l'ora dell'invio, in secondi Unix;
  • X-FE-Firma: HMAC-SHA256, in esadecimale minuscolo, di timestamp + "\n" + corpo della richiesta, calcolato con il tuo segreto.

Calcola la firma sul corpo così com'è arrivato, prima di decodificarlo, confrontala in tempo costante e scarta le richieste con un timestamp più vecchio di 5 minuti. Gli esempi qui sopra lo fanno già.

Errori e limiti

CodiceQuando
400File mancante, non XML o non leggibile (campo messaggio)
401Token mancante o non più valido: rifai il login
403E-mail non confermata, nessun pacchetto attivo o fatture del mese esaurite
422Lo SdI ha rifiutato subito il file (ad esempio file vuoto): il motivo è in messaggio
429Troppi tentativi di login in un minuto
503Invii momentaneamente sospesi per manutenzione: riprova più tardi

Il file inviato con le API deve essere XML e pesare al massimo 2 MB; le fatture firmate (.p7m) si caricano dal portale. Il numero di fatture che puoi inviare dipende dal pacchetto; la ricezione è sempre illimitata.

Guide per linguaggio

Le stesse istruzioni, con il codice completo e i dettagli di ogni ambiente:

Prova le API gratis

30 giorni e 20 fatture incluse, poi pacchetti da 5 € al mese.