API e integrazioni

Riferimento delle API

Riferimento delle API del tuo account

Qui trovi le guide per partire e la scheda di ogni endpoint, con parametri, corpo, esempi ed errori. La pagina nasce dallo stesso registro che usa il server, e un test controlla che i due non si separino.

Base URL: https://api.consensosicuro.it/api/v1/account · 138 endpoint · versione del riferimento 44c619fc12f2

Condizioni per l’integrazione via API

Indice

Introduzione e base URL

Le API lavorano sul tuo account ConsensoSicuro e su nient’altro. Una chiave vede al massimo quello che il titolare vede in dashboard, nei limiti dei permessi che le hai dato.

Tutti gli indirizzi partono da https://api.consensosicuro.it/api/v1/account. Le richieste e le risposte sono in JSON con codifica UTF-8, le date sono in formato ISO 8601 e i messaggi d’errore sono in italiano.

Il riferimento qui sotto è diviso in gruppi. Ogni endpoint ha un’ancora stabile, per esempio #post-consents, che puoi linkare dal tuo codice o dai tuoi appunti.

La stessa descrizione esiste in formato OpenAPI 3.1: la scarichi da /integrazioni/openapi.json oppure, sempre aggiornata, da GET /openapi.json sul server delle API, che non chiede la chiave.

Autenticazione e chiavi

Ogni richiesta porta la chiave nell’intestazione Authorization: Bearer. Le chiavi di produzione iniziano con ak_live_ e lavorano sui dati veri. Quelle di prova iniziano con ak_test_ e lavorano solo sui dati di prova.

Le chiavi le crea il titolare dell’account in Impostazioni → API & Integrazioni, dopo aver accettato le condizioni per l’integrazione. La chiave compare una volta sola: noi ne conserviamo solo un codice SHA-256, quindi non possiamo mostrartela di nuovo.

Questi sono i permessi che puoi dare a una chiave. Quelli segnati come delicati non sono mai preselezionati.

PermessoCosa consenteNote
account:readLeggere profilo, quota e statistiche. Dati dell'account, piano, consensi usati e residui, consumi degli ultimi 12 mesi, statistiche, profilo e Registro attività (con gli indirizzi IP mascherati nell'ultima parte). Praticamente sempre necessario.preselezionato
templates:readLeggere modelli e configurazione. Catalogo dell'account (codici dei trattamenti, modelli salvati, moduli Agorà, lingue, sedi ed esecutori) e la configurazione completa: modelli, trattamenti abilitati, clausole privacy facoltative, informativa, incarico, lingue e interruttori del profilo. È la lettura che precede ogni invio.preselezionato
consents:readLeggere i consensi. Elenco e dettaglio dei consensi: stato, firmatari, trattamenti, esecutore, sede, esiti di consegna.preselezionato
consents:createInviare consensi. Inviare consensi al paziente dal tuo gestionale. Consuma la quota del piano esattamente come un invio dalla dashboard; con una chiave in modalità Test non parte nessun messaggio e la quota non si tocca.nessuna
consents:cancelAnnullare consensi. Annullare un consenso non ancora firmato. Il consenso torna nella quota del piano.nessuna
consents:documentsScaricare i PDF. Scaricare consenso, informativa privacy e incarico firmati. Sono documenti sanitari: concedilo solo se il gestionale li archivia davvero.nessuna
consents:countersignControfirma (tatuaggi e piercing). Leggere lo stato della controfirma dell'esecutore e mandargli il link per firmarla. La controfirma vera NON passa da qui: è una sottoscrizione personale dell'esecutore con un suo fattore (app 2FA o codice sul suo telefono) e resta sua.nessuna
patients:readLeggere le anagrafiche. Elenco e dettaglio dei pazienti dell'account.nessuna
patients:writeCreare e aggiornare anagrafiche. Inserire o aggiornare i dati anagrafici dei pazienti.nessuna
patients:exportEsportare i dati. Export GDPR, export Excel e fascicolo difensivo del paziente.delicato
patients:deleteCancellare i dati (GDPR). Cancellazione definitiva dei dati di un paziente e revoca del consenso privacy. Irreversibile: concedilo solo se serve davvero, meglio con IP consentiti impostati.delicato, richiede IP consentiti
visits:readLeggere visite e cicli. Visite registrate e cicli di trattamento.nessuna
visits:writeRegistrare visite. Creare visite e cicli di trattamento.nessuna
webhooks:testProvare il webhook. Inviare un evento di prova all'indirizzo webhook configurato.nessuna
anamnesis:readLeggere le anamnesi complete. Le risposte complete dell'anamnesi raccolta alla firma (medica, estetica, domande personalizzate), decifrate, con semaforo e flag. Sono dati sanitari: ogni lettura finisce nel Registro attività.delicato
structure:readLeggere sedi e professionisti. Sedi della struttura, professionisti con «chi esegue cosa», inviti in attesa, esecutore generico per ambito, controfirme in attesa e statistiche per sede e professionista.nessuna
structure:writeGestire sedi e professionisti. Creare, modificare e rimuovere sedi e professionisti senza accesso, invitare collaboratori con accesso e revocare gli inviti, impostare l'esecutore generico. Cambia chi può eseguire cosa: concedilo solo al gestionale che amministra la struttura.delicato
configurations:writeModificare modelli e configurazione. Creare, modificare ed eliminare modelli di consenso, scegliere il predefinito, attivare i moduli Agorà, cambiare trattamenti abilitati, clausole privacy facoltative, incarico, lingue e interruttori del profilo. Un modello sbagliato blocca gli invii: concedilo solo al gestionale che configura davvero l'account.delicato
registries:readLeggere i registri. Apparecchiature (D.M. 206/2015 e MDR), lotti iniettabili, inchiostri, operatrici del centro, motivi di consulto e domande personalizzate con le loro statistiche. Lotti e inchiostri portano il nome del paziente.nessuna
registries:writeAggiornare i registri. Aggiungere, correggere ed eliminare voci dei registri, con le stesse regole della dashboard: la cancellazione di ciò che compare in un consenso firmato diventa una disattivazione.nessuna
account:writeAggiornare il profilo. Cambiare i contatti e i dati dello studio che il profilo della dashboard lascia modificare. Mai l'email di accesso, la password o i dati di fatturazione.delicato
audit:readIP completi (Registro attività e prova di firma). Gli indirizzi IP completi nel Registro attività (chi ha usato l'account) e nella prova di firma dei consensi (chi ha firmato). Senza questo permesso gli IP arrivano con l'ultima parte nascosta. Serve solo a chi deve ricostruire un accesso: concedilo con cognizione.delicato

Struttura e sede

Cliniche, poliambulatori e studi con più sedi usano una sola chiave, creata dal titolare della struttura. La sede su cui lavori si indica con l’intestazione X-Structure-Sede-Id, come fa la dashboard.

La chiave resta dentro le sedi della sua struttura. Una sede che non è della tua struttura riceve un 403, con il codice SEDE_NOT_ACCESSIBLE oppure KEY_NOT_OWNER.

Nell’invio puoi indicare la sede anche nel corpo, con structureSedeId. Se la indichi in tutti e due i punti devono coincidere, altrimenti ricevi SEDE_CONTEXT_MISMATCH.

Chi esegue il trattamento si sceglie con executor: { "memberId": "…" } per un professionista della struttura, { "owner": true } per il titolare, { "generic": true } per l’invio senza professionista nominato, se l’hai attivato per quell’ambito. I professionisti e le sedi che puoi usare sono in GET /templates.

Idempotenza

Se una richiesta cade a metà non sai se il server l’ha eseguita. Per ripeterla senza creare doppioni aggiungi l’intestazione X-Idempotency-Key con un valore tuo, lungo al massimo 128 caratteri, per esempio l’id della visita nel tuo gestionale.

L’intestazione vale sugli endpoint che creano qualcosa, compresi il reinvio e il rinnovo di un consenso. Sono questi:

Paginazione, filtri ed errori

Gli elenchi di consensi e pazienti sono paginati a cursore. Chiedi fino a 100 righe con limit (predefinito 25) e, se pagination.hasMore è vero, passa pagination.nextCursor come cursor per la pagina dopo. Un cursore che non viene da noi è un errore 400 INVALID_CURSOR.

I registri (lotti, inchiostri) e il Registro attività usano invece page, a partire da 1. Ogni endpoint elenca i suoi filtri nella tabella dei parametri.

Un errore ha sempre la stessa forma: error è un messaggio in italiano, code è un codice stabile da usare nel tuo codice. Sui corpi non validi details elenca ogni problema trovato. Guarda sempre anche lo stato HTTP: alcuni 400 e 404 arrivano senza code.

Esempio di errore 403

{
  "error": "Questa chiave non ha il permesso necessario (consents:create).",
  "code": "SCOPE_MISSING",
  "required": ["consents:create"],
  "granted": ["account:read", "consents:read"]
}

Questi errori possono arrivare da qualunque endpoint, prima ancora che la richiesta venga elaborata:

StatoCodiceSignificato
400INVALID_JSONIl corpo non è JSON valido (richiesta troncata, encoding sbagliato).
401API_KEY_REQUIREDManca l'intestazione Authorization: Bearer ak_live_… (o ak_test_…).
401API_KEY_INVALIDLa chiave non esiste, è scritta male, è stata revocata o è scaduta. La risposta è la stessa di proposito: chi trova una chiave vecchia non scopre se era vera. Il motivo preciso lo vedi nel registro richieste della dashboard.
403IP_NOT_ALLOWEDLa richiesta arriva da un indirizzo IP che la chiave non consente.
403SCOPE_MISSINGLa chiave non ha il permesso richiesto dalla rotta: required dice quale, granted quelli che ha.
403KEY_NOT_OWNERL'intestazione X-Structure-Sede-Id indica una sede fuori dalla struttura della chiave.
403SEDE_NOT_ACCESSIBLELa sede dell'intestazione X-Structure-Sede-Id non esiste o non è accessibile.
403STRUCTURE_FROZENLa struttura è sospesa: le richieste si fermano finché il titolare non la riattiva.
403STRUCTURE_CONTEXT_REQUIREDL'account lavora solo nel contesto di una struttura: indica la sede con X-Structure-Sede-Id.
400STRUCTURE_AMBIGUOUSL'account lavora per più strutture: la richiesta non dice quale.
404ACCOUNT_NOT_FOUNDL'account della chiave non è più disponibile.
403ORIGIN_NOT_ALLOWEDLa richiesta arriva da un browser su un dominio che non è il nostro. Le API si chiamano dal tuo server.
400INVALID_CHARACTERSUn campo di testo contiene caratteri invisibili (per esempio lo spazio a larghezza zero) o di controllo della direzione del testo. field dice quale. Lettere accentate, apostrofi ed emoji vanno bene.
413PAYLOAD_TOO_LARGEIl corpo supera 1 MB (16 MB solo sul caricamento di un documento del paziente).
429RATE_LIMIT_EXCEEDEDPiù di 300 richieste al minuto con la stessa chiave, o più di 900 al minuto sull'account (tutte le chiavi insieme). PDF, fascicoli, export e allegati: 120 all'ora per chiave; POST /webhooks/test: 10 all'ora per account. Oltre 600 al minuto dallo stesso IP la risposta è 429 senza code.

Sul 429 trovi l’intestazione Retry-After con i secondi da aspettare. Ogni risposta porta anche RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset, così vedi quanto margine ti resta.

Modalità Test

Con una chiave ak_test_ provi tutto il flusso senza toccare i pazienti veri. Il consenso nasce davvero e lo vedi in dashboard con il marchio «Test API», ma nessun messaggio parte verso il paziente.

Invio di un consenso passo per passo

1. Leggi il catalogo. GET /templates restituisce i codici dei trattamenti per ambito, i tuoi modelli, le lingue attive e, per le strutture, sedi ed esecutori. Salva i codici che ti servono nel tuo gestionale.

Catalogo dell’account

curl "https://api.consensosicuro.it/api/v1/account/templates" \
  -H "Authorization: Bearer ak_test_..."

2. Invia il consenso. POST /consents accetta le stesse opzioni della dashboard. Un invio con una chiave di produzione consuma la quota come un invio fatto a mano. Aggiungi X-Idempotency-Key per ripetere la richiesta senza doppioni.

Invio con chiave di prova

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: visita-119" \
  -d '{
    "patient": { "firstName": "Mario", "lastName": "Rossi", "phone": "+393331234567" },
    "template": { "treatments": ["psi-colloquio"] },
    "delivery": { "channel": "WHATSAPP", "language": "it" },
    "metadata": { "cartella": "CRT-119" }
  }'

3. Leggi la risposta. La risposta 201 ha la forma di GET /consents/{id}: lo stato PENDING_SIGNATURE, un elemento in signatures per ogni firmatario con il suo link e i tuoi metadata. Conserva l’id del consenso.

4. Aspetta il webhook. Quando il paziente firma ricevi consent.completed. Se il messaggio non arriva al paziente ricevi consent.delivery_failed, e con POST /consents/{id}/correct-phone correggi il numero mantenendo lo stesso link.

5. Scarica i documenti. GET /consents/{id}/pdf restituisce un indirizzo firmato valido un’ora. Con ?redirect=1 ricevi direttamente un 302 verso il file.

Link al PDF firmato

curl "https://api.consensosicuro.it/api/v1/account/consents/ID_DEL_CONSENSO/pdf" \
  -H "Authorization: Bearer ak_test_..."

Doppia firma per i minori passo per passo

Un consenso a doppia firma ha due firmatari indipendenti, con due link, due codici OTP, due numeri e due scadenze. Il documento vale solo quando ci sono entrambe le firme.

1. Invio con due genitori

In POST /consents metti patient.isMinor: true e due voci in guardians (relation MOTHER / FATHER / LEGAL_GUARDIAN, con numeri diversi: ciascuno firma dal proprio telefono). Il link e l'OTP vanno a quei numeri, non a patient.phone. La risposta 201 porta signatures[] con due slot, P1 e P2, e due link diversi. La doppia firma è disponibile solo per le professioni abilitate: se il trattamento la richiede e mandi un solo firmatario ricevi 400 DUAL_SIGNATURE_REQUIRED; se non la prevede e ne mandi due, 400 DUAL_SIGNATURE_TREATMENT_NOT_ELIGIBLE.

2. Prima firma → PARTIALLY_SIGNED

Quando uno dei due firma, lo stato diventa PARTIALLY_SIGNED e parte consent.partially_signed con signedSlot (chi ha firmato) e receivedSignatures su expectedSignatures. Non importa quale dei due arrivi per primo. Nel dettaglio signers[] porta lo stato di entrambi.

3. Se il messaggio non arriva a uno dei due

Le azioni per firmatario accettano slot: POST /consents/{id}/channel sposta su SMS il link di quel genitore dopo un fallimento WhatsApp, POST /consents/{id}/correct-phone ne corregge il numero mantenendo lo stesso link (nessuna quota consumata, 3 correzioni al massimo per firmatario). Funzionano anche dopo la firma del primo genitore: chi deve ancora firmare resta raggiungibile. GET /consents/{id}/delivery dice a quale dei due serve; se porta smsFallbackInProgress: true il ripiego automatico su SMS è già partito: aspetta il suo esito invece di forzare il cambio canale, o partono due messaggi allo stesso numero. Quando non resta più nulla da tentare arriva consent.delivery_failed con signerSlot.

4. Seconda firma → COMPLETED

Se i dati del minore dichiarati dai due coincidono, il consenso si perfeziona da solo: stato COMPLETED, PDF con le due pagine di firma, consent.completed con gli orari di entrambe e le URL firmate dei documenti.

5. Dati del minore diversi → ACTION_REQUIRED

Se i due genitori hanno dichiarato dati diversi (un nome scritto in due modi, una data sbagliata) il consenso non si perfeziona: stato ACTION_REQUIRED, evento consent.action_required con i campi divergenti e l'endpoint che risolve. Nel dettaglio il blocco mismatch porta i due valori per ciascun campo. Si chiude con POST /consents/{id}/resolve-mismatch (acceptFrom: "P1" | "P2"; l'altra dichiarazione resta conservata per l'audit) oppure, se la difformità non è risolvibile, con POST /consents/{id}/cancel-mismatch.

6. Rifiuto

Se uno dei due rifiuta, lo stato è REFUSED e l'evento porta refusedSlot e refusalReason: PATIENT per un rifiuto esplicito, PARENT_DISAGREEMENT quando il secondo genitore non accetta clausole che il primo aveva accettato (art. 316 c.c.: la decisione passa al giudice tutelare). Il consenso non è valido e non si recupera.

7. Scadenza e reinvio

Un consenso a doppia firma non si reinvia e non si rinnova (400 RESEND_DUAL_SIGNATURE / RENEWAL_DUAL_SIGNATURE), né via API né dalla dashboard: il consenso nuovo nascerebbe a firma singola, cioè meno valido di quello scaduto. Si rifà l'invio con POST /consents indicando di nuovo entrambi i guardians. Sui consensi a firma singola il reinvio si può fare una volta sola (409 ALREADY_RESENT con newConsentId): se serve, aggiungi X-Idempotency-Key e un retry di rete riceverà la stessa risposta della prima chiamata invece di un errore.

Ciclo di vita e stati

Il campo status usa un vocabolario di sette valori, uguale nelle risposte e nei webhook. Accanto trovi sempre internalStatus, lo stato interno del prodotto, se ti serve la granularità piena.

StatoStati interniSignificato
PENDING_SIGNATURESCHEDULED PENDING OTP_VERIFIEDIl consenso è partito (o partirà all'orario programmato) e nessuno ha ancora firmato.
PARTIALLY_SIGNEDPENDING_SECOND_PARENTDoppia firma per minori: uno dei due genitori ha firmato, l'altro no. signatures[] dice quale.
ACTION_REQUIREDMINOR_DATA_MISMATCH PENDING_COUNTERSIGNIl consenso è fermo e si sblocca con un gesto dello STUDIO, non del paziente: dati del minore divergenti tra i due genitori (blocco mismatch, si risolve con /resolve-mismatch) oppure controfirma dell'esecutore mancante (blocco countersign, si sollecita con /countersign/remind).
COMPLETEDCOMPLETEDTutte le firme raccolte: i PDF sono disponibili.
REFUSEDREFUSED REFUSED_BY_PARENT_DISAGREEMENTRifiuto esplicito di chi doveva firmare, oppure disaccordo tra i due genitori sulle clausole obbligatorie.
EXPIREDEXPIRED EXPIRED_SECOND_PARENTIl link è scaduto senza firma (o senza la seconda firma). Si reinvia con /resend. Attenzione al filtro: status=EXPIRED comprende anche EXPIRED_SECOND_PARENT, perché il valore è quello del vocabolario API. Per la granularità piena filtra con lo stato interno (status=EXPIRED_SECOND_PARENT), che resta accettato.
CANCELLEDCANCELLED CANCELLED_DATA_MISMATCHAnnullato a mano oppure chiuso perché la difformità sui dati del minore non era risolvibile.

Un consenso scaduto si reinvia con POST /consents/{id}/resend, uno firmato con validità a termine si rinnova con POST /consents/{id}/renew. In entrambi i casi nasce un consenso nuovo, con replacesConsentId che punta al vecchio.

Pazienti e GDPR

POST /patients crea l’anagrafica oppure ritrova quella che esiste già, con la stessa deduplica dell’archivio: codice fiscale, poi externalId, poi telefono. Stesso telefono e nome diverso è un 409, perché non è la stessa persona. PUT /patients/identity allinea il tuo CRM per externalId o codice fiscale.

Il codice fiscale non esce mai in chiaro: lo conserviamo come hash e la scheda dice solo se c’è, con hasFiscalCode.

L’export dei dati di un paziente (art. 15 e 20) è GET /patients/{patientId}/export. La revoca dei consensi privacy facoltativi (art. 7.3) si registra su un documento firmato, con POST /patients/{patientId}/privacy/revoke e confirm: true.

La cancellazione (art. 17) via API non è immediata. POST /patients/{patientId}/gdpr-delete la programma: l’anagrafica sparisce subito da elenchi e invii, ma i dati vengono distrutti dopo 7 giorni. Fino ad allora la annulli con POST /patients/{patientId}/gdpr-delete/cancel oppure dalla scheda del paziente in dashboard.

Webhook

Il webhook si configura in Impostazioni → API & Integrazioni: un indirizzo https pubblico e un segreto whsec_… che vedi una volta sola e puoi ruotare. Per impostare o cambiare l’indirizzo e per rigenerare il segreto la dashboard ti chiede di confermare chi sei. Gli eventi partono per tutti i consensi dell’account, inviati da dashboard o da API.

EventoQuando parte
consent.createdConsenso inviato al paziente (da dashboard o da API).
consent.resentConsenso scaduto reinviato: nasce un consenso nuovo, consent.replacesConsentId è quello vecchio.
consent.renewedConsenso a scadenza rinnovato (manualmente o dal promemoria automatico): anche qui il consenso è nuovo.
consent.phone_correctedNumero di un firmatario corretto: stesso link, nessuna quota consumata. signerSlot dice a chi.
consent.delivery_failedMessaggio non consegnato a un firmatario nemmeno dopo il ripiego automatico su SMS: serve correggere il numero.
consent.partially_signedDoppia firma: uno dei due genitori ha firmato (signedSlot, receivedSignatures su expectedSignatures).
consent.action_requiredServe un intervento dello studio: dati del minore divergenti tra i due genitori, oppure controfirma dell'esecutore mancante.
consent.countersignedTatuaggi e piercing: l'esecutore ha controfirmato. Subito dopo arriva consent.completed.
consent.completedTutte le firme ricevute: i PDF sono disponibili (URL firmate a 1 ora).
consent.refusedIl paziente (o un genitore) ha rifiutato di firmare: refusedSlot e refusalReason.
consent.expiredIl link è scaduto senza firma.
consent.cancelledIl consenso è stato annullato.
patient.createdNuova anagrafica in archivio: patient.id, il tuo externalId se lo hai passato, nome e cognome. Mai il telefono né il codice fiscale.
patient.updatedAnagrafica modificata (dati, note, sede o riferimento del gestionale): changed elenca i campi toccati, non i valori.
patient.deletedAnagrafica cancellata definitivamente (art. 17 GDPR). Porta solo gli identificativi: il nome è stato cancellato e non lo si rispedisce fuori.
patient.privacy_revokedConsensi facoltativi revocati su un documento privacy firmato (art. 7.3): consentId, le clausole revocate e se la revoca è totale.
patient.mergedDue anagrafiche si sono rivelate la stessa persona: mergedFrom è l'id che da ora risponde 404, patient.id quello buono. Aggiorna il riferimento nel tuo gestionale.
patient.deletion_scheduledCancellazione GDPR chiesta via API: l'anagrafica è già sparita da liste e invii, ma i dati vengono distrutti solo dopo cancelUntil. Fino ad allora si può annullare.
patient.deletion_postponedUna cancellazione programmata non è stata eseguita alla scadenza: nella finestra sono arrivati nuovi consensi firmati e nessuno ha dichiarato di voler distruggere anche quelli. La cancellazione viene rimandata una sola volta di altri 7 giorni: serve una nuova presa d'atto, altrimenti alla scadenza successiva viene annullata.
patient.deletion_cancelledUna cancellazione programmata è stata annullata, dal gestionale o dalla dashboard (deletion.reason: REQUESTED) oppure d'ufficio perché dopo il rinvio nessuno l'ha riconfermata (NEW_SIGNED_CONSENTS): l'anagrafica è di nuovo raggiungibile.
configuration.updatedÈ cambiata la configurazione dell'account (modelli, trattamenti abilitati, clausole privacy, incarico, lingue, interruttori del profilo, registri): change.resource dice quale risorsa (type, e id quando c'è) e change.action quale azione, senza dati. Rileggi la configurazione.
structure.updatedÈ cambiata la struttura (sedi, professionisti, inviti, esecutore generico): change.resource dice quale risorsa (type, e id quando c'è) e change.action quale azione, senza nomi. Rileggi GET /structure.
pingEvento di prova, inviato dalla dashboard o con POST /webhooks/test.

Ogni consegna è una POST con un corpo JSON e queste intestazioni: X-ConsensoSicuro-Signature, X-ConsensoSicuro-Timestamp, X-ConsensoSicuro-Event e X-ConsensoSicuro-Delivery-Id. Questo è il corpo di un evento di firma parziale:

Corpo di consent.partially_signed

{
  "event": "consent.partially_signed",
  "timestamp": "2026-09-16T09:12:00.000Z",
  "consent": {
    "id": "uuid",
    "status": "PARTIALLY_SIGNED",
    "internalStatus": "PENDING_SECOND_PARENT",
    "apiStatus": "PARTIALLY_SIGNED",
    "consentType": "FULL",
    "isMinor": true,
    "isTest": false,
    "source": "api",
    "metadata": {
      "cartella": "CRT-119"
    },
    "dualSignatureRequired": true,
    "expectedSignatures": 2,
    "receivedSignatures": 1,
    "signedSlot": "P1",
    "signatures": [
      {
        "slot": "P1",
        "role": "FIRST_PARENT",
        "firstName": "Elena",
        "lastName": "Verdi",
        "status": "SIGNED",
        "signedAt": "2026-09-16T09:12:00.000Z"
      },
      {
        "slot": "P2",
        "role": "SECOND_PARENT",
        "firstName": "Marco",
        "lastName": "Verdi",
        "status": "PENDING",
        "signedAt": null
      }
    ],
    "patient": {
      "id": "uuid",
      "firstName": "Sofia",
      "lastName": "Verdi"
    },
    "countersign": {
      "required": false,
      "status": "NOT_REQUIRED"
    },
    "language": "it",
    "isTutelato": false,
    "channel": "WHATSAPP",
    "createdAt": "2026-09-16T09:00:00.000Z",
    "scheduledAt": null,
    "expiresAt": "2026-09-23T09:00:00.000Z",
    "signedAt": null,
    "secondParentSignedAt": null,
    "otpVerifiedAt": null,
    "sede": null,
    "executor": null,
    "signingMode": "REMOTE",
    "validUntil": null,
    "replacesConsentId": null
  }
}

Alcuni blocchi compaiono solo su certi eventi:

EventoCampi in più
consent.resent / consent.renewedconsent.id è il consenso nuovo; consent.replacesConsentId quello sostituito.
consent.phone_correctedconsent.signerSlot (P1/P2), consent.channel, consent.sameLink: true.
consent.delivery_failedconsent.signerSlot e consent.delivery con stato, tentativi, se il ripiego SMS è stato provato e l'endpoint per correggere il numero.
consent.partially_signedconsent.signedSlot, consent.receivedSignatures / expectedSignatures, consent.signatures[].
consent.action_requiredconsent.actionReason (MINOR_DATA_MISMATCH o COUNTERSIGN_PENDING) e, per la difformità, consent.mismatch con i campi e gli endpoint di risoluzione. I valori del minore non escono nel webhook: stanno nel dettaglio autenticato.
consent.refusedconsent.refusedSlot e consent.refusalReason (PATIENT o PARENT_DISAGREEMENT).
consent.countersignedconsent.countersign con metodo e istante. Subito dopo arriva consent.completed.
consent.completedconsent.signatures[] con gli orari di tutte le firme e il blocco documents con le URL firmate (valide 1 ora).
patient.created / patient.updatedpatient con id, externalId, nome e cognome. Mai il telefono né il codice fiscale: ce li hai già nel tuo sistema e non servono a rimbalzare qui. patient.updated porta changed, cioè i nomi dei campi toccati, non i valori.
patient.deletedIl corpo porta solo patient.id, externalId, consentsDeleted e deletedAt. Il nome è appena stato cancellato su richiesta dell'interessato: rispedirlo fuori nell'evento che ne annuncia la cancellazione la vanificherebbe.
patient.privacy_revokedprivacy con consentId, revokedLabels (etichette di clausola, non dati della persona), totale e revokedAt.
patient.mergedmergedFrom è l'id che non vale più, patient.id quello buono. Arriva anche un patient.updated sull'id assorbito con changed: ["mergedInto"], per chi cerca per id.
patient.deletion_scheduleddeletion con cancelUntil (entro quando si può fermare), signedConsents (quante prove di consenso verranno distrutte) e cancelEndpoint.
patient.deletion_cancelledpatient e deletion.reason: REQUESTED se l'ha fermata qualcuno (dal gestionale o dalla dashboard), NEW_SIGNED_CONSENTS se l'abbiamo annullata d'ufficio perché dopo il rinvio nessuno ha riconfermato (in quel caso c'è anche deletion.newSignedConsents e deletion.automatic: true). L'anagrafica è di nuovo raggiungibile; per cancellarla comunque rifai la richiesta con acknowledgeRetention: true.
patient.deletion_postponeddeletion.newSignedConsents (le firme arrivate dopo la richiesta) e deletion.cancelUntil (la nuova scadenza). La cancellazione non è avvenuta: serve una nuova richiesta con acknowledgeRetention: true. Il rinvio avviene una volta sola: alla scadenza successiva, se nessuno ha riconfermato, la cancellazione viene annullata (patient.deletion_cancelled).
consent.* di una persona in cancellazioneSe un evento di consenso riguarda una persona con una cancellazione programmata (per esempio una firma arrivata nella finestra dei 7 giorni), il blocco patient porta solo id e deletionScheduled: true: niente nome, cognome né telefono.

X-ConsensoSicuro-Timestamp è l’istante dell’invio in secondi Unix. X-ConsensoSicuro-Signature contiene uno o due valori v1=… separati da virgola: ognuno è l’HMAC-SHA256, in esadecimale, della stringa fatta dal timestamp, da un punto e dal corpo esatto che ricevi, calcolato con il segreto. Calcolala sui byte ricevuti, prima di trasformarli in JSON, e confrontala a tempo costante. La consegna è autentica se almeno uno dei valori torna.

I valori sono due per 24 ore dopo una rotazione del segreto, uno per il segreto nuovo e uno per il precedente: in quella finestra aggiorni il gestionale senza perdere eventi, riprove comprese. Dopo resta solo il nuovo.

Per difenderti dal replay, cioè da qualcuno che rimanda una consegna vecchia, rifiuta i timestamp lontani più di 5 minuti dal tuo orologio. Ogni riprova, automatica o dal pulsante «Riprova», porta il timestamp del suo invio e passa il controllo. In più ricorda gli id X-ConsensoSicuro-Delivery-Id già elaborati e ignora i doppioni: l’id resta uguale fra un tentativo e le sue riprove. Conserva gli id per almeno 30 giorni. Il campo timestamp del corpo è invece l’istante dell’evento e non cambia fra i tentativi.

Verifica della firma in Node.js (Express)

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const SECRET = process.env.CONSENSOSICURO_WEBHOOK_SECRET; // whsec_…
const seen = new Set(); // nel gestionale: una tabella con indice unico

function validSignature(rawBody, signatureHeader, timestamp) {
  // Il timestamp è l'istante dell'invio in secondi: al massimo 5 minuti di scarto.
  if (!/^\d+$/.test(String(timestamp || ''))) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) return false;
  const expected = crypto.createHmac('sha256', SECRET).update(`${timestamp}.${rawBody}`).digest();
  // Uno o due valori «v1=…» separati da virgola: basta che uno torni.
  return String(signatureHeader || '').split(',').some((part) => {
    const [scheme, value] = part.trim().split('=');
    if (scheme !== 'v1' || !/^[0-9a-f]{64}$/i.test(value || '')) return false;
    return crypto.timingSafeEqual(Buffer.from(value, 'hex'), expected);
  });
}

