https://api.dentalspace.ai/v1. Autenticazione: header Authorization: Bearer dsk_live_... su ogni richiesta.
Specifica sorgente: openapi.json.
La chiave che sta chiamando
GET /me
Clinica, chiave (scope, sedi, scadenza), limiti e modalita’. Basta una chiave valida, senza scope.
Risposte
Campi della risposta
Stato del servizio
GET /health
Non richiede chiave.
Senza chiave. 200 se l’API e il database rispondono, 503 unavailable altrimenti.
Risposte
Campi della risposta
Specifica OpenAPI 3.1
GET /openapi.json
Non richiede chiave.
Senza chiave. Generata dagli stessi schemi che validano le richieste.
Risposte
Le sedi della clinica
GET /facilities
Scope: clinic:read.
Indirizzo, telefono e orari di apertura. Una chiave limitata a delle sedi vede solo quelle.
Parametri
Risposte
Campi della risposta
Una sede
GET /facilities/{id}
Scope: clinic:read.
Dove siete e che orari fate.
Parametri
Risposte
Campi della risposta
Le poltrone attive di una sede
GET /facilities/{id}/chairs
Scope: clinic:read.
Parametri
Risposte
Campi della risposta
I medici della clinica
GET /doctors
Scope: clinic:read.
Nome, specialita’ e sedi in cui lavorano; nessun dato di contatto. Una chiave limitata a delle sedi vede solo i medici di quelle sedi (una pagina puo’ quindi contenere meno di limit medici).
Parametri
Risposte
Campi della risposta
I calendari prenotabili
GET /calendars
Scope: clinic:read.
Con le regole di prenotazione: preavviso, orizzonte, disdetta e spostamento consentiti, prestazioni e medici.
Parametri
Risposte
Campi della risposta
Un calendario
GET /calendars/{id}
Scope: clinic:read.
Parametri
Risposte
Campi della risposta
Il listino
GET /treatments
Scope: clinic:read.
Codice, nome, durata e prezzo delle prestazioni. Di default solo quelle attive. L’elenco e’ ordinato per id: updatedSince non e’ disponibile (400).
Parametri
Risposte
Campi della risposta
Una prestazione
GET /treatments/{id}
Scope: clinic:read.
Parametri
Risposte
Campi della risposta
Chi e’ questo numero?
GET /patients/lookup
Scope: patients:read.
Confronto esatto sul numero (in qualunque formato). kind: patient se e’ di un paziente, family se lo stesso numero e’ di piu’ pazienti (genitore e figli), lead se non e’ di nessun paziente ma di un contatto commerciale, null se non lo conosciamo. I candidati hanno solo nome, anno di nascita e ultime 4 cifre.
Parametri
Risposte
Campi della risposta
Cerca un paziente
GET /patients/search
Scope: patients:read.
Trova la persona anche scritta male: nome, telefono in qualunque forma, codice fiscale, email. Restituisce pochi candidati con il punteggio; closeMatches dice quanti sono altrettanto probabili del migliore.
Parametri
Risposte
Campi della risposta
Elenco dei pazienti
GET /patients
Scope: patients:read.
Anagrafica, senza dati sanitari (quelli stanno solo nella scheda e nel /clinical, con clinical:read). Di default solo i pazienti attivi. Per cercare per nome usa /patients/search.
Parametri
Risposte
Campi della risposta
Crea un paziente
POST /patients
Scope: patients:write.
409 conflict (details.patientId) se esiste gia’ un paziente con lo stesso nome e la stessa email o lo stesso telefono. Allergie, farmaci, condizioni e note richiedono anche clinical:write. Con Idempotency-Key un nuovo tentativo non crea un secondo paziente.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
La scheda di un paziente
GET /patients/{id}
Scope: patients:read.
Con clinical:read la scheda include allergie, farmaci, condizioni e note, e la lettura entra nel registro accessi. L’header ETag va rimandato come If-Match per modificarla.
Parametri
Risposte
Campi della risposta
Modifica un paziente
PATCH /patients/{id}
Scope: patients:write.
Solo i campi indicati; null svuota un campo (non il nome). If-Match col valore di ETag della scheda: se e’ cambiata, 409. Allergie, farmaci, condizioni e note richiedono anche clinical:write.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Archivia un paziente
POST /patients/{id}/archive
Scope: patients:write.
Non cancella niente: il paziente esce dagli elenchi (archived=true per vederli) e si puo’ riattivare. Ripeterla non cambia nulla.
Parametri
Risposte
Campi della risposta
Riattiva un paziente archiviato
POST /patients/{id}/restore
Scope: patients:write.
Riporta fra gli attivi un paziente archiviato. Ripeterla non cambia nulla.
Parametri
Risposte
Campi della risposta
Gli appuntamenti di un paziente
GET /patients/{id}/appointments
Scope: patients:read e appointments:read.
upcoming=true: solo quelli che devono ancora avvenire e non sono disdetti, saltati o conclusi. Senza note cliniche. Titolo e motivo sono testo libero: la lettura finisce nel registro accessi. Ordinati per ultima modifica.
Parametri
Risposte
Campi della risposta
I preventivi di un paziente
GET /patients/{id}/treatment-plans
Scope: patients:read e treatment_plans:read.
Riepilogo senza voci, denti e diagnosi (il dettaglio e’ in /treatment-plans/\{id\}). Esclusi quelli eliminati.
Parametri
Risposte
Campi della risposta
I dati sanitari di un paziente
GET /patients/{id}/clinical
Scope: clinical:read.
Allergie, farmaci, condizioni e odontogramma. Serve lo scope clinical:read (spento di default) e ogni lettura viene registrata nel registro accessi della clinica.
Parametri
Risposte
Campi della risposta
I documenti di un paziente
GET /patients/{id}/documents
Scope: documents:read.
I documenti da firmare del paziente (consensi, moduli) con lo stato della firma. Solo i metadati: il contenuto e’ in /documents/\{id\}. Il titolo e’ testo libero: la lettura finisce nel registro accessi. Ordinati per data di creazione.
Parametri
Risposte
Campi della risposta
Orari liberi di un calendario
GET /calendars/{id}/availability
Scope: appointments:read.
Gli orari prenotabili fra from e to, con le regole del calendario (medici, fasce, durata, preavviso, orizzonte). In un calendario «scelta del paziente» lo stesso orario compare una volta per ogni medico libero, e ognuno conta in limit. La risposta dice la finestra davvero cercata (window) e se gli orari sono tutti (complete); se no, si continua con from=nextFrom. Una pagina non spezza un orario fra i suoi medici: se a uno stesso orario i medici liberi sono piu’ di limit, 400 su limit.
Parametri
Risposte
Campi della risposta
Prenota un orario
POST /calendars/{id}/bookings
Scope: appointments:write.
Ricontrolla l’orario con le regole del calendario e lo prenota (stato scheduled). 409 no_availability se non e’ piu’ libero (reason: slot_taken) o non rispetta il preavviso (reason: min_notice), con details.alternatives; 409 conflict con reason: booking_disabled se il calendario non accetta prenotazioni da fuori.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Elenco degli appuntamenti
GET /appointments
Scope: appointments:read.
In ordine di ultima modifica (updatedSince per sincronizzare), o di orario con sort=start. Senza note ne’ note cliniche: si leggono dal dettaglio, con clinical:read. Titolo e motivo sono testo libero: la lettura finisce nel registro accessi.
Parametri
Risposte
Campi della risposta
Crea un appuntamento fuori calendario
POST /appointments
Scope: appointments:write.
Per i gestionali che sanno gia’ medico, sede e orario: nessuna regola di calendario, solo il controllo di sovrapposizione su medico e poltrona (409 no_availability, reason: slot_taken). Per prenotare per conto di un paziente usa /calendars/{id}/bookings.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Un appuntamento
GET /appointments/{id}
Scope: appointments:read.
Con l’ETag da rimandare in If-Match. Note e note cliniche solo con clinical:read.
Parametri
Risposte
Campi della risposta
Modifica titolo, motivo o note
PATCH /appointments/{id}
Scope: appointments:write.
Solo i testi: per l’orario c’e’ /move, per lo stato /status. Con If-Match (l’ETag ricevuto) la modifica passa solo se nessuno ha cambiato l’appuntamento nel frattempo, altrimenti 409 conflict.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Sposta un appuntamento
POST /appointments/{id}/move
Scope: appointments:write.
Cambia solo l’orario (medico, sede e durata restano), con le regole del calendario di provenienza. 409 no_availability (slot_taken / min_notice) con details.alternatives; 409 conflict con reason reschedule_disabled, status_not_movable, doctor_not_in_calendar o no_calendar.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Disdici un appuntamento
POST /appointments/{id}/cancel
Scope: appointments:write.
La disdetta del paziente, con le regole del calendario di provenienza. 409 conflict con reason cancel_disabled, min_notice, status_not_cancellable o no_calendar. Lo staff che annulla senza regole usa /status con cancelled.
Parametri
Risposte
Campi della risposta
Conferma la presenza
POST /appointments/{id}/confirm
Scope: appointments:write.
Porta l’appuntamento in confirmed (da draft o scheduled). 409 conflict con reason: transition_not_allowed, details.status e details.allowedTransitions; 409 no_availability se rimetterlo in agenda si sovrapporrebbe a un altro.
Parametri
Risposte
Campi della risposta
Cambia lo stato
POST /appointments/{id}/status
Scope: appointments:write.
Il cambio di stato dello staff, lungo lo stesso grafo dell’app (completed no: lo scrive il completamento della visita). Nessuna regola di calendario: la disdetta del paziente e’ /cancel. 409 conflict con reason: transition_not_allowed, details.status e details.allowedTransitions; 409 no_availability se rimetterlo in agenda si sovrapporrebbe a un altro.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Elenco dei preventivi
GET /treatment-plans
Scope: treatment_plans:read.
Senza voci e senza dati clinici; quelli nel cestino non compaiono.
Parametri
Risposte
Campi della risposta
Crea una bozza di preventivo dal listino
POST /treatment-plans
Scope: treatment_plans:write.
I prezzi sono quelli del listino. Nasce sempre come bozza (draft): la conferma una persona nell’app.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Un preventivo, con le voci
GET /treatment-plans/{id}
Scope: treatment_plans:read.
Con lo scope clinical:read anche denti, superfici, odontogramma e diagnosi (e l’accesso finisce nel registro).
Parametri
Risposte
Campi della risposta
Cambia lo stato di un preventivo
POST /treatment-plans/{id}/status
Scope: treatment_plans:write.
proposed → standby (presentato) · proposed/standby → accepted/rejected. Una bozza non cambia stato via API.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Elenco delle fatture
GET /invoices
Scope: billing:read.
Parametri
Risposte
Campi della risposta
Una fattura, con le righe
GET /invoices/{id}
Scope: billing:read.
Parametri
Risposte
Campi della risposta
Gli incassi di una fattura
GET /invoices/{id}/payments
Scope: billing:read.
Parametri
Risposte
Campi della risposta
Registra un incasso
POST /invoices/{id}/payments
Scope: billing:write.
Solo su fatture emesse o scadute, fino al saldo. Chiede Idempotency-Key. Saldata, la fattura passa a paid.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Piani di pagamento e rate
GET /payment-plans
Scope: billing:read.
state=open (predefinito): rate non pagate · overdue: non pagate e scadute · all: tutte.
Parametri
Risposte
Campi della risposta
Crea un link di pagamento
POST /payment-links
Scope: billing:write.
Un link Stripe (Checkout, sul conto collegato della clinica) per il residuo di una fattura emessa o scaduta, o di una rata non pagata. L’importo non si sceglie. Non invia niente: restituisce l’URL. Chiede Idempotency-Key. Con una chiave di prova verifica tutto ma non crea il link (livemode: false, url: null).
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Un documento del paziente
GET /documents/{id}
Scope: documents:read.
Un documento da firmare (consenso, modulo, preventivo) con lo stato della firma. Non e’ un file: con clinical:read esce il contenuto (HTML) e la firma. Niente download e niente upload: vedi la guida.
Parametri
Risposte
Campi della risposta
Elenco delle attivita’
GET /tasks
Scope: tasks:read.
Ordinate per ultima modifica; updatedSince per la sincronizzazione incrementale.
Parametri
Risposte
Campi della risposta
Crea un’attivita’
POST /tasks
Scope: tasks:write.
Con Idempotency-Key un nuovo tentativo non crea un doppione. Una chiave limitata a delle sedi deve indicare facilityId.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Modifica un’attivita’
PATCH /tasks/{id}
Scope: tasks:write.
Solo i campi mandati cambiano; null toglie scadenza o assegnatario. Con If-Match (l’ETag ricevuto) la modifica fallisce con 409 se qualcuno l’ha cambiata nel frattempo.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Elenco dei lead
GET /leads
Scope: leads:read.
Ordinati per ultima modifica. Le attivita’ si leggono dal dettaglio.
Parametri
Risposte
Campi della risposta
Crea un lead
POST /leads
Scope: leads:write.
Se esiste gia’ un lead con lo stesso telefono (qualunque formato: si confronta quello normalizzato) o la stessa email, NON ne crea un altro: risponde 200 con quello esistente e deduplicated: true, senza modificarlo. Una chiamata da un numero sconosciuto e’ un lead con source: "phone". Una chiave limitata a delle sedi deve indicare facilityId.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Un lead
GET /leads/{id}
Scope: leads:read.
Parametri
Risposte
Campi della risposta
Modifica un lead
PATCH /leads/{id}
Scope: leads:write.
Solo i campi mandati cambiano; null svuota un campo facoltativo. Un cambio di status riparte il conteggio dei giorni nello stadio. Per convertire in paziente usa POST /leads/\{id\}/convert. Con If-Match la modifica fallisce con 409 se il lead e’ cambiato nel frattempo.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Aggiungi un’attivita’ a un lead
POST /leads/{id}/activities
Scope: leads:write.
Una nota o un contatto (chiamata, email, WhatsApp, incontro). I contatti aggiornano anche l’ultimo contatto del lead.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Converti un lead in paziente
POST /leads/{id}/convert
Scope: leads:write e patients:write.
Crea il paziente dai dati del lead e segna il lead come convertito. Serve il cognome: se il lead non ce l’ha, mandalo in lastName. Un lead gia’ convertito risponde 409. Chiede sia leads:write sia patients:write.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Elenco delle conversazioni WhatsApp
GET /conversations
Scope: messages:read.
Ordinate per ultima modifica; updatedSince per la sincronizzazione incrementale.
Parametri
Risposte
Campi della risposta
I messaggi di una conversazione
GET /conversations/{id}/messages
Scope: messages:read.
In ordine di arrivo. updatedSince filtra per istante di arrivo. La trascrizione dei vocali c’e’ solo con lo scope clinical:read (e patients:read) e solo se la conversazione e’ di un paziente. Il testo esce con messages:read, ma ogni lettura che lo contiene finisce nel registro accessi come accesso a dati clinici.
Parametri
Risposte
Campi della risposta
Manda un messaggio WhatsApp
POST /conversations/{id}/messages
Scope: messages:write.
Accoda un messaggio di testo al numero della conversazione (parte con il ritmo del numero della clinica, non subito). 409 se il paziente ha scritto STOP o se il bot e’ in pausa o spento su quel filo (una persona sta gestendo la conversazione). 409 anche se in coda ci sono gia’ 5 messaggi dell’API per quel numero (recipient_queue_full) o 200 per la clinica (queue_full). Passano dopo i promemoria e le automazioni della clinica; revocata la chiave, i suoi messaggi non ancora partiti si annullano. Chiede Idempotency-Key: un nuovo tentativo con la stessa chiave non manda due volte. Con una chiave di prova non parte nulla.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Stato del bot su una conversazione
PATCH /conversations/{id}/bot
Scope: messages:write.
\{ "state": "attivo" \} lo riaccende; \{ "state": "in_pausa", "pausedUntil": "<istante futuro>" \} lo mette in pausa fino a quell’istante (al massimo 7 giorni). Se una persona l’ha spento (allo_studio) si puo’ solo riaccendere, non mettere in pausa.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Registra una telefonata conclusa
POST /calls
Scope: calls:write.
Idempotente su externalCallId: 201 la prima volta, 200 con la riga gia’ registrata dopo. Un numero sconosciuto diventa un lead con source=phone.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Elenca le telefonate
GET /calls
Scope: calls:read.
In ordine di registrazione. updatedSince guarda l’istante di registrazione (una telefonata non cambia).
Parametri
Risposte
Campi della risposta
Una telefonata
GET /calls/{id}
Scope: calls:read.
Con lo scope clinical:read anche la trascrizione (l’accesso finisce nel registro).
Parametri
Risposte
Campi della risposta
Briefing prima della chiamata
POST /voice/context
Scope: patients:read e appointments:read.
Chi e’ il numero e cosa ha in sospeso, senza dati clinici. 403 reason ai_disabled se la clinica ha spento l’AI.
Corpo (JSON)
Risposte
Campi della risposta
Passa la chiamata alla segreteria
POST /voice/handoff
Scope: tasks:write.
Crea un’attivita’ di richiamo. Un numero sconosciuto diventa un lead con source=phone. 403 reason ai_disabled se la clinica ha spento l’AI.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Elenca gli eventi
GET /events
Scope: webhooks:read.
Gli eventi degli ultimi 30 giorni, nella stessa busta dei webhook. updatedSince filtra sull’istante in cui l’evento e’ diventato visibile. Si vedono solo i tipi coperti dagli scope della chiave. Gli eventi che non sono nati lasciano un events.skipped con data.reason (bulk_import: piu’ di 50 *.created insieme; no_active_key: un periodo senza chiavi attive, da data.from a data.to; error: il trigger e’ fallito), data.type, data.scope e data.count: da li’ si risincronizza con updatedSince sugli elenchi.
Parametri
Risposte
Campi della risposta
Elenca le destinazioni dei webhook
GET /webhook-endpoints
Scope: webhooks:read.
Le destinazioni create da questa chiave (o da quella che ha rigenerato).
Parametri
Risposte
Campi della risposta
Crea una destinazione dei webhook
POST /webhook-endpoints
Scope: webhooks:write.
Al massimo 20 per clinica. Il segreto si vede solo nella risposta. Gli eventi sono filtrati con gli scope e le sedi di QUESTA chiave. Ogni webhook e’ un POST JSON firmato: calcola HMAC-SHA256(segreto, “v1\n” + X-DentalSpace-Timestamp + “\n” + X-DentalSpace-Idempotency-Key + “\n” + corpo) e confrontalo con uno dei valori v1= di X-DentalSpace-Signature; rifiuta i timestamp piu’ vecchi di 5 minuti e scarta le Idempotency-Key gia’ viste. Rispondi 2xx entro 10 secondi; altrimenti si ritenta dopo 1, 5, 15 minuti e 1 ora. I redirect non si seguono.
Corpo (JSON)
Risposte
Campi della risposta
Una destinazione
GET /webhook-endpoints/{id}
Scope: webhooks:read.
Parametri
Risposte
Campi della risposta
Modifica una destinazione
PATCH /webhook-endpoints/{id}
Scope: webhooks:write.
URL, eventi, descrizione, accesa/spenta. Con If-Match (l’ETag della GET) non sovrascrive modifiche altrui.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Elimina una destinazione
DELETE /webhook-endpoints/{id}
Scope: webhooks:write.
Non riceve piu’ niente e i suoi segreti si cancellano. Le consegne gia’ fatte restano leggibili 30 giorni.
Parametri
Risposte
Campi della risposta
Ruota il segreto di una destinazione
POST /webhook-endpoints/{id}/rotate-secret
Scope: webhooks:write.
Parametri
Corpo (JSON)
Risposte
Campi della risposta
Manda un webhook di prova
POST /webhook-endpoints/{id}/test
Scope: webhooks:write.
Un evento webhook.test con data {}, firmato come gli altri. Funziona anche su una destinazione spenta.
Parametri
Risposte
Campi della risposta
Elenca le consegne dei webhook
GET /webhook-deliveries
Scope: webhooks:read.
Le consegne degli ultimi 30 giorni alle destinazioni di questa chiave.
Parametri
Risposte
Campi della risposta
Una consegna, con i suoi tentativi
GET /webhook-deliveries/{id}
Scope: webhooks:read.
Parametri
Risposte
Campi della risposta
Rimanda una consegna
POST /webhook-deliveries/{id}/redeliver
Scope: webhooks:write.
Solo una consegna succeeded o discarded, verso una destinazione accesa. Parte entro una decina di secondi. Al massimo 3 volte per consegna: oltre, 409 con reason redeliver_limit.