API codice fiscale
Non esiste un’API pubblica ufficiale del codice fiscale. Esiste un servizio web dell’Agenzia delle Entrate, fatto per essere usato da una persona con un browser. Per un software la risposta non è cercare un endpoint che non c’è, ma capire quale metà del problema si risolve in locale, che è la metà che conta.
Esiste un’API ufficiale?
No, e vale la pena dirlo chiaramente perché la ricerca «api codice fiscale» porta spesso a servizi di terze parti presentati come se fossero istituzionali. Il servizio pubblico di controllo del codice fiscale in Agenzia delle Entrate è una interfaccia per persone: confronta un codice con dati anagrafici che qualcuno digita, e restituisce una corrispondenza.
Il confine fra i due controlli
Prima di scrivere codice conviene sapere a quali domande si può rispondere.
| Domanda | Dove si risponde | Costo |
|---|---|---|
| Il codice è ben formato? | nel tuo codice, in locale | nessuno |
| Il carattere di controllo torna? | nel tuo codice, in locale | nessuno |
| La persona esiste in Anagrafe tributaria? | solo il servizio dell’Agenzia delle Entrate | interazione umana, nessun endpoint pubblico |
| A chi appartiene il codice? | nessuno, per progetto | non è una domanda a cui si possa rispondere |
Le prime due righe coprono quasi tutti i casi d’uso reali di un form o di una importazione, e non richiedono rete.
Validazione in PHP
Una classe autosufficiente, senza dipendenze e senza chiamate di rete. Le altre lingue hanno la loro pagina: JavaScript, Python e la regex di validazione.
<?php
declare(strict_types=1);
/**
* Validazione formale del codice fiscale.
* Nessuna dipendenza, nessuna chiamata di rete: tutto ciò che serve
* per il carattere di controllo sta nelle due tabelle qui sotto.
*/
final class CodiceFiscale
{
/** Valori delle posizioni dispari, contate a partire da 1. */
private const DISPARI = [
'0' => 1, '1' => 0, '2' => 5, '3' => 7, '4' => 9,
'5' => 13, '6' => 15, '7' => 17, '8' => 19, '9' => 21,
'A' => 1, 'B' => 0, 'C' => 5, 'D' => 7, 'E' => 9,
'F' => 13, 'G' => 15, 'H' => 17, 'I' => 19, 'J' => 21,
'K' => 2, 'L' => 4, 'M' => 18, 'N' => 20, 'O' => 11,
'P' => 3, 'Q' => 6, 'R' => 8, 'S' => 12, 'T' => 14,
'U' => 16, 'V' => 10, 'W' => 22, 'X' => 25, 'Y' => 24,
'Z' => 23,
];
/** Valori delle posizioni pari: cifra per le cifre, indice alfabetico per le lettere. */
private const PARI = [
'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4,
'5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4,
'F' => 5, 'G' => 6, 'H' => 7, 'I' => 8, 'J' => 9,
'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, 'O' => 14,
'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19,
'U' => 20, 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24,
'Z' => 25,
];
public static function normalizza(string $cf): string
{
return strtoupper(preg_replace('/\s+/', '', $cf) ?? '');
}
/**
* Forma attesa, omocodia compresa: nelle posizioni numeriche le cifre
* possono essere sostituite dalle lettere L, M, N, P, Q, R, S, T, U, V.
*/
public static function formaValida(string $cf): bool
{
$re = '/^[A-Z]{6}' // cognome e nome
. '[0-9LMNPQRSTUV]{2}' // anno
. '[ABCDEHLMPRST]' // mese
. '[0-9LMNPQRSTUV]{2}' // giorno, con il 40 per il sesso femminile
. '[A-Z]' // prima lettera del codice catastale
. '[0-9LMNPQRSTUV]{3}' // resto del codice catastale
. '[A-Z]$/'; // carattere di controllo
return (bool) preg_match($re, $cf);
}
/** Il sedicesimo carattere, calcolato sui quindici precedenti. */
public static function carattereDiControllo(string $primi15): string
{
$somma = 0;
for ($i = 0; $i < 15; $i++) {
$c = $primi15[$i];
// Indice 0 in base 0 e' la prima posizione in base 1, quindi dispari.
$somma += ($i % 2 === 0) ? self::DISPARI[$c] : self::PARI[$c];
}
return chr(ord('A') + ($somma % 26));
}
public static function valido(string $cf): bool
{
$cf = self::normalizza($cf);
if (!self::formaValida($cf)) {
return false;
}
return self::carattereDiControllo(substr($cf, 0, 15)) === $cf[15];
}
}
L’uso, e i limiti:
// Il carattere di controllo intercetta la trascrizione sbagliata,
// non l'esistenza della persona.
var_dump(CodiceFiscale::valido('RSSMRA85T10A562S')); // bool(true)
var_dump(CodiceFiscale::valido('RSSMRA85T10A562A')); // bool(false), controllo errato
var_dump(CodiceFiscale::valido('RSSMRA85T10A562')); // bool(false), quindici caratteri
Se ti serve un endpoint interno
Incapsulare la validazione dietro un servizio interno è ragionevole quando più applicazioni devono condividere la stessa regola. Due accortezze. La prima: non registrare i codici ricevuti, perché sono dati personali e un log di validazione diventa in fretta un archivio che nessuno aveva deciso di creare. La seconda: restituire il motivo del rifiuto, distinguendo la forma sbagliata dal carattere di controllo errato, perché sono errori diversi e portano a correzioni diverse.
I dati di test
I codici delle persone vere non vanno in una suite di test. Servono codici sintetici, formalmente validi e riferiti a nessuno, che si ottengono dal generatore per test e sviluppo oppure dalla pagina sul codice fiscale di prova.