// express.raw: la firma si calcola sul corpo così come arriva, prima di JSON.parse.
app.post('/webhook/consensosicuro', express.raw({ type: 'application/json' }), (req, res) => {
  const ok = validSignature(req.body, req.get('X-ConsensoSicuro-Signature'), req.get('X-ConsensoSicuro-Timestamp'));
  if (!ok) return res.status(401).end();

  // Protezione dal replay: ogni consegna si elabora una volta sola.
  const deliveryId = req.get('X-ConsensoSicuro-Delivery-Id');
  if (seen.has(deliveryId)) return res.status(200).end();
  seen.add(deliveryId);

  const event = JSON.parse(req.body.toString('utf8'));
  console.log(event.event, event.consent?.id);
  res.status(200).end(); // rispondi subito, elabora dopo
});

app.listen(3000);

Verifica della firma in PHP

<?php
$secret = getenv('CONSENSOSICURO_WEBHOOK_SECRET'); // whsec_…
$raw = file_get_contents('php://input');           // il corpo così come arriva
$timestamp = (string) ($_SERVER['HTTP_X_CONSENSOSICURO_TIMESTAMP'] ?? '');
$header = (string) ($_SERVER['HTTP_X_CONSENSOSICURO_SIGNATURE'] ?? '');

// Il timestamp è l'istante dell'invio in secondi: al massimo 5 minuti di scarto.
if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit;
}
$expected = hash_hmac('sha256', $timestamp . '.' . $raw, $secret);
$valid = false;
foreach (explode(',', $header) as $part) { // uno o due valori «v1=…»
    [$scheme, $value] = array_pad(explode('=', trim($part), 2), 2, '');
    if ($scheme === 'v1' && preg_match('/^[0-9a-f]{64}$/', strtolower($value)) && hash_equals($expected, strtolower($value))) {
        $valid = true;
    }
}
if (!$valid) {
    http_response_code(401);
    exit;
}

$deliveryId = $_SERVER['HTTP_X_CONSENSOSICURO_DELIVERY_ID'] ?? '';
// Protezione dal replay: se hai già elaborato $deliveryId, rispondi 200 e basta.
$event = json_decode($raw, true);
http_response_code(200);

Verifica della firma in Python (Flask)

import hashlib
import hmac
import os
import re
import time

from flask import Flask, request

app = Flask(__name__)
SECRET = os.environ["CONSENSOSICURO_WEBHOOK_SECRET"].encode()  # whsec_…
seen = set()  # nel gestionale: una tabella con indice unico


def valid_signature(raw: bytes, header: str, timestamp: str) -> bool:
    # Il timestamp è l'istante dell'invio in secondi: al massimo 5 minuti di scarto.
    if not timestamp.isdigit() or abs(int(time.time()) - int(timestamp)) > 300:
        return False
    expected = hmac.new(SECRET, timestamp.encode() + b"." + raw, hashlib.sha256).hexdigest()
    for part in header.split(","):  # uno o due valori «v1=…»
        scheme, _, value = part.strip().partition("=")
        if scheme == "v1" and re.fullmatch(r"[0-9a-fA-F]{64}", value) and hmac.compare_digest(expected, value.lower()):
            return True
    return False


@app.post("/webhook/consensosicuro")
def webhook():
    raw = request.get_data()  # il corpo così come arriva
    header = request.headers.get("X-ConsensoSicuro-Signature", "")
    timestamp = request.headers.get("X-ConsensoSicuro-Timestamp", "")
    if not valid_signature(raw, header, timestamp):
        return "", 401
    delivery_id = request.headers.get("X-ConsensoSicuro-Delivery-Id")
    if delivery_id in seen:  # protezione dal replay
        return "", 200
    seen.add(delivery_id)
    event = request.get_json()
    print(event["event"])
    return "", 200

Una consegna è riuscita quando il tuo server risponde con un codice 2xx entro 10 secondi. I reindirizzamenti non vengono seguiti. Se la consegna non riesce riproviamo con questo piano:

La stessa consegna può arrivare più di una volta, e per questo serve la deduplica descritta sopra. Dalla dashboard puoi anche rimandare a mano una consegna. Se il webhook non riesce a consegnare per 10 volte di fila o per 24 ore, scriviamo al titolare, al massimo una volta al giorno.

Le riprove seguono sempre il webhook com’è adesso. Se lo disattivi, le consegne in coda si mettono in pausa e ripartono quando lo riattivi, entro i 30 giorni di conservazione. Se cambi indirizzo, anche le riprove già in coda partono verso quello nuovo. Un server lento rallenta solo le proprie consegne: gli account si servono a turno.

I webhook rispettano le cancellazioni GDPR. patient.deleted porta solo gli identificativi, e dopo la cancellazione le consegne che riguardavano quella persona restano solo come evento, stato e codice di risposta. Se provi a rimandarle dalla dashboard ricevi 409 DELIVERY_REDACTED. Durante i 7 giorni di attesa gli eventi di consenso di quella persona portano solo patient.id e deletionScheduled: true.

Limiti

LimiteValoreErrore
Richieste per chiave300 al minuto429 RATE_LIMIT_EXCEEDED
Richieste per account, tutte le chiavi insieme900 al minuto429 RATE_LIMIT_EXCEEDED
PDF, fascicoli, export e allegati120 all’ora per chiave429 RATE_LIMIT_EXCEEDED
Prove del webhook (POST /webhooks/test)10 all’ora per account429 RATE_LIMIT_EXCEEDED
Richieste per indirizzo IP600 al minuto429
Invii di consensi via APIal giorno, una volta e mezza la quota mensile del piano e mai meno di 30429 DAILY_SEND_LIMIT
Modalità Test50 invii e 20 anagrafiche nuove al giorno429 TEST_DAILY_LIMIT
Corpo della richiesta1 MB (16 MB sul caricamento di un documento)413 PAYLOAD_TOO_LARGE
Documento del paziente10 MB, JPG, PNG, WebP o PDF400 FILE_TOO_LARGE / UNSUPPORTED_FORMAT
Elenchi a cursore100 righe per pagina400 INVALID_QUERY
metadata dell’invio30 chiavi, 1 KB400 METADATA_TOO_MANY_KEYS / METADATA_TOO_LARGE
Correzioni del numero3 per firmatario429 PHONE_CORRECTION_LIMIT
Sollecito della controfirmauno ogni 10 minuti per esecutore429 COUNTERSIGN_SESSION_RATE_LIMITED
Cancellazioni GDPR5 al giorno per chiave429 DELETE_DAILY_LIMIT
Inviti con accesso10 al giorno, 3 allo stesso indirizzo429 INVITE_LIMIT / INVITE_LIMIT_ADDRESS
Link ai PDFvalidi 1 oranessuno
Testiniente caratteri invisibili né di controllo della direzione del testo400 INVALID_CHARACTERS

Un consenso inviato via API conta nella quota del piano come uno inviato dalla dashboard. Le letture, i download e i webhook non si pagano.

Changelog

Le API del tuo account sono in produzione dal 19 ottobre 2026. Per ogni gruppo di endpoint trovi la data in cui è diventato disponibile sull’ambiente di prova, dove ogni novità passa prima di arrivare in produzione. Quando cambia qualcosa lo trovi in questa sezione.

In produzione dal 19 ottobre 2026 · Le API del tuo account sono attive

Da oggi le chiavi si creano dall’account di produzione, in Impostazioni → API & Integrazioni, e le richieste vanno a api.consensosicuro.it. Tutto quello che è descritto in questa pagina vale in produzione, modalità Test compresa. Le condizioni per l’integrazione sono alla versione 1.2.

Disponibile su staging dal 24 settembre 2026 · Sicurezza di chiavi e webhook

La firma dei webhook cambia formato: ora include il timestamp dell’invio (v1= su timestamp e corpo, X-ConsensoSicuro-Timestamp in secondi Unix) e, dopo una rotazione, porta due firme per 24 ore. Chi verificava la firma sul solo corpo deve passare al formato nuovo. Chiave di produzione, indirizzo e segreto del webhook chiedono una conferma d’identità, e il titolare riceve un’email a ogni modifica. Una chiave revocata o scaduta risponde come una inesistente, le chiavi scadono dopo 365 giorni se non scegli diversamente, e arrivano gli avvisi su IP nuovi, volumi insoliti e rifiuti a raffica. Nuovo il permesso audit:read per gli IP completi del Registro attività.

Disponibile su staging dal 24 settembre 2026 · Riferimento pubblico

Abbiamo pubblicato questa pagina, la specifica OpenAPI 3.1 (anche su GET /openapi.json), la collection Postman e gli esempi in TypeScript, PHP e Python.

Disponibile su staging dal 24 settembre 2026 · Condizioni e monitoraggio

Abbiamo aggiunto la pagina pubblica delle API e le condizioni per l’integrazione, che accetti in dashboard. Da qui in poi controlli richieste e webhook dal monitoraggio, ricevi un’email quando il webhook non risponde e le riprove durano fino a 24 ore.

Disponibile su staging dal 23 settembre 2026 · Struttura, configurazione e registri

Con questa fase gestisci dalle API le anamnesi complete, le sedi, i professionisti e gli inviti, i modelli e la configurazione. Puoi anche leggere e scrivere i registri di apparecchiature, lotti e inchiostri, e consultare consumi, statistiche, profilo e Registro attività. (87 endpoint)

Disponibile su staging dal 17 settembre 2026 · Pazienti e GDPR

Da questa versione gestisci le anagrafiche con note, documenti, visite e cicli. Puoi leggere la privacy, registrare una revoca, esportare i dati e chiedere la cancellazione differita. (28 endpoint)

Disponibile su staging dal 17 settembre 2026 · Ciclo di vita del consenso

Ora segui il consenso dopo l’invio: leggi la consegna, l’evidenza della firma e il registro del consenso. Puoi reinviarlo, rinnovarlo, cambiare canale, correggere il numero, risolvere le difformità sui dati del minore, riaprirlo per la firma in studio e sollecitare la controfirma. Il fascicolo e il PDF per email completano la fase. (13 endpoint)

Disponibile su staging dal 17 settembre 2026 · Invio

Puoi leggere il catalogo dell’account e inviare i consensi con le stesse opzioni della dashboard. La modalità Test e l’idempotenza sono disponibili fin da subito. (2 endpoint)

Disponibile su staging dal 16 settembre 2026 · Prima versione

La prima versione introduce le chiavi dell’account. Con quelle leggi l’elenco e il dettaglio dei consensi, scarichi i PDF firmati, annulli un invio e ricevi i webhook firmati, di cui puoi mandare anche un evento di prova. (8 endpoint)

Riferimento degli endpoint

Account

Chi è l'account della chiave, il profilo e il Registro attività.

GET /me

Chi è l'account della chiave

Restituisce il profilo dell'account, il piano, la quota dei consensi, le sedi e i professionisti. È la prima chiamata utile per verificare che la chiave funzioni.

Permesso: account:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/me" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "account": {
    "id": "uuid",
    "businessName": "Studio Rossi",
    "accountType": "CLINIC"
  },
  "plan": {
    "tier": "PRO",
    "subscriptionStatus": "ACTIVE"
  },
  "quota": {
    "used": 42,
    "limit": 200,
    "remaining": 158
  },
  "structure": {
    "id": "uuid",
    "name": "Studio Rossi",
    "sedi": [],
    "members": []
  },
  "context": {
    "sedeId": null,
    "allSedi": false,
    "mode": "LIVE"
  }
}

GET /account/profile

Leggi il profilo

Restituisce il profilo dell'account: identità, contatti, intestazione dei documenti, aspetto e dati di fatturazione, questi ultimi in sola lettura.

Permesso: account:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/account/profile" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "profile": {
    "id": "uuid",
    "email": "[email protected]",
    "accountType": "PROFESSIONAL",
    "businessName": "Studio Rossi",
    "professionTitle": "Psicologa clinica",
    "contact": {
      "phone": "+39…",
      "phoneVerified": true,
      "address": "Via Verdi 3",
      "city": "Padova",
      "cap": "35100"
    },
    "billing": {
      "partitaIva": "01234567890",
      "wantsInvoice": true
    }
  }
}

PATCH /account/profile

Aggiorna il profilo

Cambia solo i campi che il profilo della dashboard lascia cambiare da soli e che non toccano accesso, fatturazione o funzioni. Email di accesso, password, telefono (è un fattore di verifica), nome, dati fiscali ed etichetta della struttura si cambiano dalla dashboard. Per una struttura ragione sociale e indirizzo sono anche i dati di fatturazione, quindi via API restano in lettura. Dopo la modifica parte l'evento configuration.updated.

Permesso: account:write (delicato) · delicato

Corpo della richiesta

CampoTipoObbligatorioDescrizione
businessNamestringnoProfessionisti e studi: mai vuota (serve alla completezza del profilo). (1-200 caratteri)
professionTitlestringnoIntestazione dei documenti. (max 100 caratteri)
professionalRegisterstringnoIntestazione dei documenti. (max 200 caratteri)
structureEmailstring (email) | ""noStrutture: contatti mostrati nei documenti.
structurePhonestringnoStrutture: contatti mostrati nei documenti. (max 30 caratteri)
addressstringnoProfessionisti e studi. (max 200 caratteri)
citystringnoProfessionisti e studi. (max 100 caratteri)
capstring | ""noProfessionisti e studi. (formato ^\d{5}$)
primaryColorstringnoAspetto dei documenti. (max 20 caratteri)
logoBackgroundColorstring | nullnoAspetto dei documenti. (formato ^#[0-9A-Fa-f]{6}$)
logoDominantColorstring | nullnoAspetto dei documenti. (formato ^#[0-9A-Fa-f]{6}$)
logoScaleintegernoAspetto dei documenti. (da 50 a 160)
logoFeatherintegernoAspetto dei documenti. (da 0 a 30)

Esempio: Nuovo titolo professionale

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/account/profile" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "professionTitle": "Psicologa clinica"
}'

Risposta 200

{
  "profile": {
    "id": "uuid",
    "businessName": "Studio Rossi",
    "professionTitle": "Psicologa clinica",
    "contact": {
      "address": "Via Verdi 3",
      "city": "Padova",
      "cap": "35100"
    }
  }
}

Errori

StatoCodiceSignificato
403PROFILE_FIELD_NOT_ALLOWEDCampo di accesso, verifica, identità o fatturazione.
400FIELD_NOT_AVAILABLEstructureLabel: si cambia dalla dashboard.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /account/audit

Registro attività

Restituisce il Registro attività della dashboard, pagina per pagina: azione, paziente, chi, IP, dettagli e istante, più patientId e via (dashboard o API). Con account:read gli indirizzi IP arrivano con l'ultima parte nascosta (203.0.113.xxx); con il permesso audit:read arrivano completi.

Permesso: account:read (oppure audit:read) (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
pagequeryintegernoPagina, da 1. Esempio: 1.
limitqueryintegernoRighe per pagina, da 1 a 100 (predefinito 25). Esempio: 25.
actionquerystringnoNome di un'azione, per esempio CONSENT_SENT o ANAMNESIS_VIEWED. Esempio: CONSENT_SENT.
performedByqueryPROFESSIONAL | PATIENT | SYSTEMnoChi ha compiuto l'azione.
sedeIdquerystringnoStrutture: solo le voci riferite ai pazienti di quella sede.
fromquerystringnoInizio del periodo: una data (AAAA-MM-GG) o un istante ISO 8601. Esempio: 2026-09-01.
toquerystringnoFine del periodo, compresa: una data (AAAA-MM-GG) o un istante ISO 8601. Esempio: 2026-09-30.

Da sapere

  • Con una chiave Test compaiono solo le voci prodotte dalle chiavi Test dell'account o riferite a pazienti di prova, senza IP.
  • Una volta al giorno il registro riceve, per ogni chiave, la voce API_BULK_READ con quante liste di pazienti e di consensi, export e documenti ha letto il giorno prima.
  • Il registro non si filtra per professionista: memberId è un errore 400 INVALID_QUERY.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/account/audit" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "items": [
    {
      "id": "uuid",
      "action": "CONSENT_SENT",
      "patientId": "uuid",
      "patientName": "Mario Rossi",
      "performedBy": "PROFESSIONAL",
      "via": "api",
      "ipAddress": "203.0.113.xxx",
      "details": {
        "consentId": "uuid"
      },
      "timestamp": "2026-09-23T09:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 25,
    "total": 1,
    "totalPages": 1
  }
}

Errori

StatoCodiceSignificato
400INVALID_QUERYParametro non valido.
404SEDE_NOT_FOUNDSede altrui.
403SEDE_CONTEXT_MISMATCHsedeId in contrasto con l'intestazione X-Structure-Sede-Id.

I codici dei trattamenti, i modelli, le lingue, le sedi e gli esecutori da usare nell'invio.

GET /templates

Catalogo dell'account

Restituisce il catalogo del tuo account: codici dei trattamenti per ambito, schede con i tuoi testi, modelli salvati, moduli Agorà, lingue attive, sedi ed esecutori, operatrici e apparecchiature. È la lettura che precede ogni invio.

Permesso: templates:read (oppure account:read)

Da sapere

  • Accetta sia templates:read sia account:read: la superficie è la stessa.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/templates" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "ambiti": [
    {
      "professionType": "PSYCHOLOGIST",
      "label": "Psicologo",
      "dualSignatureAvailable": true,
      "treatments": [
        {
          "code": "psi-colloquio",
          "label": "Colloquio clinico",
          "requiresBothParentsForMinors": false,
          "minorMinAge": null
        }
      ]
    }
  ],
  "privateTreatments": [
    {
      "code": "priv-…",
      "label": "Il consenso del nostro centro",
      "category": "Trattamenti del centro",
      "sendFields": [
        {
          "key": "regime",
          "label": "Regime dell'intervento",
          "type": "select",
          "options": [
            "Day surgery",
            "Ricovero ordinario"
          ],
          "required": true
        }
      ],
      "patientChoices": [
        {
          "key": "punto_15",
          "question": "Uso delle fotografie per scopi scientifici (facoltativo)",
          "options": [
            {
              "value": "si",
              "label": "SÌ, ACCONSENTO"
            },
            {
              "value": "no",
              "label": "NON ACCONSENTO"
            }
          ],
          "required": true
        }
      ]
    }
  ],
  "agora": [
    {
      "code": "agora-iv-therapy",
      "label": "IV Therapy — infusioni vitaminiche endovenose"
    }
  ],
  "configurations": [
    {
      "id": "ck…",
      "name": "Prima visita",
      "isDefault": true,
      "treatments": [
        "psi-colloquio"
      ]
    }
  ],
  "languages": [
    "it",
    "en"
  ],
  "structure": {
    "sedi": [
      {
        "id": "uuid",
        "name": "Sede Padova",
        "city": "Padova"
      }
    ],
    "executors": [
      {
        "memberId": "uuid",
        "name": "Carla Esecutrice",
        "sedeIds": [
          "uuid"
        ],
        "professions": [
          "PHYSIOTHERAPIST"
        ],
        "canExecute": true
      }
    ]
  },
  "businessOperators": [],
  "equipment": [],
  "medicalEquipment": []
}

Consensi

Invio, elenco, dettaglio, consegna, evidenza della firma e annullamento.

GET /consents

Elenco dei consensi

Restituisce i consensi dal più recente, con filtri per stato, periodo, paziente, sede ed esecutore.

Permesso: consents:read

Parametri

NomeDoveTipoObbligatorioDescrizione
statusquerystringnoUno stato del vocabolario dell'API (PENDING_SIGNATURE, PARTIALLY_SIGNED, ACTION_REQUIRED, COMPLETED, REFUSED, EXPIRED, CANCELLED) oppure uno stato interno, per la granularità piena. Esempio: COMPLETED.
fromquerystringnoCreati da questo istante (ISO 8601 con ora). Esempio: 2026-09-01T00:00:00Z.
toquerystringnoCreati fino a questo istante (ISO 8601 con ora). Esempio: 2026-09-30T23:59:59Z.
searchquerystringnoNome, cognome o telefono del paziente.
sedeIdquerystringnoSede della struttura (in alternativa all'intestazione X-Structure-Sede-Id).
executorMemberIdquerystringnoProfessionista esecutore.
patientExternalIdquerystringnoIl tuo riferimento passato all'invio (patient.externalId). Esempio: CRM-4412.
expiringWithinHoursqueryintegernoSolo i consensi ancora in attesa il cui link scade entro N ore (da 1 a 2160). Esempio: 48.
signingModequeryREMOTE | IN_STUDIOnoREMOTE (telefono del paziente) o IN_STUDIO (tablet della sede).
apiTestquerytrue | falsenotrue = solo i consensi di prova, false = solo quelli veri.
limitqueryintegernoDa 1 a 100 (predefinito 25). Esempio: 25.
cursorquerystringnoCursore della pagina successiva (campo nextCursor). Un cursore non riconosciuto è un errore (400 INVALID_CURSOR), non un ritorno silenzioso alla prima pagina.

Da sapere

  • Con una chiave in modalità Test la lista mostra sempre e solo i consensi di prova.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "data": [
    {
      "id": "uuid",
      "status": "COMPLETED",
      "patient": {
        "firstName": "Mario",
        "lastName": "Rossi"
      }
    }
  ],
  "pagination": {
    "limit": 25,
    "total": 130,
    "hasMore": true,
    "nextCursor": "cDoy"
  }
}

Errori

StatoCodiceSignificato
400INVALID_QUERYUn parametro non è valido (stato inesistente, ore non numeriche, limite fuori intervallo).
400INVALID_CURSORIl cursore non è uno di quelli restituiti da questa API.
404SEDE_NOT_FOUNDsedeId non è di questo account.
403SEDE_CONTEXT_MISMATCHsedeId in contrasto con l'intestazione X-Structure-Sede-Id.

POST /consents

Invia un consenso

Invia un consenso al paziente con le stesse opzioni della dashboard. Consuma la quota del piano come un invio fatto a mano; con una chiave in modalità Test il consenso si crea ma non parte nessun messaggio. La risposta 201 ha la stessa forma di GET /consents/{id}.

Permesso: consents:create

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
patientobjectsìChi riceve il consenso: firstName, lastName, phone (E.164 o numero italiano). Per un minore: isMinor: true e i firmatari in guardians.
patient.firstNamestringsì(1-100 caratteri)
patient.lastNamestringsì(1-100 caratteri)
patient.phonestringnoObbligatorio per un adulto. Per un minore non viene usato: il link va ai guardians. (8-24 caratteri)
patient.dateOfBirthstringnoAAAA-MM-GG. È un tuo riferimento: l'identità del soggetto la raccoglie il modulo di firma. (formato ^\d{4}-\d{2}-\d{2}$)
patient.isMinorbooleanno
patient.externalIdstringnoI tuoi riferimenti del gestionale. Il codice fiscale non entra nel documento. (1-120 caratteri)
patient.fiscalCodestringnoI tuoi riferimenti del gestionale. Il codice fiscale non entra nel documento.
patient.idstring (uuid)noPaziente già in archivio. Obbligatorio con consentType: TREATMENT_ONLY.
guardiansarray<object>noSolo per un minore: 1 genitore o tutore, oppure 2 per la doppia firma genitoriale. Ogni voce: relation (MOTHER, FATHER, LEGAL_GUARDIAN), firstName, lastName, phone. Il link e l'OTP vanno a questi numeri: un eventuale patient.phone non viene usato (controlla signatures[].phoneHint nella risposta). L'identità del minore la inserisce il genitore alla firma, come in dashboard; nome e data di nascita che passi qui tornano in metadata.minor come tuo riferimento. (1-2 elementi)
guardians[].relation"MOTHER" | "FATHER" | "LEGAL_GUARDIAN"sì
guardians[].firstNamestringsì(1-100 caratteri)
guardians[].lastNamestringsì(1-100 caratteri)
guardians[].phonestringsì(8-24 caratteri)
consentType"FULL" | "TREATMENT_ONLY" | "PRIVACY_ONLY"noFULL (predefinito), TREATMENT_ONLY (richiede patient.id: la privacy è già firmata), PRIVACY_ONLY (sola informativa). (predefinito "FULL")
templateobjectnoconfigurationId (un tuo modello) oppure treatments[] (codici del catalogo) e privateTreatmentIds[] (schede con i tuoi testi). Con mode, templateType, professionType, secondaryProfessionId, agoraModule.
template.configurationIdstringno(1-40 caratteri)
template.treatmentsarray<string>no(max 50 elementi)
template.privateTreatmentIdsarray<string>no(max 50 elementi)
template.mode"GENERIC" | "SPECIFIC"no
template.templateType"PSYCHOLOGIST" | "PSYCHOTHERAPIST" | "PSYCHIATRIST"no
template.professionTypestringno(1-40 caratteri)
template.secondaryProfessionIdstring (uuid)no
template.agoraModuleobjectno
template.agoraModule.indicationstringno(max 300 caratteri)
template.agoraModule.devicestringno(max 300 caratteri)
template.agoraModule.zonesstringno(max 400 caratteri)
template.agoraModule.notesstringno(max 1500 caratteri)
template.privateSendValuesobjectnoSchede con i tuoi testi che hanno campi da compilare all'invio (sendFields in GET /templates): oggetto chiave → testo. Gli obbligatori vanno indicati, le scelte devono essere tra le opzioni, le date in AAAA-MM-GG o GG/MM/AAAA; un campo facoltativo non indicato prende il valore predefinito della scheda. I valori compaiono nel documento firmato, non nei messaggi.
executorobjectnoStrutture: { memberId } il professionista che esegue, { owner: true } il titolare, { generic: true } senza professionista nominato (se abilitato per l'ambito).
executor.memberIdstring (uuid)no
executor.ownerbooleanno
executor.genericbooleanno
structureSedeIdstring (uuid)noSede della struttura. In alternativa l'intestazione X-Structure-Sede-Id: se indichi entrambe devono coincidere.
officeIdstring (uuid)noProfessionista singolo: lo studio fisico che compare nel documento.
deliveryobjectnochannel (WHATSAPP o SMS), language (solo lingue attive), scheduledFor (ISO 8601 con fuso, da 1 minuto a 1 anno nel futuro), signingMode (REMOTE o IN_STUDIO).
delivery.channel"WHATSAPP" | "SMS"no
delivery.language"it" | "en" | "fr" | "de"no
delivery.scheduledForstring (date-time)no
delivery.signingMode"REMOTE" | "IN_STUDIO"no
engagementobjectno{ include: false } esclude l'incarico da questo invio; overrides per le voci modificabili per invio. Senza include l'incarico attivo parte sempre, anche se in dashboard il cursore parte spento (includeByDefault).
engagement.includebooleanno
engagement.overridesobjectno
anamnesisAckbooleannotrue quando l'anamnesi segnala controindicazioni assolute e vuoi procedere comunque.
equipmentIdsarray<string (uuid)>noApparecchiature usate (D.M. 206/2015 e MDR). (max 30 elementi)
medicalEquipmentIdsarray<string (uuid)>noApparecchiature usate (D.M. 206/2015 e MDR). (max 30 elementi)
requestMedicalClearancebooleanno
businessOperatorIdstring (uuid)no
metadataobjectnoTuoi riferimenti (max 30 chiavi, 1 KB): tornano nel dettaglio e nei webhook.

Da sapere

  • La risposta 201 contiene già il dettaglio completo. Un GET /consents/{id} lanciato nello stesso istante può rispondere 404 per qualche millisecondo, perché la scrittura si chiude alla fine della richiesta: usa il corpo del 201, oppure attendi il webhook consent.created.
  • Con X-Idempotency-Key conserviamo per 24 ore solo il riferimento al consenso creato, mai nomi, telefoni o link: la ripetizione riceve la risposta ricostruita dal consenso di allora. Se nel frattempo la persona ha chiesto la cancellazione dei dati, la ripetizione riceve 404 CONSENT_NOT_FOUND e non parte nessun nuovo invio.

Esempio: Professionista singolo

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "patient": {
    "firstName": "Mario",
    "lastName": "Rossi",
    "phone": "+393331234567"
  },
  "template": {
    "treatments": [
      "psi-colloquio"
    ]
  },
  "delivery": {
    "channel": "WHATSAPP",
    "language": "it"
  },
  "metadata": {
    "cartella": "CRT-119"
  }
}'

Esempio: Struttura: esecutore e sede

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "patient": {
    "firstName": "Luisa",
    "lastName": "Bianchi",
    "phone": "3339876543"
  },
  "template": {
    "treatments": [
      "fis-terapia-manuale"
    ],
    "professionType": "PHYSIOTHERAPIST"
  },
  "executor": {
    "memberId": "6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e"
  },
  "structureSedeId": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
  "delivery": {
    "channel": "SMS"
  }
}'

Esempio: Minore con doppia firma genitoriale

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "patient": {
    "firstName": "Sofia",
    "lastName": "Verdi",
    "isMinor": true,
    "dateOfBirth": "2014-05-09"
  },
  "guardians": [
    {
      "relation": "MOTHER",
      "firstName": "Elena",
      "lastName": "Verdi",
      "phone": "+393331111111"
    },
    {
      "relation": "FATHER",
      "firstName": "Marco",
      "lastName": "Verdi",
      "phone": "+393332222222"
    }
  ],
  "template": {
    "treatments": [
      "psi-colloquio",
      "psi-sostegno"
    ],
    "mode": "SPECIFIC"
  },
  "delivery": {
    "channel": "WHATSAPP"
  },
  "metadata": {
    "cartella": "CRT-120"
  }
}'

Risposta 201

{
  "id": "uuid",
  "status": "PENDING_SIGNATURE",
  "internalStatus": "PENDING",
  "consentType": "FULL",
  "language": "it",
  "isMinor": false,
  "isTutelato": false,
  "isTest": false,
  "source": {
    "channel": "api",
    "apiKeyId": "ck…",
    "apiKeyName": "Gestionale",
    "apiKeyPrefix": "ak_live_…",
    "test": false
  },
  "metadata": {
    "cartella": "CRT-119"
  },
  "dualSignatureRequired": false,
  "channel": "WHATSAPP",
  "createdAt": "2026-09-16T09:00:00.000Z",
  "scheduledAt": null,
  "expiresAt": "2026-09-23T09:00:00.000Z",
  "signedAt": null,
  "validUntil": null,
  "patient": {
    "id": "uuid",
    "firstName": "Mario",
    "lastName": "Rossi",
    "phoneHint": "***567"
  },
  "minor": null,
  "signers": [
    {
      "slot": "P1",
      "role": "PATIENT",
      "firstName": "Mario",
      "lastName": "Rossi",
      "phoneHint": "***567",
      "expiresAt": "2026-09-23T09:00:00.000Z",
      "status": "PENDING"
    }
  ],
  "signatures": [
    {
      "slot": "P1",
      "role": "PATIENT",
      "firstName": "Mario",
      "lastName": "Rossi",
      "phoneHint": "***567",
      "link": "https://…",
      "status": "PENDING"
    }
  ],
  "expectedSignatures": 1,
  "receivedSignatures": 0,
  "mismatch": null,
  "treatments": [
    {
      "id": "psi-colloquio"
    }
  ],
  "sede": null,
  "office": null,
  "executor": null,
  "actingProfession": "PSYCHOLOGIST",
  "actingProfessionTitle": null,
  "otp": {},
  "delivery": {
    "P1": {
      "slot": "P1",
      "state": "IN_CORSO",
      "channel": "WHATSAPP",
      "phoneHint": "***567",
      "inStudio": false,
      "attempts": [
        {
          "channel": "WHATSAPP",
          "at": "2026-09-16T09:00:00.000Z",
          "outcome": "inviato"
        }
      ],
      "smsFallbackAlreadySent": false,
      "smsFallbackInProgress": false,
      "canSwitchToSms": false,
      "canCorrectPhone": false,
      "correctionsLeft": 3
    }
  },
  "signingMode": "REMOTE",
  "studioDevice": null,
  "engagement": {
    "included": false,
    "hasDocument": false
  },
  "anamnesis": {
    "collected": false,
    "hasAbsoluteContraindications": false,
    "hasRelativeContraindications": false,
    "flags": [],
    "findingCount": 0
  },
  "lots": [],
  "privateChoices": [],
  "countersign": {
    "required": false,
    "status": "NOT_REQUIRED"
  },
  "documents": {
    "consentPdfAvailable": false,
    "privacyPdfAvailable": false,
    "engagementPdfAvailable": false,
    "dossierAvailable": false
  },
  "apiKey": {
    "id": "ck…",
    "name": "Gestionale"
  },
  "apiMetadata": {
    "cartella": "CRT-119"
  },
  "timeline": [
    {
      "event": "sent",
      "at": "2026-09-16T09:00:00.000Z"
    }
  ],
  "privacy": {
    "revokeTotal": false,
    "revokedLabels": []
  },
  "signatureLinks": [
    {
      "slot": "P1",
      "link": "https://…"
    }
  ],
  "messages": [
    {
      "slot": "P1",
      "channel": "WHATSAPP",
      "sent": true
    }
  ],
  "scheduled": false,
  "test": false
}

