# Dokicasa Sigilli su PDF — Istruzioni di integrazione per assistenti AI

Sei un assistente che integra l'**API di sigillatura PDF di Dokicasa**: appone uno o
più sigilli su un PDF e ne registra la certificazione. L'endpoint da usare e'
`POST /api/partner/certifications`, dove il bundle e' un **campo facoltativo**
(`user_bundle_id`): passandolo la certificazione si registra su una pratica DokiCasa,
omettendolo si certifica un documento che da noi non ha nessuna pratica dietro.
`POST /api/partner/user-bundle/{id}/seal` e' la rotta storica, solo col bundle: usala
solo se e' gia' integrata. Segui queste istruzioni.

## Contesto

- **Base URL:** `https://api.dokicasa.it`
- **Auth:** header `Authorization: Bearer <api_token>` su ogni chiamata.
- **La chiamata è SINCRONA:** quando risponde, il PDF sigillato è gia' pronto e
  allegato. Non c'e' niente da attendere, nessun webhook, nessun polling.
- **Content-Type:** `multipart/form-data` (con upload) o `application/json` (con
  `attachments`).

## Vincoli (non negoziabili)

1. Non esporre mai l'`api_token` nel frontend o in repository pubblici.
2. **Non e' disponibile in sandbox:** ogni chiamata passa dal fornitore di firma
   qualificata, produce un documento reale e viene addebitata. Non usarla per provare.
3. **Non ritentare in automatico** una chiamata andata a buon fine: risigillare lo
   stesso bundle aggiorna il documento, non ne crea un altro, ma resta una chiamata
   al fornitore. Sulla rotta senza bundle il ritentativo e' invece sicuro **a patto di
   rimandare lo stesso `external_id`**: con quella chiave si riceve la certificazione gia'
   emessa (`200`), senza rifirmare e senza riaddebitare. Non generare mai un `external_id`
   nuovo per ritentare la stessa certificazione.
4. Tutte le pagine devono essere **A4 verticale**, altrimenti `422 NOT_A4`. Se generi
   tu il PDF, generalo A4: i sigilli sono posizionati a misure fisse.
5. L'addebito avviene **solo a documento prodotto**: se la chiamata esce in errore,
   non e' stato scalato niente e non devi "compensare".

## Prerequisiti

1. **Listino `SEAL_PDF`** attivo sull'account, altrimenti `403 PARTNER_PRICING_REQUIRED`.
2. **Bundle** (`user_bundle`) intestato al tuo utente, **solo** per la rotta sul
   bundle. Lo step di certificazione **non** e' necessario. L'id lo restituisce la
   creazione pratica (`POST /api/v3/form/{slug}`) nel campo `user_bundle_id`. Con
   `POST /api/partner/certifications` il bundle non serve.
