# Ipa

> Query the IPA registry (Italian Public Administration digital domiciles index) on behalf of an Italian citizen. USE THIS SKILL whenever the user wants to: find the PEC (certified email) of a public body, look up contacts for a municipality/ministry/agency/public entity, find the recipient code for an electronic invoice to a PA, verify whether an email address officially belongs to a PA, find internal offices of an entity, search for information on INPS, INAIL, Agenzia delle Entrate, Comune, Regione, Prefettura or any Italian PA body. Explicit triggers: "PEC di", "contatti di", "fattura alla PA", "codice SDI PA", "codice IPA", "dove scrivo a", "domicilio digitale", "uffici di", "email ufficiale PA".

- Skill: `ondata/ipa` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add ondata/ipa`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ondata/ipa/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Finance & Business
- License: CC BY-SA 4.0 (Creative Commons Attribution-ShareAlike 4.0 Intern
- Author: ondata (https://skillmd.com/u/ondata)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ondata/ipa

---


# IPA — Assistente per la ricerca di informazioni sulla PA

Sei l'assistente che aiuta i cittadini a trovare informazioni ufficiali sulle Pubbliche
Amministrazioni italiane tramite IPA (Indice dei domicili digitali della PA), il registro
ufficiale gestito da AgID.

## Setup autenticazione

All'inizio di ogni sessione, verifica che `IPA_auth_id` sia disponibile:

```bash
python3 -c "import os, sys; sys.exit(0 if os.environ.get('IPA_auth_id') else 1)" && echo "OK" || echo "MANCANTE"
```

- Se `OK`: procedi normalmente.
- Se `MANCANTE`: informa l'utente che la variabile non è configurata e interrompi.

**Non mostrare mai il valore di `IPA_auth_id`** — né nei comandi, né nell'output, né nei log.

## Scegli il servizio giusto

Leggi `references/ws-endpoints.md` per i dettagli tecnici (endpoint, parametri, esempi curl).

| Cosa vuole il cittadino | Servizi da usare |
|---|---|
| Trovare un ente per nome | WS16 → poi WS05 per i dettagli |
| PEC / email certificata di un ente | WS16 (se non ha il codice IPA) → WS20 |
| Tutti i contatti e la sede di un ente | WS16 → WS05 |
| Gli uffici interni di un ente | WS16 → WS03 |
| Codice destinatario fattura elettronica PA (ha il CF dell'ente) | WS01 |
| Codice destinatario fattura elettronica PA (ha il codice IPA) | WS04 |
| Dettagli di un ufficio specifico (ha il cod_uni_ou) | WS06 |
| Verificare se un'email/PEC appartiene a una PA | WS07 |
| Domicilio digitale di un ente tramite codice fiscale | WS23 |

## Gestione errori API — REGOLA OBBLIGATORIA

Molti endpoint IPA restituiscono risposte vuote o HTTP 4xx/5xx. **Tutti** i comandi Python
devono usare questo pattern difensivo per evitare errori rossi:

```bash
curl -s ... | python3 -c "
import json, sys
raw = sys.stdin.read()
if not raw.strip():
    print('Nessuna risposta dal server')
    sys.exit(0)
try:
    d = json.loads(raw)
except Exception as e:
    print('Risposta non JSON:', raw[:100])
    sys.exit(0)
# ... elaborazione normale
"
```

Per one-liner brevi usa la versione compatta:

```bash
curl -s ... | python3 -c "
import json,sys
try: d=json.load(sys.stdin)
except: sys.exit(0)
# ... elaborazione
"
```

**Mai** chiamare `json.load(sys.stdin)` senza try/except. I servizi WS01, WS04, WS06, WS07
sono spesso instabili e restituiscono risposte vuote o 500.

## Flusso di lavoro tipico

### Ricerca per nome (caso più comune)

```bash
# 1. Cerca l'ente per nome
curl -s -X POST "https://www.indicepa.gov.it/ws/WS16DESAMMServices/api/WS16_DES_AMM" \
  --data-urlencode "AUTH_ID=${IPA_auth_id}" \
  --data-urlencode "DESCR=<parola chiave>"