Errori

StatoCodiceSignificato
400INVALID_BODYCampi mancanti o non validi (anche dentro patient, guardians, template, delivery): details elenca ognuno.
400INVALID_JSONIl corpo non è JSON valido (richiesta troncata, encoding sbagliato).
403PROFILE_INCOMPLETEProfilo del titolare incompleto: il documento non si può intestare. Vale anche in modalità Test.
402SUBSCRIPTION_GRACE_PERIOD / SUBSCRIPTION_REQUIREDAbbonamento scaduto o non attivo (solo chiavi di produzione).
400TEMPLATE_AMBIGUOUSconfigurationId e treatments insieme: indica una sola sorgente.
403ACCOUNT_MODELS_ONLYL'account invia solo dai suoi modelli (impostazione concordata con noi): serve template.configurationId di un modello dell'account, e trattamenti, modalità e ambito vengono dal modello. La sola informativa (PRIVACY_ONLY) non lo richiede.
400ACCOUNT_MODEL_TREATMENTS_UNAVAILABLEAccount «solo modelli»: i trattamenti del modello indicato non risolvono per questo invio (ambito diverso, modello senza ambito, catalogo non attivo). Nessun consenso creato: scegli un altro modello o scrivici.
404CONFIGURATION_NOT_FOUND / OFFICE_NOT_FOUND / EQUIPMENT_NOT_FOUNDUn id indicato non esiste o non è di questo account.
400PROFESSION_NOT_ENABLED / UNKNOWN_SECONDARY_PROFESSION / SEDE_NOT_ALLOWEDAmbito, identità secondaria o sede non di questo account.
404PATIENT_NOT_FOUNDpatient.id inesistente o di un altro account.
400PATIENT_PHONE_REQUIREDTelefono mancante per un adulto. Per un minore il numero non serve: il link va ai guardians.
400ENGAGEMENT_NOT_AVAILABLEHai passato engagement.overrides ma l'incarico non è attivo per questo invio.
400METADATA_RESERVED_KEYmetadata usa una chiave che riempie l'API (externalId, fiscalCode, minor).
400PATIENT_ID_REQUIRED«Solo consenso» richiede un paziente che ha già firmato l'informativa.
400GUARDIANS_REQUIRED / GUARDIANS_FOR_ADULTPer un minore servono i firmatari; per un adulto non vanno indicati.
400DUAL_SIGNATURE_NOT_AVAILABLELa professione dell'invio non prevede la doppia firma genitoriale.
400DUAL_SIGNATURE_REQUIREDSu un minore, il trattamento richiede il consenso di entrambi i genitori: indica 2 firmatari.
400DUAL_SIGNATURE_TREATMENT_NOT_ELIGIBLEDue firmatari su un trattamento che non li richiede.
400DUAL_SIGNATURE_SAME_PHONEI due genitori hanno lo stesso numero.
400IN_STUDIO_NOT_SCHEDULABLE / IN_STUDIO_NO_DEVICELimiti della firma sul tablet di studio: non si programma e serve un tablet collegato a quella sede. Con due guardians il flusso è misto: il 1° genitore firma sul tablet, al 2° parte il link sul suo telefono.
400LANGUAGE_NOT_ENABLEDLingua non attivata nelle Impostazioni dell'account.
400CLINIC_SEDE_REQUIRED / INVALID_CLINIC_SEDE / SEDE_CONTEXT_MISMATCHSede mancante, non tua, o in contrasto con l'intestazione.
400CLINIC_MEMBER_REQUIRED / INVALID_CLINIC_MEMBER / CLINIC_MEMBER_NOT_IN_SEDEEsecutore mancante, non tuo, o che non opera in quella sede.
400SECRETARY_CANNOT_EXECUTELa segreteria non può essere l'esecutore.
400EXECUTOR_AMBIGUOUS / EXECUTOR_CAPABILITYPiù esecutori insieme, o esecutore che non esegue quei trattamenti.
400ANAMNESIS_ACK_REQUIREDControindicazioni assolute: conferma con anamnesisAck: true.
400PRIVATE_TREATMENT_NOT_AVAILABLE / METADATA_TOO_MANY_KEYS / METADATA_TOO_LARGEScheda privata non tua; metadata oltre i limiti.
400PRIVATE_SEND_VALUE_REQUIRED / PRIVATE_SEND_VALUE_INVALID / PRIVATE_SEND_VALUE_TOO_LONG / PRIVATE_SEND_VALUE_UNKNOWNCampi da compilare della scheda con i tuoi testi: obbligatorio mancante, scelta fuori dalle opzioni o data non valida, testo troppo lungo, chiave che la scheda non prevede.
402CONSENT_QUOTA_EXCEEDEDQuota consensi del piano esaurita: la risposta riporta consentsUsed e consentsLimit.
402PAYMENT_FAILEDRinnovo dell'abbonamento non riuscito: aggiorna il metodo di pagamento.
402STRUCTURE_PLAN_INACTIVEStruttura o studio senza un piano attivo e con la prova finita: per inviare va attivato un piano.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSStessa chiave con un corpo diverso, o richiesta ancora in corso.
404nessunoRipetizione con una X-Idempotency-Key già usata, ma il consenso di allora non esiste più (per esempio perché la persona ha chiesto la cancellazione dei dati): la risposta è identica a quella di GET /consents/{id} con un id inesistente, e nessun nuovo invio parte.
422UNKNOWN_TREATMENT_CODECodice del catalogo inesistente (probabile refuso): l'elenco è in GET /templates.
422TEMPLATE_NOT_ENABLED_FOR_ACCOUNTIl codice appartiene a un ambito che il tuo account non esercita.
422MESSAGE_NOT_DELIVEREDIl numero non è raggiungibile: nessun consenso è stato creato.
429DAILY_SEND_LIMITTetto giornaliero di invii via API per questo account (chiavi di produzione).
429TEST_DAILY_LIMITTetto della modalità Test: 50 invii e 20 anagrafiche nuove al giorno.
400PRIVACY_ONLY_NO_DUAL_SIGNATURELa doppia firma genitoriale non si applica alla sola informativa privacy.
400PRIVACY_ONLY_WITH_PRIVACY_DISABLEDHai chiesto la sola informativa, ma la struttura non invia l'informativa privacy.
400DUAL_SIGNATURE_NOT_SCHEDULABLEUn consenso a doppia firma non si programma: i due link partono insieme al momento dell'invio.
400DUAL_SIGNATURE_PROFESSION_NOT_SUPPORTEDLa doppia firma è disponibile solo per psicologi, psicoterapeuti, psichiatri e medici estetici.
400DUAL_SIGNATURE_SECOND_PARENT_MISSINGMancano nome, cognome o telefono del secondo genitore.
400INVALID_PROFESSION_TYPEIl profilo dell'account non ha una professione valida: va aggiornato in dashboard.
400LANGUAGE_NOT_AVAILABLE / LANGUAGE_NOT_AVAILABLE_FOR_TEMPLATELa lingua scelta non ha la traduzione per questa professione o per questo modello.
400LANGUAGE_WITH_CUSTOM_PRIVACY / PRIVATE_TREATMENT_IT_ONLY / CUSTOM_ENGAGEMENT_IT_ONLYUn testo del centro (informativa, scheda o incarico personalizzato) esiste solo in italiano: invia in italiano.
400CUSTOM_ENGAGEMENT_INCOMPLETELe condizioni d'incarico hanno una voce da compilare: indicala in engagement.overrides o escludi l'incarico da questo invio.
400CLINIC_NO_SCOPES / CLINIC_SCOPE_REQUIRED / CLINIC_SCOPE_NOT_ALLOWEDStrutture: ambiti non impostati, ambito mancante (template.professionType) o non della struttura.
400CLINIC_MEMBER_SCOPE_MISMATCHIl professionista esecutore non esercita l'ambito dell'invio.
400EXECUTOR_REQUIREDStrutture: manca il professionista che esegue (executor).
400SEND_GENERIC_NOT_ALLOWED_FOR_PROFESSIONPer questa professione il consenso generico non è ammesso: indica almeno un trattamento.
400INVALID_BUSINESS_OPERATOR / INVALID_EQUIPMENTOperatrice o apparecchiatura non valida per questo account.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

GET /consents/{id}

Dettaglio di un consenso

Restituisce il dettaglio completo del consenso: firmatari (uno per link), difformità sui dati del minore, esiti di consegna, evidenza dell'OTP, incarico, anamnesi in forma di semaforo, registro dei lotti, controfirma, documenti disponibili e cronologia.

Permesso: consents:read

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Campi della risposta

  • status: Stato nel vocabolario dell'API. internalStatus porta quello interno del prodotto.
  • signers[] / signatures[]: Un elemento per firmatario: slot (P1/P2), ruolo (PATIENT, MOTHER, FATHER, LEGAL_GUARDIAN, FIRST_PARENT, SECOND_PARENT), nome, suffisso del numero, stato (PENDING, SIGNED, REFUSED, EXPIRED), firma e verifica OTP.
  • mismatch: C'è solo se i due genitori hanno dichiarato dati del minore diversi: i campi divergenti con i due valori e l'endpoint che risolve.
  • delivery: Stato di consegna per firmatario (P1/P2) con i tentativi.
  • countersign: Tatuaggi e piercing: se serve la controfirma, chi deve darla, quando è arrivata e come sollecitarla.
  • anamnesis: Semaforo (GREEN/YELLOW/RED) e flag di rischio. Le risposte cliniche restano in GET /consents/{id}/anamnesis.
  • privateChoices[]: Schede con i testi del centro che contengono scelte del paziente (es. uso delle fotografie): per ciascuna domanda, opzioni (value, label), required e la risposta data alla firma (answer, answerLabel; null prima della firma o se una scelta facoltativa resta senza risposta). Vuoto se il documento non ha scelte.
  • timeline: Passi in ordine: creato, inviato, consegnato, letto, firmato (per slot), controfirmato, completato, annullato, scaduto.

Da sapere

  • Con una chiave Test esistono solo i consensi di prova: un consenso vero risponde 404 CONSENT_NOT_FOUND, come un id inesistente.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "id": "uuid",
  "status": "PENDING_SIGNATURE",
  "internalStatus": "PENDING",
  "consentType": "FULL",
  "language": "it",
  "isMinor": false,
  "isTutelato": false,
  "isTest": false,
  "source": {
    "channel": "api",
    "apiKeyId": "ck…",
    "apiKeyName": "Gestionale",
    "apiKeyPrefix": "ak_live_…",
    "test": false
  },
  "metadata": {
    "cartella": "CRT-119"
  },
  "dualSignatureRequired": false,
  "channel": "WHATSAPP",
  "createdAt": "2026-09-16T09:00:00.000Z",
  "scheduledAt": null,
  "expiresAt": "2026-09-23T09:00:00.000Z",
  "signedAt": null,
  "validUntil": null,
  "patient": {
    "id": "uuid",
    "firstName": "Mario",
    "lastName": "Rossi",
    "phoneHint": "***567"
  },
  "minor": null,
  "signers": [
    {
      "slot": "P1",
      "role": "PATIENT",
      "firstName": "Mario",
      "lastName": "Rossi",
      "phoneHint": "***567",
      "expiresAt": "2026-09-23T09:00:00.000Z",
      "status": "PENDING"
    }
  ],
  "signatures": [
    {
      "slot": "P1",
      "role": "PATIENT",
      "firstName": "Mario",
      "lastName": "Rossi",
      "phoneHint": "***567",
      "expiresAt": "2026-09-23T09:00:00.000Z",
      "status": "PENDING"
    }
  ],
  "expectedSignatures": 1,
  "receivedSignatures": 0,
  "mismatch": null,
  "treatments": [
    {
      "id": "psi-colloquio"
    }
  ],
  "sede": null,
  "office": null,
  "executor": null,
  "actingProfession": "PSYCHOLOGIST",
  "actingProfessionTitle": null,
  "otp": {},
  "delivery": {
    "P1": {
      "slot": "P1",
      "state": "IN_CORSO",
      "channel": "WHATSAPP",
      "phoneHint": "***567",
      "inStudio": false,
      "attempts": [
        {
          "channel": "WHATSAPP",
          "at": "2026-09-16T09:00:00.000Z",
          "outcome": "inviato"
        }
      ],
      "smsFallbackAlreadySent": false,
      "smsFallbackInProgress": false,
      "canSwitchToSms": false,
      "canCorrectPhone": false,
      "correctionsLeft": 3
    }
  },
  "signingMode": "REMOTE",
  "studioDevice": null,
  "engagement": {
    "included": false,
    "hasDocument": false
  },
  "anamnesis": {
    "collected": false,
    "hasAbsoluteContraindications": false,
    "hasRelativeContraindications": false,
    "flags": [],
    "findingCount": 0
  },
  "lots": [],
  "privateChoices": [],
  "countersign": {
    "required": false,
    "status": "NOT_REQUIRED"
  },
  "documents": {
    "consentPdfAvailable": false,
    "privacyPdfAvailable": false,
    "engagementPdfAvailable": false,
    "dossierAvailable": false
  },
  "apiKey": {
    "id": "ck…",
    "name": "Gestionale"
  },
  "apiMetadata": {
    "cartella": "CRT-119"
  },
  "timeline": [
    {
      "event": "sent",
      "at": "2026-09-16T09:00:00.000Z"
    }
  ],
  "privacy": {
    "revokeTotal": false,
    "revokedLabels": []
  }
}

Errori

StatoCodiceSignificato
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

GET /consents/{id}/delivery

Esiti di consegna

Restituisce gli esiti di consegna per firmatario: ogni tentativo su WhatsApp e per SMS, ripieghi automatici compresi, con esito e motivo. È la lettura che dice se serve correggere un numero.

Permesso: consents:read

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Da sapere

  • Con una chiave Test esistono solo i consensi di prova: un consenso vero risponde 404 CONSENT_NOT_FOUND, come un id inesistente.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/delivery" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "consentId": "uuid",
  "status": "PENDING_SIGNATURE",
  "internalStatus": "PENDING",
  "signers": [
    {
      "slot": "P1",
      "state": "NON_CONSEGNATO",
      "channel": "SMS",
      "phoneHint": "***221",
      "attempts": [
        {
          "channel": "WHATSAPP",
          "at": "2026-09-16T09:00:05.000Z",
          "outcome": "fallito",
          "failureCode": "131026"
        },
        {
          "channel": "SMS",
          "at": "2026-09-16T09:00:12.000Z",
          "outcome": "fallito",
          "failureCode": "UNDELIVERABLE"
        }
      ],
      "smsFallbackAlreadySent": true,
      "canCorrectPhone": true,
      "correctionsLeft": 3
    }
  ]
}

Errori

StatoCodiceSignificato
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

GET /consents/{id}/evidence

Evidenza della firma

Restituisce le prove della firma per ogni firmatario: canale e numero mascherato dell'OTP, ora di invio e di verifica, IP e dispositivo di chi ha firmato, codici di controllo SHA-256 dei documenti. Con la doppia firma c'è una voce per genitore.

Permesso: consents:read

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Da sapere

  • Con una chiave Test esistono solo i consensi di prova: un consenso vero risponde 404 CONSENT_NOT_FOUND, come un id inesistente.
  • L'indirizzo IP di ogni firmatario arriva con l'ultima parte nascosta (203.0.113.xxx); completo solo con il permesso audit:read.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/evidence" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "consentId": "uuid",
  "status": "PENDING_SIGNATURE",
  "internalStatus": "PENDING",
  "expectedSignatures": 2,
  "receivedSignatures": 2,
  "signers": [
    {
      "slot": "P1",
      "role": "FIRST_PARENT",
      "signed": true,
      "signedAt": "2026-09-16T09:12:00.000Z",
      "otp": {
        "channel": "WHATSAPP",
        "phoneMasked": "+39*******1221",
        "sentAt": "2026-09-16T09:11:00.000Z",
        "verifiedAt": "2026-09-16T09:11:40.000Z"
      },
      "ipAddress": "93.51.12.xxx",
      "signatureHash": "a1b2…"
    },
    {
      "slot": "P2",
      "role": "SECOND_PARENT",
      "signed": true,
      "signedAt": "2026-09-16T10:02:00.000Z",
      "otp": {
        "channel": "SMS",
        "phoneMasked": "+39*******2332",
        "verifiedAt": "2026-09-16T10:01:30.000Z"
      }
    }
  ],
  "countersign": {
    "required": false,
    "status": "NOT_REQUIRED",
    "at": null,
    "method": null
  },
  "documents": {
    "consentPdfSha256": "a1b2…"
  }
}

Errori

StatoCodiceSignificato
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

GET /consents/{id}/audit

Registro del consenso

Restituisce il registro del consenso: cosa è successo, quando e per opera di chi (dashboard o integrazione), con l'anello della catena hash di ogni riga. Numeri di telefono, email e risposte cliniche restano fuori.

Permesso: consents:read

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Da sapere

  • Con una chiave Test esistono solo i consensi di prova: un consenso vero risponde 404 CONSENT_NOT_FOUND, come un id inesistente.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/audit" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "consentId": "uuid",
  "total": 7,
  "entries": [
    {
      "action": "CONSENT_SENT",
      "label": "Consenso inviato",
      "at": "2026-09-16T09:00:00.000Z",
      "by": "PROFESSIONAL",
      "via": "api",
      "details": {
        "consentType": "FULL",
        "dualSignature": true
      },
      "entryHash": "c3d4…"
    },
    {
      "action": "CONSENT_SIGNED_BY_PARENT_1",
      "label": "Firma del 1° genitore",
      "at": "2026-09-16T09:12:00.000Z",
      "by": "PATIENT",
      "via": "dashboard",
      "details": {
        "slot": "P1"
      }
    }
  ]
}

Errori

StatoCodiceSignificato
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

POST /consents/{id}/cancel

Annulla un consenso

Annulla un consenso non ancora firmato, con le stesse regole della dashboard. Un consenso vero torna nella quota del piano; uno di prova non l'aveva consumata. Parte l'evento consent.cancelled.

Permesso: consents:cancel

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/cancel" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "ok": true,
  "consentId": "uuid"
}

Errori

StatoCodiceSignificato
400nessunoIl consenso è in uno stato che non si annulla (per esempio è già firmato).
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

Ciclo di vita

Reinvio, rinnovo, cambio canale, correzione del numero, difformità e controfirma.

POST /consents/{id}/resend

Reinvia un consenso scaduto

Reinvia un consenso scaduto. Nasce un consenso nuovo, con un link nuovo, e consuma un'unità di quota come un invio fatto a mano. La risposta è il dettaglio del consenso nuovo, con replacesConsentId che punta a quello sostituito.

Permesso: consents:create

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Da sapere

  • Vale per l'intero consenso: slot qui non si usa (400 SLOT_NOT_APPLICABLE).

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/resend" \
  -H "Authorization: Bearer ak_test_..." \
  -H "X-Idempotency-Key: gestionale-0001"

Risposta 201

{
  "id": "uuid",
  "status": "PENDING_SIGNATURE",
  "internalStatus": "PENDING",
  "consentType": "FULL",
  "language": "it",
  "isMinor": false,
  "isTutelato": false,
  "isTest": false,
  "source": {
    "channel": "dashboard",
    "test": false
  },
  "metadata": null,
  "dualSignatureRequired": false,
  "channel": "WHATSAPP",
  "createdAt": "2026-09-16T09:00:00.000Z",
  "scheduledAt": null,
  "expiresAt": "2026-09-23T09:00:00.000Z",
  "signedAt": null,
  "validUntil": null,
  "patient": {
    "id": "uuid",
    "firstName": "Mario",
    "lastName": "Rossi",
    "phoneHint": "***567"
  },
  "minor": null,
  "signers": [
    {
      "slot": "P1",
      "role": "PATIENT",
      "firstName": "Mario",
      "lastName": "Rossi",
      "phoneHint": "***567",
      "expiresAt": "2026-09-23T09:00:00.000Z",
      "status": "PENDING"
    }
  ],
  "signatures": [
    {
      "slot": "P1",
      "link": "https://…",
      "expiresAt": "2026-09-23T09:00:00.000Z"
    }
  ],
  "expectedSignatures": 1,
  "receivedSignatures": 0,
  "mismatch": null,
  "treatments": [
    {
      "id": "psi-colloquio"
    }
  ],
  "sede": null,
  "office": null,
  "executor": null,
  "actingProfession": "PSYCHOLOGIST",
  "actingProfessionTitle": null,
  "otp": {},
  "delivery": {
    "P1": {
      "slot": "P1",
      "state": "IN_CORSO",
      "channel": "WHATSAPP",
      "phoneHint": "***567",
      "inStudio": false,
      "attempts": [
        {
          "channel": "WHATSAPP",
          "at": "2026-09-16T09:00:00.000Z",
          "outcome": "inviato"
        }
      ],
      "smsFallbackAlreadySent": false,
      "smsFallbackInProgress": false,
      "canSwitchToSms": false,
      "canCorrectPhone": false,
      "correctionsLeft": 3
    }
  },
  "signingMode": "REMOTE",
  "studioDevice": null,
  "engagement": {
    "included": false,
    "hasDocument": false
  },
  "anamnesis": {
    "collected": false,
    "hasAbsoluteContraindications": false,
    "hasRelativeContraindications": false,
    "flags": [],
    "findingCount": 0
  },
  "lots": [],
  "privateChoices": [],
  "countersign": {
    "required": false,
    "status": "NOT_REQUIRED"
  },
  "documents": {
    "consentPdfAvailable": false,
    "privacyPdfAvailable": false,
    "engagementPdfAvailable": false,
    "dossierAvailable": false
  },
  "apiKey": null,
  "apiMetadata": null,
  "timeline": [
    {
      "event": "created",
      "at": "2026-09-16T09:00:00.000Z"
    }
  ],
  "privacy": {
    "revokeTotal": false,
    "revokedLabels": []
  },
  "kind": "resend",
  "replacesConsentId": "uuid"
}

Errori

StatoCodiceSignificato
400CONSENT_NOT_EXPIREDSi reinvia solo un consenso scaduto senza firma. Un consenso firmato a scadenza si rinnova.
400RESEND_DUAL_SIGNATUREDoppia firma per minori: il consenso nuovo nascerebbe a firma singola. Rifai l'invio con i due guardians.
400IN_STUDIO_NO_MESSAGE_TO_RESENDFirma sul tablet: nessun messaggio è mai partito. Usa /reopen-in-studio.
400API_TEST_CONSENTLa copia di un consenso di prova sarebbe un consenso vero: creane uno nuovo.
400SLOT_NOT_APPLICABLEslot si usa su /channel e /correct-phone, non qui.
402CONSENT_QUOTA_EXCEEDEDIl reinvio consuma quota come un invio nuovo.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.
409ALREADY_RESENTQuesto consenso è già stato reinviato: newConsentId dice quale. Se è scaduto anche quello, reinvia quello.
502RESEND_FAILEDIl reinvio non è riuscito e nessun consenso nuovo è stato creato: riprova.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.

POST /consents/{id}/renew

Rinnova un consenso a scadenza

Rinnova un consenso firmato con validità a termine, cioè creato da un modello con scadenza. Funziona come il reinvio: nasce un consenso nuovo e l'originale viene segnato come rinnovato.

Permesso: consents:create

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/renew" \
  -H "Authorization: Bearer ak_test_..." \
  -H "X-Idempotency-Key: gestionale-0001"

Risposta 201

{
  "id": "uuid-nuovo",
  "kind": "renew",
  "replacesConsentId": "uuid-vecchio",
  "status": "PENDING_SIGNATURE"
}

Errori

StatoCodiceSignificato
400CONSENT_NOT_RENEWABLEIl consenso non è firmato o non ha una validità a termine.
400RENEWAL_DUAL_SIGNATUREDoppia firma: servono di nuovo entrambe le firme, quindi un invio nuovo.
404CONSENT_NOT_FOUNDConsenso inesistente, di un altro account, oppure vero mentre usi una chiave in modalità Test.
409ALREADY_RENEWEDQuesto consenso è già stato rinnovato: il consenso nuovo esiste già.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.

POST /consents/{id}/channel

Sposta un firmatario su SMS

Sposta su SMS il link di un firmatario quando la consegna su WhatsApp non è riuscita. Il link resta lo stesso e la quota non si consuma. È il «Reinvia via SMS» della dashboard e va in una sola direzione, da WhatsApp a SMS.

Permesso: consents:create

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
channel"SMS" | "WHATSAPP"sìSempre "SMS". Il ritorno a WhatsApp non è previsto e risponde 400 CHANNEL_SWITCH_NOT_AVAILABLE.
slotstringnoP1 (predefinito) o P2 per il secondo genitore: si sposta un firmatario alla volta. (max 10 caratteri)

Esempio: Al 2° genitore

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/channel" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "channel": "SMS",
  "slot": "P2"
}'

Risposta 200

{
  "ok": true,
  "consentId": "uuid",
  "slot": "P2",
  "channel": "SMS",
  "sameLink": true
}

Errori

StatoCodiceSignificato
400CHANNEL_SWITCH_NOT_AVAILABLEHai chiesto WHATSAPP: il passaggio esiste solo verso SMS.
400NOT_DUAL_SIGNATURE"P2" su un consenso con un solo firmatario.
400INVALID_SLOTslot diverso da P1 / P2.
400IN_STUDIO_NO_MESSAGE_TO_RESENDFirma sul tablet: nessun messaggio da spostare.
400DELIVERY_NOT_FAILEDLa consegna su WhatsApp a quel firmatario non risulta fallita: il passaggio a SMS non serve.
400NOT_SENT_VIA_WHATSAPPQuel firmatario non è stato contattato su WhatsApp, oppure è già passato a SMS.
400CONSENT_NOT_PENDINGQuel firmatario non è più in attesa di firmare.
502SMS_SEND_FAILEDL'SMS non è partito: riprova più tardi.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

POST /consents/{id}/correct-phone

Correggi il numero di un firmatario

Corregge il numero di un firmatario e gli rimanda lo stesso link. Non nasce un consenso nuovo e la quota non si consuma. Puoi correggere il numero al massimo tre volte per ogni firmatario.

Permesso: consents:create

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
phonestringsìNumero corretto (E.164 o formato italiano). (6-24 caratteri)
slotstringnoP1 (predefinito) o P2. Con la doppia firma i due numeri devono restare diversi. (max 10 caratteri)

Esempio: Numero del 1° genitore

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/correct-phone" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+393334445566",
  "slot": "P1"
}'

Risposta 200

{
  "ok": true,
  "consentId": "uuid",
  "slot": "P1",
  "channel": "WHATSAPP",
  "sameLink": true,
  "messageSent": true,
  "correctionsLeft": 2
}

Errori

StatoCodiceSignificato
400INVALID_PHONE / SAME_NUMBERNumero malformato o identico a quello già usato.
400DUAL_SIGNATURE_SAME_PHONEI due genitori avrebbero lo stesso numero: ciascuno firma dal proprio.
400CONSENT_NOT_PENDING / CONSENT_LINK_EXPIREDIl firmatario ha già firmato, o il suo link è scaduto.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.
409PHONE_BELONGS_TO_OTHER_PATIENTQuel numero è già di un altro paziente in archivio.
429PHONE_CORRECTION_LIMITTre correzioni già fatte per questo firmatario.

POST /consents/{id}/resolve-mismatch

Risolvi una difformità sui dati del minore

Serve con la doppia firma, quando i due genitori hanno dichiarato dati del minore diversi e il consenso è fermo in ACTION_REQUIRED. Scegli quale versione vale: il PDF con le due firme viene generato e il consenso diventa COMPLETED.

Permesso: consents:create

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
acceptFrom"P1" | "P2"sì"P1" per tenere i dati del 1° genitore, "P2" per quelli del 2°. L'altra dichiarazione resta conservata per l'audit.

Esempio: Vale la versione della madre

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/resolve-mismatch" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "acceptFrom": "P1"
}'

Risposta 200

{
  "ok": true,
  "acceptedFrom": "P1",
  "consent": {
    "id": "uuid",
    "status": "COMPLETED",
    "receivedSignatures": 2
  }
}

Errori

StatoCodiceSignificato
400NO_DATA_MISMATCHIl consenso non è fermo su una difformità.
400INVALID_BODYacceptFrom diverso da P1 / P2.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

POST /consents/{id}/cancel-mismatch

Chiudi una difformità non risolvibile

Chiude un consenso quando la difformità sui dati del minore non si può risolvere, per esempio per uno scambio di persona. Il consenso prende uno stato suo, diverso da quello di un annullamento a mano.

Permesso: consents:cancel

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/cancel-mismatch" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "ok": true,
  "consentId": "uuid",
  "status": "CANCELLED",
  "internalStatus": "CANCELLED_DATA_MISMATCH"
}

Errori

StatoCodiceSignificato
400NO_DATA_MISMATCHPer annullare un consenso normale usa /cancel.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

POST /consents/{id}/reopen-in-studio

Riapri sul tablet

È il «Riapri sul tablet» della dashboard. Un consenso da firmare in studio esce dalla coda del tablet dopo 12 ore: se il paziente torna, questa chiamata lo rimette in coda con lo stesso link e senza consumare quota.

Permesso: consents:create

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/reopen-in-studio" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "ok": true,
  "consentId": "uuid",
  "signingMode": "IN_STUDIO"
}

Errori

StatoCodiceSignificato
400NOT_IN_STUDIOIl consenso non è in modalità firma sul tablet.
400CONSENT_NOT_PENDING / CONSENT_LINK_EXPIREDGià firmato, oppure link scaduto: serve un nuovo invio.
400IN_STUDIO_NO_DEVICENessun tablet collegato per quella sede.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

POST /consents/{id}/countersign/remind

Sollecita la controfirma

Tatuaggi e piercing: manda all'esecutore il link per controfirmare, come «Invia ora» in dashboard. La controfirma vera non passa dall'API: è una sottoscrizione personale dell'esecutore, con un suo fattore (app di autenticazione o codice sul suo telefono), e deve restare sua. Puoi mandare il link una volta ogni 10 minuti per esecutore.

