Guida all'integrazione per gli Ordini regionali
Questa guida è per chi collega il gestionale di un Ordine all'anagrafica nazionale. Il contratto completo, con ogni rotta, campo e codice di errore, si consulta su Riferimento API e si scarica da openapi.yaml (OpenAPI 3.1): il file serve anche a generare un client. La suite di test del servizio confronta il contratto con le rotte e i codici di errore reali. L'SDK ufficiale non c'è ancora.
1. La chiave
Ogni Ordine accede con una o più chiavi personali. Le emette il CNOG dal pannello /admin, nella scheda dell'Ordine, con uno di due permessi: lettura oppure scrittura, che comprende anche la lettura. La chiave completa compare una volta sola, al momento dell'emissione. Il sistema ne conserva solo l'impronta (hash) e non può mostrarla di nuovo: se va persa se ne emette un'altra. La chiave va trattata come una password. Si tiene nella configurazione protetta del gestionale, non nel codice e non nei log.
La chiave ha la forma <prefisso>.<segreto>, cioè 12 e 64 caratteri esadecimali separati da un punto. Le chiavi nuove iniziano anche con prd_ in produzione e con sbx_ in sandbox (§ 8), mentre quelle emesse prima restano valide. Va in ogni richiesta:
Authorization: Bearer 3f9a0c1b2d4e.9b1c…(64 caratteri)
Il pannello dell'Ordine (/ordine) elenca le chiavi senza il segreto e permette di revocarle subito, per esempio se una chiave è compromessa. Una chiave revocata risponde 401 credenziale_revocata.
Conta anche la modalità dell'Ordine, che decide il CNOG. In modalità api l'Ordine scrive tramite l'API. In modalità web aggiorna i dati dal pannello e l'API resta in sola lettura: qualunque scrittura risponde 403 ordine_in_modalita_web, anche con una chiave di scrittura.
La prima chiamata da fare con una chiave nuova:
curl -H "Authorization: Bearer $CHIAVE" https://<servizio>/api/v1/ordini/io
La risposta riporta l'Ordine (id_ordine, denominazione, tipo, modalità) e il permesso della chiave. GET /api/v1 risponde senza chiave e serve solo a vedere se il servizio è raggiungibile.
1.1 Limiti di accesso della chiave
Ogni chiave può avere tre limiti. Li imposta il CNOG nella scheda dell'Ordine; se il CNOG lo abilita, anche gli operatori dell'Ordine possono impostarli sulle proprie chiavi da /ordine/chiavi.
- IP o reti consentiti. Fino a 50 voci, IPv4 o IPv6, singole o CIDR (
203.0.113.10,203.0.113.0/24,2001:db8::/48). Elenco vuoto: la chiave funziona da qualunque IP. Fuori elenco la risposta è403 ip_non_consentito. - Origini consentite per le chiamate dal browser. Fino a 50 voci nella forma
https://dominioohttps://dominio:porta, confrontate esattamente con l'intestazioneOrigin. Solo per queste il servizio risponde con le intestazioni CORS. Un'origine fuori elenco riceve403 origine_non_consentita. Senza elenco non c'è nessuna risposta CORS, quindi il browser non lascia leggere le risposte a pagine di altri siti. - Richieste al minuto. La finestra è fissa, di un minuto, e vale per chiave. Oltre il limite la risposta è
429 troppe_richiesteconRetry-After, cioè i secondi da aspettare. Le chiamate rifiutate per IP o per origine non consumano il limite.
Il vincolo sull'origine vale solo per i browser. Da un server l'intestazione Origin si può falsificare o omettere: una chiamata senza Origin non viene controllata su questo elenco. Per i gestionali il controllo vero è l'IP. Il servizio non accetta nomi a dominio tra gli IP, perché non fa una risoluzione DNS a ogni richiesta.
Il preflight (OPTIONS) del browser arriva senza chiave: riceve 204 se l'origine è nell'elenco di almeno una chiave attiva. La richiesta vera è poi controllata sull'elenco della chiave che usa.
La pagina «Prova API» dei pannelli fa chiamate dal browser: una chiave con un elenco di IP funziona solo dall'IP di chi la prova, e una chiave con un elenco di origini deve avere anche l'indirizzo del pannello per le operazioni di scrittura.
I rifiuti finiscono nel registro degli accessi con il motivo.
2. Convenzioni
Tutte le rotte iniziano con /api/v1. Il corpo delle richieste è un oggetto JSON, al massimo 1 MiB. Le risposte sono JSON in UTF-8.
Le date si scrivono AAAA-MM-GG. Le date di fatti già avvenuti (iscrizioni, eventi, richieste) vanno dal 1900 a oggi, secondo il fuso di Roma. Solo data_inizio e data_fine dei provvedimenti possono essere future. Gli istanti in risposta, come aggiornato_il, sono in UTC nel formato AAAA-MM-GG hh:mm:ss.
Un campo che il servizio non conosce non viene ignorato: la richiesta risponde 400 dati_non_validi con "dettagli": {"campo": "campo sconosciuto"}. Così un errore di battitura non passa inosservato. In modifica (PATCH) cambiano solo i campi inviati, e null svuota un campo facoltativo.
Le risposte contengono solo i campi che l'Ordine può vedere secondo la matrice «chi vede cosa», che il CNOG può cambiare. Un campo assente vuol dire «non visibile», non «vuoto». Il gestionale non deve quindi cancellare un dato locale solo perché manca nella risposta. Di un iscritto di un altro Ordine, per esempio, si vedono solo identificativo, cognome e nome.
Ogni chiamata con chiave, riuscita o no, resta nel registro degli accessi dell'Ordine, consultabile dal pannello. Ogni scrittura resta anche nel registro delle modifiche, con l'Ordine e la chiave che l'hanno fatta.
3. Primo invio: una nuova iscrizione
L'identificativo nazionale di una persona (id_persona) non dipende dal codice fiscale, perché il codice fiscale può cambiare o mancare. Prima di creare una persona bisogna quindi controllare che non esista già:
GET /api/v1/persone/ricerca?codice_fiscale=RSSMRA80A41H501U
GET /api/v1/persone/ricerca?cognome=Rossi&nome=Maria&data_nascita=1980-01-01
La ricerca è esatta. Restituisce al massimo 20 persone, con identificativo, nominativo e Ordine e stato delle posizioni aperte, senza recapiti.
Se la persona non c'è, si crea. Conviene mandare sempre riferimento_esterno, cioè il codice della persona nel gestionale: rende la chiamata ripetibile (vedi il § 6).
POST /api/v1/persone
{
"codice_fiscale": "RSSMRA80A41H501U",
"cognome": "Rossi",
"nome": "Maria",
"data_nascita": "1980-01-01",
"pec": "maria.rossi@pec.it",
"riferimento_esterno": "ANAG-000123"
}
→ 201 {"id_persona": "0192f3a4-…", …}
Poi si crea la posizione, cioè l'iscrizione in un elenco dell'Ordine. Il servizio la crea in stato «attivo» e scrive da solo l'evento ISCRIZIONE nel diario. Gli elenchi disponibili sono in GET /api/v1/elenchi.
POST /api/v1/posizioni
{
"id_persona": "0192f3a4-…",
"id_elenco": "pubblicisti",
"data_iscrizione": "2026-10-30",
"riferimento_esterno": "POS-000456"
}
→ 201
{
"id_posizione": "…",
"stato": "attivo",
"anzianita": {
"giorni": 0,
"anni": 0,
"periodi": […]
},
…
}
Un Ordine regionale iscrive solo presso di sé, quindi id_ordine si può omettere. Il CNOG invece deve indicarlo.
Codice fiscale già presente. Se il codice fiscale appartiene già a un'altra persona, la creazione (e anche la modifica) risponde 409 codice_fiscale_esistente, con dettagli.id_persona. Ci sono due casi:
- è la stessa persona: non si crea un doppione, si usa quell'
id_persona(per esempio per aprire la posizione); - è davvero un'altra persona, per esempio per un'omocodia: si ripete la chiamata con
"persona_diversa": true. La persona nasce senza codice fiscale e il codice resta incodice_fiscale_in_verifica. Si apre una segnalazione (GET /api/v1/segnalazioni) che decide il CNOG: assegna il codice corretto oppure unisce le due persone.
Persona unita. Quando il CNOG unisce due persone, il doppione risponde 410 persona_unita con dettagli.id_persona_principale. Il gestionale deve sostituire l'identificativo vecchio con quello e ripetere la chiamata. Le posizioni, i praticantati e i riferimenti esterni passano già alla persona principale.
Iscritto di un altro Ordine. Se la persona ha già una posizione aperta in un altro Ordine, la nuova posizione risponde 409 posizione_aperta_altrove: serve un trasferimento (§ 4.2). Una seconda posizione aperta nello stesso elenco risponde 409 posizione_duplicata.
4. Flussi tipici
4.1 Eventi: il solo modo di cambiare lo stato
Lo stato di una posizione (attivo, sospeso, cancellato, trasferito) non si scrive mai direttamente: è il risultato del diario degli eventi. Ogni cambiamento è un evento con la sua causale:
POST /api/v1/posizioni/{id_posizione}/eventi
{
"id_causale": "CANC_INATT",
"data_efficacia": "2026-10-30",
"riferimento_esterno": "EV-7781"
}
→ 201
{
"evento": {
…,
"stato_prima": "attivo",
"stato_dopo": "cancellato"
},
"posizione": {…}
}
Le causali sono comuni a tutti gli Ordini (GET /api/v1/causali). Il campo effetto dice cosa fa ciascuna e da quale stato si può applicare:
| effetto | da | esempi |
|---|---|---|
| sospende | attivo | SOSPESO_06MESI, SOSPESO_ART39, SOSPESO_PEC |
| riattiva | sospeso | RIATT, REVOCA_SOSPENSIONE |
| cancella | attivo, sospeso | CANC_INATT, SANZIONATO_RADIAZIONE, DECESSO |
| annota | attivo, sospeso | SANZIONATO_AVVERTIMENTO, SANZIONATO_CENSURA |
| reiscrive | cancellato | REISCR |
| cambia_elenco | attivo | PASS_ELENCO, con id_elenco_nuovo |
ISCRIZIONE e TRASF non si mandano come eventi (422 causale_non_ammessa). L'iscrizione nasce con POST /posizioni e il trasferimento con la sua pratica.
Gli eventi vanno in ordine cronologico: una data precedente all'ultimo evento della posizione risponde 409 data_non_coerente, con dettagli.ultima_data. Una causale che non vale per lo stato attuale risponde 409 transizione_non_ammessa. La reiscrizione dopo una chiusura definitiva (DECESSO) risponde 409 reiscrizione_esclusa. Quella dopo una radiazione, prima dei 5 anni, risponde 409 reiscrizione_non_ancora_ammessa con dettagli.ammessa_dal. L'evento DECESSO chiude anche le altre posizioni aperte della persona e la segna come deceduta.
L'anzianità di iscrizione la calcola il servizio dagli eventi e arriva in ogni posizione (anzianita.giorni, anni, periodi). I periodi tra una cancellazione e la reiscrizione non contano; una sospensione invece non interrompe l'anzianità.
Moroso e date di iscrizione, che non sono dati di stato, si correggono con PATCH /api/v1/posizioni/{id}.
4.2 Trasferimento tra Ordini
La pratica coinvolge due Ordini, e ciascuno fa la sua parte. La vedono solo loro due e il CNOG.
- L'Ordine d'origine avvia la pratica:
POST /api/v1/trasferimenticonid_posizione_origine,id_ordine_destinazioneedata_richiesta. La posizione deve essere attiva, e per ogni posizione può esserci una sola pratica aperta. - L'Ordine d'origine dà il nulla osta:
POST /api/v1/trasferimenti/{id}/nulla-ostacon{"esito": "favorevole" | "negato", "data": …}. Con esito negato la pratica diventa «respinto» e si chiude lì. - L'Ordine di destinazione completa:
POST /api/v1/trasferimenti/{id}/completacon{"data": …}. Cosa succede dipende da un'impostazione unica decisa dal CNOG:- automatica: la pratica diventa «completato», la posizione d'origine diventa «trasferita» (evento TRASF) e nella destinazione si apre una nuova posizione attiva che conserva l'anzianità (
id_posizione_destinazione); - con conferma: la pratica diventa «da_confermare» e la persona resta dell'Ordine d'origine finché questo non chiama
POST /api/v1/trasferimenti/{id}/conferma. Solo allora si chiude l'origine e si apre la destinazione.
- automatica: la pratica diventa «completato», la posizione d'origine diventa «trasferita» (evento TRASF) e nella destinazione si apre una nuova posizione attiva che conserva l'anzianità (
- Finché la pratica è aperta (richiesto, nulla_osta, da_confermare), ciascuno dei due Ordini, o il CNOG, può annullarla:
POST /api/v1/trasferimenti/{id}/annulla.
Per sapere quali pratiche lo riguardano, un Ordine usa GET /api/v1/trasferimenti?ruolo=destinazione&stato=nulla_osta (quelle da completare) oppure ?ruolo=origine&stato=richiesto (quelle in attesa del suo nulla osta).
4.3 Provvedimento disciplinare
Il provvedimento si registra in due passi. Registrarlo non cambia lo stato della posizione: l'effetto arriva con l'evento che lo cita.
POST /api/v1/provvedimenti
{
"id_posizione": "…",
"tipo": "sospensione",
"organo": "consiglio_disciplina_territoriale",
"data_riunione": "2026-10-20",
"definitivo": true,
"data_inizio": "2026-10-25",
"data_fine": "2027-04-24",
"riferimento_esterno": "DISC-2026-14"
}
→ 201
{
"id_provvedimento": "…",
"stato": "confermato",
"pubblicazione": {
"pubblicabile": true,
"dal": "2026-10-20",
"al": "2029-04-24",
"visibile_oggi": true
},
…
}
POST /api/v1/posizioni/{id_posizione}/eventi
{
"id_causale": "SOSPESO_06MESI",
"data_efficacia": "2026-10-25",
"id_provvedimento": "…"
}
Le sospensioni (sospensione, sospensione_cautelare, sospensione_pec) richiedono data_inizio, e quella disciplinare anche data_fine. Gli altri tipi non le ammettono. Il campo pubblicazione indica il periodo in cui l'annotazione è visibile nell'Albo. Il servizio lo calcola dalle regole del CNOG (per esempio: avvertimento 6 mesi, censura 1 anno, sospensione 2 anni dalla fine), e vale solo per i provvedimenti definitivi e confermati.
A fine sospensione l'Ordine registra l'evento RIATT. Se invece il provvedimento viene revocato (POST /api/v1/provvedimenti/{id}/revoca con {"data": …}) e la posizione è sospesa proprio per quel provvedimento, il servizio la riattiva da solo con l'evento REVOCA_SOSPENSIONE.
4.4 Praticantato, titoli, fatti della persona
Il praticantato (POST /api/v1/praticantati) è un processo legato alla persona e a una testata (GET/POST /api/v1/testate), non a una posizione. Una persona ne può avere uno solo in corso, sempre nello stesso Ordine dell'eventuale posizione aperta. Si chiude con PATCH /api/v1/praticantati/{id}, indicando stato («concluso» o «interrotto») e data_fine. Una volta chiuso non si riapre.
I titoli (/persone/{id}/titoli, per esempio l'idoneità professionale) e i fatti della persona (/persone/{id}/eventi: cambio di residenza, variazione dei dati anagrafici, correzione del codice fiscale) li leggono e li scrivono solo l'Ordine titolare e il CNOG.
4.5 Restare allineati
Per scaricare le variazioni si leggono GET /api/v1/persone e GET /api/v1/posizioni, ordinati per aggiornato_il, con aggiornati_dal uguale all'ultimo aggiornato_il ricevuto, riscritto in formato ISO: 2026-10-30 08:15:00 diventa 2026-10-30T08:15:00Z. Il filtro include l'istante indicato, quindi l'ultima riga già vista torna una seconda volta: va riconosciuta per id_persona o id_posizione.
Ogni elenco è paginato con pagina (da 1) e per_pagina (da 1 a 100, 50 se non indicato). Risponde con {"dati": […], "pagina", "per_pagina", "totale"}. Si va avanti finché pagina × per_pagina non raggiunge totale. Con ?riferimento_esterno=… si ritrova la risorsa a partire dal proprio codice.
5. Errori
Ogni errore ha la stessa forma:
{
"errore": {
"codice": "dati_non_validi",
"messaggio": "Dati non validi",
"dettagli": {
"data_nascita": "deve essere una data valida AAAA-MM-GG"
}
}
}
Il programma deve decidere in base al codice, che è stabile. Il messaggio è testo per le persone e può cambiare. I dettagli, quando ci sono, danno il campo sbagliato o l'identificativo che serve per proseguire.
| Stato | Codici | Cosa fare |
|---|---|---|
| 400 | dati_non_validi, json_non_valido, json_non_oggetto, criteri_insufficienti, richiesta_non_valida |
Correggere la richiesta; i dettagli dicono quale campo e perché. Non ripetere uguale. |
| 401 | non_autenticato, credenziale_non_valida, credenziale_revocata, chiave_altro_ambiente |
Controllare la chiave, o chiederne una nuova al CNOG. chiave_altro_ambiente: la chiave è dell'altro ambiente (§ 8). |
| 403 | ip_non_consentito, origine_non_consentita, permesso_insufficiente, ordine_in_modalita_web, ordine_non_attivo, fuori_ambito |
Operazione non permessa a questa chiave o a questo Ordine (es. dati di un altro Ordine, operazioni riservate al CNOG). Non ripetere. |
| 404 | non_trovato, rotta_sconosciuta |
L'identificativo o il percorso non esistono. |
| 405 | metodo_non_ammesso |
Metodo HTTP sbagliato; l'header Allow elenca quelli giusti. |
| 409 | codice_fiscale_esistente, persona_unita, posizione_aperta_altrove, posizione_duplicata, transizione_non_ammessa, data_non_coerente, stato_non_ammesso, trasferimento_in_corso, praticantato_in_corso, persona_deceduta, reiscrizione_esclusa, reiscrizione_non_ancora_ammessa, fusione_non_valida, testata_esistente, riferimento_duplicato |
La richiesta è corretta ma contrasta con lo stato dei dati. Rileggere la risorsa e decidere; spesso i dettagli dicono come proseguire. |
| 410 | persona_unita |
Usare dettagli.id_persona_principale. |
| 413 | corpo_troppo_grande |
Il corpo supera 1 MiB. |
| 422 | riferimento_inesistente, causale_inesistente, causale_non_ammessa, elenco_non_valido, campo_non_ammesso, provvedimento_non_valido, praticantato_non_valido, fusione_non_valida |
Un identificativo citato nel corpo non esiste o non si può usare; i dettagli dicono quale. |
| 429 | troppe_richieste |
Superato il limite al minuto della chiave: aspettare i secondi indicati da Retry-After. |
| 500 | errore_interno |
Problema del servizio. Si può ripetere più tardi (vedi § 6); se persiste, segnalarlo al CNOG con ora e percorso. |
L'elenco completo dei codici, rotta per rotta, è nel Riferimento API.
6. Ripetere una chiamata senza danni
Se la rete cade o arriva un errore 500, il gestionale non sa se la scrittura è avvenuta. Le regole per ripetere una chiamata sono queste.
- Letture (
GET): si possono sempre ripetere. - Modifiche (
PATCH): si possono ripetere con lo stesso corpo, perché il risultato è lo stesso. - Creazioni (
POSTsu persone, posizioni, eventi, provvedimenti, trasferimenti, praticantati): vanno fatte sempre conriferimento_esterno. Se quel codice è già associato a una risorsa dell'Ordine dello stesso tipo, la risposta è 200 (non 201) con la risorsa già creata, e non si crea nulla. Il servizio però non confronta i dati: una seconda chiamata con lo stesso riferimento e dati diversi restituisce comunque la risorsa originale, senza modificarla. Il riferimento vale per tipo di risorsa: lo stesso codice si può usare per una persona e per una posizione, ma non per due persone. Se due creazioni con lo stesso riferimento arrivano insieme, una delle due riceve409 riferimento_duplicato: basta ripeterla e risponderà 200. Testate, titoli e fatti della persona non hannoriferimento_esterno. Per le testate, una ripetizione risponde409 testata_esistentecondettagli.id_testata. Titoli e fatti della persona vanno invece controllati con una lettura prima di ripetere. - Azioni sulle pratiche (nulla osta, completa, conferma, annulla, revoca, risolvi, unisci): una volta eseguite cambiano stato. Ripeterle risponde
409 stato_non_ammesso, oppure410 persona_unitaper l'unione. Se si riceve questo errore dopo un tentativo dall'esito incerto, si rilegge la pratica: se è già nello stato voluto, l'operazione era riuscita. - Le risposte 4xx diverse da 409 non cambiano con la ripetizione: prima va corretta la richiesta.
Ogni scrittura avviene in una sola transazione: una risposta 4xx vuol dire che non è cambiato niente. Dopo un 500 o una connessione interrotta l'esito è incerto, e si applicano le regole qui sopra.
7. Limiti noti
L'SDK ufficiale non c'è ancora: la scelta dei linguaggi è aperta. Nel frattempo openapi.yaml permette di generare un client con gli strumenti OpenAPI.
Gli elenchi di trasferimenti, praticantati e segnalazioni accettano il parametro aggiornati_dal ma oggi non lo applicano, e la documentazione OpenAPI non lo riporta per queste rotte.
8. Dalla sandbox alla produzione
La sandbox è un'installazione a parte dell'anagrafica, con lo stesso codice e le stesse regole della produzione ma con database proprio. Serve a provare tutte le chiamate, comprese le scritture, senza toccare l'anagrafe reale.
| Sandbox | Produzione | |
|---|---|---|
| Indirizzo | quello della sandbox comunicato dal CNOG, più /api/v1 |
quello di produzione comunicato dal CNOG, più /api/v1 |
| Chiavi | iniziano con sbx_ |
iniziano con prd_; quelle emesse prima non hanno inizio e restano valide |
| Dati | fittizi, azzerati ogni notte | reali |
Intestazione X-Oprs-Ambiente |
sandbox |
produzione |
| Pannelli e documentazione | striscia che segnala l'ambiente di prova | nessuna striscia |
Le chiavi non valgono da un ambiente all'altro. Una chiave sbx_ usata in produzione, o una prd_ usata in sandbox, risponde 401 chiave_altro_ambiente. Per il passaggio servono quindi una chiave di produzione, emessa dal CNOG, e il nuovo indirizzo, entrambi nella configurazione del gestionale.
Ogni notte la sandbox torna ai soli dati di esempio. Si perde tutto ciò che è stato inserito, comprese le chiavi e gli accessi degli operatori: la mattina dopo serve una chiave nuova, e gli identificativi salvati dal gestionale non valgono più. Il test non deve dipendere da dati creati il giorno prima. Durante l'azzeramento il servizio può rispondere con errori per qualche istante.
Prima di passare in produzione:
- Controllare che tutti gli indirizzi e tutte le chiavi vengano dalla configurazione del gestionale, senza valori scritti nel codice.
- Fare la prima chiamata a
GET /api/v1/ordini/ioe controllareX-Oprs-Ambiente: produzionee l'Ordine riportato. - Non copiare in produzione i dati fittizi della sandbox, né gli identificativi che ha assegnato.