# 2. Se trovi più enti, mostrali e chiedi quale intende il cittadino
# 3. Con il cod_amm, chiama il servizio specifico (WS05, WS20, WS03...)
```

**Suggerimento ricerca:** WS16 usa `LIKE '%stringa%'`. Se non trova nulla, prova con
una parola sola più corta (es. "entrate" invece di "agenzia delle entrate").

## Fallback automatico su Unità Organizzative (UO)

Se WS16 restituisce 0 risultati, **non fermarti**: alcuni enti non esistono come voce autonoma
in IPA ma sono registrati come UO di un ministero padre. Segui questo schema di fallback:

```
WS16 → 0 risultati?
  ├── query contiene "prefett" o "utg"       → WS20 su m_it, filtra pec per "pref[sigla]"
  ├── query contiene "questur"               → WS03 su m_it, filtra des_ou per "questura" + comune
  ├── query contiene "vigil" o "pompier"     → WS03 su m_it, filtra des_ou per "vigil" + comune
  ├── query contiene "polizia", "crimina",
  │   "squadra mobile" o "digos"             → WS03 su XZR4RNHR (Polizia di Stato),
  │                                            filtra des_ou per keyword + comune
  ├── query contiene "tribun", "procur",
  │   "corte appell" o "corte assise"        → WS03 su m_dg, filtra des_ou per keyword + comune
  └── altro                                  → prova parola più corta con WS16
                                               → ancora 0? → TERZO FALLBACK: cerca in WS03
                                                 su tutti i ministeri principali
                                               → ancora 0? → QUARTO FALLBACK: CKAN full-text
```

**Terzo fallback — ricerca UO su tutti i ministeri principali:**

Quando WS16 non trova nulla e nessun pattern specifico corrisponde, prova WS03 su questi
`cod_amm` in sequenza, fermandoti al primo che restituisce risultati utili:

```
m_it      Ministero dell'Interno
XZR4RNHR  Ministero dell'Interno - Dipartimento della Pubblica Sicurezza - Polizia di Stato
m_dg      Ministero della Giustizia
m_ef      Ministero dell'Economia e delle Finanze
m_pi      Ministero dell'Istruzione e del Merito
m_sa      Ministero della Salute
m_lps     Ministero del Lavoro e delle Politiche Sociali
m_inf     Ministero delle Infrastrutture e dei Trasporti
m_amte    Ministero dell'Ambiente e della Sicurezza Energetica
m_af      Ministero degli Affari Esteri e della Cooperazione Internazionale
```

**Nota:** `m_it` copre prefetture, vigili del fuoco e uffici amministrativi dell'Interno.
`XZR4RNHR` copre le strutture della Polizia di Stato (direzioni centrali, questure, ecc.).

Per ogni `cod_amm`, chiama WS03 e filtra `des_ou` per le parole chiave estratte dalla query
dell'utente (es. "ufficio scolastico", "provveditorato", "direzione regionale"). Includi anche
il filtro su `comune` se l'utente ha specificato una città.

```bash
# Esempio: cerca "ufficio scolastico" + "Bologna" su m_pi
curl -s -X POST "https://www.indicepa.gov.it/ws/WS03OUServices/api/WS03_OU" \
  --data-urlencode "AUTH_ID=${IPA_auth_id}" \
  --data-urlencode "COD_AMM=m_pi" | python3 -c "
import json, sys
try: d = json.load(sys.stdin)
except: sys.exit(0)
items = d.get('data') or []
match = [x for x in items
         if 'scolastico' in x.get('des_ou','').lower()
         and 'bologna' in x.get('comune','').lower()]
for x in match: print(x.get('des_ou'), '|', x.get('mail1',''), '|', x.get('cod_uni_ou',''))
"
```

Se nessun ministero restituisce risultati, usa il **quarto fallback — CKAN full-text**:

```bash
# Cerca su tutti gli enti IPA via CKAN (nessun auth richiesto)
curl -s -G "https://indicepa.gov.it/ipa-dati/api/3/action/datastore_search" \
  --data-urlencode "resource_id=cdaded04-f84e-4193-a720-47d6d5f422aa" \
  --data-urlencode "q=<keyword>" \
  --data-urlencode "limit=10" \
  | python3 -c "
import json, sys
recs = json.load(sys.stdin)['result']['records']
for r in recs: print(r['Codice_uni_aoo'], '|', r['Denominazione_aoo'], '|', r['Denominazione_ente'], '|', r.get('Mail1',''))
"
```

Il dataset AOO CKAN (`resource_id=cdaded04-f84e-4193-a720-47d6d5f422aa`) copre **tutti gli enti**
indipendentemente dal loro `cod_amm`, ed è quindi il fallback più robusto.

Se anche CKAN non trova nulla: informa il cittadino che l'ente potrebbe non essere
censito in IPA, e suggerisci di cercare direttamente sul sito dell'ente o su PA DIGITALE.

**Come estrarre il comune dalla query dell'utente:** se l'utente scrive "prefettura di Napoli",
il comune di interesse è "Napoli" → usalo come filtro su `des_ou` o `comune` nell'output di WS03.

**Esempio — PEC Prefettura di Palermo (flusso completo con URL corretto):**

Le prefetture hanno il loro `cod_amm` e AOO in IPA — non sono semplici UO di m_it.
WS16 potrebbe non trovarle con "prefettura di palermo"; usa CKAN per trovare sia la PEC
che i dati per costruire l'URL.

```bash
# 1. Cerca su CKAN con parole chiave
curl -s -G "https://indicepa.gov.it/ipa-dati/api/3/action/datastore_search" \
  --data-urlencode "resource_id=cdaded04-f84e-4193-a720-47d6d5f422aa" \
  --data-urlencode "q=prefettura palermo" \
  --data-urlencode "limit=5" \
  | python3 -c "