Permesso: consents:countersign

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/countersign/remind" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "ok": true,
  "consentId": "uuid",
  "sent": true,
  "channel": "WHATSAPP",
  "sentAt": "2026-09-16T11:00:00.000Z",
  "expiresAt": "2026-09-17T11:00:00.000Z",
  "consentsInReminder": 3
}

Errori

StatoCodiceSignificato
400COUNTERSIGN_NOT_REQUIREDQuesto consenso non prevede la controfirma.
400API_TEST_CONSENTConsenso di prova: non entra nel giro della controfirma (il link all'esecutore è un messaggio vero).
400ALREADY_COUNTERSIGNEDL'esecutore ha già controfirmato.
400COUNTERSIGN_NOT_PENDINGIl cliente non ha ancora firmato.
400COUNTERSIGNER_NOT_ASSIGNEDNessun esecutore associato: assegnalo dalla dashboard.
400STRUCTURE_REQUIREDLa controfirma con link all'esecutore esiste solo negli studi con professionisti configurati.
409COUNTERSIGN_PHONE_NOT_VERIFIEDIl numero dell'esecutore non è verificato: il link non può partire.
429COUNTERSIGN_SESSION_RATE_LIMITEDLink già mandato da meno di 10 minuti.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

Documenti

I PDF firmati, il fascicolo difensivo e l'invio dei PDF per email.

GET /consents/{id}/pdf

PDF del consenso firmato

Restituisce un link temporaneo al PDF del consenso firmato, valido un'ora. Il file resta in uno spazio privato e non ha un indirizzo pubblico.

Permesso: consents:documents

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.
redirectquerystringno1 (o true) per ricevere un 302 verso il file invece del JSON. Esempio: 1.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/pdf" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "kind": "pdf",
  "downloadUrl": "https://…",
  "expiresInSeconds": 3600
}

Errori

StatoCodiceSignificato
404DOCUMENT_NOT_AVAILABLEIl documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

GET /consents/{id}/privacy-pdf

PDF dell'informativa privacy

Restituisce un link temporaneo al PDF dell'informativa privacy firmata dal paziente, valido un'ora.

Permesso: consents:documents

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.
redirectquerystringno1 (o true) per ricevere un 302 verso il file invece del JSON. Esempio: 1.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/privacy-pdf" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "kind": "privacy",
  "downloadUrl": "https://…",
  "expiresInSeconds": 3600
}

Errori

StatoCodiceSignificato
404DOCUMENT_NOT_AVAILABLEIl documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

GET /consents/{id}/engagement-pdf

PDF della lettera d'incarico

Restituisce un link temporaneo al PDF della lettera d'incarico sottoscritta dal paziente, valido un'ora.

Permesso: consents:documents

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.
redirectquerystringno1 (o true) per ricevere un 302 verso il file invece del JSON. Esempio: 1.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/engagement-pdf" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "kind": "engagement",
  "downloadUrl": "https://…",
  "expiresInSeconds": 3600
}

Errori

StatoCodiceSignificato
404DOCUMENT_NOT_AVAILABLEIl documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

GET /consents/{id}/dossier

Fascicolo difensivo del consenso

Restituisce il fascicolo difensivo: un PDF unico con consenso, anamnesi, registro dei lotti e prova d'integrità della catena di audit. Si genera al momento, quindi arriva direttamente nel corpo come application/pdf, oppure in base64 con ?format=json.

Permesso: consents:documents

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.
formatqueryjson | pdfnojson per ricevere contentBase64 invece dei byte del PDF; pdf (o nessun valore) per il file.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/dossier" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200 (application/pdf, oppure JSON con ?format=json)

{
  "kind": "dossier",
  "filename": "fascicolo-…pdf",
  "contentType": "application/pdf",
  "sizeBytes": 148213,
  "contentBase64": "JVBERi0…"
}

Errori

StatoCodiceSignificato
404DOCUMENT_NOT_AVAILABLEIl documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento.
400INVALID_QUERYformat diverso da json e pdf.
404DOSSIER_NOT_AVAILABLEIl fascicolo non è disponibile per questo consenso.
502DOSSIER_GENERATION_FAILEDUno dei documenti firmati non è leggibile: il fascicolo non è stato composto.

POST /consents/{id}/send-pdf-email

Manda i PDF al paziente per email

Manda, o rimanda, i PDF firmati al paziente per email. Senza email usa l'indirizzo lasciato alla firma.

Permesso: consents:documents

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
emailstring (email)noFacoltativo: un indirizzo diverso da quello raccolto alla firma. Vale solo per questa spedizione e non sostituisce il recapito del paziente sul consenso, come succede invece in dashboard. L'indirizzo finisce mascherato nel Registro attività. (max 200 caratteri)

Esempio: A un indirizzo nuovo

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/send-pdf-email" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "email": "[email protected]"
}'

Risposta 200

{
  "ok": true,
  "consentId": "uuid",
  "email": "ar*******@studio.it",
  "messageId": "uuid",
  "sentAt": "2026-09-16T10:30:00.000Z"
}

Errori

StatoCodiceSignificato
400INVALID_BODYIndirizzo email non valido.
400API_TEST_CONSENTIl PDF di prova porta la filigrana e sparisce dopo 7 giorni: non si spedisce.
404DOCUMENT_NOT_AVAILABLEIl documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento.
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account o è di un paziente con una cancellazione programmata. Con una chiave Test risponde così anche un consenso vero. Con una chiave di produzione, su un id inesistente, il 404 può arrivare senza code.

Pazienti

Anagrafiche, note e documenti del paziente.

GET /patients

Elenco e ricerca dei pazienti

Restituisce le anagrafiche, con la ricerca. Il telefono esce in chiaro: sei il titolare del trattamento e in dashboard lo vedi già per intero. Ogni riga porta lo stato della privacy, così sai subito se puoi mandare un «solo consenso» o se serve l'informativa completa.

Permesso: patients:read

Parametri

NomeDoveTipoObbligatorioDescrizione
searchquerystringnoNome, cognome o telefono. Un valore che è un codice fiscale ben formato viene cercato come tale (per hash).
fiscalCodequerystringnoCodice fiscale esatto: ricerca per hash, mai in chiaro a database. Esempio: RSSMRA80A01F205X.
externalIdquerystringnoIl tuo riferimento del gestionale, esatto. Esempio: CRM-4412.
sedeIdquerystringnoSede della struttura (in alternativa all'intestazione X-Structure-Sede-Id).
executorMemberIdquerystringnoSolo i pazienti con almeno un consenso eseguito da quel professionista.
apiTestquerytrue | falsenotrue = solo le anagrafiche di prova, false = solo quelle vere.
limitqueryintegernoDa 1 a 100 (predefinito 25). Esempio: 25.
cursorquerystringnoCursore della pagina successiva (campo nextCursor).

Da sapere

  • Una chiave in modalità Test lavora in un archivio parallelo: le anagrafiche che crea hanno telefono, codice fiscale ed externalId in uno spazio dei nomi separato, quindi non collidono mai con quelle vere e non le toccano. Con una chiave Test l'esito di una creazione è sempre lo stesso (l'anagrafica di prova viene creata, o ritrovata se è una prova che hai già creato tu), che telefono, codice fiscale o externalId siano o non siano di un tuo paziente. Per questo una chiave di prova non permette di scoprire chi è in archivio.
  • Con una chiave Test la lista mostra solo le anagrafiche di prova, che cancelliamo in automatico quando restano senza consensi.
  • Vale anche al contrario: per una chiave di produzione le anagrafiche di prova non esistono, e un invio col loro id risponde 404.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "data": [
    {
      "id": "uuid",
      "firstName": "Mario",
      "lastName": "Rossi",
      "phone": "+393331112233",
      "externalId": "CRM-4412",
      "hasFiscalCode": true,
      "isTest": false,
      "consentsCount": 3,
      "visitsCount": 5,
      "privacy": {
        "state": "ACTIVE",
        "signedAt": "2026-05-02T10:00:00.000Z",
        "consentId": "uuid"
      }
    }
  ],
  "pagination": {
    "limit": 25,
    "total": 240,
    "hasMore": true,
    "nextCursor": "cDoy"
  }
}

Errori

StatoCodiceSignificato
400INVALID_QUERYUn parametro non è valido (limite fuori intervallo, codice fiscale malformato).
400INVALID_CURSORIl cursore non è uno di quelli restituiti da questa API.
404SEDE_NOT_FOUNDsedeId non è di questo account.
403SEDE_CONTEXT_MISMATCHsedeId in contrasto con l'intestazione X-Structure-Sede-Id.

POST /patients

Crea o ritrova un paziente

Crea l'anagrafica, oppure ritrova quella che esiste già. La ricerca dei doppioni è la stessa dell'archivio: prima il codice fiscale, poi externalId, poi il telefono. Se telefono e nome coincidono è la stessa persona e ricevi 200 con created: false. Se il telefono coincide ma il nome no, ricevi 409, come in dashboard. Se due anagrafiche si rivelano la stessa persona vengono unite: ricevi merged: true e il webhook patient.merged con l'id che da quel momento non vale più.

Permesso: patients:write

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
firstNamestringsìObbligatori. (1-80 caratteri)
lastNamestringsìObbligatori. (1-80 caratteri)
phonestringnoFormato internazionale E.164 (+393331112233). (formato ^\+[1-9]\d{6,14}$)
fiscalCodestringnoServe almeno uno tra telefono, codice fiscale ed externalId. Con una chiave in modalità Test l'externalId che ti rimandiamo è sempre quello che hai scritto tu.
externalIdstringnoServe almeno uno tra telefono, codice fiscale ed externalId. Con una chiave in modalità Test l'externalId che ti rimandiamo è sempre quello che hai scritto tu. (1-120 caratteri)
notesstringnoNote della scheda. Si scrivono solo su un'anagrafica appena creata: su una che esiste già questa chiamata serve a ritrovarla e non deve cancellare quello che la segreteria ha annotato. Per modificarle ci sono PATCH /patients/{patientId} e gli endpoint /notes. (max 5000 caratteri)
sedeIdstring (uuid)noSolo per le strutture, per le quali è obbligatoria (400 SEDE_REQUIRED): un'anagrafica senza sede non comparirebbe in nessuna vista. Un professionista singolo non la indica.

Da sapere

  • Con X-Idempotency-Key a database conserviamo solo gli identificativi della risposta, mai nome e telefono.

Esempio: Anagrafica nuova

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "firstName": "Mario",
  "lastName": "Rossi",
  "phone": "+393331112233",
  "fiscalCode": "RSSMRA80A01F205X",
  "externalId": "CRM-4412",
  "notes": "Inviato dal gestionale"
}'

Esempio: Solo telefono (la persona si aggancia a quella che già esiste)

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "firstName": "Mario",
  "lastName": "Rossi",
  "phone": "+393331112233"
}'

Risposta 201

{
  "patient": {
    "id": "uuid",
    "externalId": "CRM-4412",
    "isTest": false
  },
  "created": true,
  "merged": false
}

Errori

StatoCodiceSignificato
400INVALID_BODYCampi mancanti o telefono non in E.164.
400SEDE_REQUIREDAccount struttura senza sedeId né intestazione di sede.
400INVALID_SEDEsedeId non è una sede della tua struttura (o l'account non è una struttura).
403SEDE_CONTEXT_MISMATCHsedeId in contrasto con l'intestazione X-Structure-Sede-Id.
409PHONE_BELONGS_TO_OTHERQuel numero appartiene già a un'altra persona in archivio, con la stessa regola dell'invio dalla dashboard. Se il paziente ha cambiato numero, usa PATCH /patients/{patientId}.
422IDENTITY_CONFLICTCodice fiscale ed externalId puntano a due persone diverse già registrate: lo risolve il tuo gestionale, non lo indoviniamo noi.
429TEST_DAILY_LIMITChiave Test: 20 anagrafiche di prova nuove al giorno.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.

PUT /patients/identity

Allinea un paziente dal tuo CRM

Registra, o ritrova, una persona per externalId o fiscalCode. Puoi ripetere la chiamata quante volte vuoi senza creare doppioni. È la chiamata con cui allinei il tuo CRM prima ancora di avere un consenso da mandare.

Permesso: patients:write

Corpo della richiesta

CampoTipoObbligatorioDescrizione
externalIdstringnoAlmeno uno dei due: sono le chiavi con cui si ritrova la persona. (1-120 caratteri)
fiscalCodestringnoAlmeno uno dei due: sono le chiavi con cui si ritrova la persona.
firstNamestringsìObbligatori. (1-80 caratteri)
lastNamestringsìObbligatori. (1-80 caratteri)
phonestringnoFormato internazionale E.164. (formato ^\+[1-9]\d{6,14}$)
sedeIdstring (uuid)noSolo per le strutture: la sede dell'anagrafica.

Esempio: Allineamento dal CRM

curl -X PUT "https://api.consensosicuro.it/api/v1/account/patients/identity" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "externalId": "CRM-4412",
  "fiscalCode": "RSSMRA80A01F205X",
  "firstName": "Mario",
  "lastName": "Rossi",
  "phone": "+393331112233"
}'

Risposta 201

{
  "patient": {
    "id": "uuid",
    "externalId": "CRM-4412",
    "hasFiscalCode": true,
    "isTest": false,
    "sedeId": null
  },
  "created": true,
  "merged": false,
  "idempotent": false
}

Errori

StatoCodiceSignificato
400INVALID_BODYManca sia externalId sia fiscalCode, oppure un campo non è valido.
400INVALID_SEDEsedeId non è una sede della tua struttura (o l'account non è una struttura).
403SEDE_CONTEXT_MISMATCHsedeId in contrasto con l'intestazione X-Structure-Sede-Id.
422IDENTITY_CONFLICTLe due chiavi puntano a due persone diverse già registrate.

GET /patients/{patientId}

Scheda del paziente

Restituisce la scheda completa: anagrafica, identità (externalId e se il codice fiscale è registrato), privacy per soggetto con le clausole accettate e rifiutate, anamnesi in forma di semaforo, consensi, visite e cicli, documenti e note.

Permesso: patients:read

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Campi della risposta

  • privacy[]: Una voce per soggetto (l'adulto e ciascun minore o tutelato): documento firmato, quando, codice di controllo, clausole accettate e rifiutate, eventuale revoca.
  • anamnesis[]: Semaforo GREEN/YELLOW/RED e flag di rischio per consenso. Le risposte cliniche si leggono con GET /consents/{id}/anamnesis.
  • identity: Il codice fiscale è conservato solo come hash e non viene mai restituito in chiaro: hasFiscalCode dice se c'è.

Da sapere

  • Con una chiave Test esistono solo le anagrafiche di prova: l'id di un paziente vero risponde 404 PATIENT_NOT_FOUND, con lo stesso corpo di un id inesistente.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "id": "uuid",
  "firstName": "Mario",
  "lastName": "Rossi",
  "phone": "+393331112233",
  "notes": null,
  "identity": {
    "externalId": "CRM-4412",
    "hasFiscalCode": true
  },
  "isTest": false,
  "sedeId": null,
  "createdAt": "2026-09-16T09:00:00.000Z",
  "updatedAt": "2026-09-16T09:00:00.000Z",
  "counts": {
    "consents": 3,
    "visits": 5,
    "cycles": 1,
    "documents": 2
  },
  "privacy": [
    {
      "subject": {
        "kind": "PATIENT"
      },
      "state": "ACTIVE",
      "consentId": "uuid",
      "clauses": {
        "accepted": [
          "marketing"
        ],
        "refused": [
          "profilazione"
        ],
        "shown": [
          "marketing",
          "profilazione"
        ]
      }
    }
  ],
  "anamnesis": [
    {
      "consentId": "uuid",
      "severity": "YELLOW",
      "flags": [
        "anticoagulanti"
      ]
    }
  ],
  "consents": [],
  "engagements": [],
  "cycles": [],
  "visits": [],
  "documents": []
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

PATCH /patients/{patientId}

Modifica l'anagrafica

Modifica i soli campi anagrafici: nome, cognome, telefono, externalId, note e sede. Un campo fuori da questo elenco è un errore 400, non una riga ignorata in silenzio. I dati che il paziente ha dichiarato e firmato nel consenso (nascita, residenza, codice fiscale sul documento) non si toccano: sono congelati sul consenso perché sono la prova di cosa aveva davanti quando ha firmato.

Permesso: patients:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
firstNamestringno(1-80 caratteri)
lastNamestringno(1-80 caratteri)
phonestringno(formato ^\+[1-9]\d{6,14}$)
externalIdstring | nullno(1-120 caratteri)
notesstring | nullno(max 5000 caratteri)
sedeIdstring (uuid) | nullno

Da sapere

  • Con una chiave Test esistono solo le anagrafiche di prova: l'id di un paziente vero risponde 404 PATIENT_NOT_FOUND, con lo stesso corpo di un id inesistente.

Esempio: Nuovo numero

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+393339998877"
}'

Risposta 200

{
  "patient": {
    "id": "uuid",
    "phone": "+393339998877"
  },
  "changed": [
    "phone"
  ]
}

Errori

StatoCodiceSignificato
400INVALID_BODYCampo non previsto, valore non valido o corpo vuoto.
400INVALID_SEDEsedeId non è una sede della tua struttura (o l'account non è una struttura).
403SEDE_CONTEXT_MISMATCHsedeId in contrasto con l'intestazione X-Structure-Sede-Id.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).
422IDENTITY_CONFLICTIl telefono o l'externalId indicati sono già di un'altra anagrafica di questo account. Su un'anagrafica di prova il confronto avviene solo fra le prove: il numero di una persona vera non genera mai questo errore.

GET /patients/{patientId}/consents

Consensi del paziente

Restituisce l'elenco breve dei consensi del paziente, senza il resto della scheda. Per il dettaglio di uno usa GET /consents/{id}.

Permesso: patients:read

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/consents" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "patientId": "uuid",
  "data": [
    {
      "id": "uuid",
      "status": "COMPLETED",
      "consentType": "FULL",
      "isMinor": false,
      "isTutelato": false,
      "minorName": null,
      "language": "it",
      "createdAt": "2026-09-16T09:00:00.000Z",
      "signedAt": "2026-09-16T09:12:00.000Z",
      "expiresAt": null,
      "hasConsentPdf": true,
      "hasPrivacyPdf": true,
      "privacyRevokedAt": null
    }
  ]
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

GET /patients/{patientId}/notes

Leggi le note della scheda

Restituisce le note della scheda. Nel prodotto le note sono un unico campo libero, non un elenco di note con un id ciascuna: per questo non esiste un DELETE /notes/{noteId}.

Permesso: patients:read

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/notes" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "patientId": "uuid",
  "notes": "[2026-09-16 · Segreteria] Richiamare per il controllo.",
  "updatedAt": "2026-09-16T09:00:00.000Z"
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

PUT /patients/{patientId}/notes

Sostituisci le note

Sostituisce l'intero testo delle note.

Permesso: patients:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
notesstringsìIl testo nuovo, fino a 5000 caratteri. Prende il posto di quello che c'era. (max 5000 caratteri)

Esempio: Testo nuovo

curl -X PUT "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/notes" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "notes": "Preferisce essere contattato al mattino."
}'

Risposta 200

{
  "patientId": "uuid",
  "notes": "Preferisce essere contattato al mattino."
}

Errori

StatoCodiceSignificato
400INVALID_BODYnotes mancante o troppo lungo.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

POST /patients/{patientId}/notes

Aggiungi una riga alle note

Accoda in fondo una riga datata, nella forma «[2026-09-16 · Autore] testo»: è quello che fa a mano chi scrive in quel riquadro.

Permesso: patients:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
textstringsìIl testo della riga, fino a 2000 caratteri. (1-2000 caratteri)
authorstringnoChi l'ha scritta nel tuo gestionale: finisce nella riga datata. (max 80 caratteri)

Esempio: Nota della segreteria

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/notes" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "text": "Richiamare per il controllo.",
  "author": "Segreteria"
}'

Risposta 201

{
  "patientId": "uuid",
  "notes": "[2026-09-16 · Segreteria] Richiamare per il controllo.",
  "appended": "[2026-09-16 · Segreteria] Richiamare per il controllo."
}

Errori

StatoCodiceSignificato
400INVALID_BODYtext mancante o troppo lungo.
400NOTES_TOO_LONGCon la riga nuova le note supererebbero i 5000 caratteri: sostituiscile con PUT /notes.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

DELETE /patients/{patientId}/notes

Svuota le note

Svuota il campo delle note della scheda.

Permesso: patients:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/notes" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "patientId": "uuid",
  "notes": null
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

GET /patients/{patientId}/documents

Documenti del paziente

Restituisce l'elenco dei documenti del paziente (nulla osta del medico, prescrizioni, allegati) con tipo, nome, dimensione, chi l'ha caricato e consenso collegato.

Permesso: patients:read

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/documents" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "patientId": "uuid",
  "data": [
    {
      "id": "uuid",
      "type": "MEDICAL_CLEARANCE",
      "fileName": "nulla-osta.pdf",
      "mimeType": "application/pdf",
      "sizeBytes": 182044,
      "uploadedBy": "PROFESSIONAL",
      "note": "Nulla osta del medico curante",
      "consentId": null,
      "createdAt": "2026-09-16T09:00:00.000Z"
    }
  ]
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

POST /patients/{patientId}/documents

Carica un documento

Carica un documento del paziente in multipart/form-data con il campo file, oppure in JSON con fileName, mimeType e contentBase64. Accettati JPG, PNG, WebP e PDF fino a 10 MB: il tipo dichiarato viene confrontato col contenuto reale del file, quindi un PDF rinominato .jpg viene rifiutato.

Permesso: patients:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
fileNamestringsìNome del file, fino a 200 caratteri. (1-200 caratteri)
contentBase64stringsìIl contenuto del file in base64. Il corpo JSON di questa sola rotta accetta fino a 16 MB. (min 1 caratteri)
mimeType"image/jpeg" | "image/png" | "image/webp" | "application/pdf"sìimage/jpeg, image/png, image/webp o application/pdf.
notestringnoFacoltativa: una nota sul documento. (max 500 caratteri)

In alternativa, in multipart/form-data:

  • file: Il file (JPG, PNG, WebP o PDF, fino a 10 MB).
  • note: Facoltativa: una nota sul documento.

Esempio: PDF in base64

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/documents" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "fileName": "nulla-osta.pdf",
  "mimeType": "application/pdf",
  "contentBase64": "JVBERi0xLjQKJcOkw7zDtsOfCjEgMCBvYmoKPDwvVHlwZS9DYXRhbG9nL1BhZ2VzIDIgMCBSPj4KZW5kb2JqCjIgMCBvYmoKPDwvVHlwZS9QYWdlcy9LaWRzWzMgMCBSXS9Db3VudCAxPj4KZW5kb2JqCjMgMCBvYmoKPDwvVHlwZS9QYWdlL1BhcmVudCAyIDAgUi9NZWRpYUJveFswIDAgNjEyIDc5Ml0+PgplbmRvYmoKdHJhaWxlcgo8PC9Sb290IDEgMCBSPj4KJSVFT0YK",
  "note": "Nulla osta del medico curante"
}'

Risposta 201

{
  "patientId": "uuid",
  "document": {
    "id": "uuid",
    "type": "MEDICAL_CLEARANCE",
    "fileName": "nulla-osta.pdf",
    "mimeType": "application/pdf",
    "sizeBytes": 182044,
    "uploadedBy": "PROFESSIONAL",
    "note": "Nulla osta del medico curante",
    "consentId": null,
    "createdAt": "2026-09-16T09:00:00.000Z"
  }
}

Errori

StatoCodiceSignificato
400UNSUPPORTED_FORMATFormato non ammesso.
400FILE_TOO_LARGEOltre i 10 MB.
400FILE_REQUIREDNessun file: manca il campo file in multipart o contentBase64 in JSON.
400INVALID_BASE64contentBase64 non è un base64 valido, oppure è vuoto.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.

GET /patients/{patientId}/documents/{docId}

Scarica un documento

Restituisce un link temporaneo al documento, valido un'ora. Il file resta in uno spazio privato e non ha un indirizzo pubblico.

Permesso: consents:documents

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
docIdpathstringsìId del documento del paziente. Esempio: c4d5e6f7-0a1b-4c2d-8e3f-4a5b6c7d8e9f.

Da sapere

  • Scaricare un documento sanitario porta un file fuori: serve lo stesso permesso dei PDF dei consensi, non patients:read.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/documents/c4d5e6f7-0a1b-4c2d-8e3f-4a5b6c7d8e9f" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "url": "https://…",
  "fileName": "nulla-osta.pdf",
  "expiresInSeconds": 3600
}

Errori

StatoCodiceSignificato
404DOCUMENT_NOT_FOUNDIl documento non esiste o non è di questo paziente.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

DELETE /patients/{patientId}/documents/{docId}

Elimina un documento

Elimina un documento caricato dal centro. Quelli che il paziente carica durante la firma fanno parte del fascicolo probatorio e non si eliminano.

Permesso: patients:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
docIdpathstringsìId del documento del paziente. Esempio: c4d5e6f7-0a1b-4c2d-8e3f-4a5b6c7d8e9f.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/documents/c4d5e6f7-0a1b-4c2d-8e3f-4a5b6c7d8e9f" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "success": true
}

Errori

StatoCodiceSignificato
403DOCUMENT_PROTECTEDDocumento caricato dal paziente durante la firma: fa parte del fascicolo probatorio.
404DOCUMENT_NOT_FOUNDIl documento non esiste o non è di questo paziente.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

GDPR

Informativa privacy, revoca, export e cancellazione dei dati.

GET /patients/{patientId}/privacy

Stato dell'informativa privacy

Restituisce lo stato dell'informativa privacy per soggetto: una voce per l'adulto e per ciascun minore o tutelato, con documento, data, codice di controllo, clausole ed eventuale revoca.

Permesso: patients:read

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/privacy" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "patientId": "uuid",
  "subjects": [
    {
      "subject": {
        "kind": "PATIENT"
      },
      "state": "ACTIVE",
      "consentId": "uuid",
      "clauses": {
        "accepted": [
          "marketing"
        ],
        "refused": [
          "profilazione"
        ],
        "shown": [
          "marketing",
          "profilazione"
        ]
      }
    }
  ]
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

GET /patients/{patientId}/privacy/{consentId}/revocable

Clausole revocabili

Dice quali clausole facoltative si possono revocare su quel documento privacy firmato e cosa è già stato revocato.

Permesso: patients:read

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
consentIdpathstringsìId del consenso che porta l'informativa privacy firmata. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/privacy/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/revocable" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "consentId": "uuid",
  "signedAt": "2026-05-02T10:00:00.000Z",
  "items": [
    {
      "id": "consent-marketing",
      "label": "Marketing"
    }
  ],
  "revokedAt": null,
  "revokedItems": null,
  "patientId": "uuid"
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

POST /patients/{patientId}/privacy/revoke

Revoca consensi privacy facoltativi

Revoca i consensi facoltativi (art. 7.3 GDPR). La revoca si registra su un documento firmato, quindi serve consentId: un paziente può avere più informative (una per soggetto, o rifirmate nel tempo) e revocarle tutte insieme sarebbe un atto che nessuno ha chiesto. Gli effetti sono identici alla dashboard, conferma scritta al firmatario compresa.

Permesso: patients:delete (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
consentIdstring (uuid)sìIl consenso che porta l'informativa firmata.
items"ALL" | array<string>sìUn elenco di clausole (gli id di GET /patients/{patientId}/privacy/{consentId}/revocable, per esempio consent-marketing) oppure "ALL".
reasonstringnoFacoltativo: il motivo, per il Registro attività. (max 300 caratteri)
confirmtruesìDeve valere true.

Da sapere

  • Il permesso patients:delete si concede solo a una chiave con gli IP consentiti impostati.

Esempio: Revoca di alcune clausole

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/privacy/revoke" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "consentId": "3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09",
  "items": [
    "consent-marketing"
  ],
  "confirm": true
}'

Esempio: Revoca totale

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/privacy/revoke" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "consentId": "3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09",
  "items": "ALL",
  "confirm": true,
  "reason": "Richiesta scritta"
}'

Risposta 200

{
  "success": true,
  "patientId": "uuid",
  "consentId": "uuid",
  "revokedAt": "2026-09-16T09:00:00.000Z",
  "totale": false,
  "revokedLabels": [
    "Marketing"
  ],
  "emailSent": true
}

Errori

StatoCodiceSignificato
400INVALID_BODYconfirm: true mancante.
400ALREADY_REVOKEDI consensi di quel documento risultano già revocati.
403DELETE_REQUIRES_IP_ALLOWLISTLa chiave non ha IP consentiti impostati.
403TEST_KEY_DESTRUCTIVEChiave in modalità Test: la revoca non si prova, si legge.
404CONSENT_NOT_FOUNDconsentId non è un documento privacy di questo paziente.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

GET /patients/{patientId}/export

Esporta i dati del paziente

Esporta i dati del paziente per l'accesso e la portabilità (art. 15 e 20 GDPR): anagrafica, consensi decifrati, registro attività e i link temporanei ai PDF sottoscritti, validi un'ora. Ogni export finisce nel Registro attività.

