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
- Specifica OpenAPI 3.1 (
openapi.json) - Collection Postman (
postman.json) - Client TypeScript di esempio (
consensosicuro-client.ts) - Ricevitore del webhook in PHP (
webhook.php.txt) - Client Python di esempio (
client.py)
Condizioni per l’integrazione via API
Indice
- Introduzione e base URL
- Autenticazione e chiavi
- Struttura e sede
- Idempotenza
- Paginazione, filtri ed errori
- Modalità Test
- Invio di un consenso passo per passo
- Doppia firma per i minori passo per passo
- Ciclo di vita e stati
- Pazienti e GDPR
- Webhook
- Limiti
- Changelog
- Riferimento degli endpoint
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.
- Alla creazione scegli i permessi (scope). Ogni endpoint dice quale gli serve e alcuni ne accettano due in alternativa.
- Puoi limitare la chiave a un massimo di 20 indirizzi IP o reti (/8, /16, /24). Per il permesso
patients:deletegli IP consentiti sono obbligatori. - Scade dopo 365 giorni, o dopo 90 se lo scegli. Una chiave senza scadenza si crea solo scegliendolo apposta, e la dashboard ti chiede una conferma.
- Per creare una chiave di produzione la dashboard ti chiede di confermare chi sei, con la password e, se l’hai attivata, il codice della verifica in due passaggi. Le chiavi di prova non la chiedono.
- La revoca è immediata e definitiva. Una chiave revocata o scaduta riceve 401
API_KEY_INVALID, la stessa risposta di una chiave inesistente: chi trova una chiave vecchia non scopre se era vera. Il motivo preciso lo vedi nel registro richieste della dashboard. - A ogni chiave creata o revocata, e a ogni cambio del webhook, il titolare riceve un’email. Se una chiave viene usata da un indirizzo IP nuovo, fa molte più richieste del solito o riceve una raffica di rifiuti, arriva un avviso per email e nella card Monitoraggio.
- Il Registro attività (
GET /account/audit) conaccount:readmostra gli indirizzi IP con l’ultima parte nascosta. Per vederli completi serve il permessoaudit:read.
Questi sono i permessi che puoi dare a una chiave. Quelli segnati come delicati non sono mai preselezionati.
| Permesso | Cosa consente | Note |
|---|---|---|
account:read | Leggere 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:read | Leggere 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:read | Leggere i consensi. Elenco e dettaglio dei consensi: stato, firmatari, trattamenti, esecutore, sede, esiti di consegna. | preselezionato |
consents:create | Inviare 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:cancel | Annullare consensi. Annullare un consenso non ancora firmato. Il consenso torna nella quota del piano. | nessuna |
consents:documents | Scaricare i PDF. Scaricare consenso, informativa privacy e incarico firmati. Sono documenti sanitari: concedilo solo se il gestionale li archivia davvero. | nessuna |
consents:countersign | Controfirma (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:read | Leggere le anagrafiche. Elenco e dettaglio dei pazienti dell'account. | nessuna |
patients:write | Creare e aggiornare anagrafiche. Inserire o aggiornare i dati anagrafici dei pazienti. | nessuna |
patients:export | Esportare i dati. Export GDPR, export Excel e fascicolo difensivo del paziente. | delicato |
patients:delete | Cancellare 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:read | Leggere visite e cicli. Visite registrate e cicli di trattamento. | nessuna |
visits:write | Registrare visite. Creare visite e cicli di trattamento. | nessuna |
webhooks:test | Provare il webhook. Inviare un evento di prova all'indirizzo webhook configurato. | nessuna |
anamnesis:read | Leggere 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:read | Leggere 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:write | Gestire 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:write | Modificare 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:read | Leggere 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:write | Aggiornare 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:write | Aggiornare 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:read | IP 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.
- Stessa chiave e stesso corpo entro 24 ore: ricevi la risposta della prima volta, con lo stesso codice di stato e il campo
idempotent: true. - Stessa chiave e corpo diverso: ricevi 409
IDEMPOTENCY_KEY_REUSED. - Stessa chiave mentre la prima richiesta è ancora in corso: ricevi 409
IDEMPOTENCY_IN_PROGRESS. Aspetta un attimo e riprova.
L’intestazione vale sugli endpoint che creano qualcosa, compresi il reinvio e il rinnovo di un consenso. Sono questi:
- POST /consents
- POST /consents/{id}/resend
- POST /consents/{id}/renew
- POST /patients
- POST /patients/{patientId}/documents
- POST /patients/{patientId}/visits
- POST /structure/sedi
- POST /structure/members
- POST /structure/invites
- POST /configurations
- POST /equipment
- POST /medical-equipment
- POST /lots
- POST /consents/{id}/lots
- POST /inks
- POST /operators
- POST /consultation-reasons
- POST /custom-questions
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:
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_JSON | Il corpo non è JSON valido (richiesta troncata, encoding sbagliato). |
| 401 | API_KEY_REQUIRED | Manca l'intestazione Authorization: Bearer ak_live_… (o ak_test_…). |
| 401 | API_KEY_INVALID | La 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. |
| 403 | IP_NOT_ALLOWED | La richiesta arriva da un indirizzo IP che la chiave non consente. |
| 403 | SCOPE_MISSING | La chiave non ha il permesso richiesto dalla rotta: required dice quale, granted quelli che ha. |
| 403 | KEY_NOT_OWNER | L'intestazione X-Structure-Sede-Id indica una sede fuori dalla struttura della chiave. |
| 403 | SEDE_NOT_ACCESSIBLE | La sede dell'intestazione X-Structure-Sede-Id non esiste o non è accessibile. |
| 403 | STRUCTURE_FROZEN | La struttura è sospesa: le richieste si fermano finché il titolare non la riattiva. |
| 403 | STRUCTURE_CONTEXT_REQUIRED | L'account lavora solo nel contesto di una struttura: indica la sede con X-Structure-Sede-Id. |
| 400 | STRUCTURE_AMBIGUOUS | L'account lavora per più strutture: la richiesta non dice quale. |
| 404 | ACCOUNT_NOT_FOUND | L'account della chiave non è più disponibile. |
| 403 | ORIGIN_NOT_ALLOWED | La richiesta arriva da un browser su un dominio che non è il nostro. Le API si chiamano dal tuo server. |
| 400 | INVALID_CHARACTERS | Un 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. |
| 413 | PAYLOAD_TOO_LARGE | Il corpo supera 1 MB (16 MB solo sul caricamento di un documento del paziente). |
| 429 | RATE_LIMIT_EXCEEDED | Più 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.
- Per firmare un consenso di prova usi il codice
000000, così arrivi fino al PDF firmato. - I consensi di prova non consumano la quota del piano e vengono cancellati dopo 7 giorni.
- Puoi fare fino a 50 invii di prova al giorno, con al massimo 20 anagrafiche nuove.
- Le anagrafiche di prova stanno in un archivio separato: una chiave Test non legge e non modifica i pazienti veri, e per lei un id vero risponde 404 come un id che non esiste.
- Le azioni che distruggono dati (cancellazione GDPR, revoca della privacy) rispondono 403
TEST_KEY_DESTRUCTIVE. Le modifiche alla configurazione, che è condivisa con la dashboard, rispondono 403TEST_KEY_READ_ONLY. - I webhook partono anche per i consensi di prova, con
consent.isTest: true.
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.
| Stato | Stati interni | Significato |
|---|---|---|
PENDING_SIGNATURE | SCHEDULED PENDING OTP_VERIFIED | Il consenso è partito (o partirà all'orario programmato) e nessuno ha ancora firmato. |
PARTIALLY_SIGNED | PENDING_SECOND_PARENT | Doppia firma per minori: uno dei due genitori ha firmato, l'altro no. signatures[] dice quale. |
ACTION_REQUIRED | MINOR_DATA_MISMATCH PENDING_COUNTERSIGN | Il 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). |
COMPLETED | COMPLETED | Tutte le firme raccolte: i PDF sono disponibili. |
REFUSED | REFUSED REFUSED_BY_PARENT_DISAGREEMENT | Rifiuto esplicito di chi doveva firmare, oppure disaccordo tra i due genitori sulle clausole obbligatorie. |
EXPIRED | EXPIRED EXPIRED_SECOND_PARENT | Il 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. |
CANCELLED | CANCELLED CANCELLED_DATA_MISMATCH | Annullato 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.
- Il permesso
patients:deletesi dà solo a una chiave con gli IP consentiti impostati. - Se il paziente ha consensi firmati serve anche
acknowledgeRetention: true: stai dichiarando di voler distruggere la prova che aveva acconsentito. - Ogni chiave può chiedere al massimo 5 cancellazioni al giorno.
- Alla richiesta parte
patient.deletion_scheduled, all’esecuzionepatient.deletedcon i soli identificativi.
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.
| Evento | Quando parte |
|---|---|
consent.created | Consenso inviato al paziente (da dashboard o da API). |
consent.resent | Consenso scaduto reinviato: nasce un consenso nuovo, consent.replacesConsentId è quello vecchio. |
consent.renewed | Consenso a scadenza rinnovato (manualmente o dal promemoria automatico): anche qui il consenso è nuovo. |
consent.phone_corrected | Numero di un firmatario corretto: stesso link, nessuna quota consumata. signerSlot dice a chi. |
consent.delivery_failed | Messaggio non consegnato a un firmatario nemmeno dopo il ripiego automatico su SMS: serve correggere il numero. |
consent.partially_signed | Doppia firma: uno dei due genitori ha firmato (signedSlot, receivedSignatures su expectedSignatures). |
consent.action_required | Serve un intervento dello studio: dati del minore divergenti tra i due genitori, oppure controfirma dell'esecutore mancante. |
consent.countersigned | Tatuaggi e piercing: l'esecutore ha controfirmato. Subito dopo arriva consent.completed. |
consent.completed | Tutte le firme ricevute: i PDF sono disponibili (URL firmate a 1 ora). |
consent.refused | Il paziente (o un genitore) ha rifiutato di firmare: refusedSlot e refusalReason. |
consent.expired | Il link è scaduto senza firma. |
consent.cancelled | Il consenso è stato annullato. |
patient.created | Nuova anagrafica in archivio: patient.id, il tuo externalId se lo hai passato, nome e cognome. Mai il telefono né il codice fiscale. |
patient.updated | Anagrafica modificata (dati, note, sede o riferimento del gestionale): changed elenca i campi toccati, non i valori. |
patient.deleted | Anagrafica cancellata definitivamente (art. 17 GDPR). Porta solo gli identificativi: il nome è stato cancellato e non lo si rispedisce fuori. |
patient.privacy_revoked | Consensi facoltativi revocati su un documento privacy firmato (art. 7.3): consentId, le clausole revocate e se la revoca è totale. |
patient.merged | Due 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_scheduled | Cancellazione 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_postponed | Una 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_cancelled | Una 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. |
ping | Evento 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:
| Evento | Campi in più |
|---|---|
| consent.resent / consent.renewed | consent.id è il consenso nuovo; consent.replacesConsentId quello sostituito. |
| consent.phone_corrected | consent.signerSlot (P1/P2), consent.channel, consent.sameLink: true. |
| consent.delivery_failed | consent.signerSlot e consent.delivery con stato, tentativi, se il ripiego SMS è stato provato e l'endpoint per correggere il numero. |
| consent.partially_signed | consent.signedSlot, consent.receivedSignatures / expectedSignatures, consent.signatures[]. |
| consent.action_required | consent.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.refused | consent.refusedSlot e consent.refusalReason (PATIENT o PARENT_DISAGREEMENT). |
| consent.countersigned | consent.countersign con metodo e istante. Subito dopo arriva consent.completed. |
| consent.completed | consent.signatures[] con gli orari di tutte le firme e il blocco documents con le URL firmate (valide 1 ora). |
| patient.created / patient.updated | patient 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.deleted | Il 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_revoked | privacy con consentId, revokedLabels (etichette di clausola, non dati della persona), totale e revokedAt. |
| patient.merged | mergedFrom è 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_scheduled | deletion con cancelUntil (entro quando si può fermare), signedConsents (quante prove di consenso verranno distrutte) e cancelEndpoint. |
| patient.deletion_cancelled | patient 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_postponed | deletion.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 cancellazione | Se 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:
- Primo tentativo al momento dell’evento.
- Riprova 1: dopo 30 secondi.
- Riprova 2: dopo 2 minuti.
- Riprova 3: dopo 10 minuti.
- Riprova 4: dopo 30 minuti.
- Riprova 5: dopo 2 ore.
- Riprova 6: dopo 6 ore.
- Riprova 7: dopo 24 ore.
- Dopo 8 tentativi la consegna resta «non consegnata» e si rimanda a mano dalla dashboard. Le consegne restano consultabili per 30 giorni.
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
| Limite | Valore | Errore |
|---|---|---|
| Richieste per chiave | 300 al minuto | 429 RATE_LIMIT_EXCEEDED |
| Richieste per account, tutte le chiavi insieme | 900 al minuto | 429 RATE_LIMIT_EXCEEDED |
| PDF, fascicoli, export e allegati | 120 all’ora per chiave | 429 RATE_LIMIT_EXCEEDED |
Prove del webhook (POST /webhooks/test) | 10 all’ora per account | 429 RATE_LIMIT_EXCEEDED |
| Richieste per indirizzo IP | 600 al minuto | 429 |
| Invii di consensi via API | al giorno, una volta e mezza la quota mensile del piano e mai meno di 30 | 429 DAILY_SEND_LIMIT |
| Modalità Test | 50 invii e 20 anagrafiche nuove al giorno | 429 TEST_DAILY_LIMIT |
| Corpo della richiesta | 1 MB (16 MB sul caricamento di un documento) | 413 PAYLOAD_TOO_LARGE |
| Documento del paziente | 10 MB, JPG, PNG, WebP o PDF | 400 FILE_TOO_LARGE / UNSUPPORTED_FORMAT |
| Elenchi a cursore | 100 righe per pagina | 400 INVALID_QUERY |
metadata dell’invio | 30 chiavi, 1 KB | 400 METADATA_TOO_MANY_KEYS / METADATA_TOO_LARGE |
| Correzioni del numero | 3 per firmatario | 429 PHONE_CORRECTION_LIMIT |
| Sollecito della controfirma | uno ogni 10 minuti per esecutore | 429 COUNTERSIGN_SESSION_RATE_LIMITED |
| Cancellazioni GDPR | 5 al giorno per chiave | 429 DELETE_DAILY_LIMIT |
| Inviti con accesso | 10 al giorno, 3 allo stesso indirizzo | 429 INVITE_LIMIT / INVITE_LIMIT_ADDRESS |
| Link ai PDF | validi 1 ora | nessuno |
| Testi | niente caratteri invisibili né di controllo della direzione del testo | 400 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 (4): Chi è l'account della chiave, il profilo e il Registro attività.
- Catalogo (1): I codici dei trattamenti, i modelli, le lingue, le sedi e gli esecutori da usare nell'invio.
- Consensi (7): Invio, elenco, dettaglio, consegna, evidenza della firma e annullamento.
- Ciclo di vita (8): Reinvio, rinnovo, cambio canale, correzione del numero, difformità e controfirma.
- Documenti (5): I PDF firmati, il fascicolo difensivo e l'invio dei PDF per email.
- Pazienti (14): Anagrafiche, note e documenti del paziente.
- GDPR (7): Informativa privacy, revoca, export e cancellazione dei dati.
- Visite (7): Visite e cicli di trattamento.
- Anamnesi (1): Le risposte complete dell'anamnesi raccolta alla firma.
- Struttura (18): Sedi, professionisti, inviti, esecutore generico, controfirme e statistiche.
- Configurazione (25): Modelli, trattamenti abilitati, informativa, incarico, lingue, interruttori e moduli Agorà.
- Registri (38): Apparecchiature, lotti iniettabili, inchiostri, operatrici, motivi di consulto e domande personalizzate.
- Consumi (2): Quota, messaggi e statistiche.
- Webhook (1): La prova del webhook configurato in dashboard.
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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
businessName | string | no | Professionisti e studi: mai vuota (serve alla completezza del profilo). (1-200 caratteri) |
professionTitle | string | no | Intestazione dei documenti. (max 100 caratteri) |
professionalRegister | string | no | Intestazione dei documenti. (max 200 caratteri) |
structureEmail | string (email) | "" | no | Strutture: contatti mostrati nei documenti. |
structurePhone | string | no | Strutture: contatti mostrati nei documenti. (max 30 caratteri) |
address | string | no | Professionisti e studi. (max 200 caratteri) |
city | string | no | Professionisti e studi. (max 100 caratteri) |
cap | string | "" | no | Professionisti e studi. (formato ^\d{5}$) |
primaryColor | string | no | Aspetto dei documenti. (max 20 caratteri) |
logoBackgroundColor | string | null | no | Aspetto dei documenti. (formato ^#[0-9A-Fa-f]{6}$) |
logoDominantColor | string | null | no | Aspetto dei documenti. (formato ^#[0-9A-Fa-f]{6}$) |
logoScale | integer | no | Aspetto dei documenti. (da 50 a 160) |
logoFeather | integer | no | Aspetto 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
| Stato | Codice | Significato |
|---|---|---|
| 403 | PROFILE_FIELD_NOT_ALLOWED | Campo di accesso, verifica, identità o fatturazione. |
| 400 | FIELD_NOT_AVAILABLE | structureLabel: si cambia dalla dashboard. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
page | query | integer | no | Pagina, da 1. Esempio: 1. |
limit | query | integer | no | Righe per pagina, da 1 a 100 (predefinito 25). Esempio: 25. |
action | query | string | no | Nome di un'azione, per esempio CONSENT_SENT o ANAMNESIS_VIEWED. Esempio: CONSENT_SENT. |
performedBy | query | PROFESSIONAL | PATIENT | SYSTEM | no | Chi ha compiuto l'azione. |
sedeId | query | string | no | Strutture: solo le voci riferite ai pazienti di quella sede. |
from | query | string | no | Inizio del periodo: una data (AAAA-MM-GG) o un istante ISO 8601. Esempio: 2026-09-01. |
to | query | string | no | Fine 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_READcon 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_QUERY | Parametro non valido. |
| 404 | SEDE_NOT_FOUND | Sede altrui. |
| 403 | SEDE_CONTEXT_MISMATCH | sedeId in contrasto con l'intestazione X-Structure-Sede-Id. |
Catalogo
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:readsiaaccount: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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
status | query | string | no | Uno 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. |
from | query | string | no | Creati da questo istante (ISO 8601 con ora). Esempio: 2026-09-01T00:00:00Z. |
to | query | string | no | Creati fino a questo istante (ISO 8601 con ora). Esempio: 2026-09-30T23:59:59Z. |
search | query | string | no | Nome, cognome o telefono del paziente. |
sedeId | query | string | no | Sede della struttura (in alternativa all'intestazione X-Structure-Sede-Id). |
executorMemberId | query | string | no | Professionista esecutore. |
patientExternalId | query | string | no | Il tuo riferimento passato all'invio (patient.externalId). Esempio: CRM-4412. |
expiringWithinHours | query | integer | no | Solo i consensi ancora in attesa il cui link scade entro N ore (da 1 a 2160). Esempio: 48. |
signingMode | query | REMOTE | IN_STUDIO | no | REMOTE (telefono del paziente) o IN_STUDIO (tablet della sede). |
apiTest | query | true | false | no | true = solo i consensi di prova, false = solo quelli veri. |
limit | query | integer | no | Da 1 a 100 (predefinito 25). Esempio: 25. |
cursor | query | string | no | Cursore 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_QUERY | Un parametro non è valido (stato inesistente, ore non numeriche, limite fuori intervallo). |
| 400 | INVALID_CURSOR | Il cursore non è uno di quelli restituiti da questa API. |
| 404 | SEDE_NOT_FOUND | sedeId non è di questo account. |
| 403 | SEDE_CONTEXT_MISMATCH | sedeId 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
patient | object | sì | Chi riceve il consenso: firstName, lastName, phone (E.164 o numero italiano). Per un minore: isMinor: true e i firmatari in guardians. |
patient.firstName | string | sì | (1-100 caratteri) |
patient.lastName | string | sì | (1-100 caratteri) |
patient.phone | string | no | Obbligatorio per un adulto. Per un minore non viene usato: il link va ai guardians. (8-24 caratteri) |
patient.dateOfBirth | string | no | AAAA-MM-GG. È un tuo riferimento: l'identità del soggetto la raccoglie il modulo di firma. (formato ^\d{4}-\d{2}-\d{2}$) |
patient.isMinor | boolean | no | |
patient.externalId | string | no | I tuoi riferimenti del gestionale. Il codice fiscale non entra nel documento. (1-120 caratteri) |
patient.fiscalCode | string | no | I tuoi riferimenti del gestionale. Il codice fiscale non entra nel documento. |
patient.id | string (uuid) | no | Paziente già in archivio. Obbligatorio con consentType: TREATMENT_ONLY. |
guardians | array<object> | no | Solo 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[].firstName | string | sì | (1-100 caratteri) |
guardians[].lastName | string | sì | (1-100 caratteri) |
guardians[].phone | string | sì | (8-24 caratteri) |
consentType | "FULL" | "TREATMENT_ONLY" | "PRIVACY_ONLY" | no | FULL (predefinito), TREATMENT_ONLY (richiede patient.id: la privacy è già firmata), PRIVACY_ONLY (sola informativa). (predefinito "FULL") |
template | object | no | configurationId (un tuo modello) oppure treatments[] (codici del catalogo) e privateTreatmentIds[] (schede con i tuoi testi). Con mode, templateType, professionType, secondaryProfessionId, agoraModule. |
template.configurationId | string | no | (1-40 caratteri) |
template.treatments | array<string> | no | (max 50 elementi) |
template.privateTreatmentIds | array<string> | no | (max 50 elementi) |
template.mode | "GENERIC" | "SPECIFIC" | no | |
template.templateType | "PSYCHOLOGIST" | "PSYCHOTHERAPIST" | "PSYCHIATRIST" | no | |
template.professionType | string | no | (1-40 caratteri) |
template.secondaryProfessionId | string (uuid) | no | |
template.agoraModule | object | no | |
template.agoraModule.indication | string | no | (max 300 caratteri) |
template.agoraModule.device | string | no | (max 300 caratteri) |
template.agoraModule.zones | string | no | (max 400 caratteri) |
template.agoraModule.notes | string | no | (max 1500 caratteri) |
template.privateSendValues | object | no | Schede 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. |
executor | object | no | Strutture: { memberId } il professionista che esegue, { owner: true } il titolare, { generic: true } senza professionista nominato (se abilitato per l'ambito). |
executor.memberId | string (uuid) | no | |
executor.owner | boolean | no | |
executor.generic | boolean | no | |
structureSedeId | string (uuid) | no | Sede della struttura. In alternativa l'intestazione X-Structure-Sede-Id: se indichi entrambe devono coincidere. |
officeId | string (uuid) | no | Professionista singolo: lo studio fisico che compare nel documento. |
delivery | object | no | channel (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.scheduledFor | string (date-time) | no | |
delivery.signingMode | "REMOTE" | "IN_STUDIO" | no | |
engagement | object | no | { 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.include | boolean | no | |
engagement.overrides | object | no | |
anamnesisAck | boolean | no | true quando l'anamnesi segnala controindicazioni assolute e vuoi procedere comunque. |
equipmentIds | array<string (uuid)> | no | Apparecchiature usate (D.M. 206/2015 e MDR). (max 30 elementi) |
medicalEquipmentIds | array<string (uuid)> | no | Apparecchiature usate (D.M. 206/2015 e MDR). (max 30 elementi) |
requestMedicalClearance | boolean | no | |
businessOperatorId | string (uuid) | no | |
metadata | object | no | Tuoi 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-Keyconserviamo 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 404CONSENT_NOT_FOUNDe 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Campi mancanti o non validi (anche dentro patient, guardians, template, delivery): details elenca ognuno. |
| 400 | INVALID_JSON | Il corpo non è JSON valido (richiesta troncata, encoding sbagliato). |
| 403 | PROFILE_INCOMPLETE | Profilo del titolare incompleto: il documento non si può intestare. Vale anche in modalità Test. |
| 402 | SUBSCRIPTION_GRACE_PERIOD / SUBSCRIPTION_REQUIRED | Abbonamento scaduto o non attivo (solo chiavi di produzione). |
| 400 | TEMPLATE_AMBIGUOUS | configurationId e treatments insieme: indica una sola sorgente. |
| 403 | ACCOUNT_MODELS_ONLY | L'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. |
| 400 | ACCOUNT_MODEL_TREATMENTS_UNAVAILABLE | Account «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. |
| 404 | CONFIGURATION_NOT_FOUND / OFFICE_NOT_FOUND / EQUIPMENT_NOT_FOUND | Un id indicato non esiste o non è di questo account. |
| 400 | PROFESSION_NOT_ENABLED / UNKNOWN_SECONDARY_PROFESSION / SEDE_NOT_ALLOWED | Ambito, identità secondaria o sede non di questo account. |
| 404 | PATIENT_NOT_FOUND | patient.id inesistente o di un altro account. |
| 400 | PATIENT_PHONE_REQUIRED | Telefono mancante per un adulto. Per un minore il numero non serve: il link va ai guardians. |
| 400 | ENGAGEMENT_NOT_AVAILABLE | Hai passato engagement.overrides ma l'incarico non è attivo per questo invio. |
| 400 | METADATA_RESERVED_KEY | metadata usa una chiave che riempie l'API (externalId, fiscalCode, minor). |
| 400 | PATIENT_ID_REQUIRED | «Solo consenso» richiede un paziente che ha già firmato l'informativa. |
| 400 | GUARDIANS_REQUIRED / GUARDIANS_FOR_ADULT | Per un minore servono i firmatari; per un adulto non vanno indicati. |
| 400 | DUAL_SIGNATURE_NOT_AVAILABLE | La professione dell'invio non prevede la doppia firma genitoriale. |
| 400 | DUAL_SIGNATURE_REQUIRED | Su un minore, il trattamento richiede il consenso di entrambi i genitori: indica 2 firmatari. |
| 400 | DUAL_SIGNATURE_TREATMENT_NOT_ELIGIBLE | Due firmatari su un trattamento che non li richiede. |
| 400 | DUAL_SIGNATURE_SAME_PHONE | I due genitori hanno lo stesso numero. |
| 400 | IN_STUDIO_NOT_SCHEDULABLE / IN_STUDIO_NO_DEVICE | Limiti 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. |
| 400 | LANGUAGE_NOT_ENABLED | Lingua non attivata nelle Impostazioni dell'account. |
| 400 | CLINIC_SEDE_REQUIRED / INVALID_CLINIC_SEDE / SEDE_CONTEXT_MISMATCH | Sede mancante, non tua, o in contrasto con l'intestazione. |
| 400 | CLINIC_MEMBER_REQUIRED / INVALID_CLINIC_MEMBER / CLINIC_MEMBER_NOT_IN_SEDE | Esecutore mancante, non tuo, o che non opera in quella sede. |
| 400 | SECRETARY_CANNOT_EXECUTE | La segreteria non può essere l'esecutore. |
| 400 | EXECUTOR_AMBIGUOUS / EXECUTOR_CAPABILITY | Più esecutori insieme, o esecutore che non esegue quei trattamenti. |
| 400 | ANAMNESIS_ACK_REQUIRED | Controindicazioni assolute: conferma con anamnesisAck: true. |
| 400 | PRIVATE_TREATMENT_NOT_AVAILABLE / METADATA_TOO_MANY_KEYS / METADATA_TOO_LARGE | Scheda privata non tua; metadata oltre i limiti. |
| 400 | PRIVATE_SEND_VALUE_REQUIRED / PRIVATE_SEND_VALUE_INVALID / PRIVATE_SEND_VALUE_TOO_LONG / PRIVATE_SEND_VALUE_UNKNOWN | Campi 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. |
| 402 | CONSENT_QUOTA_EXCEEDED | Quota consensi del piano esaurita: la risposta riporta consentsUsed e consentsLimit. |
| 402 | PAYMENT_FAILED | Rinnovo dell'abbonamento non riuscito: aggiorna il metodo di pagamento. |
| 402 | STRUCTURE_PLAN_INACTIVE | Struttura o studio senza un piano attivo e con la prova finita: per inviare va attivato un piano. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | Stessa chiave con un corpo diverso, o richiesta ancora in corso. |
| 404 | nessuno | Ripetizione 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. |
| 422 | UNKNOWN_TREATMENT_CODE | Codice del catalogo inesistente (probabile refuso): l'elenco è in GET /templates. |
| 422 | TEMPLATE_NOT_ENABLED_FOR_ACCOUNT | Il codice appartiene a un ambito che il tuo account non esercita. |
| 422 | MESSAGE_NOT_DELIVERED | Il numero non è raggiungibile: nessun consenso è stato creato. |
| 429 | DAILY_SEND_LIMIT | Tetto giornaliero di invii via API per questo account (chiavi di produzione). |
| 429 | TEST_DAILY_LIMIT | Tetto della modalità Test: 50 invii e 20 anagrafiche nuove al giorno. |
| 400 | PRIVACY_ONLY_NO_DUAL_SIGNATURE | La doppia firma genitoriale non si applica alla sola informativa privacy. |
| 400 | PRIVACY_ONLY_WITH_PRIVACY_DISABLED | Hai chiesto la sola informativa, ma la struttura non invia l'informativa privacy. |
| 400 | DUAL_SIGNATURE_NOT_SCHEDULABLE | Un consenso a doppia firma non si programma: i due link partono insieme al momento dell'invio. |
| 400 | DUAL_SIGNATURE_PROFESSION_NOT_SUPPORTED | La doppia firma è disponibile solo per psicologi, psicoterapeuti, psichiatri e medici estetici. |
| 400 | DUAL_SIGNATURE_SECOND_PARENT_MISSING | Mancano nome, cognome o telefono del secondo genitore. |
| 400 | INVALID_PROFESSION_TYPE | Il profilo dell'account non ha una professione valida: va aggiornato in dashboard. |
| 400 | LANGUAGE_NOT_AVAILABLE / LANGUAGE_NOT_AVAILABLE_FOR_TEMPLATE | La lingua scelta non ha la traduzione per questa professione o per questo modello. |
| 400 | LANGUAGE_WITH_CUSTOM_PRIVACY / PRIVATE_TREATMENT_IT_ONLY / CUSTOM_ENGAGEMENT_IT_ONLY | Un testo del centro (informativa, scheda o incarico personalizzato) esiste solo in italiano: invia in italiano. |
| 400 | CUSTOM_ENGAGEMENT_INCOMPLETE | Le condizioni d'incarico hanno una voce da compilare: indicala in engagement.overrides o escludi l'incarico da questo invio. |
| 400 | CLINIC_NO_SCOPES / CLINIC_SCOPE_REQUIRED / CLINIC_SCOPE_NOT_ALLOWED | Strutture: ambiti non impostati, ambito mancante (template.professionType) o non della struttura. |
| 400 | CLINIC_MEMBER_SCOPE_MISMATCH | Il professionista esecutore non esercita l'ambito dell'invio. |
| 400 | EXECUTOR_REQUIRED | Strutture: manca il professionista che esegue (executor). |
| 400 | SEND_GENERIC_NOT_ALLOWED_FOR_PROFESSION | Per questa professione il consenso generico non è ammesso: indica almeno un trattamento. |
| 400 | INVALID_BUSINESS_OPERATOR / INVALID_EQUIPMENT | Operatrice o apparecchiatura non valida per questo account. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
Campi della risposta
status: Stato nel vocabolario dell'API.internalStatusporta 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),requirede 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | nessuno | Il consenso è in uno stato che non si annulla (per esempio è già firmato). |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
X-Idempotency-Key | header | string | no | Facoltativa, 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:
slotqui 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | CONSENT_NOT_EXPIRED | Si reinvia solo un consenso scaduto senza firma. Un consenso firmato a scadenza si rinnova. |
| 400 | RESEND_DUAL_SIGNATURE | Doppia firma per minori: il consenso nuovo nascerebbe a firma singola. Rifai l'invio con i due guardians. |
| 400 | IN_STUDIO_NO_MESSAGE_TO_RESEND | Firma sul tablet: nessun messaggio è mai partito. Usa /reopen-in-studio. |
| 400 | API_TEST_CONSENT | La copia di un consenso di prova sarebbe un consenso vero: creane uno nuovo. |
| 400 | SLOT_NOT_APPLICABLE | slot si usa su /channel e /correct-phone, non qui. |
| 402 | CONSENT_QUOTA_EXCEEDED | Il reinvio consuma quota come un invio nuovo. |
| 404 | CONSENT_NOT_FOUND | Il 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. |
| 409 | ALREADY_RESENT | Questo consenso è già stato reinviato: newConsentId dice quale. Se è scaduto anche quello, reinvia quello. |
| 502 | RESEND_FAILED | Il reinvio non è riuscito e nessun consenso nuovo è stato creato: riprova. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-Idempotency-Key vuota o più lunga di 128 caratteri. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | CONSENT_NOT_RENEWABLE | Il consenso non è firmato o non ha una validità a termine. |
| 400 | RENEWAL_DUAL_SIGNATURE | Doppia firma: servono di nuovo entrambe le firme, quindi un invio nuovo. |
| 404 | CONSENT_NOT_FOUND | Consenso inesistente, di un altro account, oppure vero mentre usi una chiave in modalità Test. |
| 409 | ALREADY_RENEWED | Questo consenso è già stato rinnovato: il consenso nuovo esiste già. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-Idempotency-Key vuota o più lunga di 128 caratteri. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
channel | "SMS" | "WHATSAPP" | sì | Sempre "SMS". Il ritorno a WhatsApp non è previsto e risponde 400 CHANNEL_SWITCH_NOT_AVAILABLE. |
slot | string | no | P1 (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
| Stato | Codice | Significato |
|---|---|---|
| 400 | CHANNEL_SWITCH_NOT_AVAILABLE | Hai chiesto WHATSAPP: il passaggio esiste solo verso SMS. |
| 400 | NOT_DUAL_SIGNATURE | "P2" su un consenso con un solo firmatario. |
| 400 | INVALID_SLOT | slot diverso da P1 / P2. |
| 400 | IN_STUDIO_NO_MESSAGE_TO_RESEND | Firma sul tablet: nessun messaggio da spostare. |
| 400 | DELIVERY_NOT_FAILED | La consegna su WhatsApp a quel firmatario non risulta fallita: il passaggio a SMS non serve. |
| 400 | NOT_SENT_VIA_WHATSAPP | Quel firmatario non è stato contattato su WhatsApp, oppure è già passato a SMS. |
| 400 | CONSENT_NOT_PENDING | Quel firmatario non è più in attesa di firmare. |
| 502 | SMS_SEND_FAILED | L'SMS non è partito: riprova più tardi. |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
phone | string | sì | Numero corretto (E.164 o formato italiano). (6-24 caratteri) |
slot | string | no | P1 (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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_PHONE / SAME_NUMBER | Numero malformato o identico a quello già usato. |
| 400 | DUAL_SIGNATURE_SAME_PHONE | I due genitori avrebbero lo stesso numero: ciascuno firma dal proprio. |
| 400 | CONSENT_NOT_PENDING / CONSENT_LINK_EXPIRED | Il firmatario ha già firmato, o il suo link è scaduto. |
| 404 | CONSENT_NOT_FOUND | Il 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. |
| 409 | PHONE_BELONGS_TO_OTHER_PATIENT | Quel numero è già di un altro paziente in archivio. |
| 429 | PHONE_CORRECTION_LIMIT | Tre 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
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
| Stato | Codice | Significato |
|---|---|---|
| 400 | NO_DATA_MISMATCH | Il consenso non è fermo su una difformità. |
| 400 | INVALID_BODY | acceptFrom diverso da P1 / P2. |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | NO_DATA_MISMATCH | Per annullare un consenso normale usa /cancel. |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | NOT_IN_STUDIO | Il consenso non è in modalità firma sul tablet. |
| 400 | CONSENT_NOT_PENDING / CONSENT_LINK_EXPIRED | Già firmato, oppure link scaduto: serve un nuovo invio. |
| 400 | IN_STUDIO_NO_DEVICE | Nessun tablet collegato per quella sede. |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | COUNTERSIGN_NOT_REQUIRED | Questo consenso non prevede la controfirma. |
| 400 | API_TEST_CONSENT | Consenso di prova: non entra nel giro della controfirma (il link all'esecutore è un messaggio vero). |
| 400 | ALREADY_COUNTERSIGNED | L'esecutore ha già controfirmato. |
| 400 | COUNTERSIGN_NOT_PENDING | Il cliente non ha ancora firmato. |
| 400 | COUNTERSIGNER_NOT_ASSIGNED | Nessun esecutore associato: assegnalo dalla dashboard. |
| 400 | STRUCTURE_REQUIRED | La controfirma con link all'esecutore esiste solo negli studi con professionisti configurati. |
| 409 | COUNTERSIGN_PHONE_NOT_VERIFIED | Il numero dell'esecutore non è verificato: il link non può partire. |
| 429 | COUNTERSIGN_SESSION_RATE_LIMITED | Link già mandato da meno di 10 minuti. |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
redirect | query | string | no | 1 (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
| Stato | Codice | Significato |
|---|---|---|
| 404 | DOCUMENT_NOT_AVAILABLE | Il documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento. |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
redirect | query | string | no | 1 (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
| Stato | Codice | Significato |
|---|---|---|
| 404 | DOCUMENT_NOT_AVAILABLE | Il documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento. |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
redirect | query | string | no | 1 (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
| Stato | Codice | Significato |
|---|---|---|
| 404 | DOCUMENT_NOT_AVAILABLE | Il documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento. |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
format | query | json | pdf | no | json 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | DOCUMENT_NOT_AVAILABLE | Il documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento. |
| 400 | INVALID_QUERY | format diverso da json e pdf. |
| 404 | DOSSIER_NOT_AVAILABLE | Il fascicolo non è disponibile per questo consenso. |
| 502 | DOSSIER_GENERATION_FAILED | Uno 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
email | string (email) | no | Facoltativo: 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Indirizzo email non valido. |
| 400 | API_TEST_CONSENT | Il PDF di prova porta la filigrana e sparisce dopo 7 giorni: non si spedisce. |
| 404 | DOCUMENT_NOT_AVAILABLE | Il documento esiste solo dopo la firma: il consenso non è ancora completato, oppure l'invio non comprendeva questo documento. |
| 404 | CONSENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
search | query | string | no | Nome, cognome o telefono. Un valore che è un codice fiscale ben formato viene cercato come tale (per hash). |
fiscalCode | query | string | no | Codice fiscale esatto: ricerca per hash, mai in chiaro a database. Esempio: RSSMRA80A01F205X. |
externalId | query | string | no | Il tuo riferimento del gestionale, esatto. Esempio: CRM-4412. |
sedeId | query | string | no | Sede della struttura (in alternativa all'intestazione X-Structure-Sede-Id). |
executorMemberId | query | string | no | Solo i pazienti con almeno un consenso eseguito da quel professionista. |
apiTest | query | true | false | no | true = solo le anagrafiche di prova, false = solo quelle vere. |
limit | query | integer | no | Da 1 a 100 (predefinito 25). Esempio: 25. |
cursor | query | string | no | Cursore 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_QUERY | Un parametro non è valido (limite fuori intervallo, codice fiscale malformato). |
| 400 | INVALID_CURSOR | Il cursore non è uno di quelli restituiti da questa API. |
| 404 | SEDE_NOT_FOUND | sedeId non è di questo account. |
| 403 | SEDE_CONTEXT_MISMATCH | sedeId 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
firstName | string | sì | Obbligatori. (1-80 caratteri) |
lastName | string | sì | Obbligatori. (1-80 caratteri) |
phone | string | no | Formato internazionale E.164 (+393331112233). (formato ^\+[1-9]\d{6,14}$) |
fiscalCode | string | no | Serve 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. |
externalId | string | no | Serve 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) |
notes | string | no | Note 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) |
sedeId | string (uuid) | no | Solo 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-Keya 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Campi mancanti o telefono non in E.164. |
| 400 | SEDE_REQUIRED | Account struttura senza sedeId né intestazione di sede. |
| 400 | INVALID_SEDE | sedeId non è una sede della tua struttura (o l'account non è una struttura). |
| 403 | SEDE_CONTEXT_MISMATCH | sedeId in contrasto con l'intestazione X-Structure-Sede-Id. |
| 409 | PHONE_BELONGS_TO_OTHER | Quel 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}. |
| 422 | IDENTITY_CONFLICT | Codice fiscale ed externalId puntano a due persone diverse già registrate: lo risolve il tuo gestionale, non lo indoviniamo noi. |
| 429 | TEST_DAILY_LIMIT | Chiave Test: 20 anagrafiche di prova nuove al giorno. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-Idempotency-Key vuota o più lunga di 128 caratteri. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
externalId | string | no | Almeno uno dei due: sono le chiavi con cui si ritrova la persona. (1-120 caratteri) |
fiscalCode | string | no | Almeno uno dei due: sono le chiavi con cui si ritrova la persona. |
firstName | string | sì | Obbligatori. (1-80 caratteri) |
lastName | string | sì | Obbligatori. (1-80 caratteri) |
phone | string | no | Formato internazionale E.164. (formato ^\+[1-9]\d{6,14}$) |
sedeId | string (uuid) | no | Solo 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Manca sia externalId sia fiscalCode, oppure un campo non è valido. |
| 400 | INVALID_SEDE | sedeId non è una sede della tua struttura (o l'account non è una struttura). |
| 403 | SEDE_CONTEXT_MISMATCH | sedeId in contrasto con l'intestazione X-Structure-Sede-Id. |
| 422 | IDENTITY_CONFLICT | Le 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | 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:hasFiscalCodedice 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
firstName | string | no | (1-80 caratteri) |
lastName | string | no | (1-80 caratteri) |
phone | string | no | (formato ^\+[1-9]\d{6,14}$) |
externalId | string | null | no | (1-120 caratteri) |
notes | string | null | no | (max 5000 caratteri) |
sedeId | string (uuid) | null | no |
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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Campo non previsto, valore non valido o corpo vuoto. |
| 400 | INVALID_SEDE | sedeId non è una sede della tua struttura (o l'account non è una struttura). |
| 403 | SEDE_CONTEXT_MISMATCH | sedeId in contrasto con l'intestazione X-Structure-Sede-Id. |
| 404 | PATIENT_NOT_FOUND | Il paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta). |
| 422 | IDENTITY_CONFLICT | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
notes | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | notes mancante o troppo lungo. |
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
text | string | sì | Il testo della riga, fino a 2000 caratteri. (1-2000 caratteri) |
author | string | no | Chi 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | text mancante o troppo lungo. |
| 400 | NOTES_TOO_LONG | Con la riga nuova le note supererebbero i 5000 caratteri: sostituiscile con PUT /notes. |
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
fileName | string | sì | Nome del file, fino a 200 caratteri. (1-200 caratteri) |
contentBase64 | string | sì | 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. |
note | string | no | Facoltativa: 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | UNSUPPORTED_FORMAT | Formato non ammesso. |
| 400 | FILE_TOO_LARGE | Oltre i 10 MB. |
| 400 | FILE_REQUIRED | Nessun file: manca il campo file in multipart o contentBase64 in JSON. |
| 400 | INVALID_BASE64 | contentBase64 non è un base64 valido, oppure è vuoto. |
| 404 | PATIENT_NOT_FOUND | Il paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta). |
| 400 | IDEMPOTENCY_KEY_INVALID | X-Idempotency-Key vuota o più lunga di 128 caratteri. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
docId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | DOCUMENT_NOT_FOUND | Il documento non esiste o non è di questo paziente. |
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
docId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 403 | DOCUMENT_PROTECTED | Documento caricato dal paziente durante la firma: fa parte del fascicolo probatorio. |
| 404 | DOCUMENT_NOT_FOUND | Il documento non esiste o non è di questo paziente. |
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
consentId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
consentId | string (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". |
reason | string | no | Facoltativo: il motivo, per il Registro attività. (max 300 caratteri) |
confirm | true | sì | Deve valere true. |
Da sapere
- Il permesso
patients:deletesi 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | confirm: true mancante. |
| 400 | ALREADY_REVOKED | I consensi di quel documento risultano già revocati. |
| 403 | DELETE_REQUIRES_IP_ALLOWLIST | La chiave non ha IP consentiti impostati. |
| 403 | TEST_KEY_DESTRUCTIVE | Chiave in modalità Test: la revoca non si prova, si legge. |
| 404 | CONSENT_NOT_FOUND | consentId non è un documento privacy di questo paziente. |
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
Da sapere
- Permesso delicato:
patients:exportnon è 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
format | query | json | pdf | no | json 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_QUERY | format diverso da json e pdf. |
| 404 | PATIENT_NOT_FOUND | Il paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta). |
| 500 | DOSSIER_UNAVAILABLE | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
confirm | true | sì | Deve valere true: la cancellazione è irreversibile. |
acknowledgeRetention | true | no | Obbligatorio 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. |
reason | string | no | Motivo: 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_postponede il titolare riceve un avviso. Per procedere annulli e rifai la richiesta conacknowledgeRetention: 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 partepatient.deletion_cancelledcondeletion.reason: "NEW_SIGNED_CONSENTS". - Una seconda richiesta su un paziente che ha già una cancellazione programmata non sposta la scadenza: risponde 409
PATIENT_DELETION_SCHEDULEDconexecuteAfterecancelEndpoint. 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 (409PATIENT_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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | confirm: true mancante. |
| 400 | RETENTION_ACK_REQUIRED | Il paziente ha consensi firmati e manca acknowledgeRetention: true. |
| 403 | DELETE_REQUIRES_IP_ALLOWLIST | La chiave deve avere gli IP consentiti impostati. Si scelgono alla creazione della chiave e non si cambiano dopo. |
| 403 | TEST_KEY_DESTRUCTIVE | Chiave in modalità Test: una cancellazione non si prova, si esegue. |
| 404 | PATIENT_NOT_FOUND | Non esiste, non è di questo account oppure è fuori dal contesto di sede. |
| 409 | PATIENT_DELETION_SCHEDULED | La cancellazione di questo paziente è già programmata: la risposta porta executeAfter (quando verrà eseguita) e cancelEndpoint (per annullarla e rifarla, se serve riconfermarla). |
| 429 | DELETE_DAILY_LIMIT | Tetto 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Non 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
visitDate | string (date-time) | no | Istante ISO 8601. Se manca, adesso. |
reason | string | no | Motivo in chiaro: viene agganciato alla tua tassonomia dei motivi di consulto. (max 200 caratteri) |
reasonId | string (uuid) | no | In alternativa, l'id di un motivo di consulto (GET /consultation-reasons). |
pathology | string | no | (max 2000 caratteri) |
notes | string | no | (max 5000 caratteri) |
sedeId | string (uuid) | null | no | |
consentId | string (uuid) | no | Consenso firmato da cui nasce la visita (uno a uno, come in dashboard). |
subjectConsentId | string (uuid) | null | no | Consenso che identifica il soggetto, quando è un minore o un tutelato. |
cycleId | string (uuid) | no | Ciclo esistente, oppure il titolo di un ciclo nuovo che nasce con questa visita. |
cycleTitle | string | no | Ciclo esistente, oppure il titolo di un ciclo nuovo che nasce con questa visita. (max 200 caratteri) |
isFirstVisit | boolean | no |
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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Campo non previsto o valore non valido. |
| 404 | CONSENT_NOT_FOUND | consentId o subjectConsentId non esiste o non è di questo paziente. |
| 404 | PATIENT_NOT_FOUND | Il paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta). |
| 403 | VISITS_DISABLED | La funzione visita non è attiva per questo account (o per la struttura per cui lavori): letture ed eliminazioni restano disponibili. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-Idempotency-Key vuota o più lunga di 128 caratteri. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
visitId | path | string | sì | Id della visita. Esempio: d1e2f3a4-b5c6-4d7e-8f9a-0b1c2d3e4f5a. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
reason | string | no | (max 200 caratteri) |
reasonId | string (uuid) | null | no | |
pathology | string | no | (max 2000 caratteri) |
notes | string | no | (max 5000 caratteri) |
subjectConsentId | string (uuid) | null | no | |
isFirstVisit | boolean | no |
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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Corpo vuoto o valore non valido. |
| 404 | VISIT_NOT_FOUND | La visita non esiste o non è di questo paziente. |
| 404 | PATIENT_NOT_FOUND | Il paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta). |
| 403 | VISITS_DISABLED | La 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
visitId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | VISIT_NOT_FOUND | La visita non esiste o non è di questo paziente. |
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
cycleId | path | string | sì | Id del ciclo di trattamento. Esempio: e2f3a4b5-c6d7-4e8f-9a0b-1c2d3e4f5a6b. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
title | string | no | (max 200 caratteri) |
isActive | boolean | no |
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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Corpo vuoto o valore non valido. |
| 404 | CYCLE_NOT_FOUND | Il ciclo non esiste o non è di questo paziente. |
| 404 | PATIENT_NOT_FOUND | Il paziente non esiste, non è di questo account, è fuori dal contesto di sede o ha una cancellazione programmata (stessa risposta). |
| 403 | VISITS_DISABLED | La 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
patientId | path | string | sì | Id del paziente. Esempio: 8b1d4f6a-2c3e-4a5b-9d7e-1f2a3b4c5d6e. |
cycleId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CYCLE_NOT_FOUND | Il ciclo non esiste o non è di questo paziente. |
| 404 | PATIENT_NOT_FOUND | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | 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.findingsdice 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:readnon è 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | ANAMNESIS_NOT_AVAILABLE | Nessuna anamnesi è attiva sull'account (in dashboard la funzione non c'è). |
| 404 | CONSENT_NOT_FOUND | Il 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | NOT_A_STRUCTURE | L'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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
name | string | sì | Obbligatorio, fino a 120 caratteri. (1-120 caratteri) |
address | string | no | Facoltativi. Finiscono nei documenti firmati in quella sede. (max 200 caratteri) |
city | string | no | Facoltativi. Finiscono nei documenti firmati in quella sede. (max 120 caratteri) |
cap | string | no | Facoltativi. Finiscono nei documenti firmati in quella sede. (max 10 caratteri) |
province | string | no | Facoltativi. Finiscono nei documenti firmati in quella sede. (max 120 caratteri) |
Da sapere
- Permesso delicato:
structure:writecambia 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 404 | NOT_A_STRUCTURE | L'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
sedeId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | SEDE_NOT_FOUND | Sede inesistente, disattivata o di un altro account (stessa risposta). |
| 404 | NOT_A_STRUCTURE | L'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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
sedeId | path | string | sì | Id della sede. Esempio: 5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
name | string | no | (1-120 caratteri) |
address | string | no | (max 200 caratteri) |
city | string | no | (max 120 caratteri) |
cap | string | no | (max 10 caratteri) |
province | string | no | (max 120 caratteri) |
Da sapere
- Permesso delicato:
structure:writecambia 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | SEDE_NOT_FOUND | Sede inesistente o altrui. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
sedeId | path | string | sì | Id della sede. Esempio: 5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
reassignToSedeId | string | no | Facoltativo: sede attiva a cui spostare chi lavorava solo in questa. |
Da sapere
- Permesso delicato:
structure:writecambia 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | SEDE_NOT_FOUND | Sede (o reassignToSedeId) inesistente, disattivata o di un altro account. |
| 409 | SEDE_HAS_MEMBERS | Studi: alcuni professionisti lavorano solo qui; members li elenca. |
| 400 | LAST_SEDE | È l'ultima sede attiva. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
sedeId | query | string | no | Solo i professionisti di quella sede. |
status | query | ACTIVE | SUSPENDED | PENDING | no | Stato 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_QUERY | Stato non valido. |
| 404 | SEDE_NOT_FOUND | Sede altrui. |
| 404 | NOT_A_STRUCTURE | L'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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
firstName | string | sì | Obbligatori. (1-100 caratteri) |
lastName | string | sì | Obbligatori. (1-100 caratteri) |
fiscalCode | string | no | Strutture sanitarie: dati che intestano i documenti. (max 32 caratteri) |
professionalRegister | string | no | Strutture sanitarie: dati che intestano i documenti. (max 200 caratteri) |
specialization | string | no | Strutture sanitarie: dati che intestano i documenti. (max 200 caratteri) |
partitaIva | string | no | Strutture 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" | no | Strutture sanitarie: COLLABORATOR (predefinito) o REFERENTE di sede. |
sedeIds | array<string> | no | Sedi della struttura in cui lavora (obbligatorio negli studi). Una sede altrui è un 404. (1-50 elementi) |
professions | array<string> | no | Strutture 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) |
enabledTreatmentIds | array<string> | no | Strutture sanitarie: trattamenti che può eseguire, dal catalogo delle sue professioni (vuoto = tutti). (max 500 elementi) |
onlyAssignedModels | boolean | no | Strutture sanitarie: true = invia solo i modelli a lui associati. |
Studio
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
firstName | string | sì | Obbligatori. (1-80 caratteri) |
lastName | string | sì | Obbligatori. (1-80 caratteri) |
phone | string | sì | Studi: obbligatorio, è il numero su cui arriva il link di controfirma. (6-30 caratteri) |
contactEmail | string (email) | null | no | Studi: email di contatto (non di accesso), sede fisica del documento, canale del link di verifica. |
sedeIds | array<string> | sì | Sedi della struttura in cui lavora (obbligatorio negli studi). Una sede altrui è un 404. (1-20 elementi) |
officeId | string (uuid) | null | no | Studi: email di contatto (non di accesso), sede fisica del documento, canale del link di verifica. |
tattooCapabilities | array<"TATTOO" | "PMU" | "PIERCING"> | no | Studi tatuaggi: TATTOO, PMU, PIERCING tra quelle dello studio. (max 3 elementi) |
relationshipType | "EMPLOYEE" | "EXTERNAL_COLLABORATOR" | no | EMPLOYEE o EXTERNAL_COLLABORATOR (obbligatorio nelle strutture sanitarie). |
channel | "WHATSAPP" | "SMS" | no | Studi: email di contatto (non di accesso), sede fisica del documento, canale del link di verifica. |
Da sapere
- Permesso delicato:
structure:writecambia 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 400 | PROFESSION_NOT_IN_SCOPES | Professioni fuori dagli ambiti della struttura; rejected le elenca. |
| 400 | TREATMENT_NOT_IN_SCOPES | Trattamenti che non sono nel catalogo delle professioni del professionista. |
| 403 | ACCESS_NOT_MANAGED_VIA_API | Il corpo contiene email di accesso o attivazione dell'utenza: si passa da POST /structure/invites. |
| 404 | SEDE_NOT_FOUND | Una delle sedi non è di questa struttura. |
| 404 | STUDIO_TATTOO_ONLY | Studio che in dashboard non gestisce professionisti senza accesso (per esempio un centro estetico). |
| 429 | VERIFICATION_LIMIT | Studi: raggiunto il limite giornaliero di link di verifica del telefono. |
| 404 | NOT_A_STRUCTURE | L'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 404 | OFFICE_NOT_FOUND | Studi: officeId non è una sede fisica di questo account. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
memberId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | MEMBER_NOT_FOUND | Professionista inesistente, rimosso o di un altro account (stessa risposta). |
| 404 | NOT_A_STRUCTURE | L'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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
memberId | path | string | sì | Id del professionista della struttura. Esempio: 6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e. |
Corpo della richiesta
Struttura sanitaria
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
firstName | string | no | (1-100 caratteri) |
lastName | string | no | (1-100 caratteri) |
fiscalCode | string | no | (max 32 caratteri) |
professionalRegister | string | no | (max 200 caratteri) |
specialization | string | no | (max 200 caratteri) |
partitaIva | string | no | (max 32 caratteri) |
relationshipType | "EMPLOYEE" | "EXTERNAL_COLLABORATOR" | no | |
role | "COLLABORATOR" | "REFERENTE" | no | |
sedeIds | array<string> | no | (1-50 elementi) |
professions | array<string> | no | (max 20 elementi) |
enabledTreatmentIds | array<string> | no | (max 500 elementi) |
onlyAssignedModels | boolean | no | |
status | "ACTIVE" | "SUSPENDED" | no | ACTIVE o SUSPENDED. |
Studio
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
firstName | string | no | (1-80 caratteri) |
lastName | string | no | (1-80 caratteri) |
phone | string | no | (6-30 caratteri) |
contactEmail | string (email) | null | no | |
sedeIds | array<string> | no | (1-20 elementi) |
officeId | string (uuid) | null | no | |
tattooCapabilities | array<"TATTOO" | "PMU" | "PIERCING"> | no | (max 3 elementi) |
relationshipType | "EMPLOYEE" | "EXTERNAL_COLLABORATOR" | no | |
channel | "WHATSAPP" | "SMS" | no | |
status | "ACTIVE" | "SUSPENDED" | no | ACTIVE o SUSPENDED. |
countersignDigestTime | string | null | no | Studi: preferenze di controfirma del professionista. (formato ^([01]\d|2[0-3]):(00|15|30|45)$) |
countersignDigestEnabled | boolean | no | Studi: preferenze di controfirma del professionista. |
countersignRequireCode | boolean | no | Studi: preferenze di controfirma del professionista. |
Da sapere
- Permesso delicato:
structure:writecambia 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | MEMBER_NOT_FOUND | Professionista inesistente o di un altro account. |
| 409 | MEMBER_REVOKED | Il professionista è stato rimosso dall'organico: non si modifica né si riattiva. |
| 403 | MEMBER_IDENTITY_READ_ONLY | Nome, codice fiscale (e negli studi telefono ed email) di un professionista che ha un accesso. |
| 400 | PROFESSION_NOT_IN_SCOPES | Professioni fuori dagli ambiti della struttura. |
| 400 | TREATMENT_NOT_IN_SCOPES | Trattamenti fuori dal catalogo delle professioni risultanti. |
| 409 | MEMBER_HAS_PENDING_COUNTERSIGN | Studi: sospensione con controfirme in attesa. |
| 429 | VERIFICATION_LIMIT | Studi: troppi link di verifica oggi per questo professionista. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 404 | OFFICE_NOT_FOUND | Studi: officeId non è una sede fisica di questo account. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
memberId | path | string | sì | Id del professionista della struttura. Esempio: 6b7c8d9e-0f1a-4b2c-9d3e-4f5a6b7c8d9e. |
reassignTo | query | string | no | Studi: id del professionista a cui passano le controfirme in attesa. Si può mandare anche nel corpo JSON. |
Da sapere
- Permesso delicato:
structure:writecambia 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | MEMBER_NOT_FOUND | Professionista (o reassignTo) inesistente, rimosso o altrui. |
| 409 | MEMBER_HAS_PENDING_COUNTERSIGN | Studi: controfirme in attesa, serve reassignTo. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | NOT_A_STRUCTURE | L'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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
firstName | string | sì | (1-100 caratteri) |
lastName | string | sì | (1-100 caratteri) |
fiscalCode | string | no | (max 32 caratteri) |
professionalRegister | string | no | (max 200 caratteri) |
specialization | string | no | (max 200 caratteri) |
partitaIva | string | no | (max 32 caratteri) |
relationshipType | "EMPLOYEE" | "EXTERNAL_COLLABORATOR" | sì | |
role | "COLLABORATOR" | "REFERENTE" | no | COLLABORATOR o REFERENTE. |
sedeIds | array<string> | no | (1-50 elementi) |
professions | array<string> | no | (max 20 elementi) |
enabledTreatmentIds | array<string> | no | (max 500 elementi) |
onlyAssignedModels | boolean | no | |
email | string (email) | sì | Obbligatoria: è l'indirizzo a cui parte l'invito. |
Studio
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
email | string (email) | sì | Obbligatoria: è l'indirizzo a cui parte l'invito. |
name | string | sì | Studi: nome di chi inviti. (1-160 caratteri) |
role | "COLLABORATOR" | "REFERENTE" | no | COLLABORATOR o REFERENTE. |
officeId | string (uuid) | no | |
tattooCapabilities | array<"TATTOO" | "PMU" | "PIERCING"> | no | (max 3 elementi) |
Da sapere
- Permesso delicato:
structure:writecambia 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 409 | DUPLICATE_EMAIL | C'è già un invito in attesa per questo indirizzo; inviteId è il suo id. |
| 409 | MEMBER_ALREADY_PRESENT | L'indirizzo è già di un professionista della struttura. |
| 409 | INVITE_OWN_EMAIL | È l'indirizzo del titolare dell'account. |
| 429 | INVITE_LIMIT | Raggiunti i 10 inviti di oggi. |
| 429 | INVITE_LIMIT_ADDRESS | Questo indirizzo ha già ricevuto 3 inviti oggi. |
| 400 | PROFESSION_NOT_IN_SCOPES | Strutture sanitarie: professioni fuori dagli ambiti. |
| 404 | NOT_A_STRUCTURE | L'account è un professionista singolo: sedi e professionisti esistono solo per strutture e studi. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 404 | OFFICE_NOT_FOUND | Studi: officeId non è una sede fisica di questo account. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
inviteId | path | string | sì | Id dell'invito. Esempio: 7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f. |
Da sapere
- Permesso delicato:
structure:writecambia 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | INVITE_NOT_FOUND | Non c'è un invito in attesa con quell'id in questa struttura. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | GENERIC_EXECUTOR_NOT_AVAILABLE | Studi: l'esecutore controfirma, la modalità non esiste. |
| 404 | NOT_A_STRUCTURE | L'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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
enabled | boolean | sì | Obbligatorio. |
scopes | array<string> | no | Ambiti abilitati (vuoto = tutti quelli ammessi). (max 30 elementi) |
texts | object | no | Testo per ambito: { "PSYCHOLOGIST": "Psicologo iscritto all'Albo" }, fino a 1500 caratteri. |
text | string | no | Testo di ripiego per gli ambiti senza un testo proprio. (max 1500 caratteri) |
Da sapere
- Permesso delicato:
structure:writecambia 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | GENERIC_EXECUTOR_SCOPE_INVALID | Ambito non della struttura o escluso (tatuaggio). |
| 404 | GENERIC_EXECUTOR_NOT_AVAILABLE | Studi: la modalità non esiste. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | NOT_A_STRUCTURE | L'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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
sedeId | query | string | no | Solo quella sede (di questa struttura). In conflitto con X-Structure-Sede-Id è 403. |
memberId | query | string | no | Solo quel professionista. |
from | query | string | no | Inizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01. |
to | query | string | no | Fine 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_QUERY | Data non valida o periodo invertito. |
| 404 | SEDE_NOT_FOUND | Sede di un altro account. |
| 404 | MEMBER_NOT_FOUND | Professionista di un altro account. |
| 403 | SEDE_CONTEXT_MISMATCH | sedeId in contrasto con l'intestazione X-Structure-Sede-Id. |
| 404 | NOT_A_STRUCTURE | L'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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
name | string | sì | 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" | no | CUSTOM (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" | null | no | Professione (o ambito della struttura) del modello: una di quelle in GET /templates. |
treatments | array<string> | no | Codici dei trattamenti, dal catalogo della stessa professione. Obbligatori con SPECIFIC. (max 300 elementi, predefinito []) |
dualSignatureEnabled | boolean | no | Doppia firma genitoriale pre-attivata. Su una professione (o trattamenti) che non la prevede è 400 DUAL_SIGNATURE_NOT_AVAILABLE. (predefinito false) |
medicalClearanceEnabled | boolean | null | no | Infermieri, estetiste e tatuatori: chiede prescrizione o nulla osta all'invio (null = automatico). |
sendMethod | "SMS" | "WHATSAPP" | null | no | WHATSAPP, SMS o null (vale la preferenza dell'account). |
isDefault | boolean | no | true = il modello si apre da solo all'invio. Ce n'è uno solo: gli altri perdono il segno. (predefinito false) |
memberIds | array<string> | no | Strutture: 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 []) |
validityMonths | integer | null | no | Consensi a scadenza: validità in mesi (1-120) e rinnovo automatico 30 giorni prima. (da 1 a 120) |
autoRenewReminder | boolean | no | Consensi 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:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 400 | CONFIG_TEMPLATE_TYPE_NOT_ALLOWED | Professione non esercitata dall'account. |
| 400 | CONFIG_SPECIFIC_NO_TREATMENTS | Modello specifico senza trattamenti. |
| 400 | CONFIG_INVALID_TREATMENTS | Trattamenti che non sono del catalogo della professione. |
| 400 | DUAL_SIGNATURE_NOT_AVAILABLE | Doppia firma su una professione o su trattamenti che non la prevedono. |
| 404 | MEMBER_NOT_FOUND | Un professionista di memberIds non esiste, non è attivo, è la segreteria o è di un altro account. |
| 400 | MEMBER_PROFESSION_MISMATCH | Un professionista di memberIds non esercita l'ambito del modello. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | CONFIG_MAX_REACHED | Limite di modelli raggiunto. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
configId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONFIG_NOT_FOUND | Modello 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
configId | path | string | sì | Id del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
name | string | sì | 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" | no | CUSTOM (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" | null | no | Professione (o ambito della struttura) del modello: una di quelle in GET /templates. |
treatments | array<string> | no | Codici dei trattamenti, dal catalogo della stessa professione. Obbligatori con SPECIFIC. (max 300 elementi, predefinito []) |
dualSignatureEnabled | boolean | no | Doppia firma genitoriale pre-attivata. Su una professione (o trattamenti) che non la prevede è 400 DUAL_SIGNATURE_NOT_AVAILABLE. (predefinito false) |
medicalClearanceEnabled | boolean | null | no | Infermieri, estetiste e tatuatori: chiede prescrizione o nulla osta all'invio (null = automatico). |
sendMethod | "SMS" | "WHATSAPP" | null | no | WHATSAPP, SMS o null (vale la preferenza dell'account). |
isDefault | boolean | no | true = il modello si apre da solo all'invio. Ce n'è uno solo: gli altri perdono il segno. (predefinito false) |
memberIds | array<string> | no | Strutture: 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 []) |
validityMonths | integer | null | no | Consensi a scadenza: validità in mesi (1-120) e rinnovo automatico 30 giorni prima. (da 1 a 120) |
autoRenewReminder | boolean | no | Consensi 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:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONFIG_NOT_FOUND | Modello inesistente o di un altro account (stessa risposta). |
| 400 | AGORA_CONFIG_LOCKED | Modello Agorà: testo e identità non si cambiano. |
| 400 | PRIVATE_CONFIG_LOCKED | Modello col testo del centro: non si cambia da qui. |
| 400 | CONFIG_SOURCE_IMMUTABLE | La sorgente di un modello non si cambia. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 400 | CONFIG_TEMPLATE_TYPE_NOT_ALLOWED | Professione non esercitata dall'account. |
| 400 | CONFIG_SPECIFIC_NO_TREATMENTS | Modello specifico senza trattamenti. |
| 400 | CONFIG_INVALID_TREATMENTS | Trattamenti che non sono del catalogo della professione. |
| 400 | DUAL_SIGNATURE_NOT_AVAILABLE | Doppia firma su una professione o su trattamenti che non la prevedono. |
| 404 | MEMBER_NOT_FOUND | Un professionista di memberIds non esiste, non è attivo, è la segreteria o è di un altro account. |
| 400 | MEMBER_PROFESSION_MISMATCH | Un professionista di memberIds non esercita l'ambito del modello. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
configId | path | string | sì | Id del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
name | string | no | Nome interno del modello, da 1 a 50 caratteri. Non lo vede il paziente. (1-50 caratteri) |
mode | "STANDARD" | "SPECIFIC" | no | STANDARD (consenso generico della professione) o SPECIFIC (trattamenti elencati). |
source | "CUSTOM" | "AGORA" | "PRIVATE" | no | CUSTOM (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" | null | no | Professione (o ambito della struttura) del modello: una di quelle in GET /templates. |
treatments | array<string> | no | Codici dei trattamenti, dal catalogo della stessa professione. Obbligatori con SPECIFIC. (max 300 elementi) |
dualSignatureEnabled | boolean | no | Doppia firma genitoriale pre-attivata. Su una professione (o trattamenti) che non la prevede è 400 DUAL_SIGNATURE_NOT_AVAILABLE. |
medicalClearanceEnabled | boolean | null | no | Infermieri, estetiste e tatuatori: chiede prescrizione o nulla osta all'invio (null = automatico). |
sendMethod | "SMS" | "WHATSAPP" | null | no | WHATSAPP, SMS o null (vale la preferenza dell'account). |
isDefault | boolean | no | true = il modello si apre da solo all'invio. Ce n'è uno solo: gli altri perdono il segno. |
memberIds | array<string> | no | Strutture: 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) |
validityMonths | integer | null | no | Consensi a scadenza: validità in mesi (1-120) e rinnovo automatico 30 giorni prima. (da 1 a 120) |
autoRenewReminder | boolean | no | Consensi 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:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONFIG_NOT_FOUND | Modello inesistente o di un altro account (stessa risposta). |
| 400 | AGORA_CONFIG_LOCKED | Modello Agorà: testo e identità non si cambiano. |
| 400 | PRIVATE_CONFIG_LOCKED | Modello col testo del centro: non si cambia da qui. |
| 400 | CONFIG_SOURCE_IMMUTABLE | La sorgente di un modello non si cambia. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 400 | CONFIG_TEMPLATE_TYPE_NOT_ALLOWED | Professione non esercitata dall'account. |
| 400 | CONFIG_SPECIFIC_NO_TREATMENTS | Modello specifico senza trattamenti. |
| 400 | CONFIG_INVALID_TREATMENTS | Trattamenti che non sono del catalogo della professione. |
| 400 | DUAL_SIGNATURE_NOT_AVAILABLE | Doppia firma su una professione o su trattamenti che non la prevedono. |
| 404 | MEMBER_NOT_FOUND | Un professionista di memberIds non esiste, non è attivo, è la segreteria o è di un altro account. |
| 400 | MEMBER_PROFESSION_MISMATCH | Un professionista di memberIds non esercita l'ambito del modello. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
configId | path | string | sì | Id del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d. |
Da sapere
- Permesso delicato: un modello sbagliato blocca gli invii, quindi
configurations:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONFIG_NOT_FOUND | Modello inesistente o di un altro account (stessa risposta). |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
configId | path | string | sì | Id del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d. |
Da sapere
- Permesso delicato: un modello sbagliato blocca gli invii, quindi
configurations:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONFIG_NOT_FOUND | Modello inesistente o di un altro account (stessa risposta). |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
configId | path | string | sì | Id del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d. |
Da sapere
- Permesso delicato: un modello sbagliato blocca gli invii, quindi
configurations:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONFIG_NOT_FOUND | Modello inesistente o di un altro account (stessa risposta). |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
configId | path | string | sì | Id del modello di consenso. Esempio: clx8k2m4p0001qz7h3v5n9b2d. |
format | query | pdf | json | no | pdf (predefinito) o json. |
lang | query | it | no | Solo 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONFIG_NOT_FOUND | Modello inesistente o di un altro account (stessa risposta). |
| 400 | PREVIEW_LANGUAGE_NOT_AVAILABLE | Anteprima di un modello in una lingua diversa dall'italiano. |
| 400 | INVALID_QUERY | Formato 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
professionType | query | string | no | Professione 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | PROFESSION_NOT_FOUND | Professione 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
professionType | string | no | Riga da scrivere (predefinito: la principale). (1-40 caratteri) |
treatments | array<string> | no | Codici del catalogo della professione; custom-… per i personalizzati. (max 500 elementi) |
settings | object | no | Solo templateMode ("generic" o "specific") e customMode (booleano). Qualunque altra chiave è 400. |
settings.templateMode | "generic" | "specific" | no | |
settings.customMode | boolean | no | |
customTreatments | array<object> | no | [{ id: "custom-…", label, description? }], al massimo 100, id diversi fra loro. (max 100 elementi) |
customTreatments[].id | string | sì | (formato ^custom-[A-Za-z0-9_-]{1,60}$) |
customTreatments[].label | string | sì | (1-200 caratteri) |
customTreatments[].description | string | no | (max 2000 caratteri) |
enabledOptionalConsents | array<string> | no | Clausole facoltative dell'informativa (id da GET /privacy-policy). (max 50 elementi) |
Da sapere
- Permesso delicato: un modello sbagliato blocca gli invii, quindi
configurations:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | UNKNOWN_OPTIONAL_CONSENT | Clausola facoltativa inesistente. |
| 400 | DUPLICATE_CUSTOM_TREATMENT | Due trattamenti personalizzati con lo stesso id. |
| 404 | PROFESSION_NOT_FOUND | Professione non esercitata dall'account. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
doc | query | 1 | 2 | sì | 1 per l'informativa privacy, 2 per il consenso. |
lang | query | it | en | fr | de | no | it, en, fr o de. Le lingue diverse dall'italiano devono essere attive (GET /languages). |
professionType | query | string | no | Professione o ambito. |
format | query | pdf | json | no | pdf (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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_QUERY | doc mancante o diverso da 1 e 2, formato non valido. |
| 400 | LANGUAGE_NOT_ACTIVE | Lingua 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
enabledOptionalConsents | array<string> | no | Clausole facoltative attive. (max 50 elementi) |
privacyInformativaEnabled | boolean | no | Solo strutture: false = si invia il solo consenso. |
Da sapere
- Permesso delicato: un modello sbagliato blocca gli invii, quindi
configurations:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 403 | CUSTOM_PRIVACY_ADMIN_ONLY | Il corpo contiene testi dell'informativa (intro, sections, …). |
| 400 | PRIVACY_TOGGLE_CLINIC_ONLY | L'interruttore dell'informativa esiste solo per le strutture. |
| 400 | UNKNOWN_OPTIONAL_CONSENT | Clausola inesistente. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
memberId | query | string | no | Strutture: 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | ENGAGEMENT_NOT_AVAILABLE | L'incarico non esiste per questo account (verticale estetica senza condizioni di servizio). |
| 404 | MEMBER_NOT_FOUND | memberId 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
enabled | boolean | sì | Obbligatorio. |
clauses | object | no | Scelte per clausola: { [clauseId]: { enabled, vars } }, validate sul catalogo di GET /engagement. (predefinito {}) |
insurance | object | null | no | { company, policyNumber, coverage, expiresAt: AAAA-MM-GG }. |
insurance.company | string | sì | (1-120 caratteri) |
insurance.policyNumber | string | sì | (1-60 caratteri) |
insurance.coverage | string | no | (max 60 caratteri) |
insurance.expiresAt | string | sì | (formato ^\d{4}-\d{2}-\d{2}$) |
priceList | array<object> | null | no | [{ label, amountCents, notes? }], fino a 50 voci. (max 50 elementi) |
perSendVars | array<string> | no | Variabili modificabili a ogni invio, parametri fiscali per professione, valori del modello personalizzato. (max 50 elementi) |
professionVars | object | no | Variabili modificabili a ogni invio, parametri fiscali per professione, valori del modello personalizzato. |
customValues | object | no | Variabili modificabili a ogni invio, parametri fiscali per professione, valori del modello personalizzato. |
includeByDefault | boolean | no | Come 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. |
memberId | string | no | Strutture: medico a cui si riferisce la configurazione. |
Da sapere
- Permesso delicato: un modello sbagliato blocca gli invii, quindi
configurations:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | ENGAGEMENT_NOT_AVAILABLE | L'incarico non esiste per questo account. |
| 404 | MEMBER_NOT_FOUND | memberId non è un medico attivo di questa struttura. |
| 400 | INVALID_BODY | Polizza mancante o scaduta, massimale assente, clausola non valida. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
memberId | query | string | no | Strutture: 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, salvoengagement.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
| Stato | Codice | Significato |
|---|---|---|
| 404 | ENGAGEMENT_NOT_AVAILABLE | L'incarico non esiste per questo account (stessa risposta di GET /engagement). |
| 404 | MEMBER_NOT_FOUND | memberId 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
memberId | query | string | no | Strutture: incarico di quel medico. |
professionType | query | string | no | Professione con cui simulare l'invio. |
language | query | it | en | fr | de | no | en, 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | ENGAGEMENT_NOT_AVAILABLE | Incarico non disponibile. |
| 400 | INVALID_QUERY | Lingua 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
languages | array<"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:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | CONSENT_LANGUAGES_NOT_AVAILABLE | Account struttura. |
| 400 | CONSENT_LANGUAGES_PROFESSION_NOT_SUPPORTED | Nessuna professione dell'account ha le traduzioni. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
medicalAnamnesisEnabled | boolean | no | Sanità, dove la funzione esiste. |
medicalTraceabilityEnabled | boolean | no | Sanità, dove la funzione esiste. |
customQuestionsEnabled | boolean | no | Strutture «Associazione». |
customQuestionsTitle | string | no | Strutture «Associazione». (max 100 caratteri) |
privacyInformativaEnabled | boolean | no | Strutture. |
messageChannel | "SMS" | "WHATSAPP" | no | WHATSAPP o SMS (bloccato su WhatsApp con il pacchetto annuale). |
Da sapere
- Permesso delicato: un modello sbagliato blocca gli invii, quindi
configurations:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | SETTING_NOT_AVAILABLE | L'interruttore non esiste per questo account. |
| 400 | MESSAGE_CHANNEL_LOCKED | Pacchetto annuale: i messaggi partono su WhatsApp. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | AGORA_NOT_ELIGIBLE | L'account non esercita la medicina estetica. |
| 404 | AGORA_DISABLED | Moduli 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
code | path | string | sì | 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:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 403 | AGORA_SUBSCRIPTION_REQUIRED | Serve un abbonamento attivo. |
| 404 | AGORA_TREATMENT_NOT_AVAILABLE | Codice inesistente o non ancora attivabile. |
| 404 | AGORA_NOT_ELIGIBLE | L'account non esercita la medicina estetica. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
code | path | string | sì | 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:writenon è 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | AGORA_NOT_ACTIVE | Il modulo non è attivo. |
| 404 | AGORA_TREATMENT_NOT_AVAILABLE | Codice inesistente. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
brand | string | sì | Obbligatori. (1-100 caratteri) |
model | string | sì | Obbligatori. (1-100 caratteri) |
dmScheda | string | null | no | Scheda del D.M. e data ISO dell'ultima manutenzione. (max 200 caratteri) |
lastMaintenanceAt | string (date-time) | null | no | Scheda 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 400 | EQUIPMENT_LIMIT | Massimo 30 apparecchiature. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
equipmentId | path | string | sì | Id dell'apparecchiatura. Esempio: 8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
brand | string | no | Tutti facoltativi, almeno uno. (1-100 caratteri) |
model | string | no | Tutti facoltativi, almeno uno. (1-100 caratteri) |
dmScheda | string | null | no | Tutti facoltativi, almeno uno. (max 200 caratteri) |
lastMaintenanceAt | string (date-time) | null | no | Tutti facoltativi, almeno uno. |
active | boolean | no | Tutti 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | EQUIPMENT_NOT_FOUND | Inesistente o altrui. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
equipmentId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | EQUIPMENT_NOT_FOUND | Inesistente o altrui. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
brand | string | sì | Obbligatori. (1-100 caratteri) |
model | string | sì | Obbligatori. (1-100 caratteri) |
serialNumber | string | null | no | Facoltativi. (max 100 caratteri) |
ceMarking | string | null | no | Facoltativi. (max 200 caratteri) |
lastMaintenanceAt | string | null | no | Facoltativi. |
notes | string | null | no | Facoltativi. (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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 400 | EQUIPMENT_LIMIT | Massimo 30 apparecchiature. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
equipmentId | path | string | sì | Id dell'apparecchiatura. Esempio: 8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
brand | string | no | Tutti facoltativi, almeno uno. (1-100 caratteri) |
model | string | no | Tutti facoltativi, almeno uno. (1-100 caratteri) |
serialNumber | string | null | no | Tutti facoltativi, almeno uno. (max 100 caratteri) |
ceMarking | string | null | no | Tutti facoltativi, almeno uno. (max 200 caratteri) |
lastMaintenanceAt | string | null | no | Tutti facoltativi, almeno uno. |
notes | string | null | no | Tutti facoltativi, almeno uno. (max 500 caratteri) |
active | boolean | no | Tutti 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | EQUIPMENT_NOT_FOUND | Inesistente o altrui. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
equipmentId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | EQUIPMENT_NOT_FOUND | Inesistente o altrui. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
search | query | string | no | Numero di lotto o nome del prodotto, anche parziale: è la ricerca per il richiamo. lotNumber è un sinonimo. Esempio: C1234. |
patientId | query | string | no | Solo le registrazioni di quel paziente. |
memberId | query | string | no | Solo quelle di quell'esecutore. |
from | query | string | no | Inizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01. |
to | query | string | no | Fine del periodo (AAAA-MM-GG). Esempio: 2026-09-30. |
page | query | integer | no | Pagina, 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 400 | INVALID_QUERY | Data o pagina non valida. |
POST /lots
Registra un lotto
Registra un lotto iniettabile. La registrazione finisce nel Registro attività.
Permesso: registries:write
Parametri
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
patientId | string (uuid) | sì | Obbligatori; treatmentCode è un iniettabile del catalogo di medicina estetica. |
consentId | string | null | no | Facoltativi. (1-60 caratteri) |
productName | string | sì | Obbligatori; treatmentCode è un iniettabile del catalogo di medicina estetica. (1-200 caratteri) |
productType | "FILLER" | "TOSSINA" | "BIOSTIMOLATORE" | "ALTRO" | null | no | Facoltativi. |
lotNumber | string | null | no | Obbligatorio, oppure ricavato da udiRaw (stringa GS1 letta dallo scanner). (1-100 caratteri) |
expiryDate | string | null | no | Facoltativi. |
udiRaw | string | null | no | (max 2000 caratteri) |
usedAt | string | null | no | Facoltativi. |
treatedArea | string | null | no | Facoltativi. (max 200 caratteri) |
quantity | string | null | no | Facoltativi. (max 100 caratteri) |
notes | string | null | no | Facoltativi. (max 500 caratteri) |
memberId | string (uuid) | null | no | Facoltativi. |
treatmentCode | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | PATIENT_NOT_FOUND | Paziente inesistente o altrui. |
| 404 | CONSENT_NOT_FOUND | Consenso inesistente, altrui o di un altro paziente. |
| 404 | MEMBER_NOT_FOUND | Esecutore inesistente o altrui. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
lotId | path | string | sì | Id della riga del registro lotti. Esempio: 9e0f1a2b-3c4d-4e5f-8a6b-7c8d9e0f1a2b. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
productName | string | no | Tutti facoltativi, almeno uno. (1-200 caratteri) |
productType | "FILLER" | "TOSSINA" | "BIOSTIMOLATORE" | "ALTRO" | null | no | Tutti facoltativi, almeno uno. |
lotNumber | string | null | no | Tutti facoltativi, almeno uno. (1-100 caratteri) |
expiryDate | string | null | no | Tutti facoltativi, almeno uno. |
udiRaw | string | null | no | Tutti facoltativi, almeno uno. (max 2000 caratteri) |
usedAt | string | null | no | Tutti facoltativi, almeno uno. |
treatedArea | string | null | no | Tutti facoltativi, almeno uno. (max 200 caratteri) |
quantity | string | null | no | Tutti facoltativi, almeno uno. (max 100 caratteri) |
notes | string | null | no | Tutti facoltativi, almeno uno. (max 500 caratteri) |
treatmentCode | string | no | Tutti 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | LOT_NOT_FOUND | Registrazione inesistente o altrui. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
lotId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | LOT_NOT_FOUND | Registrazione inesistente o altrui. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | REGISTRY_NOT_AVAILABLE | Nessun registro dei lotti attivo sull'account. |
| 404 | CONSENT_NOT_FOUND | Consenso 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
id | path | string | sì | Id del consenso. Esempio: 3f2a8c1e-5b7d-4e2a-9c41-7d8e2b6a1f09. |
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
productName | string | sì | (1-200 caratteri) |
productType | "FILLER" | "TOSSINA" | "BIOSTIMOLATORE" | "ALTRO" | null | no | |
lotNumber | string | null | no | (1-100 caratteri) |
expiryDate | string | null | no | |
udiRaw | string | null | no | (max 2000 caratteri) |
usedAt | string | null | no | |
treatedArea | string | null | no | (max 200 caratteri) |
quantity | string | null | no | (max 100 caratteri) |
notes | string | null | no | (max 500 caratteri) |
memberId | string (uuid) | null | no | |
treatmentCode | string | sì | (1-60 caratteri) |
Da sapere
- I campi sono quelli di POST /lots senza
patientIdeconsentId.
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
| Stato | Codice | Significato |
|---|---|---|
| 404 | CONSENT_NOT_FOUND | Consenso inesistente, altrui o fuori dal contesto di sede. |
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
search | query | string | no | Numero di lotto o nome del prodotto, anche parziale: è la ricerca per il richiamo. lotNumber è un sinonimo. Esempio: C1234. |
patientId | query | string | no | Solo le registrazioni di quel paziente. |
memberId | query | string | no | Solo quelle di quell'esecutore. |
from | query | string | no | Inizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01. |
to | query | string | no | Fine del periodo (AAAA-MM-GG). Esempio: 2026-09-30. |
page | query | integer | no | Pagina, 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 400 | INVALID_QUERY | Data o pagina non valida. |
POST /inks
Registra un inchiostro
Registra un lotto di inchiostro. La registrazione finisce nel Registro attività.
Permesso: registries:write
Parametri
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
patientId | string (uuid) | sì | Obbligatori. |
consentId | string | null | no | Facoltativi. (1-60 caratteri) |
brand | string | sì | Obbligatori. (1-200 caratteri, formato ^[^\p{C}]*$) |
productName | string | null | no | Facoltativi. (max 200 caratteri, formato ^[^\p{C}]*$) |
color | string | null | no | Facoltativi. (max 100 caratteri, formato ^[^\p{C}]*$) |
inkType | "TATTOO_INK" | "PMU_PIGMENT" | "OTHER" | null | no | Facoltativi. |
lotNumber | string | sì | Obbligatori. (1-100 caratteri, formato ^[^\p{C}]*$) |
reachCompliant | boolean | no | Facoltativi. |
reachRef | string | null | no | Facoltativi. (max 300 caratteri, formato ^[^\p{C}]*$) |
expiryDate | string | null | no | Facoltativi. |
usedAt | string | null | no | Facoltativi. |
treatedArea | string | null | no | Facoltativi. (max 200 caratteri, formato ^[^\p{C}]*$) |
quantity | string | null | no | Facoltativi. (max 100 caratteri, formato ^[^\p{C}]*$) |
notes | string | null | no | Facoltativi. (max 500 caratteri, formato ^(?:[^\p{C}]|[\n\r\t])*$) |
memberId | string (uuid) | null | no | Facoltativi. |
treatmentCode | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | PATIENT_NOT_FOUND | Cliente inesistente o altrui. |
| 404 | CONSENT_NOT_FOUND | Consenso inesistente o altrui. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
inkId | path | string | sì | Id della riga del registro inchiostri. Esempio: 0f1a2b3c-4d5e-4f6a-9b7c-8d9e0f1a2b3c. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
brand | string | no | Tutti facoltativi, almeno uno. (1-200 caratteri, formato ^[^\p{C}]*$) |
productName | string | null | no | Tutti facoltativi, almeno uno. (max 200 caratteri, formato ^[^\p{C}]*$) |
color | string | null | no | Tutti facoltativi, almeno uno. (max 100 caratteri, formato ^[^\p{C}]*$) |
inkType | "TATTOO_INK" | "PMU_PIGMENT" | "OTHER" | null | no | Tutti facoltativi, almeno uno. |
lotNumber | string | no | Tutti facoltativi, almeno uno. (1-100 caratteri, formato ^[^\p{C}]*$) |
reachCompliant | boolean | no | Tutti facoltativi, almeno uno. |
reachRef | string | null | no | Tutti facoltativi, almeno uno. (max 300 caratteri, formato ^[^\p{C}]*$) |
expiryDate | string | null | no | Tutti facoltativi, almeno uno. |
usedAt | string | null | no | Tutti facoltativi, almeno uno. |
treatedArea | string | null | no | Tutti facoltativi, almeno uno. (max 200 caratteri, formato ^[^\p{C}]*$) |
quantity | string | null | no | Tutti facoltativi, almeno uno. (max 100 caratteri, formato ^[^\p{C}]*$) |
notes | string | null | no | Tutti facoltativi, almeno uno. (max 500 caratteri, formato ^(?:[^\p{C}]|[\n\r\t])*$) |
treatmentCode | string | no | Tutti 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | INK_NOT_FOUND | Registrazione inesistente o altrui. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
inkId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | INK_NOT_FOUND | Registrazione inesistente o altrui. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
search | query | string | no | Numero di lotto o nome del prodotto, anche parziale: è la ricerca per il richiamo. lotNumber è un sinonimo. Esempio: C1234. |
patientId | query | string | no | Solo le registrazioni di quel paziente. |
memberId | query | string | no | Solo quelle di quell'esecutore. |
from | query | string | no | Inizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01. |
to | query | string | no | Fine del periodo (AAAA-MM-GG). Esempio: 2026-09-30. |
page | query | integer | no | Pagina, da 1; 50 righe per pagina. Esempio: 1. |
consentId | query | string | no | Solo i materiali registrati su quel consenso. |
kind | query | string | no | Solo 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 400 | INVALID_QUERY | Data, 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
includeArchived | query | string | no | true per vedere anche i prodotti archiviati. Esempio: false. |
kind | query | string | no | Solo 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 400 | INVALID_QUERY | Parametro 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
firstName | string | sì | Obbligatori. (1-100 caratteri) |
lastName | string | sì | Obbligatori. (1-100 caratteri) |
qualification | string | null | no | Facoltativa: 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 400 | OPERATOR_LIMIT | Massimo 30 operatrici. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
operatorId | path | string | sì | Id dell'operatrice. Esempio: 1a2b3c4d-5e6f-4a7b-8c8d-9e0f1a2b3c4d. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
firstName | string | no | Tutti facoltativi, almeno uno. (1-100 caratteri) |
lastName | string | no | Tutti facoltativi, almeno uno. (1-100 caratteri) |
qualification | string | null | no | Tutti facoltativi, almeno uno. (max 200 caratteri) |
active | boolean | no | Tutti 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | OPERATOR_NOT_FOUND | Inesistente o altrui. |
| 400 | EMPTY_PATCH | Corpo senza nessun campo da modificare. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
operatorId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | OPERATOR_NOT_FOUND | Inesistente o altrui. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
label | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 403 | VISITS_DISABLED | La funzione visita non è attiva per questo account (o per la struttura per cui lavori): letture ed eliminazioni restano disponibili. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
reasonId | path | string | sì | Id del motivo di consulto. Esempio: 2b3c4d5e-6f7a-4b8c-9d9e-0f1a2b3c4d5e. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
label | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | REASON_NOT_FOUND | Inesistente o altrui. |
| 409 | REASON_ALREADY_EXISTS | Esiste già un motivo con quel testo. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 403 | VISITS_DISABLED | La 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
reasonId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | REASON_NOT_FOUND | Inesistente o altrui. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
sedeId | query | string | no | Strutture: solo quella sede. |
from | query | string | no | Inizio del periodo (AAAA-MM-GG). Esempio: 2026-09-01. |
to | query | string | no | Fine 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | SEDE_NOT_FOUND | Sede altrui. |
| 400 | INVALID_QUERY | Data 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
ids | array<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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | QUESTION_NOT_FOUND | Un id è inesistente o altrui. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
X-Idempotency-Key | header | string | no | Facoltativa, 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
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
label | string | sì | Obbligatori. type: YES_NO, MULTIPLE_CHOICE, FREE_TEXT. (1-300 caratteri) |
help | string | "" | no | Come in dashboard. (max 500 caratteri) |
type | "YES_NO" | "MULTIPLE_CHOICE" | "FREE_TEXT" | sì | Obbligatori. type: YES_NO, MULTIPLE_CHOICE, FREE_TEXT. |
options | array<object> | no | Scelta multipla: da 2 a 10 [{ label }]; le chiavi opt-N restano stabili. (max 10 elementi) |
options[].key | string | no | (formato ^opt-\d+$) |
options[].label | string | sì | (1-200 caratteri) |
displayDropdown | boolean | no | Come in dashboard. |
allowOther | boolean | no | Come in dashboard. |
multiSelect | boolean | no | Come in dashboard. |
required | boolean | no | Come in dashboard. |
active | boolean | no | Come in dashboard. |
askPolicy | "ONCE" | "EVERY_CONSENT" | "EXPIRES" | no | ONCE, EVERY_CONSENT o EXPIRES (con i mesi). |
expiryMonths | integer | null | no | ONCE, 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 400 | CUSTOM_QUESTIONS_LIMIT | Massimo 20 domande attive. |
| 400 | CUSTOM_QUESTIONS_OPTIONS_INVALID | La scelta multipla ha meno di 2 o più di 10 opzioni. |
| 400 | CUSTOM_QUESTIONS_EXPIRY_REQUIRED | Con askPolicy EXPIRES servono i mesi in expiryMonths. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 409 | IDEMPOTENCY_KEY_REUSED / IDEMPOTENCY_IN_PROGRESS | La stessa X-Idempotency-Key è già stata usata con un corpo diverso, oppure la prima richiesta con quella chiave è ancora in corso. |
| 403 | TEST_KEY_READ_ONLY | Chiave in modalità Test: legge la configurazione ma non la modifica, perché sono dati veri condivisi con la dashboard. |
| 400 | IDEMPOTENCY_KEY_INVALID | X-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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
questionId | path | string | sì | Id della domanda personalizzata. Esempio: 3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f. |
Corpo della richiesta
| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
label | string | sì | (1-300 caratteri) |
help | string | "" | no | (max 500 caratteri) |
type | "YES_NO" | "MULTIPLE_CHOICE" | "FREE_TEXT" | sì | |
options | array<object> | no | (max 10 elementi) |
options[].key | string | no | (formato ^opt-\d+$) |
options[].label | string | sì | (1-200 caratteri) |
displayDropdown | boolean | no | |
allowOther | boolean | no | |
multiSelect | boolean | no | |
required | boolean | no | |
active | boolean | no | |
askPolicy | "ONCE" | "EVERY_CONSENT" | "EXPIRES" | no | |
expiryMonths | integer | null | no | (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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | QUESTION_NOT_FOUND | Inesistente o altrui. |
| 400 | CUSTOM_QUESTIONS_LIMIT | Massimo 20 domande attive. |
| 400 | CUSTOM_QUESTIONS_OPTIONS_INVALID | La scelta multipla ha meno di 2 o più di 10 opzioni. |
| 400 | CUSTOM_QUESTIONS_EXPIRY_REQUIRED | Con askPolicy EXPIRES servono i mesi in expiryMonths. |
| 400 | INVALID_BODY | Corpo non valido: un campo non previsto, un tipo sbagliato o un valore fuori intervallo. details elenca ogni problema. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
questionId | path | string | sì | 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
| Stato | Codice | Significato |
|---|---|---|
| 404 | FEATURE_NOT_AVAILABLE | Il registro non esiste per questo account (verticale, funzione spenta o tracciabilità non attiva), come in dashboard. |
| 404 | QUESTION_NOT_FOUND | Inesistente o altrui. |
| 403 | TEST_KEY_READ_ONLY | Chiave 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
| Nome | Dove | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|---|
from | query | string | no | Inizio del periodo: una data (AAAA-MM-GG) o un istante ISO 8601. Esempio: 2026-09-01. |
to | query | string | no | Fine del periodo, compresa: una data (AAAA-MM-GG) o un istante ISO 8601. Esempio: 2026-09-30. |
sedeId | query | string | no | Strutture: solo quella sede (di questo account, altrimenti 404). In conflitto con X-Structure-Sede-Id è 403. |
memberId | query | string | no | Strutture: 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | INVALID_QUERY | Data non valida o periodo invertito. |
| 403 | SEDE_CONTEXT_MISMATCH | sedeId 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
| Stato | Codice | Significato |
|---|---|---|
| 400 | WEBHOOK_NOT_CONFIGURED | Non c'è nessun webhook: si configura in Impostazioni → API & Integrazioni. |
| 400 | WEBHOOK_DISABLED | Il webhook è configurato ma disattivato. |
| 409 | WEBHOOK_SECRET_UNAVAILABLE | Il 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. |