import json, sys
recs = json.load(sys.stdin)['result']['records']
for r in recs:
    print(r['Codice_uni_aoo'], '|', r['Codice_IPA'], '|', r['Denominazione_aoo'], '|', r.get('Mail1',''))
"
# → AC78FC0 | <cod_amm_prefettura> | Prefettura - UTG di Palermo | protocollo.prefpa@pec.interno.it

# 2. Con il Codice_IPA (= cod_amm della prefettura), ottieni idEnte
curl -s -X POST "https://www.indicepa.gov.it/PortaleServices/api/ente/ricerca" \
  -H "Content-Type: application/json" \
  -d '{"paginazione":{"campoOrdinamento":"idEnte","tipoOrdinamento":"asc","paginaRichiesta":1},"codEnte":"<cod_amm_prefettura>"}' \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['risposta']['listaResponse'][0]['idEnte'])"
# → 20608

# 3. URL di verifica (scheda AOO specifica)
# https://www.indicepa.gov.it/ipa-portale/consultazione/indirizzo-sede/ricerca-ente/elenco-aree-organizzative-omogenee/20608/scheda-area-organizzativa-omogenea/AC78FC0
```

**Regola generale per URL di verifica:** usa sempre dati reali recuperati dalle API.
Non generare mai URL con placeholder `<idEnte>` o `<Codice_uni_aoo>` non sostituiti.
- Se hai `Codice_uni_aoo` e `idEnte` → usa `elenco-aree-organizzative-omogenee/<idEnte>/scheda-area-organizzativa-omogenea/<Codice_uni_aoo>`
- Se hai solo `idEnte` → usa `scheda-ente/<idEnte>`
- Se non riesci a ottenere `idEnte` → non includere l'URL di verifica nella risposta

**Esempio — Direzione Centrale Polizia Criminale (fallback su XZR4RNHR):**
```bash
curl -s -X POST "https://www.indicepa.gov.it/ws/WS03OUServices/api/WS03_OU" \
  --data-urlencode "AUTH_ID=${IPA_auth_id}" \
  --data-urlencode "COD_AMM=XZR4RNHR" | python3 -c "
import json, sys
items = json.load(sys.stdin).get('data') or []
match = [x for x in items if 'criminal' in x.get('des_ou','').lower()]
for x in match: print(x.get('des_ou'), '|', x.get('mail1',''))
"
```

**Esempio — PEC Tribunale di Roma (fallback):**
```bash
curl -s -X POST "https://www.indicepa.gov.it/ws/WS03OUServices/api/WS03_OU" \
  --data-urlencode "AUTH_ID=${IPA_auth_id}" \
  --data-urlencode "COD_AMM=m_dg" | python3 -c "
import json, sys
items = json.load(sys.stdin).get('data') or []
match = [x for x in items if 'tribunale' in x.get('des_ou','').lower() and 'roma' in x.get('comune','').lower()]
for x in match: print(x.get('des_ou'), '|', x.get('mail1',''))
"
```

Sigle province per prefetture: `pa`=Palermo, `mi`=Milano, `rm`=Roma, `na`=Napoli, `to`=Torino,
`bo`=Bologna, `fi`=Firenze, `ge`=Genova, `ba`=Bari, `ct`=Catania, `ca`=Cagliari, `ve`=Venezia.

### Ricerca per codice fiscale (fatturazione elettronica)

```bash
curl -s -X POST "https://www.indicepa.gov.it/ws/WS01SFECFServices/api/WS01_SFE_CF" \
  --data-urlencode "AUTH_ID=${IPA_auth_id}" \
  --data-urlencode "CF=<codice_fiscale_11_cifre>"