Permesso: patients:export (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Da sapere

  • Permesso delicato: patients:export non è mai preselezionato alla creazione della chiave.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/export" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "exportedAt": "2026-09-16T09:00:00.000Z",
  "patient": {
    "id": "uuid",
    "firstName": "Mario",
    "externalId": "CRM-4412"
  },
  "consents": [
    {
      "id": "uuid",
      "status": "COMPLETED"
    }
  ],
  "auditLogs": [
    {
      "action": "PATIENT_VIEWED",
      "timestamp": "2026-09-16T09:00:00.000Z",
      "performedBy": "PROFESSIONAL"
    }
  ],
  "documents": [
    {
      "consentId": "uuid",
      "consentPdf": {
        "url": "https://…",
        "sha256": "…"
      }
    }
  ],
  "patientDocuments": [],
  "documentUrlsExpireInSeconds": 3600
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

GET /patients/{patientId}/dossier

Fascicolo difensivo del paziente

Restituisce il fascicolo difensivo del paziente in un PDF unico, generato al momento. Arriva nel corpo come application/pdf, oppure in base64 con ?format=json.

Permesso: patients:export (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
formatqueryjson | pdfnojson per ricevere contentBase64 invece dei byte del PDF; pdf (o nessun valore) per il file.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/dossier" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200 (application/pdf, oppure JSON con ?format=json)

{
  "fileName": "fascicolo-rossi.pdf",
  "mimeType": "application/pdf",
  "contentBase64": "JVBERi0…"
}

Errori

StatoCodiceSignificato
400INVALID_QUERYformat diverso da json e pdf.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).
500DOSSIER_UNAVAILABLEIl fascicolo non è stato generato: riprova, e se succede ancora scrivici.

POST /patients/{patientId}/gdpr-delete

Chiedi la cancellazione (art. 17)

Programma la cancellazione definitiva (art. 17 GDPR) senza eseguirla subito. L'anagrafica sparisce all'istante da liste, ricerche, scheda e invii, e da quel momento risponde 404 ovunque tranne sull'annullamento; i dati vengono distrutti dopo 7 giorni. Fino ad allora il titolare può fermare tutto dalla dashboard, e tu con POST /gdpr-delete/cancel. In dashboard la cancellazione la autorizza una persona che digita la propria password in quel momento; qui la autorizza una chiave configurata una volta, e un ciclo sbagliato del gestionale distruggerebbe in pochi minuti la prova di consenso di centinaia di prestazioni. Se ti serve cancellare all'istante, si fa dalla dashboard.

Permesso: patients:delete (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
confirmtruesìDeve valere true: la cancellazione è irreversibile.
acknowledgeRetentiontruenoObbligatorio se il paziente ha consensi firmati. I consensi firmati sono la prova che aveva acconsentito: la legge può richiederne la conservazione (per la responsabilità professionale si parla di dieci anni) e questa cancellazione li distrugge. La risposta d'errore ti dice quanti sono.
reasonstringnoMotivo: finisce nel Registro attività e nell'avviso al titolare. (max 300 caratteri)

Da sapere

  • Tetto: al massimo 5 richieste al giorno per chiave, contate sul giorno italiano. Il contatore riparte a mezzanotte, e annullare una cancellazione non libera un posto per oggi (429 DELETE_DAILY_LIMIT). Oltre il tetto al titolare arriva un avviso. Il tetto è per chiave: un'altra chiave dello stesso account ne ha altre cinque.
  • Revocare la chiave non annulla le cancellazioni già programmate: la richiesta è dell'account, non della chiave, e la cancellazione automatica avviene lo stesso. Per fermarle le annulli una per una, da qui o dalla scheda del paziente in dashboard.
  • Se durante i 7 giorni la persona firma altri consensi, alla scadenza la cancellazione non viene eseguita. Viene rimandata una sola volta di altri 7 giorni, parte patient.deletion_postponed e il titolare riceve un avviso. Per procedere annulli e rifai la richiesta con acknowledgeRetention: true. Se alla seconda scadenza nessuno l'ha riconfermata, la cancellazione viene annullata d'ufficio: il paziente torna visibile, al titolare arriva un avviso e parte patient.deletion_cancelled con deletion.reason: "NEW_SIGNED_CONSENTS".
  • Una seconda richiesta su un paziente che ha già una cancellazione programmata non sposta la scadenza: risponde 409 PATIENT_DELETION_SCHEDULED con executeAfter e cancelEndpoint. Per riconfermarla (per esempio dopo nuove firme) annullala e rifai la richiesta.
  • La cancellazione elimina il paziente e tutti i suoi consensi, anche quelli firmati, con i PDF, le firme, i documenti sanitari, le visite, i cicli, le anamnesi, gli indirizzi email nei registri di consegna e le eventuali anagrafiche doppie unite a lui. Restano il Registro attività, che dice che la cancellazione è avvenuta e da quale chiave ma non chi fosse la persona, e le righe delle consegne webhook che la riguardavano, ridotte a evento, stato e codice di risposta: il corpo inviato viene cancellato e un replay risponde 409 DELIVERY_REDACTED. Durante i 7 giorni di attesa il replay e i nuovi tentativi delle consegne che portano il suo nome sono sospesi (409 PATIENT_DELETION_SCHEDULED).

Esempio: Paziente senza consensi firmati

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/gdpr-delete" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "confirm": true,
  "reason": "Richiesta dell'\''interessato del 16/09/2026"
}'

Esempio: Paziente con consensi firmati

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/gdpr-delete" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "confirm": true,
  "acknowledgeRetention": true,
  "reason": "Richiesta dell'\''interessato del 16/09/2026"
}'

Risposta 200

{
  "success": true,
  "scheduled": true,
  "patientId": "uuid",
  "externalId": "CRM-4412",
  "hiddenNow": true,
  "executeAfter": "2026-09-23T09:00:00.000Z",
  "cancelUntil": "2026-09-23T09:00:00.000Z",
  "cancelEndpoint": "/api/v1/account/patients/uuid/gdpr-delete/cancel",
  "graceDays": 7,
  "consentsToDelete": 3,
  "signedConsents": 2,
  "dailyCap": 5,
  "scheduledToday": 1,
  "message": "Cancellazione programmata. Il paziente non è più raggiungibi…"
}

Errori

StatoCodiceSignificato
400INVALID_BODYconfirm: true mancante.
400RETENTION_ACK_REQUIREDIl paziente ha consensi firmati e manca acknowledgeRetention: true.
403DELETE_REQUIRES_IP_ALLOWLISTLa chiave deve avere gli IP consentiti impostati. Si scelgono alla creazione della chiave e non si cambiano dopo.
403TEST_KEY_DESTRUCTIVEChiave in modalità Test: una cancellazione non si prova, si esegue.
404PATIENT_NOT_FOUNDNon esiste, non è di questo account oppure è fuori dal contesto di sede.
409PATIENT_DELETION_SCHEDULEDLa cancellazione di questo paziente è già programmata: la risposta porta executeAfter (quando verrà eseguita) e cancelEndpoint (per annullarla e rifarla, se serve riconfermarla).
429DELETE_DAILY_LIMITTetto giornaliero della chiave raggiunto.

POST /patients/{patientId}/gdpr-delete/cancel

Annulla una cancellazione programmata

Ferma una cancellazione programmata: il paziente torna raggiungibile e i dati non vengono distrutti. È l'unico endpoint che vede un'anagrafica già nascosta. Lo stesso pulsante c'è nella scheda del paziente in dashboard, e il titolare lo riceve nell'email di avviso.

Permesso: patients:delete (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/gdpr-delete/cancel" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "success": true,
  "patientId": "uuid",
  "cancelledScheduledFor": "2026-09-23T09:00:00.000Z",
  "message": "Cancellazione annullata: il paziente è di nuovo raggiungibile."
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDNon c'è nessuna cancellazione programmata per questo paziente (o è già stata eseguita).

Visite

Visite e cicli di trattamento.

GET /patients/{patientId}/visits

Visite del paziente

Restituisce le visite del paziente raggruppate per ciclo di trattamento, e le stesse visite in un elenco semplice per chi non usa i cicli.

Permesso: visits:read

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/visits" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "patientId": "uuid",
  "cycles": [
    {
      "id": "uuid",
      "title": "Riabilitazione spalla",
      "isActive": true,
      "startedAt": "2026-09-02T09:00:00.000Z",
      "visits": [
        {
          "id": "uuid",
          "visitNumber": 7,
          "visitDate": "2026-09-16T09:00:00.000Z",
          "cycleId": "uuid"
        }
      ]
    }
  ],
  "data": [
    {
      "id": "uuid",
      "visitNumber": 7,
      "visitDate": "2026-09-16T09:00:00.000Z",
      "cycleId": "uuid"
    }
  ]
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

POST /patients/{patientId}/visits

Registra una visita

Registra una visita. Può essere collegata a un consenso firmato e può essere intestata a un minore o a un tutelato indicando subjectConsentId. Il ciclo nasce con la prima visita (cycleTitle): è così anche in dashboard, non esiste un «crea ciclo» a sé.

Permesso: visits:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
visitDatestring (date-time)noIstante ISO 8601. Se manca, adesso.
reasonstringnoMotivo in chiaro: viene agganciato alla tua tassonomia dei motivi di consulto. (max 200 caratteri)
reasonIdstring (uuid)noIn alternativa, l'id di un motivo di consulto (GET /consultation-reasons).
pathologystringno(max 2000 caratteri)
notesstringno(max 5000 caratteri)
sedeIdstring (uuid) | nullno
consentIdstring (uuid)noConsenso firmato da cui nasce la visita (uno a uno, come in dashboard).
subjectConsentIdstring (uuid) | nullnoConsenso che identifica il soggetto, quando è un minore o un tutelato.
cycleIdstring (uuid)noCiclo esistente, oppure il titolo di un ciclo nuovo che nasce con questa visita.
cycleTitlestringnoCiclo esistente, oppure il titolo di un ciclo nuovo che nasce con questa visita. (max 200 caratteri)
isFirstVisitbooleanno

Esempio: Prima visita di un ciclo nuovo

curl -X POST "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/visits" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "reason": "Dolore alla spalla",
  "cycleTitle": "Riabilitazione spalla",
  "isFirstVisit": true
}'

Risposta 201

{
  "patientId": "uuid",
  "visit": {
    "id": "uuid",
    "visitNumber": 7,
    "visitDate": "2026-09-16T09:00:00.000Z",
    "cycleId": "uuid"
  }
}

Errori

StatoCodiceSignificato
400INVALID_BODYCampo non previsto o valore non valido.
404CONSENT_NOT_FOUNDconsentId o subjectConsentId non esiste o non è di questo paziente.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).
403VISITS_DISABLEDLa funzione visita non è attiva per questo account (o per la struttura per cui lavori): letture ed eliminazioni restano disponibili.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.

PATCH /patients/{patientId}/visits/{visitId}

Modifica una visita

Modifica motivo, patologia, note, soggetto e il segno di prima visita.

Permesso: visits:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
visitIdpathstringsìId della visita. Esempio: d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
reasonstringno(max 200 caratteri)
reasonIdstring (uuid) | nullno
pathologystringno(max 2000 caratteri)
notesstringno(max 5000 caratteri)
subjectConsentIdstring (uuid) | nullno
isFirstVisitbooleanno

Esempio: Aggiungi una nota

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/visits/d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "notes": "Migliora la mobilità."
}'

Risposta 200

{
  "patientId": "uuid",
  "visit": {
    "id": "uuid"
  }
}

Errori

StatoCodiceSignificato
400INVALID_BODYCorpo vuoto o valore non valido.
404VISIT_NOT_FOUNDLa visita non esiste o non è di questo paziente.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).
403VISITS_DISABLEDLa funzione visita non è attiva per questo account (o per la struttura per cui lavori): letture ed eliminazioni restano disponibili.

DELETE /patients/{patientId}/visits/{visitId}

Elimina una visita

Elimina la visita, come in dashboard.

Permesso: visits:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
visitIdpathstringsìId della visita. Esempio: d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/visits/d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "success": true
}

Errori

StatoCodiceSignificato
404VISIT_NOT_FOUNDLa visita non esiste o non è di questo paziente.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

GET /patients/{patientId}/cycles

Cicli di trattamento

Restituisce i cicli di trattamento del paziente, con il numero di visite di ciascuno.

Permesso: visits:read

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/cycles" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "patientId": "uuid",
  "data": [
    {
      "id": "uuid",
      "title": "Riabilitazione spalla",
      "isActive": true,
      "isMinor": false,
      "isTutelato": false,
      "startedAt": "2026-09-02T09:00:00.000Z",
      "visitsCount": 4
    }
  ]
}

Errori

StatoCodiceSignificato
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

PATCH /patients/{patientId}/cycles/{cycleId}

Modifica un ciclo

Cambia il titolo del percorso o lo chiude (isActive: false).

Permesso: visits:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
cycleIdpathstringsìId del ciclo di trattamento. Esempio: e2f3a4b5-c6d7-4e8f-9a0b-1c2d3e4f5a6b.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
titlestringno(max 200 caratteri)
isActivebooleanno

Esempio: Chiudi il ciclo

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/cycles/e2f3a4b5-c6d7-4e8f-9a0b-1c2d3e4f5a6b" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "isActive": false
}'

Risposta 200

{
  "patientId": "uuid",
  "cycle": {
    "id": "uuid",
    "title": "Riabilitazione spalla",
    "isActive": false,
    "startedAt": "2026-09-02T09:00:00.000Z"
  }
}

Errori

StatoCodiceSignificato
400INVALID_BODYCorpo vuoto o valore non valido.
404CYCLE_NOT_FOUNDIl ciclo non esiste o non è di questo paziente.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).
403VISITS_DISABLEDLa funzione visita non è attiva per questo account (o per la struttura per cui lavori): letture ed eliminazioni restano disponibili.

DELETE /patients/{patientId}/cycles/{cycleId}

Elimina un ciclo

Elimina il ciclo di trattamento. Cancellare un ciclo cancella anche le sue visite.

Permesso: visits:write

Parametri

NomeDoveTipoObbligatorioDescrizione
patientIdpathstringsìId del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e.
cycleIdpathstringsìId del ciclo di trattamento. Esempio: e2f3a4b5-c6d7-4e8f-9a0b-1c2d3e4f5a6b.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/patients/8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e/cycles/e2f3a4b5-c6d7-4e8f-9a0b-1c2d3e4f5a6b" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "success": true
}

Errori

StatoCodiceSignificato
404CYCLE_NOT_FOUNDIl ciclo non esiste o non è di questo paziente.
404PATIENT_NOT_FOUNDIl paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta).

Anamnesi

Le risposte complete dell'anamnesi raccolta alla firma.

GET /consents/{id}/anamnesis

Anamnesi completa di un consenso

Restituisce l'anamnesi completa raccolta alla firma del consenso: risposte decifrate, risposte positive già etichettate, flag di rischio con la loro etichetta, fototipo e semaforo sui trattamenti di quel consenso. Copre l'anamnesi medica (sanità, se attiva sull'account), quella estetica (centri estetici e account ibridi) e le domande personalizzate delle strutture «Associazione». Sono dati sanitari: ogni lettura finisce nel Registro attività, come la consultazione dalla dashboard.

Permesso: anamnesis:read (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Campi della risposta

  • anamnesis.kind: MEDICAL o AESTHETIC. null se sul consenso non c'è un'anamnesi strutturata.
  • anamnesis.answers: Tutte le risposte, con chiave l'id interno della domanda: { value, detail?, answeredAt }.
  • anamnesis.positives: Le sole risposte positive ancora valide, già con l'etichetta della domanda.
  • anamnesis.severity: GREEN, YELLOW o RED sui trattamenti del consenso; evaluation.findings dice perché.
  • customQuestions: Le domande personalizzate com'erano quando il paziente ha risposto, con la risposta.

Da sapere

  • Legge solo i consensi di prova con una chiave Test: un consenso vero risponde 404 CONSENT_NOT_FOUND, come un id inesistente.
  • Permesso delicato: anamnesis:read non è mai preselezionato alla creazione della chiave.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/anamnesis" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "consentId": "uuid",
  "collected": true,
  "anamnesis": {
    "kind": "MEDICAL",
    "collectedAt": "2026-09-16T09:12:00.000Z",
    "expiresAt": "2027-09-16T09:12:00.000Z",
    "severity": "YELLOW",
    "flags": [
      {
        "code": "MED_CARDIOPATIE",
        "label": "Cardiopatie"
      }
    ],
    "fototipo": "III",
    "positives": [
      {
        "id": "med-cardiopatie",
        "label": "Cardiopatie",
        "detail": "Aritmia in terapia"
      }
    ],
    "answers": {
      "med-cardiopatie": {
        "value": true,
        "detail": "Aritmia in terapia",
        "answeredAt": "2026-09-16T09:10:00.000Z"
      }
    }
  },
  "customQuestions": null
}

Errori

StatoCodiceSignificato
404ANAMNESIS_NOT_AVAILABLENessuna anamnesi è attiva sull'account (in dashboard la funzione non c'è).
404CONSENT_NOT_FOUNDIl consenso non esiste, non è di questo account, è fuori dal contesto di sede o, con una chiave Test, non è di prova (stessa risposta).

Struttura

Sedi, professionisti, inviti, esecutore generico, controfirme e statistiche.

GET /structure

Riepilogo della struttura

Restituisce il riepilogo della struttura: tipo di account, sedi, professionisti con «chi esegue cosa», esecutore generico per ambito, controfirme in attesa aggregate e se c'è una segreteria. Risponde anche al professionista singolo, con type: INDIVIDUAL e liste vuote.

Permesso: structure:read

Campi della risposta

  • type: CLINIC (struttura sanitaria), STUDIO (studio con collaboratori, per esempio tatuaggi) o INDIVIDUAL.
  • members[]: Professionisti (mai la segreteria): professioni, trattamenti abilitati, solo modelli assegnati, capacità tattoo, sedi, accesso.
  • secretary: { present, count }: se la struttura ha una segreteria. I dati della segreteria non escono.

Da sapere

  • Con una chiave Test le controfirme in attesa contano solo i consensi di prova.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/structure" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "type": "CLINIC",
  "structure": {
    "id": "uuid",
    "name": "Poliambulatorio Alfa",
    "label": "Poliambulatorio"
  },
  "sedi": [
    {
      "id": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d",
      "name": "Sede Padova",
      "address": "Via Roma 1",
      "city": "Padova",
      "cap": "35100",
      "province": "PD"
    }
  ],
  "members": [
    {
      "id": "uuid",
      "firstName": "Carla",
      "lastName": "Esecutrice",
      "role": "COLLABORATOR",
      "status": "ACTIVE",
      "relationshipType": "EMPLOYEE",
      "sedeIds": [
        "uuid-sede"
      ],
      "professions": [
        "PHYSIOTHERAPIST"
      ],
      "enabledTreatmentIds": null,
      "onlyAssignedModels": false,
      "tattooCapabilities": [],
      "access": {
        "hasAccess": false,
        "email": null,
        "linked": false,
        "pendingInvite": false
      },
      "canExecute": true
    }
  ],
  "genericExecutor": {
    "available": true,
    "enabled": true,
    "scopes": [
      "PSYCHOLOGIST"
    ],
    "text": null
  },
  "countersign": {
    "total": 0,
    "byMember": [],
    "bySede": []
  },
  "secretary": {
    "present": true,
    "count": 1
  }
}

GET /structure/sedi

Elenco delle sedi

Restituisce le sedi attive della struttura.

Permesso: structure:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/structure/sedi" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "type": "CLINIC",
  "sedi": [
    {
      "id": "uuid",
      "name": "Sede Verona",
      "address": "Via Roma 1",
      "city": "Verona",
      "cap": "37100",
      "province": "VR"
    }
  ]
}

Errori

StatoCodiceSignificato
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.

POST /structure/sedi

Aggiungi una sede

Aggiunge una sede con le regole della dashboard: nelle strutture sanitarie la gestisce solo il titolare, negli studi le sedi dello studio coincidono con quelle del profilo.

Permesso: structure:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
namestringsìObbligatorio, fino a 120 caratteri. (1-120 caratteri)
addressstringnoFacoltativi. Finiscono nei documenti firmati in quella sede. (max 200 caratteri)
citystringnoFacoltativi. Finiscono nei documenti firmati in quella sede. (max 120 caratteri)
capstringnoFacoltativi. Finiscono nei documenti firmati in quella sede. (max 10 caratteri)
provincestringnoFacoltativi. Finiscono nei documenti firmati in quella sede. (max 120 caratteri)

Da sapere

  • Permesso delicato: structure:write cambia chi può eseguire cosa e non è mai preselezionato alla creazione della chiave.

Esempio: Nuova sede

curl -X POST "https://api.consensosicuro.it/api/v1/account/structure/sedi" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "name": "Sede Verona",
  "address": "Via Roma 1",
  "city": "Verona",
  "cap": "37100",
  "province": "VR"
}'

Risposta 201

{
  "sede": {
    "id": "uuid",
    "name": "Sede Verona",
    "address": "Via Roma 1",
    "city": "Verona",
    "cap": "37100",
    "province": "VR"
  }
}

Errori

StatoCodiceSignificato
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

GET /structure/sedi/{sedeId}

Dettaglio di una sede

Restituisce una sede con i professionisti che ci lavorano.

Permesso: structure:read

Parametri

NomeDoveTipoObbligatorioDescrizione
sedeIdpathstringsìId della sede. Esempio: 5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/structure/sedi/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "sede": {
    "id": "uuid",
    "name": "Sede Verona",
    "address": "Via Roma 1",
    "city": "Verona",
    "cap": "37100",
    "province": "VR"
  },
  "members": [
    {
      "id": "uuid",
      "firstName": "Carla",
      "lastName": "Esecutrice",
      "role": "COLLABORATOR",
      "status": "ACTIVE",
      "relationshipType": "EMPLOYEE",
      "sedeIds": [
        "uuid-sede"
      ],
      "professions": [
        "PHYSIOTHERAPIST"
      ],
      "enabledTreatmentIds": null,
      "onlyAssignedModels": false,
      "tattooCapabilities": [],
      "access": {
        "hasAccess": false,
        "email": null,
        "linked": false,
        "pendingInvite": false
      },
      "canExecute": true
    }
  ]
}

Errori

StatoCodiceSignificato
404SEDE_NOT_FOUNDSede inesistente, disattivata o di un altro account (stessa risposta).
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.

PATCH /structure/sedi/{sedeId}

Modifica una sede

Modifica nome e indirizzo di una sede. Tutti i campi sono facoltativi, ma ne serve almeno uno.

Permesso: structure:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
sedeIdpathstringsìId della sede. Esempio: 5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
namestringno(1-120 caratteri)
addressstringno(max 200 caratteri)
citystringno(max 120 caratteri)
capstringno(max 10 caratteri)
provincestringno(max 120 caratteri)

Da sapere

  • Permesso delicato: structure:write cambia chi può eseguire cosa e non è mai preselezionato alla creazione della chiave.

Esempio: Nuovo indirizzo

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/structure/sedi/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "address": "Via Mazzini 12",
  "cap": "37121"
}'

Risposta 200

{
  "sede": {
    "id": "uuid",
    "name": "Sede Verona",
    "address": "Via Roma 1",
    "city": "Verona",
    "cap": "37100",
    "province": "VR"
  }
}

Errori

StatoCodiceSignificato
404SEDE_NOT_FOUNDSede inesistente o altrui.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /structure/sedi/{sedeId}

Disattiva una sede

Disattiva la sede come in dashboard (la storia dei consensi resta). Chi lavora anche in altre sedi perde solo questa. Chi lavora solo qui va spostato: nelle strutture sanitarie sulla sede indicata in reassignToSedeId (o sulla prima rimasta), negli studi la disattivazione si ferma con 409 SEDE_HAS_MEMBERS e l'elenco, finché non indichi reassignToSedeId. La segreteria rimasta senza sedi viene sospesa. L'ultima sede attiva non si toglie.

Permesso: structure:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
sedeIdpathstringsìId della sede. Esempio: 5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
reassignToSedeIdstringnoFacoltativo: sede attiva a cui spostare chi lavorava solo in questa.

Da sapere

  • Permesso delicato: structure:write cambia chi può eseguire cosa e non è mai preselezionato alla creazione della chiave.

Esempio: Sposta chi lavorava solo qui

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/structure/sedi/5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "reassignToSedeId": "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d"
}'

Risposta 200

{
  "success": true,
  "sedeId": "uuid",
  "deactivated": true,
  "suspendedSecretaries": []
}

Errori

StatoCodiceSignificato
404SEDE_NOT_FOUNDSede (o reassignToSedeId) inesistente, disattivata o di un altro account.
409SEDE_HAS_MEMBERSStudi: alcuni professionisti lavorano solo qui; members li elenca.
400LAST_SEDEÈ l'ultima sede attiva.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /structure/members

Elenco dei professionisti

Restituisce i professionisti della struttura, con «chi esegue cosa» e l'accesso. La segreteria non compare.

Permesso: structure:read

Parametri

NomeDoveTipoObbligatorioDescrizione
sedeIdquerystringnoSolo i professionisti di quella sede.
statusqueryACTIVE | SUSPENDED | PENDINGnoStato del professionista.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/structure/members" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "type": "CLINIC",
  "members": [
    {
      "id": "uuid",
      "firstName": "Carla",
      "lastName": "Esecutrice",
      "role": "COLLABORATOR",
      "status": "ACTIVE",
      "relationshipType": "EMPLOYEE",
      "sedeIds": [
        "uuid-sede"
      ],
      "professions": [
        "PHYSIOTHERAPIST"
      ],
      "enabledTreatmentIds": null,
      "onlyAssignedModels": false,
      "tattooCapabilities": [],
      "access": {
        "hasAccess": false,
        "email": null,
        "linked": false,
        "pendingInvite": false
      },
      "canExecute": true
    }
  ]
}

Errori

StatoCodiceSignificato
400INVALID_QUERYStato non valido.
404SEDE_NOT_FOUNDSede altrui.
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.

POST /structure/members

Aggiungi un professionista senza accesso

Crea un professionista senza accesso (un'anagrafica che esegue, come in dashboard): per dargli un accesso si usa un invito. Nelle strutture sanitarie si indicano professioni, trattamenti abilitati e «solo modelli assegnati»; negli studi tatuaggi telefono (per la controfirma) e capacità, e al professionista parte il link per confermare il numero (al massimo 10 link al giorno per account).

Permesso: structure:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

Struttura sanitaria

CampoTipoObbligatorioDescrizione
firstNamestringsìObbligatori. (1-100 caratteri)
lastNamestringsìObbligatori. (1-100 caratteri)
fiscalCodestringnoStrutture sanitarie: dati che intestano i documenti. (max 32 caratteri)
professionalRegisterstringnoStrutture sanitarie: dati che intestano i documenti. (max 200 caratteri)
specializationstringnoStrutture sanitarie: dati che intestano i documenti. (max 200 caratteri)
partitaIvastringnoStrutture sanitarie: dati che intestano i documenti. (max 32 caratteri)
relationshipType"EMPLOYEE" | "EXTERNAL_COLLABORATOR"sìEMPLOYEE o EXTERNAL_COLLABORATOR (obbligatorio nelle strutture sanitarie).
role"COLLABORATOR" | "REFERENTE"noStrutture sanitarie: COLLABORATOR (predefinito) o REFERENTE di sede.
sedeIdsarray<string>noSedi della struttura in cui lavora (obbligatorio negli studi). Una sede altrui è un 404. (1-50 elementi)
professionsarray<string>noStrutture sanitarie: ambiti che esegue, tra quelli della struttura (vuoto = tutti). Un ambito che la struttura non ha è 400 PROFESSION_NOT_IN_SCOPES. (max 20 elementi)
enabledTreatmentIdsarray<string>noStrutture sanitarie: trattamenti che può eseguire, dal catalogo delle sue professioni (vuoto = tutti). (max 500 elementi)
onlyAssignedModelsbooleannoStrutture sanitarie: true = invia solo i modelli a lui associati.

Studio

CampoTipoObbligatorioDescrizione
firstNamestringsìObbligatori. (1-80 caratteri)
lastNamestringsìObbligatori. (1-80 caratteri)
phonestringsìStudi: obbligatorio, è il numero su cui arriva il link di controfirma. (6-30 caratteri)
contactEmailstring (email) | nullnoStudi: email di contatto (non di accesso), sede fisica del documento, canale del link di verifica.
sedeIdsarray<string>sìSedi della struttura in cui lavora (obbligatorio negli studi). Una sede altrui è un 404. (1-20 elementi)
officeIdstring (uuid) | nullnoStudi: email di contatto (non di accesso), sede fisica del documento, canale del link di verifica.
tattooCapabilitiesarray<"TATTOO" | "PMU" | "PIERCING">noStudi tatuaggi: TATTOO, PMU, PIERCING tra quelle dello studio. (max 3 elementi)
relationshipType"EMPLOYEE" | "EXTERNAL_COLLABORATOR"noEMPLOYEE o EXTERNAL_COLLABORATOR (obbligatorio nelle strutture sanitarie).
channel"WHATSAPP" | "SMS"noStudi: email di contatto (non di accesso), sede fisica del documento, canale del link di verifica.

Da sapere

  • Permesso delicato: structure:write cambia chi può eseguire cosa e non è mai preselezionato alla creazione della chiave.
  • Il corpo segue il tipo di account: le strutture sanitarie usano la prima variante, gli studi la seconda.

Esempio: Struttura sanitaria

curl -X POST "https://api.consensosicuro.it/api/v1/account/structure/members" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "firstName": "Paola",
  "lastName": "Fisio",
  "relationshipType": "EMPLOYEE",
  "sedeIds": [
    "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d"
  ],
  "professions": [
    "PHYSIOTHERAPIST"
  ],
  "enabledTreatmentIds": [
    "fis-terapia-manuale"
  ],
  "onlyAssignedModels": false
}'

Esempio: Studio tatuaggi

curl -X POST "https://api.consensosicuro.it/api/v1/account/structure/members" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "firstName": "Tino",
  "lastName": "Tatuatore",
  "phone": "+393331234567",
  "sedeIds": [
    "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d"
  ],
  "tattooCapabilities": [
    "TATTOO"
  ]
}'

Risposta 201

{
  "member": {
    "id": "uuid",
    "firstName": "Carla",
    "lastName": "Esecutrice",
    "role": "COLLABORATOR",
    "status": "ACTIVE",
    "relationshipType": "EMPLOYEE",
    "sedeIds": [
      "uuid-sede"
    ],
    "professions": [
      "PHYSIOTHERAPIST"
    ],
    "enabledTreatmentIds": null,
    "onlyAssignedModels": false,
    "tattooCapabilities": [],
    "access": {
      "hasAccess": false,
      "email": null,
      "linked": false,
      "pendingInvite": false
    },
    "canExecute": true
  }
}

Errori

StatoCodiceSignificato
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
400PROFESSION_NOT_IN_SCOPESProfessioni fuori dagli ambiti della struttura; rejected le elenca.
400TREATMENT_NOT_IN_SCOPESTrattamenti che non sono nel catalogo delle professioni del professionista.
403ACCESS_NOT_MANAGED_VIA_APIIl corpo contiene email di accesso o attivazione dell'utenza: si passa da POST /structure/invites.
404SEDE_NOT_FOUNDUna delle sedi non è di questa struttura.
404STUDIO_TATTOO_ONLYStudio che in dashboard non gestisce professionisti senza accesso (per esempio un centro estetico).
429VERIFICATION_LIMITStudi: raggiunto il limite giornaliero di link di verifica del telefono.
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
404OFFICE_NOT_FOUNDStudi: officeId non è una sede fisica di questo account.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

GET /structure/members/{memberId}

Dettaglio di un professionista

Restituisce un professionista nella stessa forma dell'elenco.

Permesso: structure:read

Parametri

NomeDoveTipoObbligatorioDescrizione
memberIdpathstringsìId del professionista della struttura. Esempio: 6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/structure/members/6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "member": {
    "id": "uuid",
    "firstName": "Carla",
    "lastName": "Esecutrice",
    "role": "COLLABORATOR",
    "status": "ACTIVE",
    "relationshipType": "EMPLOYEE",
    "sedeIds": [
      "uuid-sede"
    ],
    "professions": [
      "PHYSIOTHERAPIST"
    ],
    "enabledTreatmentIds": null,
    "onlyAssignedModels": false,
    "tattooCapabilities": [],
    "access": {
      "hasAccess": false,
      "email": null,
      "linked": false,
      "pendingInvite": false
    },
    "canExecute": true
  }
}

Errori

StatoCodiceSignificato
404MEMBER_NOT_FOUNDProfessionista inesistente, rimosso o di un altro account (stessa risposta).
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.

PATCH /structure/members/{memberId}

Modifica un professionista

Cambia «chi esegue cosa», sedi, stato (ACTIVE / SUSPENDED) e dati dei documenti. Di chi ha un accesso (utenza collegata o invito aperto) nome, codice fiscale e, negli studi, telefono ed email restano della dashboard. Negli studi la sospensione segue le regole della dashboard: con controfirme in attesa chiede prima a chi passarle (409). Negli studi un cambio di numero manda un nuovo link di verifica (al massimo 5 al giorno per professionista).

Permesso: structure:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
memberIdpathstringsìId del professionista della struttura. Esempio: 6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e.

Corpo della richiesta

Struttura sanitaria

CampoTipoObbligatorioDescrizione
firstNamestringno(1-100 caratteri)
lastNamestringno(1-100 caratteri)
fiscalCodestringno(max 32 caratteri)
professionalRegisterstringno(max 200 caratteri)
specializationstringno(max 200 caratteri)
partitaIvastringno(max 32 caratteri)
relationshipType"EMPLOYEE" | "EXTERNAL_COLLABORATOR"no
role"COLLABORATOR" | "REFERENTE"no
sedeIdsarray<string>no(1-50 elementi)
professionsarray<string>no(max 20 elementi)
enabledTreatmentIdsarray<string>no(max 500 elementi)
onlyAssignedModelsbooleanno
status"ACTIVE" | "SUSPENDED"noACTIVE o SUSPENDED.