3. **Almeno un sigillo disponibile** (vedi sotto: tuo, del capogruppo, o dell'ente).

## Endpoint

| Metodo | Path | Uso |
|---|---|---|
| `GET`  | `/api/users/me/seals` | I sigilli che puoi usare |
| `POST` | `/api/partner/user-bundle/{user_bundle_id}/seal` | Appone i sigilli e allega il PDF alla pratica |
| `POST` | `/api/partner/certifications` | Certifica un documento, con `user_bundle_id` o senza (`external_id` obbligatorio) |
| `GET`  | `/api/partner/certifications/{id}` | Rilegge una certificazione |
| `GET`  | `/api/partner/certifications` | Il registro delle certificazioni emesse |

## 1. Leggere i sigilli disponibili

`GET /api/users/me/seals` restituisce **tre provenienze insieme**: i sigilli del tuo
utente, quelli del tuo capogruppo, e quelli dell'**ente certificatore**
(gli enti, le associazioni) per cui il tuo utente e' abilitato a certificare — questi
ultimi possono essere censiti su un altro account. Se sei abilitato su piu' enti, ci
sono i sigilli di tutti, ognuno col suo ente.

```json
[
  { "id": 42, "first_name": "Mario", "last_name": "Rossi", "is_verified": true,
    "certification_body_id": null, "certification_body_name": null },
  { "id": 57, "first_name": "Associazione", "last_name": "Proprietari", "is_verified": true,
    "certification_body_id": 3, "certification_body_name": "Associazione Proprietari" }
]
```

- `id` e' quello da passare all'apposizione.
- `certification_body_id` / `certification_body_name`: l'ente per conto del quale
  rilasci la certificazione con quel sigillo. Sono `null` per i sigilli che non
  appartengono a nessun ente.
- **Non tenere in cache gli id a lungo** e non cablarli nel codice: leggi la lista e
  fai scegliere, oppure filtra per ente quando l'utente certifica per conto di una
  precisa associazione.

## 2. Apporre i sigilli

Due modi, alternativi. Se carichi un `file`, questo ha la precedenza sugli
`attachments`.

**Con un PDF caricato (due sigilli):**

```bash
curl -X POST https://api.dokicasa.it/api/partner/user-bundle/12345/seal \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -F "file=@contratto.pdf" \
  -F "seals[0][id]=42" \
  -F "seals[1][id]=57"
```

**Con allegati gia' online (un sigillo):**

```bash
curl -X POST https://api.dokicasa.it/api/partner/user-bundle/12345/seal \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "seals": [{ "id": 42 }],
        "attachments": [
          { "name": "Contratto", "url": "https://tuo-storage/contratto.pdf" },
          { "name": "Allegato A", "url": "https://tuo-storage/allegato-a.pdf" }
        ]
      }'
```

`seal_id` e' la scorciatoia per il caso a un sigillo solo, alternativa a `seals[]`.

**Risposta `201 Created`:**

```json
{
  "status": "READY",
  "numero_certificazione": "12/2026",
  "document": {
    "name": "certificazione_12345_12.pdf",
    "path": "https://.../certificazione_12345_12.pdf"
  },
  "seals": [
    { "id": 57, "certification_body_id": 3, "certification_body_name": "Associazione Proprietari" }
  ]
}
```

`seals` sono i sigilli apposti, ognuno con il suo ente (`null` se non e' di nessun ente).

La certificazione resta sul bundle. Se lo step di certificazione esiste gia', lo stesso
documento finisce anche fra i suoi allegati (nome *Documento Certificato*), rileggibili da
`GET /api/user-service/{id}/attachments`.

## 3. Certificare con o senza bundle

`POST /api/partner/certifications` — stessa sigillatura e stessa numerazione della rotta
sui sigilli. Campi: `external_id` (**obbligatorio**, max 100 caratteri), `user_bundle_id`
(**facoltativo**: col bundle la certificazione si registra sulla pratica e finisce fra
gli allegati dello step di certificazione; senza, il documento non ha nessuna pratica
dietro), `seals[].id` (o `seal_id`), `file` **oppure** `attachments[].url`. Se il chiamante
non ha una pratica da noi, **non inventarne una**: ometti `user_bundle_id`.

```bash
curl -X POST https://api.dokicasa.it/api/partner/certifications \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -F "external_id=CONTRATTO-2026-0031" \
  -F "file=@contratto.pdf" \
  -F "seals[0][id]=42"
```

Risposta `201`: `id`, `status`, `external_id`, `numero_certificazione`, `number`, `year`,
`certified_at`, `user_bundle_id` (`null`), `document`, `documents`, `seals`.

**`external_id` e' la regola piu' importante di questa rotta.** Ogni chiamata e' una firma
qualificata reale e un numero di serie consumato. Usa come chiave l'id del documento sul
gestionale che stai integrando, salvalo prima di chiamare, e **rimanda esattamente quello**
per ogni ritentativo: la stessa chiave restituisce la certificazione gia' emessa con `200`,
senza rifirmare e senza riaddebitare. Se ricevi `409 CERTIFICATION_IN_PROGRESS` la stessa
chiave e' in lavorazione: aspetta e rileggi con
`GET /api/partner/certifications?external_id=...`, non chiamare di nuovo con una chiave nuova.

Per rileggere: `GET /api/partner/certifications/{id}` e
`GET /api/partner/certifications` (registro paginato, filtri `external_id` e `status`).
Vale anche per le certificazioni fatte sul bundle: la risposta di `.../seal` porta
`certification_id`.

## Come esce il documento

- Piu' allegati vengono **uniti in un unico PDF**, nell'ordine in cui li passi.
- Ogni pagina riceve l'etichetta `Certificazione N/AAAA` in alto a sinistra, con
  sotto il testo dell'ente che certifica.
- I sigilli vanno **su tutte le pagine**, in fondo, affiancati da sinistra a destra.
  Un sigillo **da solo** puo' andare a destra, se e' il timbro di un'associazione
  inquilini: e' cosi' che due associazioni certificano lo stesso documento, una per
  volta, ognuna dal suo lato.
- Lo stesso sigillo passato due volte viene applicato una volta sola.

## Numerazione

Il numero e' **progressivo per anno**. La serie e' quella dell'**ente**, se i sigilli
usati appartengono a un ente; altrimenti e' quella del **capogruppo**, o la tua se non
ne hai uno. Ogni serie parte da `1`.

Un bundle che ha gia' un numero **se lo tiene**: risigillarlo (per esempio dopo aver
corretto un allegato) aggiorna il documento senza bruciare un numero nuovo e senza
lasciare buchi nella serie. Senza bundle la stessa cosa la fa `external_id`. Non
implementare una tua numerazione a lato.

## Errori

| HTTP | `error` | Quando |
|---|---|---|
| `403` | `PARTNER_PRICING_REQUIRED` | Nessun listino `SEAL_PDF` sull'account |
| `403` | `BUNDLE_NOT_OWNED` | Il bundle non e' intestato al tuo utente |
| `403` | `SEAL_NOT_OWNED` | Un sigillo non e' tuo, del capogruppo o del tuo ente |
| `400` | `AMOUNT_NOT_ENOUGH` | Wallet insufficiente; la risposta porta `missing_amount` e `checkout_link` |
| `422` | `NO_DOCUMENT` | Ne' `file` ne' `attachments` |
| `422` | `NOT_A4` | Un allegato non e' A4 verticale (dice quale e le dimensioni) |
| `403` | `CERTIFICATION_NOT_OWNED` | La certificazione che rileggi non e' tua |
| `409` | `CERTIFICATION_IN_PROGRESS` | Stesso `external_id` gia' in lavorazione |

Su `AMOUNT_NOT_ENOUGH` non ritentare in loop: porta l'utente al `checkout_link`.

Prima di tutto questo c'e' la validazione, che risponde `422` con `message` e `errors`:
gli URL degli allegati devono essere **http/https verso un indirizzo pubblico** (niente
`file://`, niente indirizzi privati), al massimo **10 sigilli** e **20 allegati** per
chiamata, `external_id` obbligatorio entro 100 caratteri, `user_bundle_id` esistente e non
cancellato. Se generi tu gli URL, usa link pubblici e temporanei, non percorsi interni.

## Checklist finale

- [ ] Token in variabile d'ambiente, mai nel client.
- [ ] Sigilli letti da `GET /api/users/me/seals`, non cablati.
- [ ] Se l'utente certifica per un'associazione, sigillo scelto per
      `certification_body_id`.
- [ ] PDF garantito A4 verticale prima di chiamare.
- [ ] Nessun ritento automatico: la chiamata e' sincrona e a consumo. Sulla rotta senza
      bundle, `external_id` salvato prima di chiamare e rimandato identico a ogni
      ritentativo.
- [ ] Errori gestiti per codice `error`, non per testo del messaggio.
- [ ] Documentazione di riferimento: https://docs.dokicasa.it/sigilli