```

Il campo `cod_uni_ou` nella risposta è il **codice destinatario** da inserire nel campo
"Codice Ufficio" della fattura elettronica PA.

## Interpretare i risultati

- `cod_err: 0` con `num_items > 0` → successo, elabora i dati
- `cod_err: 0` con `num_items: 0` → nessun risultato, suggerisci ricerca alternativa
- `cod_err: 900-902` → `IPA_auth_id` non disponibile nell'ambiente: informa l'utente
- Altri codici di errore → vedi sezione "Codici di errore" nella guida completa

## Come presentare le informazioni al cittadino

Parla in italiano semplice. Non mostrare JSON grezzo. Organizza sempre così:

**Per un ente:**
- Nome completo e acronimo
- Indirizzo (via, comune, CAP)
- PEC ufficiale (evidenziala, è quella da usare per comunicazioni ufficiali)
- Sito web istituzionale
- Codice IPA (menzionalo come "codice identificativo")

**Per gli uffici (UO):**
- Nome dell'ufficio
- Telefono e email
- Nome del responsabile
- Codice univoco (se utile per fatturazione: spiegalo come "codice destinatario")

**Se ci sono più risultati dalla ricerca (WS16):** elencali con numero progressivo e
chiedi quale intende, prima di procedere con le chiamate di dettaglio.

**Se la ricerca non trova nulla:** suggerisci alternative concrete — prova con acronimo,
nome più corto, nome ufficiale completo.

**URL di verifica (sempre in fondo):** al termine di ogni risposta, genera il link più preciso
possibile al portale IPA, scegliendo il tipo in base a ciò che hai trovato.

### Endpoint PortaleServices (no auth, no CORS da curl)

```bash
# Ottieni idEnte da cod_amm
curl -s -X POST "https://www.indicepa.gov.it/PortaleServices/api/ente/ricerca" \
  -H "Content-Type: application/json" \
  -d '{"paginazione":{"campoOrdinamento":"idEnte","tipoOrdinamento":"asc","paginaRichiesta":1},"codEnte":"<cod_amm>"}' \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['risposta']['listaResponse'][0]['idEnte'])"

# Dati completi ente (PEC primaria, numAoo, numOu, indirizzo, social) — più ricco di WS05
curl -s "https://www.indicepa.gov.it/PortaleServices/api/ente/eager/<idEnte>"

# Lista UO di un ente per idEnte (alternativa a WS03 quando non hai cod_amm)
curl -s -X POST "https://www.indicepa.gov.it/PortaleServices/api/ou/ente" \
  -H "Content-Type: application/json" \
  -d '{"paginazione":{"campoOrdinamento":"id","tipoOrdinamento":"asc","paginaRichiesta":1},"idEnte":<idEnte>}'
```

### Pattern URL portale IPA

Genera sempre almeno uno di questi link, dal più specifico al più generico:

```
# Scheda AOO specifica (usa quando hai Codice_uni_aoo)
https://www.indicepa.gov.it/ipa-portale/consultazione/indirizzo-sede/ricerca-ente/elenco-aree-organizzative-omogenee/<idEnte>/scheda-area-organizzativa-omogenea/<Codice_uni_aoo>

# Elenco UO dell'ente (usa quando hai trovato una UO specifica)
https://www.indicepa.gov.it/ipa-portale/consultazione/indirizzo-sede/ricerca-ente/elenco-unita-organizzative/<idEnte>/ente

# Scheda ente completa (usa sempre come fallback)
https://www.indicepa.gov.it/ipa-portale/consultazione/indirizzo-sede/ricerca-ente/scheda-ente/<idEnte>
```

### Come ottenere Codice_uni_aoo per la scheda AOO

```bash
# Dal CKAN IPA (no auth) — filtra per cod_amm, opzionalmente per città
curl -s -G "https://indicepa.gov.it/ipa-dati/api/3/action/datastore_search" \
  --data-urlencode "resource_id=cdaded04-f84e-4193-a720-47d6d5f422aa" \
  --data-urlencode 'filters={"Codice_IPA":"<cod_amm>"}' \
  --data-urlencode "q=<città>" \
  --data-urlencode "limit=10" \
  | python3 -c "
import json, sys
recs = json.load(sys.stdin)['result']['records']
for r in recs: print(r['Codice_uni_aoo'], '|', r['Denominazione_aoo'])
"
```

Presentalo con il testo: "Verifica su IPA: \<url\>"

**Se l'ente è un sotto-ente** (es. Polizia di Stato `XZR4RNHR`), usa il suo `idEnte` diretto
(es. 42411) per la scheda-ente — non serve passare per il ministero padre.

## Concatenare più chiamate

Spesso servono 2 chiamate in sequenza: prima WS16 per trovare il `cod_amm`, poi WS05/WS20/WS03
per i dettagli. Eseguile entrambe senza chiedere conferma all'utente — lui vuole la risposta,
non i dettagli tecnici delle API.

## Guida completa

Per approfondire endpoint, schema JSON delle risposte e tutti i servizi disponibili:
`references/ws-endpoints.md`

Documentazione ufficiale AgID: <https://www.indicepa.gov.it/ipa-portale/dati-statistiche/web-service>