Studio

CampoTipoObbligatorioDescrizione
firstNamestringno(1-80 caratteri)
lastNamestringno(1-80 caratteri)
phonestringno(6-30 caratteri)
contactEmailstring (email) | nullno
sedeIdsarray<string>no(1-20 elementi)
officeIdstring (uuid) | nullno
tattooCapabilitiesarray<"TATTOO" | "PMU" | "PIERCING">no(max 3 elementi)
relationshipType"EMPLOYEE" | "EXTERNAL_COLLABORATOR"no
channel"WHATSAPP" | "SMS"no
status"ACTIVE" | "SUSPENDED"noACTIVE o SUSPENDED.
countersignDigestTimestring | nullnoStudi: preferenze di controfirma del professionista. (formato ^([01]\d|2[0-3]):(00|15|30|45)$)
countersignDigestEnabledbooleannoStudi: preferenze di controfirma del professionista.
countersignRequireCodebooleannoStudi: preferenze di controfirma del professionista.

Da sapere

  • Permesso delicato: structure:write cambia chi può eseguire cosa e non è mai preselezionato alla creazione della chiave.
  • Gli altri campi sono gli stessi della creazione, tutti facoltativi.

Esempio: Solo modelli assegnati

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/structure/members/6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "onlyAssignedModels": true
}'

Risposta 200

{
  "member": {
    "id": "uuid",
    "firstName": "Carla",
    "lastName": "Esecutrice",
    "role": "COLLABORATOR",
    "status": "ACTIVE",
    "relationshipType": "EMPLOYEE",
    "sedeIds": [
      "uuid-sede"
    ],
    "professions": [
      "PHYSIOTHERAPIST"
    ],
    "enabledTreatmentIds": null,
    "onlyAssignedModels": false,
    "tattooCapabilities": [],
    "access": {
      "hasAccess": false,
      "email": null,
      "linked": false,
      "pendingInvite": false
    },
    "canExecute": true
  }
}

Errori

StatoCodiceSignificato
404MEMBER_NOT_FOUNDProfessionista inesistente o di un altro account.
409MEMBER_REVOKEDIl professionista è stato rimosso dall'organico: non si modifica né si riattiva.
403MEMBER_IDENTITY_READ_ONLYNome, codice fiscale (e negli studi telefono ed email) di un professionista che ha un accesso.
400PROFESSION_NOT_IN_SCOPESProfessioni fuori dagli ambiti della struttura.
400TREATMENT_NOT_IN_SCOPESTrattamenti fuori dal catalogo delle professioni risultanti.
409MEMBER_HAS_PENDING_COUNTERSIGNStudi: sospensione con controfirme in attesa.
429VERIFICATION_LIMITStudi: troppi link di verifica oggi per questo professionista.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
404OFFICE_NOT_FOUNDStudi: officeId non è una sede fisica di questo account.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /structure/members/{memberId}

Rimuovi un professionista

Rimuove il professionista dall'organico come in dashboard (stato REVOKED, i consensi eseguiti restano suoi). Negli studi tatuaggi, con controfirme in attesa, serve reassignTo con il professionista che le prende.

Permesso: structure:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
memberIdpathstringsìId del professionista della struttura. Esempio: 6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e.
reassignToquerystringnoStudi: id del professionista a cui passano le controfirme in attesa. Si può mandare anche nel corpo JSON.

Da sapere

  • Permesso delicato: structure:write cambia chi può eseguire cosa e non è mai preselezionato alla creazione della chiave.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/structure/members/6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "success": true,
  "memberId": "uuid",
  "status": "REVOKED"
}

Errori

StatoCodiceSignificato
404MEMBER_NOT_FOUNDProfessionista (o reassignTo) inesistente, rimosso o altrui.
409MEMBER_HAS_PENDING_COUNTERSIGNStudi: controfirme in attesa, serve reassignTo.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /structure/invites

Inviti in attesa

Restituisce gli inviti con accesso che aspettano di essere accettati.

Permesso: structure:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/structure/invites" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "invites": [
    {
      "id": "uuid",
      "email": "[email protected]",
      "name": "Irene Invitata",
      "role": "REFERENTE",
      "status": "PENDING",
      "sedeIds": [
        "uuid-sede"
      ],
      "invitedAt": "2026-09-23T09:00:00.000Z"
    }
  ]
}

Errori

StatoCodiceSignificato
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.

POST /structure/invites

Invita un collaboratore con accesso

Invita un collaboratore con accesso: parte l'email con il link per creare o collegare l'account. Nelle strutture sanitarie l'invito porta con sé l'anagrafica del medico (stessi campi di POST /structure/members più email); negli studi email, nome, ruolo e capacità. Tetti: 10 inviti al giorno per account e 3 allo stesso indirizzo (giornata di Roma). Non si invita l'indirizzo del titolare, né chi è già un professionista della struttura; un invito ancora in attesa per lo stesso indirizzo è un 409 con il suo id.

Permesso: structure:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

Struttura sanitaria

CampoTipoObbligatorioDescrizione
firstNamestringsì(1-100 caratteri)
lastNamestringsì(1-100 caratteri)
fiscalCodestringno(max 32 caratteri)
professionalRegisterstringno(max 200 caratteri)
specializationstringno(max 200 caratteri)
partitaIvastringno(max 32 caratteri)
relationshipType"EMPLOYEE" | "EXTERNAL_COLLABORATOR"sì
role"COLLABORATOR" | "REFERENTE"noCOLLABORATOR o REFERENTE.
sedeIdsarray<string>no(1-50 elementi)
professionsarray<string>no(max 20 elementi)
enabledTreatmentIdsarray<string>no(max 500 elementi)
onlyAssignedModelsbooleanno
emailstring (email)sìObbligatoria: è l'indirizzo a cui parte l'invito.

Studio

CampoTipoObbligatorioDescrizione
emailstring (email)sìObbligatoria: è l'indirizzo a cui parte l'invito.
namestringsìStudi: nome di chi inviti. (1-160 caratteri)
role"COLLABORATOR" | "REFERENTE"noCOLLABORATOR o REFERENTE.
officeIdstring (uuid)no
tattooCapabilitiesarray<"TATTOO" | "PMU" | "PIERCING">no(max 3 elementi)

Da sapere

  • Permesso delicato: structure:write cambia chi può eseguire cosa e non è mai preselezionato alla creazione della chiave.

Esempio: Struttura sanitaria

curl -X POST "https://api.consensosicuro.it/api/v1/account/structure/invites" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "firstName": "Irene",
  "lastName": "Invitata",
  "email": "[email protected]",
  "relationshipType": "EMPLOYEE",
  "role": "REFERENTE",
  "sedeIds": [
    "5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d"
  ]
}'

Esempio: Studio

curl -X POST "https://api.consensosicuro.it/api/v1/account/structure/invites" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "email": "[email protected]",
  "name": "Collega Invitato",
  "tattooCapabilities": [
    "PIERCING"
  ]
}'

Risposta 201

{
  "invite": {
    "id": "uuid",
    "email": "[email protected]",
    "name": "Irene Invitata",
    "role": "REFERENTE",
    "status": "PENDING",
    "sedeIds": [
      "uuid-sede"
    ],
    "invitedAt": "2026-09-23T09:00:00.000Z"
  }
}

Errori

StatoCodiceSignificato
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
409DUPLICATE_EMAILC'è già un invito in attesa per questo indirizzo; inviteId è il suo id.
409MEMBER_ALREADY_PRESENTL'indirizzo è già di un professionista della struttura.
409INVITE_OWN_EMAILÈ l'indirizzo del titolare dell'account.
429INVITE_LIMITRaggiunti i 10 inviti di oggi.
429INVITE_LIMIT_ADDRESSQuesto indirizzo ha già ricevuto 3 inviti oggi.
400PROFESSION_NOT_IN_SCOPESStrutture sanitarie: professioni fuori dagli ambiti.
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
404OFFICE_NOT_FOUNDStudi: officeId non è una sede fisica di questo account.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

DELETE /structure/invites/{inviteId}

Revoca un invito

Revoca un invito non ancora accettato: il link smette di funzionare.

Permesso: structure:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
inviteIdpathstringsìId dell'invito. Esempio: 7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f.

Da sapere

  • Permesso delicato: structure:write cambia chi può eseguire cosa e non è mai preselezionato alla creazione della chiave.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/structure/invites/7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "success": true,
  "inviteId": "uuid",
  "status": "REVOKED"
}

Errori

StatoCodiceSignificato
404INVITE_NOT_FOUNDNon c'è un invito in attesa con quell'id in questa struttura.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /structure/generic-executor

Esecutore generico per ambito

Restituisce la configurazione dell'invio «senza professionista nominato» per ambito, che esiste solo nelle strutture sanitarie: il consenso esce intestato alla struttura con una qualifica al posto del medico. Il tatuaggio è escluso per legge.

Permesso: structure:read

Campi della risposta

  • genericExecutor.eligibleScopes: Ambiti ammessi, ciascuno col testo salvato e quello suggerito.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/structure/generic-executor" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "genericExecutor": {
    "available": true,
    "enabled": true,
    "scopes": [
      "PSYCHOLOGIST"
    ],
    "eligibleScopes": [
      {
        "scope": "PSYCHOLOGIST",
        "label": "Psicologia",
        "text": null,
        "suggestedText": "…"
      }
    ],
    "text": null,
    "texts": {}
  }
}

Errori

StatoCodiceSignificato
404GENERIC_EXECUTOR_NOT_AVAILABLEStudi: l'esecutore controfirma, la modalità non esiste.
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.

PUT /structure/generic-executor

Imposta l'esecutore generico

Accende o spegne la modalità «senza professionista nominato» e ne imposta ambiti e testi, con le regole del profilo della dashboard.

Permesso: structure:write (delicato) · delicato

Corpo della richiesta

CampoTipoObbligatorioDescrizione
enabledbooleansìObbligatorio.
scopesarray<string>noAmbiti abilitati (vuoto = tutti quelli ammessi). (max 30 elementi)
textsobjectnoTesto per ambito: { "PSYCHOLOGIST": "Psicologo iscritto all'Albo" }, fino a 1500 caratteri.
textstringnoTesto di ripiego per gli ambiti senza un testo proprio. (max 1500 caratteri)

Da sapere

  • Permesso delicato: structure:write cambia chi può eseguire cosa e non è mai preselezionato alla creazione della chiave.

Esempio: Un ambito

curl -X PUT "https://api.consensosicuro.it/api/v1/account/structure/generic-executor" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": true,
  "scopes": [
    "PSYCHOLOGIST"
  ],
  "texts": {
    "PSYCHOLOGIST": "Psicologo iscritto all'\''Albo della struttura"
  }
}'

Risposta 200

{
  "genericExecutor": {
    "available": true,
    "enabled": true,
    "scopes": [
      "PSYCHOLOGIST"
    ],
    "texts": {
      "PSYCHOLOGIST": "Psicologo iscritto all'Albo della struttura"
    }
  }
}

Errori

StatoCodiceSignificato
400GENERIC_EXECUTOR_SCOPE_INVALIDAmbito non della struttura o escluso (tatuaggio).
404GENERIC_EXECUTOR_NOT_AVAILABLEStudi: la modalità non esiste.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /structure/countersigns/pending

Controfirme in attesa

Restituisce i consensi firmati dal cliente che aspettano la controfirma dell'esecutore (tatuaggi e piercing), per professionista e per sede: lo stesso riquadro della dashboard.

Permesso: structure:read

Da sapere

  • Con una chiave Test contano solo i consensi di prova.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/structure/countersigns/pending" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "total": 3,
  "byMember": [
    {
      "memberId": "uuid",
      "professionalId": "uuid",
      "name": "Tino Tatuatore",
      "count": 3,
      "oldestAt": "2026-09-20T18:00:00.000Z",
      "oldestConsentId": "uuid"
    }
  ],
  "bySede": [
    {
      "sedeId": "uuid",
      "count": 3
    }
  ]
}

Errori

StatoCodiceSignificato
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.

GET /structure/stats

Statistiche della struttura

Restituisce la panoramica storica per sede e professionista della pagina Struttura, più le statistiche della dashboard con i filtri richiesti (e, nelle strutture sanitarie, quelle per medico).

Permesso: structure:read

Parametri

NomeDoveTipoObbligatorioDescrizione
sedeIdquerystringnoSolo quella sede (di questa struttura). In conflitto con X-Structure-Sede-Id è 403.
memberIdquerystringnoSolo quel professionista.
fromquerystringnoInizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01.
toquerystringnoFine del periodo (AAAA-MM-GG): comprende tutta la giornata. Esempio: 2026-09-30.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/structure/stats" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "type": "CLINIC",
  "filters": {
    "sedeId": null,
    "memberId": null,
    "from": "2026-09-01",
    "to": "2026-09-30T23:59:59.999Z"
  },
  "overview": {
    "totalConsents": 130,
    "completedConsents": 118,
    "sedi": [
      {
        "sedeId": "uuid",
        "sedeName": "Sede Padova",
        "consents": 80
      }
    ]
  },
  "statistics": {
    "overview": {
      "totalConsents": 42
    }
  },
  "members": {
    "members": []
  }
}

Errori

StatoCodiceSignificato
400INVALID_QUERYData non valida o periodo invertito.
404SEDE_NOT_FOUNDSede di un altro account.
404MEMBER_NOT_FOUNDProfessionista di un altro account.
403SEDE_CONTEXT_MISMATCHsedeId in contrasto con l'intestazione X-Structure-Sede-Id.
404NOT_A_STRUCTUREL'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi.

Configurazione

Modelli, trattamenti abilitati, informativa, incarico, lingue, interruttori e moduli Agorà.

GET /configurations

Elenco dei modelli

Restituisce i modelli di consenso dell'account con il limite dei modelli personalizzati: 10, oppure 30 per le strutture. I moduli Agorà non contano.

Permesso: templates:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/configurations" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "configurations": [
    {
      "id": "ck…",
      "name": "Prima visita",
      "source": "CUSTOM",
      "mode": "SPECIFIC",
      "professionType": "PSYCHOLOGIST",
      "treatments": [
        "psi-colloquio",
        "psi-valutazione"
      ],
      "isDefault": true,
      "dualSignatureEnabled": false,
      "medicalClearanceEnabled": null,
      "sendMethod": "WHATSAPP",
      "memberIds": [],
      "validityMonths": null,
      "autoRenewReminder": false
    }
  ],
  "limits": {
    "customModels": 10
  }
}

POST /configurations

Crea un modello

Crea un modello con tutte le validazioni della dashboard: un modello sbagliato bloccherebbe gli invii, quindi qui non passa. Nessun valore viene corretto in silenzio: professionisti non ammessi o doppia firma non prevista sono errori.

Permesso: configurations:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
namestringsìNome interno del modello, da 1 a 50 caratteri. Non lo vede il paziente. (1-50 caratteri)
mode"STANDARD" | "SPECIFIC"sìSTANDARD (consenso generico della professione) o SPECIFIC (trattamenti elencati).
source"CUSTOM" | "AGORA" | "PRIVATE"noCUSTOM (predefinito). Per i moduli Agorà usa POST /agora/{code}/activate.
professionType"OSTEOPATH" | "PHYSIOTHERAPIST" | "CHIROPRACTOR" | "PODIATRIST" | "PSYCHOLOGIST" | "PSYCHOTHERAPIST" | "PSYCHIATRIST" | "NUTRITIONIST" | "SPEECH_THERAPIST" | "AESTHETIC_DOCTOR" | "COMPLEMENTARY_MEDICINE" | "OCCUPATIONAL_THERAPIST" | "TNPEE" | "GYNECOLOGIST" | "DERMATOLOGIST" | "BEAUTICIAN" | "TATTOO_PIERCER" | "DENTIST" | "OPHTHALMOLOGIST" | "DENTAL_HYGIENIST" | "NURSE" | "OTHER" | nullnoProfessione (o ambito della struttura) del modello: una di quelle in GET /templates.
treatmentsarray<string>noCodici dei trattamenti, dal catalogo della stessa professione. Obbligatori con SPECIFIC. (max 300 elementi, predefinito [])
dualSignatureEnabledbooleannoDoppia firma genitoriale pre-attivata. Su una professione (o trattamenti) che non la prevede è 400 DUAL_SIGNATURE_NOT_AVAILABLE. (predefinito false)
medicalClearanceEnabledboolean | nullnoInfermieri, estetiste e tatuatori: chiede prescrizione o nulla osta all'invio (null = automatico).
sendMethod"SMS" | "WHATSAPP" | nullnoWHATSAPP, SMS o null (vale la preferenza dell'account).
isDefaultbooleannotrue = il modello si apre da solo all'invio. Ce n'è uno solo: gli altri perdono il segno. (predefinito false)
memberIdsarray<string>noStrutture: professionisti attivi a cui il modello è associato, che esercitano il suo ambito (vuoto = tutti quelli dell'ambito). Un professionista rimosso, la segreteria o un altro ambito sono un errore, mai scartati in silenzio. (max 50 elementi, predefinito [])
validityMonthsinteger | nullnoConsensi a scadenza: validità in mesi (1-120) e rinnovo automatico 30 giorni prima. (da 1 a 120)
autoRenewReminderbooleannoConsensi a scadenza: validità in mesi (1-120) e rinnovo automatico 30 giorni prima.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: Modello specifico

curl -X POST "https://api.consensosicuro.it/api/v1/account/configurations" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "name": "Prima visita",
  "mode": "SPECIFIC",
  "professionType": "PSYCHOLOGIST",
  "treatments": [
    "psi-colloquio",
    "psi-valutazione"
  ],
  "isDefault": true
}'

Esempio: Consenso a scadenza

curl -X POST "https://api.consensosicuro.it/api/v1/account/configurations" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "name": "Laser annuale",
  "mode": "SPECIFIC",
  "professionType": "BEAUTICIAN",
  "treatments": [
    "bea-laser-diodo"
  ],
  "validityMonths": 12,
  "autoRenewReminder": true
}'

Risposta 201

{
  "configuration": {
    "id": "ck…",
    "name": "Prima visita",
    "source": "CUSTOM",
    "mode": "SPECIFIC",
    "professionType": "PSYCHOLOGIST",
    "treatments": [
      "psi-colloquio",
      "psi-valutazione"
    ],
    "isDefault": true,
    "dualSignatureEnabled": false,
    "medicalClearanceEnabled": null,
    "sendMethod": "WHATSAPP",
    "memberIds": [],
    "validityMonths": null,
    "autoRenewReminder": false
  }
}

Errori

StatoCodiceSignificato
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
400CONFIG_TEMPLATE_TYPE_NOT_ALLOWEDProfessione non esercitata dall'account.
400CONFIG_SPECIFIC_NO_TREATMENTSModello specifico senza trattamenti.
400CONFIG_INVALID_TREATMENTSTrattamenti che non sono del catalogo della professione.
400DUAL_SIGNATURE_NOT_AVAILABLEDoppia firma su una professione o su trattamenti che non la prevedono.
404MEMBER_NOT_FOUNDUn professionista di memberIds non esiste, non è attivo, è la segreteria o è di un altro account.
400MEMBER_PROFESSION_MISMATCHUn professionista di memberIds non esercita l'ambito del modello.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400CONFIG_MAX_REACHEDLimite di modelli raggiunto.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

GET /configurations/{configId}

Dettaglio di un modello

Restituisce un modello nella stessa forma dell'elenco.

Permesso: templates:read

Parametri

NomeDoveTipoObbligatorioDescrizione
configIdpathstringsìId del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/configurations/clx8k2m4p0001qz7h3v5n9b2d" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "configuration": {
    "id": "ck…",
    "name": "Prima visita",
    "source": "CUSTOM",
    "mode": "SPECIFIC",
    "professionType": "PSYCHOLOGIST",
    "treatments": [
      "psi-colloquio",
      "psi-valutazione"
    ],
    "isDefault": true,
    "dualSignatureEnabled": false,
    "medicalClearanceEnabled": null,
    "sendMethod": "WHATSAPP",
    "memberIds": [],
    "validityMonths": null,
    "autoRenewReminder": false
  }
}

Errori

StatoCodiceSignificato
404CONFIG_NOT_FOUNDModello inesistente o di un altro account (stessa risposta).

PUT /configurations/{configId}

Sostituisci un modello

Sostituisce il modello intero, come il salvataggio della dashboard: i campi facoltativi non inviati tornano al valore predefinito (nessun canale, nessuna scadenza, nessun rinnovo, documento medico automatico, nessun professionista associato). Per cambiare solo alcuni campi usa PATCH. Nei modelli Agorà e in quelli con il testo del centro vale come PATCH e si cambiano solo canale, predefinito, doppia firma, documento medico e professionisti.

Permesso: configurations:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
configIdpathstringsìId del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
namestringsìNome interno del modello, da 1 a 50 caratteri. Non lo vede il paziente. (1-50 caratteri)
mode"STANDARD" | "SPECIFIC"sìSTANDARD (consenso generico della professione) o SPECIFIC (trattamenti elencati).
source"CUSTOM" | "AGORA" | "PRIVATE"noCUSTOM (predefinito). Per i moduli Agorà usa POST /agora/{code}/activate.
professionType"OSTEOPATH" | "PHYSIOTHERAPIST" | "CHIROPRACTOR" | "PODIATRIST" | "PSYCHOLOGIST" | "PSYCHOTHERAPIST" | "PSYCHIATRIST" | "NUTRITIONIST" | "SPEECH_THERAPIST" | "AESTHETIC_DOCTOR" | "COMPLEMENTARY_MEDICINE" | "OCCUPATIONAL_THERAPIST" | "TNPEE" | "GYNECOLOGIST" | "DERMATOLOGIST" | "BEAUTICIAN" | "TATTOO_PIERCER" | "DENTIST" | "OPHTHALMOLOGIST" | "DENTAL_HYGIENIST" | "NURSE" | "OTHER" | nullnoProfessione (o ambito della struttura) del modello: una di quelle in GET /templates.
treatmentsarray<string>noCodici dei trattamenti, dal catalogo della stessa professione. Obbligatori con SPECIFIC. (max 300 elementi, predefinito [])
dualSignatureEnabledbooleannoDoppia firma genitoriale pre-attivata. Su una professione (o trattamenti) che non la prevede è 400 DUAL_SIGNATURE_NOT_AVAILABLE. (predefinito false)
medicalClearanceEnabledboolean | nullnoInfermieri, estetiste e tatuatori: chiede prescrizione o nulla osta all'invio (null = automatico).
sendMethod"SMS" | "WHATSAPP" | nullnoWHATSAPP, SMS o null (vale la preferenza dell'account).
isDefaultbooleannotrue = il modello si apre da solo all'invio. Ce n'è uno solo: gli altri perdono il segno. (predefinito false)
memberIdsarray<string>noStrutture: professionisti attivi a cui il modello è associato, che esercitano il suo ambito (vuoto = tutti quelli dell'ambito). Un professionista rimosso, la segreteria o un altro ambito sono un errore, mai scartati in silenzio. (max 50 elementi, predefinito [])
validityMonthsinteger | nullnoConsensi a scadenza: validità in mesi (1-120) e rinnovo automatico 30 giorni prima. (da 1 a 120)
autoRenewReminderbooleannoConsensi a scadenza: validità in mesi (1-120) e rinnovo automatico 30 giorni prima.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: Modello intero

curl -X PUT "https://api.consensosicuro.it/api/v1/account/configurations/clx8k2m4p0001qz7h3v5n9b2d" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Prima visita",
  "mode": "SPECIFIC",
  "professionType": "PSYCHOLOGIST",
  "treatments": [
    "psi-colloquio"
  ],
  "isDefault": true,
  "sendMethod": "WHATSAPP"
}'

Risposta 200

{
  "configuration": {
    "id": "ck…",
    "name": "Prima visita",
    "source": "CUSTOM",
    "mode": "SPECIFIC",
    "professionType": "PSYCHOLOGIST",
    "treatments": [
      "psi-colloquio",
      "psi-valutazione"
    ],
    "isDefault": true,
    "dualSignatureEnabled": false,
    "medicalClearanceEnabled": null,
    "sendMethod": "WHATSAPP",
    "memberIds": [],
    "validityMonths": null,
    "autoRenewReminder": false
  }
}

Errori

StatoCodiceSignificato
404CONFIG_NOT_FOUNDModello inesistente o di un altro account (stessa risposta).
400AGORA_CONFIG_LOCKEDModello Agorà: testo e identità non si cambiano.
400PRIVATE_CONFIG_LOCKEDModello col testo del centro: non si cambia da qui.
400CONFIG_SOURCE_IMMUTABLELa sorgente di un modello non si cambia.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
400CONFIG_TEMPLATE_TYPE_NOT_ALLOWEDProfessione non esercitata dall'account.
400CONFIG_SPECIFIC_NO_TREATMENTSModello specifico senza trattamenti.
400CONFIG_INVALID_TREATMENTSTrattamenti che non sono del catalogo della professione.
400DUAL_SIGNATURE_NOT_AVAILABLEDoppia firma su una professione o su trattamenti che non la prevedono.
404MEMBER_NOT_FOUNDUn professionista di memberIds non esiste, non è attivo, è la segreteria o è di un altro account.
400MEMBER_PROFESSION_MISMATCHUn professionista di memberIds non esercita l'ambito del modello.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

PATCH /configurations/{configId}

Modifica un modello

Cambia solo i campi inviati: tutti gli altri restano come sono salvati. Le validazioni valgono sul modello che risulterebbe. I campi sono gli stessi di POST /configurations, tutti facoltativi; ne serve almeno uno.

Permesso: configurations:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
configIdpathstringsìId del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
namestringnoNome interno del modello, da 1 a 50 caratteri. Non lo vede il paziente. (1-50 caratteri)
mode"STANDARD" | "SPECIFIC"noSTANDARD (consenso generico della professione) o SPECIFIC (trattamenti elencati).
source"CUSTOM" | "AGORA" | "PRIVATE"noCUSTOM (predefinito). Per i moduli Agorà usa POST /agora/{code}/activate.
professionType"OSTEOPATH" | "PHYSIOTHERAPIST" | "CHIROPRACTOR" | "PODIATRIST" | "PSYCHOLOGIST" | "PSYCHOTHERAPIST" | "PSYCHIATRIST" | "NUTRITIONIST" | "SPEECH_THERAPIST" | "AESTHETIC_DOCTOR" | "COMPLEMENTARY_MEDICINE" | "OCCUPATIONAL_THERAPIST" | "TNPEE" | "GYNECOLOGIST" | "DERMATOLOGIST" | "BEAUTICIAN" | "TATTOO_PIERCER" | "DENTIST" | "OPHTHALMOLOGIST" | "DENTAL_HYGIENIST" | "NURSE" | "OTHER" | nullnoProfessione (o ambito della struttura) del modello: una di quelle in GET /templates.
treatmentsarray<string>noCodici dei trattamenti, dal catalogo della stessa professione. Obbligatori con SPECIFIC. (max 300 elementi)
dualSignatureEnabledbooleannoDoppia firma genitoriale pre-attivata. Su una professione (o trattamenti) che non la prevede è 400 DUAL_SIGNATURE_NOT_AVAILABLE.
medicalClearanceEnabledboolean | nullnoInfermieri, estetiste e tatuatori: chiede prescrizione o nulla osta all'invio (null = automatico).
sendMethod"SMS" | "WHATSAPP" | nullnoWHATSAPP, SMS o null (vale la preferenza dell'account).
isDefaultbooleannotrue = il modello si apre da solo all'invio. Ce n'è uno solo: gli altri perdono il segno.
memberIdsarray<string>noStrutture: professionisti attivi a cui il modello è associato, che esercitano il suo ambito (vuoto = tutti quelli dell'ambito). Un professionista rimosso, la segreteria o un altro ambito sono un errore, mai scartati in silenzio. (max 50 elementi)
validityMonthsinteger | nullnoConsensi a scadenza: validità in mesi (1-120) e rinnovo automatico 30 giorni prima. (da 1 a 120)
autoRenewReminderbooleannoConsensi a scadenza: validità in mesi (1-120) e rinnovo automatico 30 giorni prima.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: Solo il canale

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/configurations/clx8k2m4p0001qz7h3v5n9b2d" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "sendMethod": "SMS"
}'

Risposta 200

{
  "configuration": {
    "id": "ck…",
    "name": "Prima visita",
    "source": "CUSTOM",
    "mode": "SPECIFIC",
    "professionType": "PSYCHOLOGIST",
    "treatments": [
      "psi-colloquio",
      "psi-valutazione"
    ],
    "isDefault": true,
    "dualSignatureEnabled": false,
    "medicalClearanceEnabled": null,
    "sendMethod": "WHATSAPP",
    "memberIds": [],
    "validityMonths": null,
    "autoRenewReminder": false
  }
}

Errori

StatoCodiceSignificato
404CONFIG_NOT_FOUNDModello inesistente o di un altro account (stessa risposta).
400AGORA_CONFIG_LOCKEDModello Agorà: testo e identità non si cambiano.
400PRIVATE_CONFIG_LOCKEDModello col testo del centro: non si cambia da qui.
400CONFIG_SOURCE_IMMUTABLELa sorgente di un modello non si cambia.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
400CONFIG_TEMPLATE_TYPE_NOT_ALLOWEDProfessione non esercitata dall'account.
400CONFIG_SPECIFIC_NO_TREATMENTSModello specifico senza trattamenti.
400CONFIG_INVALID_TREATMENTSTrattamenti che non sono del catalogo della professione.
400DUAL_SIGNATURE_NOT_AVAILABLEDoppia firma su una professione o su trattamenti che non la prevedono.
404MEMBER_NOT_FOUNDUn professionista di memberIds non esiste, non è attivo, è la segreteria o è di un altro account.
400MEMBER_PROFESSION_MISMATCHUn professionista di memberIds non esercita l'ambito del modello.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /configurations/{configId}

Elimina un modello

Elimina un modello. I consensi già inviati o programmati con quel modello restano e perdono solo il riferimento.

Permesso: configurations:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
configIdpathstringsìId del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/configurations/clx8k2m4p0001qz7h3v5n9b2d" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "success": true,
  "configurationId": "ck…"
}

Errori

StatoCodiceSignificato
404CONFIG_NOT_FOUNDModello inesistente o di un altro account (stessa risposta).
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

POST /configurations/{configId}/default

Rendi predefinito un modello

Rende il modello predefinito: gli altri perdono il segno.

Permesso: configurations:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
configIdpathstringsìId del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/configurations/clx8k2m4p0001qz7h3v5n9b2d/default" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "configuration": {
    "id": "ck…",
    "name": "Prima visita",
    "source": "CUSTOM",
    "mode": "SPECIFIC",
    "professionType": "PSYCHOLOGIST",
    "treatments": [
      "psi-colloquio",
      "psi-valutazione"
    ],
    "isDefault": true,
    "dualSignatureEnabled": false,
    "medicalClearanceEnabled": null,
    "sendMethod": "WHATSAPP",
    "memberIds": [],
    "validityMonths": null,
    "autoRenewReminder": false
  }
}

Errori

StatoCodiceSignificato
404CONFIG_NOT_FOUNDModello inesistente o di un altro account (stessa risposta).
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /configurations/{configId}/default

Togli il predefinito

Toglie il predefinito senza sceglierne un altro.

Permesso: configurations:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
configIdpathstringsìId del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/configurations/clx8k2m4p0001qz7h3v5n9b2d/default" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "configuration": {
    "id": "ck…",
    "name": "Prima visita",
    "source": "CUSTOM",
    "mode": "SPECIFIC",
    "professionType": "PSYCHOLOGIST",
    "treatments": [
      "psi-colloquio",
      "psi-valutazione"
    ],
    "isDefault": false,
    "dualSignatureEnabled": false,
    "medicalClearanceEnabled": null,
    "sendMethod": "WHATSAPP",
    "memberIds": [],
    "validityMonths": null,
    "autoRenewReminder": false
  }
}

Errori

StatoCodiceSignificato
404CONFIG_NOT_FOUNDModello inesistente o di un altro account (stessa risposta).
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /configurations/{configId}/preview

Anteprima di un modello

Restituisce l'anteprima del consenso (Doc 2) che firmerà il paziente con questo modello, senza dati del paziente. Si genera al momento e arriva come PDF nel corpo (con ?format=json in base64). È in italiano come in dashboard; per i testi in un'altra lingua attiva c'è GET /template-config/preview.

Permesso: templates:read

Parametri

NomeDoveTipoObbligatorioDescrizione
configIdpathstringsìId del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d.
formatquerypdf | jsonnopdf (predefinito) o json.
langqueryitnoSolo it. Un'altra lingua è 400 PREVIEW_LANGUAGE_NOT_AVAILABLE.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/configurations/clx8k2m4p0001qz7h3v5n9b2d/preview" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200 (application/pdf, oppure JSON con ?format=json)

{
  "fileName": "consenso-prima-visita.pdf",
  "contentType": "application/pdf",
  "sizeBytes": 48211,
  "contentBase64": "JVBERi0x…"
}

Errori

StatoCodiceSignificato
404CONFIG_NOT_FOUNDModello inesistente o di un altro account (stessa risposta).
400PREVIEW_LANGUAGE_NOT_AVAILABLEAnteprima di un modello in una lingua diversa dall'italiano.
400INVALID_QUERYFormato non valido.

GET /template-config

Trattamenti abilitati e impostazioni

Restituisce i trattamenti abilitati per professione (la preselezione all'invio senza modello), le impostazioni, i trattamenti personalizzati e le clausole privacy facoltative, che sono condivise fra le professioni e vivono sulla principale.

Permesso: templates:read

Parametri

NomeDoveTipoObbligatorioDescrizione
professionTypequerystringnoProfessione o ambito della riga (predefinito: la principale). Esempio: PSYCHOLOGIST.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/template-config" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "professionType": "PSYCHOLOGIST",
  "professions": [
    "PSYCHOLOGIST"
  ],
  "config": {
    "treatments": [
      "psi-colloquio"
    ],
    "settings": {},
    "customTreatments": [],
    "updatedAt": "2026-09-23T09:00:00.000Z"
  },
  "enabledOptionalConsents": [
    "consent-reminders"
  ],
  "availableTreatments": [
    {
      "code": "psi-colloquio",
      "label": "Colloquio clinico",
      "category": "Tipo di intervento",
      "source": "PSYCHOLOGIST"
    }
  ]
}

Errori

StatoCodiceSignificato
404PROFESSION_NOT_FOUNDProfessione non esercitata dall'account.

PUT /template-config

Aggiorna trattamenti e impostazioni

Scrive trattamenti abilitati, impostazioni, trattamenti personalizzati e clausole facoltative. Tutto è validato prima di scrivere.

Permesso: configurations:write (delicato) · delicato

Corpo della richiesta

CampoTipoObbligatorioDescrizione
professionTypestringnoRiga da scrivere (predefinito: la principale). (1-40 caratteri)
treatmentsarray<string>noCodici del catalogo della professione; custom-… per i personalizzati. (max 500 elementi)
settingsobjectnoSolo templateMode ("generic" o "specific") e customMode (booleano). Qualunque altra chiave è 400.
settings.templateMode"generic" | "specific"no
settings.customModebooleanno
customTreatmentsarray<object>no[{ id: "custom-…", label, description? }], al massimo 100, id diversi fra loro. (max 100 elementi)
customTreatments[].idstringsì(formato ^custom-[A-Za-z0-9_-]{1,60}$)
customTreatments[].labelstringsì(1-200 caratteri)
customTreatments[].descriptionstringno(max 2000 caratteri)
enabledOptionalConsentsarray<string>noClausole facoltative dell'informativa (id da GET /privacy-policy). (max 50 elementi)

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: Clausole e trattamenti

curl -X PUT "https://api.consensosicuro.it/api/v1/account/template-config" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "treatments": [
    "psi-colloquio"
  ],
  "enabledOptionalConsents": [
    "consent-reminders",
    "consent-marketing"
  ],
  "settings": {
    "templateMode": "specific"
  }
}'

Risposta 200

{
  "professionType": "PSYCHOLOGIST",
  "professions": [
    "PSYCHOLOGIST"
  ],
  "config": {
    "treatments": [
      "psi-colloquio"
    ],
    "settings": {
      "templateMode": "specific"
    },
    "customTreatments": [],
    "updatedAt": "2026-09-23T09:00:00.000Z"
  },
  "enabledOptionalConsents": [
    "consent-reminders",
    "consent-marketing"
  ],
  "availableTreatments": []
}

Errori

StatoCodiceSignificato
400UNKNOWN_OPTIONAL_CONSENTClausola facoltativa inesistente.
400DUPLICATE_CUSTOM_TREATMENTDue trattamenti personalizzati con lo stesso id.
404PROFESSION_NOT_FOUNDProfessione non esercitata dall'account.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /template-config/preview

Anteprima dei documenti dell'account

Restituisce l'anteprima dei documenti dell'account (1 = informativa privacy, 2 = consenso), anche in una lingua attiva: è la stessa della pagina «Il tuo consenso».

Permesso: templates:read

Parametri

NomeDoveTipoObbligatorioDescrizione
docquery1 | 2sì1 per l'informativa privacy, 2 per il consenso.
langqueryit | en | fr | denoit, en, fr o de. Le lingue diverse dall'italiano devono essere attive (GET /languages).
professionTypequerystringnoProfessione o ambito.
formatquerypdf | jsonnopdf (predefinito) o json.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/template-config/preview?doc=1" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200 (application/pdf, oppure JSON con ?format=json)

{
  "fileName": "anteprima-doc2.pdf",
  "contentType": "application/pdf",
  "sizeBytes": 51200,
  "contentBase64": "JVBERi0x…"
}

Errori

StatoCodiceSignificato
400INVALID_QUERYdoc mancante o diverso da 1 e 2, formato non valido.
400LANGUAGE_NOT_ACTIVELingua non attiva sull'account.

GET /privacy-policy

Informativa privacy e clausole facoltative

Restituisce l'informativa personalizzata dell'account, se c'è, le clausole facoltative attive con il loro catalogo e, per le strutture, se l'informativa si invia.

Permesso: templates:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/privacy-policy" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "customPolicy": null,
  "privacyInformativaEnabled": true,
  "privacyToggleAvailable": false,
  "enabledOptionalConsents": [
    "consent-reminders"
  ],
  "optionalConsents": [
    {
      "id": "consent-reminders",
      "label": "Promemoria",
      "description": "…",
      "informational": false
    }
  ]
}

PUT /privacy-policy

Scegli le clausole facoltative

Sceglie le clausole facoltative e, per le strutture, se inviare l'informativa. I testi di un'informativa personalizzata li carica ConsensoSicuro: non si scrivono né dalla dashboard né via API.

Permesso: configurations:write (delicato) · delicato

Corpo della richiesta

CampoTipoObbligatorioDescrizione
enabledOptionalConsentsarray<string>noClausole facoltative attive. (max 50 elementi)
privacyInformativaEnabledbooleannoSolo strutture: false = si invia il solo consenso.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: Due clausole

curl -X PUT "https://api.consensosicuro.it/api/v1/account/privacy-policy" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "enabledOptionalConsents": [
    "consent-reminders",
    "consent-photos"
  ]
}'

Risposta 200

{
  "customPolicy": null,
  "privacyInformativaEnabled": true,
  "privacyToggleAvailable": false,
  "enabledOptionalConsents": [
    "consent-photos"
  ],
  "optionalConsents": []
}

Errori

StatoCodiceSignificato
403CUSTOM_PRIVACY_ADMIN_ONLYIl corpo contiene testi dell'informativa (intro, sections, …).
400PRIVACY_TOGGLE_CLINIC_ONLYL'interruttore dell'informativa esiste solo per le strutture.
400UNKNOWN_OPTIONAL_CONSENTClausola inesistente.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /engagement

Lettera d'incarico

Restituisce la lettera d'incarico (sanità) o le condizioni di servizio (centri con un modello assegnato), cioè la configurazione della dashboard con il catalogo delle clausole. Le strutture la leggono per singolo medico con memberId.

Permesso: templates:read

Parametri

NomeDoveTipoObbligatorioDescrizione
memberIdquerystringnoStrutture: incarico di quel medico (la polizza resta quella della struttura).

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/engagement" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "enabled": true,
  "clauses": {},
  "insurance": {
    "company": "Assicurazioni Prova",
    "policyNumber": "RC-123",
    "coverage": "1.000.000 €",
    "expiresAt": "2027-12-31"
  },
  "membershipId": null,
  "membershipName": null,
  "isClinic": false,
  "baseEnabled": false,
  "includeByDefault": true,
  "priceList": null,
  "perSendVars": [
    "C1-modalita.modalita_esecuzione"
  ],
  "professionVars": {},
  "professions": [
    {
      "type": "PSYCHOLOGIST",
      "label": "Psicologo",
      "description": "percorso di sostegno psicologico e consulenza, con colloqui individuali",
      "specificExample": "quelle oggetto del consenso informato sottoscritto contestua…",
      "fiscal": {}
    }
  ],
  "catalog": {
    "version": "draft-2026-08"
  },
  "memberId": null
}

Errori

StatoCodiceSignificato
404ENGAGEMENT_NOT_AVAILABLEL'incarico non esiste per questo account (verticale estetica senza condizioni di servizio).
404MEMBER_NOT_FOUNDmemberId non è un medico attivo di questa struttura.

PUT /engagement

Aggiorna la lettera d'incarico

Scrive la configurazione dell'incarico con le regole della dashboard: con l'incarico attivo servono gli estremi della polizza RC in corso di validità e il massimale.

Permesso: configurations:write (delicato) · delicato

Corpo della richiesta

CampoTipoObbligatorioDescrizione
enabledbooleansìObbligatorio.
clausesobjectnoScelte per clausola: { [clauseId]: { enabled, vars } }, validate sul catalogo di GET /engagement. (predefinito {})
insuranceobject | nullno{ company, policyNumber, coverage, expiresAt: AAAA-MM-GG }.
insurance.companystringsì(1-120 caratteri)
insurance.policyNumberstringsì(1-60 caratteri)
insurance.coveragestringno(max 60 caratteri)
insurance.expiresAtstringsì(formato ^\d{4}-\d{2}-\d{2}$)
priceListarray<object> | nullno[{ label, amountCents, notes? }], fino a 50 voci. (max 50 elementi)
perSendVarsarray<string>noVariabili modificabili a ogni invio, parametri fiscali per professione, valori del modello personalizzato. (max 50 elementi)
professionVarsobjectnoVariabili modificabili a ogni invio, parametri fiscali per professione, valori del modello personalizzato.
customValuesobjectnoVariabili modificabili a ogni invio, parametri fiscali per professione, valori del modello personalizzato.
includeByDefaultbooleannoCome parte il cursore «Allega a questo invio» nel dialog della dashboard: true allegato (predefinito), false escluso. Solo configurazione della struttura, non per singolo medico. Non cambia le API di invio: con l'incarico attivo il documento parte, salvo engagement.include: false.
memberIdstringnoStrutture: medico a cui si riferisce la configurazione.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: Incarico attivo

curl -X PUT "https://api.consensosicuro.it/api/v1/account/engagement" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": true,
  "clauses": {},
  "insurance": {
    "company": "Assicurazioni Prova",
    "policyNumber": "RC-123",
    "coverage": "1.000.000 €",
    "expiresAt": "2027-12-31"
  }
}'

Risposta 200

{
  "success": true,
  "enabled": true,
  "includeByDefault": true,
  "catalogVersion": "draft-2026-08",
  "memberId": null
}

Errori

StatoCodiceSignificato
404ENGAGEMENT_NOT_AVAILABLEL'incarico non esiste per questo account.
404MEMBER_NOT_FOUNDmemberId non è un medico attivo di questa struttura.
400INVALID_BODYPolizza mancante o scaduta, massimale assente, clausola non valida.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /engagement/send-state

Incarico valido all'invio

Dice quale incarico vale all'invio e se la polizza è valida, come il dialog di invio della dashboard. Con l'incarico spento la risposta è ridotta a available, enabled e catalog; con l'incarico acceso porta anche isBase (vale la configurazione della struttura o quella del medico), insuranceValid, clauses, priceList e perSendVars.

Permesso: templates:read

Parametri

NomeDoveTipoObbligatorioDescrizione
memberIdquerystringnoStrutture: il medico esecutore scelto.

Campi della risposta

  • available, enabled: Se l'incarico esiste per l'account e se è acceso.
  • catalog: Il catalogo delle clausole, delle sezioni e delle variabili da compilare a ogni invio.
  • isCustom, custom: Presenti quando l'account usa un modello personalizzato.
  • membershipId, membershipName: Strutture: il medico di cui vale la configurazione, oppure null.
  • isBase: true quando vale la configurazione della struttura invece di quella del medico.
  • clauses, priceList, perSendVars: Le clausole scelte, il listino e le variabili che chiedi a ogni invio.
  • insuranceValid: false quando la polizza indicata è scaduta.
  • includeByDefault: Come parte il cursore nel dialog della dashboard (true allegato, false escluso). Le API di invio non lo seguono: con l'incarico attivo allegano il documento, salvo engagement.include: false.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/engagement/send-state" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "available": true,
  "enabled": false,
  "catalog": {
    "version": "2026-09-16",
    "clauses": [
      {}
    ],
    "sections": [
      {}
    ],
    "draftNotice": "Modelli standard verificati legalmente e aggiornati alla nor…",
    "perSendVarKeys": [
      "C1-modalita.modalita_esecuzione"
    ],
    "professionDescriptions": {}
  }
}

Errori

StatoCodiceSignificato
404ENGAGEMENT_NOT_AVAILABLEL'incarico non esiste per questo account (stessa risposta di GET /engagement).
404MEMBER_NOT_FOUNDmemberId non è un medico attivo di questa struttura.

GET /engagement/preview

Anteprima dell'incarico

Restituisce l'anteprima del documento d'incarico salvato, come lo vedrà il paziente con un consenso generico.

Permesso: templates:read

Parametri

NomeDoveTipoObbligatorioDescrizione
memberIdquerystringnoStrutture: incarico di quel medico.
professionTypequerystringnoProfessione con cui simulare l'invio.
languagequeryit | en | fr | denoen, fr o de per i modelli personalizzati tradotti.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/engagement/preview" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "snapshot": {
    "sections": []
  },
  "draftNotice": "…"
}

Errori

StatoCodiceSignificato
404ENGAGEMENT_NOT_AVAILABLEIncarico non disponibile.
400INVALID_QUERYLingua non valida.

GET /languages

Lingue attive

Restituisce le lingue in cui l'account può inviare. L'italiano c'è sempre, e available dice quali altre si possono attivare.

Permesso: templates:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/languages" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "languages": [
    "it",
    "en"
  ],
  "extraLanguages": [
    "en"
  ],
  "available": [
    "en",
    "fr",
    "de"
  ],
  "eligible": true
}

PUT /languages

Imposta le lingue

Imposta le lingue attive con le regole del profilo: solo per le professioni coperte dalle traduzioni (psicologia, psicoterapia, tatuaggi) e mai per le strutture.

Permesso: configurations:write (delicato) · delicato

Corpo della richiesta

CampoTipoObbligatorioDescrizione
languagesarray<"it" | "en" | "fr" | "de">sìElenco completo delle lingue attive, per esempio ["it", "en"]. ["it"] spegne quelle aggiuntive. (max 4 elementi)

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: Italiano e inglese

curl -X PUT "https://api.consensosicuro.it/api/v1/account/languages" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "languages": [
    "it",
    "en"
  ]
}'

Risposta 200

{
  "languages": [
    "it",
    "en"
  ],
  "extraLanguages": [
    "en"
  ],
  "available": [
    "en",
    "fr",
    "de"
  ],
  "eligible": true
}

Errori

StatoCodiceSignificato
400CONSENT_LANGUAGES_NOT_AVAILABLEAccount struttura.
400CONSENT_LANGUAGES_PROFESSION_NOT_SUPPORTEDNessuna professione dell'account ha le traduzioni.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /settings

Interruttori del profilo

Restituisce gli interruttori del profilo, ciascuno con il suo valore e con available, che dice se per questo account l'interruttore esiste.

Permesso: templates:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/settings" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "settings": {
    "medicalAnamnesisEnabled": {
      "value": true,
      "available": true
    },
    "medicalTraceabilityEnabled": {
      "value": false,
      "available": true
    },
    "customQuestionsEnabled": {
      "value": false,
      "available": false
    },
    "messageChannel": {
      "value": "WHATSAPP",
      "available": true,
      "locked": false
    }
  },
  "businessFeatures": false
}

PATCH /settings

Cambia gli interruttori

Cambia gli interruttori che il titolare cambia da solo: anamnesi medica, tracciabilità, domande personalizzate (con il loro titolo), informativa privacy (strutture) e canale dei messaggi. Un interruttore che per questo account non esiste è 400.

Permesso: configurations:write (delicato) · delicato

Corpo della richiesta

CampoTipoObbligatorioDescrizione
medicalAnamnesisEnabledbooleannoSanità, dove la funzione esiste.
medicalTraceabilityEnabledbooleannoSanità, dove la funzione esiste.
customQuestionsEnabledbooleannoStrutture «Associazione».
customQuestionsTitlestringnoStrutture «Associazione». (max 100 caratteri)
privacyInformativaEnabledbooleannoStrutture.
messageChannel"SMS" | "WHATSAPP"noWHATSAPP o SMS (bloccato su WhatsApp con il pacchetto annuale).

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: Anamnesi medica accesa

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/settings" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "medicalAnamnesisEnabled": true
}'

Risposta 200

{
  "settings": {
    "medicalAnamnesisEnabled": {
      "value": true,
      "available": true
    }
  },
  "businessFeatures": false
}

Errori

StatoCodiceSignificato
400SETTING_NOT_AVAILABLEL'interruttore non esiste per questo account.
400MESSAGE_CHANNEL_LOCKEDPacchetto annuale: i messaggi partono su WhatsApp.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /agora

Moduli Agorà

Restituisce i moduli Agorà di medicina estetica e dice quali sono attivi.

Permesso: templates:read

Campi della risposta

  • subscriptionRequired: true se l'account non ha un abbonamento attivo: i moduli ufficiali non si attivano in prova.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/agora" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "subscriptionRequired": false,
  "treatments": [
    {
      "code": "agora-iv-therapy",
      "label": "IV therapy",
      "activatable": true,
      "active": true,
      "configurationId": "ck…"
    }
  ]
}

Errori

StatoCodiceSignificato
404AGORA_NOT_ELIGIBLEL'account non esercita la medicina estetica.
404AGORA_DISABLEDModuli Agorà non disponibili.

POST /agora/{code}/activate

Attiva un modulo Agorà

Attiva un modulo: nasce il modello source: AGORA, come dalla card della dashboard. Si può ripetere: se il modulo è già attivo risponde 200 con lo stesso modello e idempotent: true.

Permesso: configurations:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
codepathstringsìCodice del modulo Agorà, come compare in GET /agora. Esempio: agora-filler-ialuronico.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/agora/agora-filler-ialuronico/activate" \
  -H "Authorization: Bearer ak_test_..."

Risposta 201

{
  "activated": true,
  "idempotent": false,
  "configuration": {
    "id": "ck…",
    "name": "IV therapy",
    "source": "AGORA",
    "mode": "SPECIFIC",
    "professionType": "PSYCHOLOGIST",
    "treatments": [
      "agora-iv-therapy"
    ],
    "isDefault": true,
    "dualSignatureEnabled": false,
    "medicalClearanceEnabled": null,
    "sendMethod": "WHATSAPP",
    "memberIds": [],
    "validityMonths": null,
    "autoRenewReminder": false
  }
}

Errori

StatoCodiceSignificato
403AGORA_SUBSCRIPTION_REQUIREDServe un abbonamento attivo.
404AGORA_TREATMENT_NOT_AVAILABLECodice inesistente o non ancora attivabile.
404AGORA_NOT_ELIGIBLEL'account non esercita la medicina estetica.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

POST /agora/{code}/deactivate

Disattiva un modulo Agorà

Disattiva un modulo: il modello si elimina, i consensi già inviati restano intatti.

Permesso: configurations:write (delicato) · delicato

Parametri

NomeDoveTipoObbligatorioDescrizione
codepathstringsìCodice del modulo Agorà, come compare in GET /agora. Esempio: agora-filler-ialuronico.

Da sapere

  • Permesso delicato: un modello sbagliato blocca gli invii, quindi configurations:write non è mai preselezionato alla creazione della chiave.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/agora/agora-filler-ialuronico/deactivate" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "activated": false,
  "treatmentCode": "agora-iv-therapy",
  "removed": 1
}

Errori

StatoCodiceSignificato
404AGORA_NOT_ACTIVEIl modulo non è attivo.
404AGORA_TREATMENT_NOT_AVAILABLECodice inesistente.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

Registri

Apparecchiature, lotti iniettabili, inchiostri, operatrici, motivi di consulto e domande personalizzate.

GET /equipment

Apparecchiature estetiche

Restituisce le apparecchiature elettromeccaniche del centro (D.M. 206/2015) con marca, modello, scheda ministeriale e ultima manutenzione.

Permesso: registries:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/equipment" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "equipment": [
    {
      "id": "uuid",
      "brand": "Luxor",
      "model": "IPL 3000",
      "dmScheda": "Scheda 16c, luce pulsata",
      "lastMaintenanceAt": null,
      "active": true
    }
  ]
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.

POST /equipment

Aggiungi un'apparecchiatura estetica

Aggiunge un'apparecchiatura. Il registro ne tiene al massimo 30.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
brandstringsìObbligatori. (1-100 caratteri)
modelstringsìObbligatori. (1-100 caratteri)
dmSchedastring | nullnoScheda del D.M. e data ISO dell'ultima manutenzione. (max 200 caratteri)
lastMaintenanceAtstring (date-time) | nullnoScheda del D.M. e data ISO dell'ultima manutenzione.

Esempio: Apparecchiatura

curl -X POST "https://api.consensosicuro.it/api/v1/account/equipment" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "brand": "Luxor",
  "model": "IPL 3000",
  "dmScheda": "Scheda 16c, luce pulsata",
  "lastMaintenanceAt": "2026-06-01T00:00:00.000Z"
}'

Risposta 201

{
  "equipment": {
    "id": "uuid",
    "brand": "Luxor",
    "model": "IPL 3000",
    "dmScheda": "Scheda 16c, luce pulsata",
    "lastMaintenanceAt": "2026-06-01T00:00:00.000Z",
    "active": true
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
400EQUIPMENT_LIMITMassimo 30 apparecchiature.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

PATCH /equipment/{equipmentId}

Modifica un'apparecchiatura estetica

Modifica un'apparecchiatura. Con active la riattivi o la disattivi.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
equipmentIdpathstringsìId dell'apparecchiatura. Esempio: 8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
brandstringnoTutti facoltativi, almeno uno. (1-100 caratteri)
modelstringnoTutti facoltativi, almeno uno. (1-100 caratteri)
dmSchedastring | nullnoTutti facoltativi, almeno uno. (max 200 caratteri)
lastMaintenanceAtstring (date-time) | nullnoTutti facoltativi, almeno uno.
activebooleannoTutti facoltativi, almeno uno.

Esempio: Ultima manutenzione

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/equipment/8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "lastMaintenanceAt": "2026-09-01T09:00:00Z"
}'

Risposta 200

{
  "equipment": {
    "id": "uuid",
    "brand": "Luxor",
    "model": "IPL 3500",
    "active": true
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404EQUIPMENT_NOT_FOUNDInesistente o altrui.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /equipment/{equipmentId}

Elimina un'apparecchiatura estetica

Elimina l'apparecchiatura. Se compare in un consenso la disattiva invece di eliminarla, così la riga di conformità nei documenti firmati resta leggibile.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
equipmentIdpathstringsìId dell'apparecchiatura. Esempio: 8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/equipment/8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "deleted": false,
  "deactivated": true
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404EQUIPMENT_NOT_FOUNDInesistente o altrui.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /medical-equipment

Apparecchiature MDR

Restituisce le apparecchiature MDR della medicina estetica. Il registro esiste quando sull'account è attiva la tracciabilità.

Permesso: registries:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/medical-equipment" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "equipment": [
    {
      "id": "uuid",
      "brand": "Fotona",
      "model": "SP Dynamis",
      "serialNumber": "SN-1",
      "ceMarking": "CE 0123 · MDR classe IIb",
      "active": true
    }
  ]
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.

POST /medical-equipment

Aggiungi un'apparecchiatura MDR

Aggiunge un'apparecchiatura MDR. Il registro ne tiene al massimo 30.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
brandstringsìObbligatori. (1-100 caratteri)
modelstringsìObbligatori. (1-100 caratteri)
serialNumberstring | nullnoFacoltativi. (max 100 caratteri)
ceMarkingstring | nullnoFacoltativi. (max 200 caratteri)
lastMaintenanceAtstring | nullnoFacoltativi.
notesstring | nullnoFacoltativi. (max 500 caratteri)

Esempio: Laser

curl -X POST "https://api.consensosicuro.it/api/v1/account/medical-equipment" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "brand": "Fotona",
  "model": "SP Dynamis",
  "serialNumber": "FT-2231",
  "ceMarking": "CE 0123"
}'

Risposta 201

{
  "equipment": {
    "id": "uuid",
    "brand": "Fotona",
    "model": "SP Dynamis",
    "serialNumber": null,
    "ceMarking": null,
    "active": true
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
400EQUIPMENT_LIMITMassimo 30 apparecchiature.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

PATCH /medical-equipment/{equipmentId}

Modifica un'apparecchiatura MDR

Modifica un'apparecchiatura MDR.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
equipmentIdpathstringsìId dell'apparecchiatura. Esempio: 8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
brandstringnoTutti facoltativi, almeno uno. (1-100 caratteri)
modelstringnoTutti facoltativi, almeno uno. (1-100 caratteri)
serialNumberstring | nullnoTutti facoltativi, almeno uno. (max 100 caratteri)
ceMarkingstring | nullnoTutti facoltativi, almeno uno. (max 200 caratteri)
lastMaintenanceAtstring | nullnoTutti facoltativi, almeno uno.
notesstring | nullnoTutti facoltativi, almeno uno. (max 500 caratteri)
activebooleannoTutti facoltativi, almeno uno.

Esempio: Nota di revisione

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/medical-equipment/8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "notes": "Revisione annuale"
}'

Risposta 200

{
  "equipment": {
    "id": "uuid",
    "brand": "Fotona",
    "model": "SP Dynamis",
    "notes": "Revisione annuale",
    "active": true
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404EQUIPMENT_NOT_FOUNDInesistente o altrui.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /medical-equipment/{equipmentId}

Elimina un'apparecchiatura MDR

Elimina l'apparecchiatura MDR. Se compare in un consenso la disattiva invece di eliminarla.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
equipmentIdpathstringsìId dell'apparecchiatura. Esempio: 8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/medical-equipment/8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "deleted": true,
  "deactivated": false
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404EQUIPMENT_NOT_FOUNDInesistente o altrui.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /lots/pending

Consensi senza lotto

Restituisce i consensi con iniettabili firmati negli ultimi 90 giorni che non hanno ancora un lotto registrato.

Permesso: registries:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/lots/pending" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "pending": [
    {
      "consentId": "uuid",
      "signedAt": "2026-09-20T10:00:00.000Z",
      "patient": {
        "id": "uuid",
        "firstName": "Giulia",
        "lastName": "Vera"
      },
      "treatments": [
        "Tossina botulinica"
      ]
    }
  ],
  "count": 1
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.

GET /lots/config

Configurazione del registro lotti

Restituisce i trattamenti iniettabili del catalogo, i tipi di prodotto e i prodotti già usati dall'account.

Permesso: registries:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/lots/config" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "injectableTreatments": [
    {
      "id": "est-botulino",
      "label": "Tossina botulinica"
    }
  ],
  "productTypes": [
    "FILLER",
    "TOSSINA",
    "BIOSTIMOLATORE",
    "Altro"
  ],
  "products": []
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.

GET /lots

Registro dei lotti iniettabili

Restituisce il registro dei lotti iniettabili: chi ha ricevuto quale lotto, quando e con quale esecutore. search serve per il richiamo di un lotto. Con la chiave Test il registro contiene solo i pazienti di prova (totale e pagine compresi).

Permesso: registries:read

Parametri

NomeDoveTipoObbligatorioDescrizione
searchquerystringnoNumero di lotto o nome del prodotto, anche parziale: è la ricerca per il richiamo. lotNumber è un sinonimo. Esempio: C1234.
patientIdquerystringnoSolo le registrazioni di quel paziente.
memberIdquerystringnoSolo quelle di quell'esecutore.
fromquerystringnoInizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01.
toquerystringnoFine del periodo (AAAA-MM-GG). Esempio: 2026-09-30.
pagequeryintegernoPagina, da 1; 50 righe per pagina. Esempio: 1.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/lots" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "lots": [
    {
      "id": "uuid",
      "treatmentLabel": "Tossina botulinica",
      "productName": "Vistabel",
      "lotNumber": "C1234",
      "expired": false,
      "patient": {
        "id": "uuid",
        "firstName": "Giulia",
        "lastName": "Vera"
      }
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 50,
  "stats": {
    "month": 0,
    "distinctProducts": 0,
    "total": 0
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
400INVALID_QUERYData o pagina non valida.

POST /lots

Registra un lotto

Registra un lotto iniettabile. La registrazione finisce nel Registro attività.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
patientIdstring (uuid)sìObbligatori; treatmentCode è un iniettabile del catalogo di medicina estetica.
consentIdstring | nullnoFacoltativi. (1-60 caratteri)
productNamestringsìObbligatori; treatmentCode è un iniettabile del catalogo di medicina estetica. (1-200 caratteri)
productType"FILLER" | "TOSSINA" | "BIOSTIMOLATORE" | "ALTRO" | nullnoFacoltativi.
lotNumberstring | nullnoObbligatorio, oppure ricavato da udiRaw (stringa GS1 letta dallo scanner). (1-100 caratteri)
expiryDatestring | nullnoFacoltativi.
udiRawstring | nullno(max 2000 caratteri)
usedAtstring | nullnoFacoltativi.
treatedAreastring | nullnoFacoltativi. (max 200 caratteri)
quantitystring | nullnoFacoltativi. (max 100 caratteri)
notesstring | nullnoFacoltativi. (max 500 caratteri)
memberIdstring (uuid) | nullnoFacoltativi.
treatmentCodestringsìObbligatori; treatmentCode è un iniettabile del catalogo di medicina estetica. (1-60 caratteri)

Esempio: Lotto di tossina

curl -X POST "https://api.consensosicuro.it/api/v1/account/lots" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "patientId": "8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e",
  "treatmentCode": "est-botulino",
  "productName": "Vistabel",
  "productType": "TOSSINA",
  "lotNumber": "C1234",
  "expiryDate": "2027-05-31",
  "quantity": "20 U"
}'

Risposta 201

{
  "lot": {
    "id": "uuid",
    "treatmentLabel": "Tossina botulinica",
    "productName": "Vistabel",
    "lotNumber": "C1234",
    "expired": false,
    "patient": {
      "id": "uuid",
      "firstName": "Giulia",
      "lastName": "Vera"
    }
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404PATIENT_NOT_FOUNDPaziente inesistente o altrui.
404CONSENT_NOT_FOUNDConsenso inesistente, altrui o di un altro paziente.
404MEMBER_NOT_FOUNDEsecutore inesistente o altrui.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

PATCH /lots/{lotId}

Correggi un lotto

Corregge una registrazione del registro lotti, che finisce nel Registro attività. Paziente, consenso ed esecutore restano quelli di prima.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
lotIdpathstringsìId della riga del registro lotti. Esempio: 9e0f1a2b-3c4d-4e5f-8a6b-7c8d9e0f1a2b.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
productNamestringnoTutti facoltativi, almeno uno. (1-200 caratteri)
productType"FILLER" | "TOSSINA" | "BIOSTIMOLATORE" | "ALTRO" | nullnoTutti facoltativi, almeno uno.
lotNumberstring | nullnoTutti facoltativi, almeno uno. (1-100 caratteri)
expiryDatestring | nullnoTutti facoltativi, almeno uno.
udiRawstring | nullnoTutti facoltativi, almeno uno. (max 2000 caratteri)
usedAtstring | nullnoTutti facoltativi, almeno uno.
treatedAreastring | nullnoTutti facoltativi, almeno uno. (max 200 caratteri)
quantitystring | nullnoTutti facoltativi, almeno uno. (max 100 caratteri)
notesstring | nullnoTutti facoltativi, almeno uno. (max 500 caratteri)
treatmentCodestringnoTutti facoltativi, almeno uno. (1-60 caratteri)

Esempio: Correggi il numero di lotto

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/lots/9e0f1a2b-3c4d-4e5f-8a6b-7c8d9e0f1a2b" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "lotNumber": "C1235",
  "notes": "Numero di lotto corretto"
}'

Risposta 200

{
  "lot": {
    "id": "uuid",
    "treatmentLabel": "Tossina botulinica",
    "productName": "Vistabel",
    "lotNumber": "C1234",
    "expired": false,
    "patient": {
      "id": "uuid",
      "firstName": "Giulia",
      "lastName": "Vera"
    },
    "quantity": "25 U"
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404LOT_NOT_FOUNDRegistrazione inesistente o altrui.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /lots/{lotId}

Elimina un lotto

Elimina una registrazione del registro lotti. L'eliminazione finisce nel Registro attività.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
lotIdpathstringsìId della riga del registro lotti. Esempio: 9e0f1a2b-3c4d-4e5f-8a6b-7c8d9e0f1a2b.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/lots/9e0f1a2b-3c4d-4e5f-8a6b-7c8d9e0f1a2b" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "deleted": true
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404LOT_NOT_FOUNDRegistrazione inesistente o altrui.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /consents/{id}/lots

Lotti di un consenso

Restituisce i lotti iniettabili e gli inchiostri registrati su un consenso, nel perimetro di sede della richiesta.

Permesso: registries:read

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/lots" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "consentId": "uuid",
  "lots": [
    {
      "id": "uuid",
      "treatmentCode": "est-botulino",
      "productName": "Vistabel",
      "lotNumber": "C1234"
    }
  ],
  "inks": []
}

Errori

StatoCodiceSignificato
404REGISTRY_NOT_AVAILABLENessun registro dei lotti attivo sull'account.
404CONSENT_NOT_FOUNDConsenso inesistente, altrui o fuori dal contesto di sede.

POST /consents/{id}/lots

Registra un lotto su un consenso

Registra un lotto iniettabile sul consenso. Paziente e consenso li ricaviamo dal percorso; le altre regole sono quelle del registro lotti.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
idpathstringsìId del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09.
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
productNamestringsì(1-200 caratteri)
productType"FILLER" | "TOSSINA" | "BIOSTIMOLATORE" | "ALTRO" | nullno
lotNumberstring | nullno(1-100 caratteri)
expiryDatestring | nullno
udiRawstring | nullno(max 2000 caratteri)
usedAtstring | nullno
treatedAreastring | nullno(max 200 caratteri)
quantitystring | nullno(max 100 caratteri)
notesstring | nullno(max 500 caratteri)
memberIdstring (uuid) | nullno
treatmentCodestringsì(1-60 caratteri)

Da sapere

  • I campi sono quelli di POST /lots senza patientId e consentId.

Esempio: Lotto di tossina

curl -X POST "https://api.consensosicuro.it/api/v1/account/consents/3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09/lots" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "treatmentCode": "est-botulino",
  "productName": "Vistabel",
  "productType": "TOSSINA",
  "lotNumber": "C1234",
  "quantity": "20 U"
}'

Risposta 201

{
  "lot": {
    "id": "uuid",
    "treatmentLabel": "Tossina botulinica",
    "productName": "Vistabel",
    "lotNumber": "C1234",
    "expired": false,
    "patient": {
      "id": "uuid",
      "firstName": "Giulia",
      "lastName": "Vera"
    }
  }
}

Errori

StatoCodiceSignificato
404CONSENT_NOT_FOUNDConsenso inesistente, altrui o fuori dal contesto di sede.
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

GET /inks/pending

Consensi senza inchiostro registrato

Restituisce i consensi con inchiostro firmati negli ultimi 90 giorni che non hanno ancora un lotto registrato.

Permesso: registries:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/inks/pending" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "pending": [],
  "count": 0
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.

GET /inks/config

Configurazione del registro inchiostri

Restituisce i trattamenti con inchiostro, i tipi di inchiostro e le marche già usate.

Permesso: registries:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/inks/config" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "inkTreatments": [
    {
      "id": "tat-tatuaggio",
      "label": "Tatuaggio"
    }
  ],
  "inkTypes": [
    "TATTOO_INK",
    "PMU_PIGMENT",
    "OTHER"
  ],
  "brands": []
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.

GET /inks

Registro degli inchiostri

Restituisce il registro degli inchiostri e dei pigmenti (tatuaggi e PMU). Con la chiave Test contiene solo i pazienti di prova.

Permesso: registries:read

Parametri

NomeDoveTipoObbligatorioDescrizione
searchquerystringnoNumero di lotto o nome del prodotto, anche parziale: è la ricerca per il richiamo. lotNumber è un sinonimo. Esempio: C1234.
patientIdquerystringnoSolo le registrazioni di quel paziente.
memberIdquerystringnoSolo quelle di quell'esecutore.
fromquerystringnoInizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01.
toquerystringnoFine del periodo (AAAA-MM-GG). Esempio: 2026-09-30.
pagequeryintegernoPagina, da 1; 50 righe per pagina. Esempio: 1.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/inks" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "inks": [
    {
      "id": "uuid",
      "brand": "Eternal",
      "color": "Nero",
      "lotNumber": "ET-77",
      "reachCompliant": true
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 50,
  "stats": {
    "month": 0,
    "distinctBrands": 0,
    "total": 0
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
400INVALID_QUERYData o pagina non valida.

POST /inks

Registra un inchiostro

Registra un lotto di inchiostro. La registrazione finisce nel Registro attività.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
patientIdstring (uuid)sìObbligatori.
consentIdstring | nullnoFacoltativi. (1-60 caratteri)
brandstringsìObbligatori. (1-200 caratteri, formato ^[^\p{C}]*$)
productNamestring | nullnoFacoltativi. (max 200 caratteri, formato ^[^\p{C}]*$)
colorstring | nullnoFacoltativi. (max 100 caratteri, formato ^[^\p{C}]*$)
inkType"TATTOO_INK" | "PMU_PIGMENT" | "OTHER" | nullnoFacoltativi.
lotNumberstringsìObbligatori. (1-100 caratteri, formato ^[^\p{C}]*$)
reachCompliantbooleannoFacoltativi.
reachRefstring | nullnoFacoltativi. (max 300 caratteri, formato ^[^\p{C}]*$)
expiryDatestring | nullnoFacoltativi.
usedAtstring | nullnoFacoltativi.
treatedAreastring | nullnoFacoltativi. (max 200 caratteri, formato ^[^\p{C}]*$)
quantitystring | nullnoFacoltativi. (max 100 caratteri, formato ^[^\p{C}]*$)
notesstring | nullnoFacoltativi. (max 500 caratteri, formato ^(?:[^\p{C}]|[\n\r\t])*$)
memberIdstring (uuid) | nullnoFacoltativi.
treatmentCodestringsìObbligatori. (1-60 caratteri)

Esempio: Inchiostro nero

curl -X POST "https://api.consensosicuro.it/api/v1/account/inks" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "patientId": "8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e",
  "treatmentCode": "tat-tatuaggio",
  "brand": "Eternal",
  "color": "Nero",
  "lotNumber": "ET-77",
  "reachCompliant": true
}'

Risposta 201

{
  "ink": {
    "id": "uuid",
    "brand": "Eternal",
    "color": "Nero",
    "lotNumber": "ET-77",
    "reachCompliant": true
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404PATIENT_NOT_FOUNDCliente inesistente o altrui.
404CONSENT_NOT_FOUNDConsenso inesistente o altrui.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

PATCH /inks/{inkId}

Correggi un inchiostro

Corregge una registrazione del registro inchiostri, che finisce nel Registro attività. Paziente, consenso ed esecutore restano quelli di prima.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
inkIdpathstringsìId della riga del registro inchiostri. Esempio: 0f1a2b3c-4d5e-4f6a-9b7c-8d9e0f1a2b3c.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
brandstringnoTutti facoltativi, almeno uno. (1-200 caratteri, formato ^[^\p{C}]*$)
productNamestring | nullnoTutti facoltativi, almeno uno. (max 200 caratteri, formato ^[^\p{C}]*$)
colorstring | nullnoTutti facoltativi, almeno uno. (max 100 caratteri, formato ^[^\p{C}]*$)
inkType"TATTOO_INK" | "PMU_PIGMENT" | "OTHER" | nullnoTutti facoltativi, almeno uno.
lotNumberstringnoTutti facoltativi, almeno uno. (1-100 caratteri, formato ^[^\p{C}]*$)
reachCompliantbooleannoTutti facoltativi, almeno uno.
reachRefstring | nullnoTutti facoltativi, almeno uno. (max 300 caratteri, formato ^[^\p{C}]*$)
expiryDatestring | nullnoTutti facoltativi, almeno uno.
usedAtstring | nullnoTutti facoltativi, almeno uno.
treatedAreastring | nullnoTutti facoltativi, almeno uno. (max 200 caratteri, formato ^[^\p{C}]*$)
quantitystring | nullnoTutti facoltativi, almeno uno. (max 100 caratteri, formato ^[^\p{C}]*$)
notesstring | nullnoTutti facoltativi, almeno uno. (max 500 caratteri, formato ^(?:[^\p{C}]|[\n\r\t])*$)
treatmentCodestringnoTutti facoltativi, almeno uno. (1-60 caratteri)

Esempio: Colore

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/inks/0f1a2b3c-4d5e-4f6a-9b7c-8d9e0f1a2b3c" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "color": "Nero intenso"
}'

Risposta 200

{
  "ink": {
    "id": "uuid",
    "brand": "Eternal",
    "color": "Nero intenso",
    "lotNumber": "ET-77",
    "reachCompliant": true
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404INK_NOT_FOUNDRegistrazione inesistente o altrui.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /inks/{inkId}

Elimina un inchiostro

Elimina una registrazione del registro inchiostri. L'eliminazione finisce nel Registro attività.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
inkIdpathstringsìId della riga del registro inchiostri. Esempio: 0f1a2b3c-4d5e-4f6a-9b7c-8d9e0f1a2b3c.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/inks/0f1a2b3c-4d5e-4f6a-9b7c-8d9e0f1a2b3c" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "deleted": true
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404INK_NOT_FOUNDRegistrazione inesistente o altrui.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /materials

Materiali della seduta

Restituisce i materiali registrati nelle sedute di tatuaggio, PMU e piercing: aghi, cartucce, grip o manipoli, caps monouso, pomate e altri materiali, con marca, lotto, scadenza o ciclo di sterilizzazione. Gli inchiostri sono in /inks. Con la chiave Test contiene solo i pazienti di prova.

Permesso: registries:read

Parametri

NomeDoveTipoObbligatorioDescrizione
searchquerystringnoNumero di lotto o nome del prodotto, anche parziale: è la ricerca per il richiamo. lotNumber è un sinonimo. Esempio: C1234.
patientIdquerystringnoSolo le registrazioni di quel paziente.
memberIdquerystringnoSolo quelle di quell'esecutore.
fromquerystringnoInizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01.
toquerystringnoFine del periodo (AAAA-MM-GG). Esempio: 2026-09-30.
pagequeryintegernoPagina, da 1; 50 righe per pagina. Esempio: 1.
consentIdquerystringnoSolo i materiali registrati su quel consenso.
kindquerystringnoSolo un tipo: NEEDLE, CARTRIDGE, GRIP, CAP, OINTMENT oppure OTHER. Esempio: NEEDLE.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/materials" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "materials": [
    {
      "id": "uuid",
      "kind": "NEEDLE",
      "kindLabel": "Aghi",
      "brand": "Cheyenne",
      "productName": "Safety 0,30 RL 7",
      "lotNumber": "CH-889921",
      "expiryDate": "2029-03-31T00:00:00.000Z",
      "sterilizationRef": null,
      "quantity": "3",
      "treatmentCode": "tat-tatuaggio",
      "consentId": "uuid",
      "memberId": null
    }
  ],
  "total": 1,
  "page": 1,
  "pageSize": 50
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
400INVALID_QUERYData, pagina o tipo non validi.

GET /supply-presets

I prodotti dello studio

Restituisce la libreria «I miei prodotti» dello studio di tatuaggio: inchiostri e materiali salvati con il lotto attuale e la scadenza, nell'ordine scelto dallo studio. I prodotti archiviati si vedono solo con includeArchived=true.

Permesso: registries:read

Parametri

NomeDoveTipoObbligatorioDescrizione
includeArchivedquerystringnotrue per vedere anche i prodotti archiviati. Esempio: false.
kindquerystringnoSolo un tipo: INK, NEEDLE, CARTRIDGE, GRIP, CAP, OINTMENT oppure OTHER. Esempio: INK.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/supply-presets" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "presets": [
    {
      "id": "uuid",
      "kind": "INK",
      "kindLabel": "Inchiostro",
      "brand": "Eternal",
      "productName": null,
      "color": "Nero",
      "lotNumber": "ET-77",
      "expiryDate": "2027-06-30T00:00:00.000Z",
      "useCount": 12,
      "archivedAt": null
    }
  ],
  "kinds": [
    {
      "value": "INK",
      "label": "Inchiostro",
      "plural": "Inchiostri"
    }
  ]
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
400INVALID_QUERYParametro non valido.

GET /operators

Operatrici del centro

Restituisce le operatrici del centro estetico che non hanno un accesso alla dashboard.

Permesso: registries:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/operators" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "operators": [
    {
      "id": "uuid",
      "firstName": "Olga",
      "lastName": "Operatrice",
      "qualification": "Estetista qualificata (L. 1/1990)",
      "active": true
    }
  ]
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.

POST /operators

Aggiungi un'operatrice

Aggiunge un'operatrice. Il registro ne tiene al massimo 30.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
firstNamestringsìObbligatori. (1-100 caratteri)
lastNamestringsìObbligatori. (1-100 caratteri)
qualificationstring | nullnoFacoltativa: compare nel consenso. (max 200 caratteri)

Esempio: Operatrice

curl -X POST "https://api.consensosicuro.it/api/v1/account/operators" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "firstName": "Olga",
  "lastName": "Operatrice",
  "qualification": "Estetista qualificata"
}'

Risposta 201

{
  "operator": {
    "id": "uuid",
    "firstName": "Olga",
    "lastName": "Operatrice",
    "qualification": null,
    "active": true
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
400OPERATOR_LIMITMassimo 30 operatrici.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

PATCH /operators/{operatorId}

Modifica un'operatrice

Modifica un'operatrice. Con active la riattivi o la disattivi.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
operatorIdpathstringsìId dell'operatrice. Esempio: 1a2b3c4d-5e6f-4a7b-8c8d-9e0f1a2b3c4d.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
firstNamestringnoTutti facoltativi, almeno uno. (1-100 caratteri)
lastNamestringnoTutti facoltativi, almeno uno. (1-100 caratteri)
qualificationstring | nullnoTutti facoltativi, almeno uno. (max 200 caratteri)
activebooleannoTutti facoltativi, almeno uno.

Esempio: Qualifica

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/operators/1a2b3c4d-5e6f-4a7b-8c8d-9e0f1a2b3c4d" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "qualification": "Estetista qualificata"
}'

Risposta 200

{
  "operator": {
    "id": "uuid",
    "firstName": "Olga",
    "lastName": "Operatrice",
    "qualification": null,
    "active": false
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404OPERATOR_NOT_FOUNDInesistente o altrui.
400EMPTY_PATCHCorpo senza nessun campo da modificare.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /operators/{operatorId}

Elimina un'operatrice

Elimina l'operatrice. Se compare in un consenso la disattiva invece di eliminarla.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
operatorIdpathstringsìId dell'operatrice. Esempio: 1a2b3c4d-5e6f-4a7b-8c8d-9e0f1a2b3c4d.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/operators/1a2b3c4d-5e6f-4a7b-8c8d-9e0f1a2b3c4d" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "deleted": true,
  "deactivated": false
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404OPERATOR_NOT_FOUNDInesistente o altrui.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /consultation-reasons

Motivi di consulto

Restituisce i motivi di consulto usati nelle visite, dal più usato.

Permesso: registries:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/consultation-reasons" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "reasons": [
    {
      "id": "uuid",
      "label": "Ansia da prestazione",
      "usageCount": 4,
      "createdAt": "2026-09-01T09:00:00.000Z"
    }
  ]
}

POST /consultation-reasons

Aggiungi un motivo di consulto

Aggiunge un motivo di consulto. Un testo uguale a uno esistente, a parte maiuscole e spazi, è lo stesso motivo: ricevi quello che c'è già.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
labelstringsìDa 1 a 120 caratteri. (1-120 caratteri)

Esempio: Motivo nuovo

curl -X POST "https://api.consensosicuro.it/api/v1/account/consultation-reasons" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "label": "Dolore lombare"
}'

Risposta 201

{
  "reason": {
    "id": "uuid",
    "label": "Ansia da prestazione",
    "usageCount": 0,
    "createdAt": "2026-09-23T09:00:00.000Z"
  }
}

Errori

StatoCodiceSignificato
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
403VISITS_DISABLEDLa funzione visita non è attiva per questo account (o per la struttura per cui lavori): letture ed eliminazioni restano disponibili.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

PATCH /consultation-reasons/{reasonId}

Rinomina un motivo di consulto

Rinomina un motivo, anche nelle visite che lo usano.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
reasonIdpathstringsìId del motivo di consulto. Esempio: 2b3c4d5e-6f7a-4b8c-9d9e-0f1a2b3c4d5e.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
labelstringsìDa 1 a 120 caratteri. (1-120 caratteri)

Esempio: Rinomina

curl -X PATCH "https://api.consensosicuro.it/api/v1/account/consultation-reasons/2b3c4d5e-6f7a-4b8c-9d9e-0f1a2b3c4d5e" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "label": "Lombalgia"
}'

Risposta 200

{
  "reason": {
    "id": "uuid",
    "label": "Ansia",
    "usageCount": 4,
    "createdAt": "2026-09-01T09:00:00.000Z"
  }
}

Errori

StatoCodiceSignificato
404REASON_NOT_FOUNDInesistente o altrui.
409REASON_ALREADY_EXISTSEsiste già un motivo con quel testo.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
403VISITS_DISABLEDLa funzione visita non è attiva per questo account (o per la struttura per cui lavori): letture ed eliminazioni restano disponibili.

DELETE /consultation-reasons/{reasonId}

Elimina un motivo di consulto

Elimina un motivo. Le visite che lo usavano restano intatte.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
reasonIdpathstringsìId del motivo di consulto. Esempio: 2b3c4d5e-6f7a-4b8c-9d9e-0f1a2b3c4d5e.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/consultation-reasons/2b3c4d5e-6f7a-4b8c-9d9e-0f1a2b3c4d5e" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "success": true
}

Errori

StatoCodiceSignificato
404REASON_NOT_FOUNDInesistente o altrui.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /custom-questions/stats

Statistiche delle domande personalizzate

Restituisce i totali delle risposte per ogni domanda attiva. Le risposte a testo libero non vengono mostrate.

Permesso: registries:read

Parametri

NomeDoveTipoObbligatorioDescrizione
sedeIdquerystringnoStrutture: solo quella sede.
fromquerystringnoInizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01.
toquerystringnoFine del periodo (AAAA-MM-GG). Esempio: 2026-09-30.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/custom-questions/stats" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "kpi": {},
  "questions": []
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404SEDE_NOT_FOUNDSede altrui.
400INVALID_QUERYData non valida.

POST /custom-questions/reorder

Riordina le domande personalizzate

Riordina le domande: passa in ids gli id nell'ordine che vuoi.

Permesso: registries:write

Corpo della richiesta

CampoTipoObbligatorioDescrizione
idsarray<string>sìGli id delle domande, tutti dell'account e senza ripetizioni. (1-200 elementi)

Esempio: Nuovo ordine

curl -X POST "https://api.consensosicuro.it/api/v1/account/custom-questions/reorder" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f"
  ]
}'

Risposta 200

{
  "success": true
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404QUESTION_NOT_FOUNDUn id è inesistente o altrui.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

GET /custom-questions

Domande personalizzate

Restituisce le domande personalizzate dell'anamnesi delle strutture «Associazione», in ordine e comprese quelle disattivate.

Permesso: registries:read

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/custom-questions" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "enabled": true,
  "maxActive": 20,
  "questions": [
    {
      "id": "uuid",
      "label": "Come ci hai conosciuto?",
      "type": "MULTIPLE_CHOICE",
      "required": false,
      "active": true
    }
  ]
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.

POST /custom-questions

Aggiungi una domanda personalizzata

Aggiunge una domanda. Le domande attive possono essere al massimo 20.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
X-Idempotency-KeyheaderstringnoFacoltativa, fino a 128 caratteri. Un nuovo tentativo con la stessa chiave e lo stesso corpo riceve la risposta della prima volta invece di creare un doppione. Vale 24 ore. Esempio: visita-119.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
labelstringsìObbligatori. type: YES_NO, MULTIPLE_CHOICE, FREE_TEXT. (1-300 caratteri)
helpstring | ""noCome in dashboard. (max 500 caratteri)
type"YES_NO" | "MULTIPLE_CHOICE" | "FREE_TEXT"sìObbligatori. type: YES_NO, MULTIPLE_CHOICE, FREE_TEXT.
optionsarray<object>noScelta multipla: da 2 a 10 [{ label }]; le chiavi opt-N restano stabili. (max 10 elementi)
options[].keystringno(formato ^opt-\d+$)
options[].labelstringsì(1-200 caratteri)
displayDropdownbooleannoCome in dashboard.
allowOtherbooleannoCome in dashboard.
multiSelectbooleannoCome in dashboard.
requiredbooleannoCome in dashboard.
activebooleannoCome in dashboard.
askPolicy"ONCE" | "EVERY_CONSENT" | "EXPIRES"noONCE, EVERY_CONSENT o EXPIRES (con i mesi).
expiryMonthsinteger | nullnoONCE, EVERY_CONSENT o EXPIRES (con i mesi). (da 1 a 120)

Esempio: Scelta multipla

curl -X POST "https://api.consensosicuro.it/api/v1/account/custom-questions" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: gestionale-0001" \
  -d '{
  "label": "Come ci hai conosciuto?",
  "type": "MULTIPLE_CHOICE",
  "options": [
    {
      "label": "Passaparola"
    },
    {
      "label": "Internet"
    }
  ]
}'

Risposta 201

{
  "question": {
    "id": "uuid",
    "label": "Come ci hai conosciuto?",
    "type": "MULTIPLE_CHOICE",
    "options": [
      {
        "key": "opt-1",
        "label": "Passaparola"
      }
    ],
    "required": false,
    "active": true
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
400CUSTOM_QUESTIONS_LIMITMassimo 20 domande attive.
400CUSTOM_QUESTIONS_OPTIONS_INVALIDLa scelta multipla ha meno di 2 o più di 10 opzioni.
400CUSTOM_QUESTIONS_EXPIRY_REQUIREDCon askPolicy EXPIRES servono i mesi in expiryMonths.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
409IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESSLa stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.
400IDEMPOTENCY_KEY_INVALIDX-Idempotency-Key vuota o più lunga di 128 caratteri.

PUT /custom-questions/{questionId}

Sostituisci una domanda personalizzata

Sostituisce la domanda intera, come il salvataggio della dashboard.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
questionIdpathstringsìId della domanda personalizzata. Esempio: 3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f.

Corpo della richiesta

CampoTipoObbligatorioDescrizione
labelstringsì(1-300 caratteri)
helpstring | ""no(max 500 caratteri)
type"YES_NO" | "MULTIPLE_CHOICE" | "FREE_TEXT"sì
optionsarray<object>no(max 10 elementi)
options[].keystringno(formato ^opt-\d+$)
options[].labelstringsì(1-200 caratteri)
displayDropdownbooleanno
allowOtherbooleanno
multiSelectbooleanno
requiredbooleanno
activebooleanno
askPolicy"ONCE" | "EVERY_CONSENT" | "EXPIRES"no
expiryMonthsinteger | nullno(da 1 a 120)

Da sapere

  • I campi sono gli stessi della creazione.

Esempio: Domanda sì o no

curl -X PUT "https://api.consensosicuro.it/api/v1/account/custom-questions/3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f" \
  -H "Authorization: Bearer ak_test_..." \
  -H "Content-Type: application/json" \
  -d '{
  "label": "Hai già fatto sedute simili?",
  "type": "YES_NO"
}'

Risposta 200

{
  "question": {
    "id": "uuid",
    "label": "Come ci hai conosciuto?",
    "type": "MULTIPLE_CHOICE",
    "required": false,
    "active": true
  }
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404QUESTION_NOT_FOUNDInesistente o altrui.
400CUSTOM_QUESTIONS_LIMITMassimo 20 domande attive.
400CUSTOM_QUESTIONS_OPTIONS_INVALIDLa scelta multipla ha meno di 2 o più di 10 opzioni.
400CUSTOM_QUESTIONS_EXPIRY_REQUIREDCon askPolicy EXPIRES servono i mesi in expiryMonths.
400INVALID_BODYCorpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

DELETE /custom-questions/{questionId}

Elimina una domanda personalizzata

Elimina la domanda. Se ha già delle risposte la disattiva invece di eliminarla, così le risposte passate restano leggibili.

Permesso: registries:write

Parametri

NomeDoveTipoObbligatorioDescrizione
questionIdpathstringsìId della domanda personalizzata. Esempio: 3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f.

Esempio: richiesta

curl -X DELETE "https://api.consensosicuro.it/api/v1/account/custom-questions/3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "deleted": false,
  "deactivated": true
}

Errori

StatoCodiceSignificato
404FEATURE_NOT_AVAILABLEIl registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard.
404QUESTION_NOT_FOUNDInesistente o altrui.
403TEST_KEY_READ_ONLYChiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard.

Consumi

Quota, messaggi e statistiche.

GET /account/usage

Quota e consumi

Restituisce la quota del periodo (la stessa che decide se un invio passa), il piano, il pacchetto prepagato o extra, i messaggi WhatsApp e SMS del periodo e i consumi degli ultimi 12 mesi. I consensi di prova e quelli annullati non contano.

Permesso: account:read

Campi della risposta

  • monthly[]: Dal mese più vecchio al corrente: consensi inviati, completati, WhatsApp, SMS.

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/account/usage" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "plan": {
    "tier": "PROFESSIONAL",
    "subscriptionStatus": "ACTIVE",
    "billingInterval": "MONTHLY"
  },
  "period": {
    "start": "2026-09-05T00:00:00.000Z",
    "end": "2026-10-05T00:00:00.000Z"
  },
  "quota": {
    "used": 42,
    "limit": 200,
    "remaining": 158,
    "isLifetime": false,
    "mode": "plan",
    "packOnly": false
  },
  "pack": null,
  "extraPackCatalog": [
    {
      "size": 50,
      "priceEur": 20
    }
  ],
  "messages": {
    "periodStart": "2026-09-05T00:00:00.000Z",
    "whatsapp": 38,
    "sms": 6,
    "total": 44
  },
  "monthly": [
    {
      "month": "2026-09",
      "consentsSent": 42,
      "consentsCompleted": 37,
      "whatsapp": 38,
      "sms": 6
    }
  ]
}

GET /account/stats

Statistiche

Restituisce le statistiche della dashboard (consensi per stato, andamento, trattamenti) con gli stessi filtri.

Permesso: account:read

Parametri

NomeDoveTipoObbligatorioDescrizione
fromquerystringnoInizio del periodo: una data (AAAA-MM-GG) o un istante ISO 8601. Esempio: 2026-09-01.
toquerystringnoFine del periodo, compresa: una data (AAAA-MM-GG) o un istante ISO 8601. Esempio: 2026-09-30.
sedeIdquerystringnoStrutture: solo quella sede (di questo account, altrimenti 404). In conflitto con X-Structure-Sede-Id è 403.
memberIdquerystringnoStrutture: solo quel professionista (di questo account, altrimenti 404).

Esempio: richiesta

curl "https://api.consensosicuro.it/api/v1/account/account/stats" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "filters": {
    "from": "2026-01-01",
    "to": null,
    "sedeId": null,
    "memberId": null
  },
  "statistics": {
    "overview": {
      "totalConsents": 130
    }
  },
  "treatments": {}
}

Errori

StatoCodiceSignificato
400INVALID_QUERYData non valida o periodo invertito.
403SEDE_CONTEXT_MISMATCHsedeId in contrasto con l'intestazione X-Structure-Sede-Id.

Webhook

La prova del webhook configurato in dashboard.

POST /webhooks/test

Prova il webhook

Invia un evento ping firmato all'indirizzo del webhook configurato in dashboard. Serve a verificare che il tuo server riceva e controlli la firma prima di aspettare un consenso vero.

Permesso: webhooks:test

Da sapere

  • Al massimo 10 prove all'ora per account: ogni prova è una chiamata vera verso il tuo server.

Esempio: richiesta

curl -X POST "https://api.consensosicuro.it/api/v1/account/webhooks/test" \
  -H "Authorization: Bearer ak_test_..."

Risposta 200

{
  "delivered": true,
  "deliveryId": "uuid",
  "responseStatus": 200
}

Errori

StatoCodiceSignificato
400WEBHOOK_NOT_CONFIGUREDNon c'è nessun webhook: si configura in Impostazioni → API & Integrazioni.
400WEBHOOK_DISABLEDIl webhook è configurato ma disattivato.
409WEBHOOK_SECRET_UNAVAILABLEIl segreto del webhook non si legge più (succede solo per un guasto nostro): va rigenerato in dashboard. Finché non lo fai nessun evento parte, e ti avvisiamo per email.