Disponibil pe planul Enterprise. Cheile API și webhook-urile se configurează de proprietarul clinicii în Setări → Integrări.
Pe scurt
| Operație | Cerere |
|---|---|
| Caută pacient după telefon | GET /api/v1/patients?phone=0722111222 |
| Creează pacient (doar telefonul e obligatoriu) | POST /api/v1/patients |
| Lista medicilor | GET /api/v1/doctors |
| Programările dintr-un interval | GET /api/v1/appointments?from=…&to=… |
| Creează programare | POST /api/v1/appointments |
| Notificări la programare nouă / mutată / confirmată / anulată | webhook-uri |
Adresa și autentificarea
- Adresa de bază:
https://<clinica>.perlato.ro/api/v1(aceeași adresă la care lucrează clinica în Perlato). - Fiecare cerere poartă cheia API în antet:
Authorization: Bearer plt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(X-Api-Key: plt_… e acceptat ca alternativă.)
- Cheia se afișează o singură dată, la creare. Folosește câte o cheie pentru fiecare sistem conectat, ca să o poți revoca separat. O cheie revocată primește imediat
401. - Limite: 120 de cereri pe minut pentru fiecare cheie și 500 de creări (pacienți + programări) pe zi pentru fiecare cheie; peste limită:
429. Cererile repetate cu o cheie greșită sunt blocate temporar pe adresa IP. - În Setări → Integrări vezi, pentru fiecare cheie, când a fost folosită ultima dată și câte citiri și creări a făcut azi.
Păstrarea cheii
- Cheia stă doar pe server (în configurația centralei sau a aplicației voastre), niciodată în cod care rulează în browser, într-o aplicație mobilă sau într-un depozit de cod.
- Folosește întotdeauna
https://. O cerere trimisă din greșeală pehttp://expune cheia înainte de redirecționare. - Cheile nu expiră. Schimb-o (creezi una nouă, apoi o revoci pe cea veche) cel puțin o dată pe an și ori de câte ori se schimbă furnizorul sau persoanele care au avut acces la ea.
- Accesul nu se poate restrânge la anumite adrese IP; o cheie scăpată se revocă imediat din Setări → Integrări.
- Corpurile cererilor și răspunsurilor sunt JSON (UTF-8).
Erori
| Cod | Când |
|---|---|
401 | Cheie lipsă, greșită sau revocată |
403 | Planul clinicii nu include API-ul |
404 | Pacientul nu există |
409 | Intervalul e ocupat în calendar (medic sau cabinet) |
422 | Date invalide (telefon, dată); mesajul e în câmpul detail |
429 | Prea multe cereri |
Exemplu: {"detail": "Număr de telefon invalid. Format acceptat: 07XXXXXXXX sau +407XXXXXXXX."}
Ce date expune API-ul
API-ul întoarce doar ce îi trebuie unui sistem de programări: numele, telefonul și emailul pacientului, iar pentru programări ora, durata, medicul, serviciul, statusul și sursa. Observațiile medicale din programări și din fișa pacientului nu se transmit prin API și nici prin webhook-uri.
Orele
- În răspunsuri, fiecare programare are
startîn UTC (cuZ) șistart_localîn ora României. - În cereri,
starte ISO 8601. Fără fus orar înseamnă ora României (2026-10-05T14:30:00); cu fus, e convertit (2026-10-05T14:30:00+03:00,2026-10-05T11:30:00Z).
Pacienți
Caută după telefon
GET /api/v1/patients?phone=0722111222Numărul e recunoscut în formatele 0722111222, 0722 111 222, 0722-111-222 și +40722111222. Pe același telefon pot exista mai mulți pacienți (de exemplu un copil pe telefonul părintelui), deci răspunsul e o listă, în ordinea creării.
curl -s "https://clinica.perlato.ro/api/v1/patients?phone=%2B40722111222" \
-H "Authorization: Bearer $PERLATO_KEY"{
"patients": [
{
"id": "6c1f…",
"name": "Maria Pop",
"phone": "0722111222",
"email": "",
"created_at": "2026-09-12T08:15:00Z",
"next_appointment": {
"id": "a91d…",
"patient_id": "6c1f…",
"doctor_id": "d23…",
"doctor_name": "Dr. Ionescu",
"start": "2026-10-05T11:30:00Z",
"start_local": "2026-10-05T14:30:00",
"duration_minutes": 30,
"service": "Urgență",
"status": "scheduled",
"source": "phone"
}
}
]
}Niciun pacient → {"patients": []}.
Creează pacient
POST /api/v1/patients
{"phone": "0722111222", "name": "Maria Pop"}phonee obligatoriu,nameopțional. Fără nume, pacientul apare ca „Pacient nou 0722111222”, iar medicul îl completează apoi în fișă.- Sigur la apeluri repetate: dacă există deja un pacient cu acel telefon (și, când trimiți numele, cu un nume compatibil), primești pacientul existent cu
200și"created": false. Altfel201și"created": true.
curl -s -X POST "https://clinica.perlato.ro/api/v1/patients" \
-H "Authorization: Bearer $PERLATO_KEY" -H "Content-Type: application/json" \
-d '{"phone": "0722111222"}'{"patient": {"id": "6c1f…", "name": "Pacient nou 0722111222", "phone": "0722111222", "email": "", "created_at": "2026-10-03T20:41:00Z", "next_appointment": null}, "created": true}Medici
GET /api/v1/doctors{"doctors": [{"id": "d23…", "name": "Dr. Ionescu"}, {"id": "e45…", "name": "Dr. Pop"}]}Programări
Lista
GET /api/v1/appointments?from=2026-10-05T00:00:00&to=2026-10-06T00:00:00&doctor_id=d23…from,toopționale (implicit: de acum o oră, 7 zile înainte); intervalul maxim e de 62 de zile, iarfrompoate fi cel mult cu 31 de zile în urmă (API-ul e pentru programările apropiate, nu pentru arhivă).- Filtre opționale:
doctor_id,patient_id. status:scheduled(programată),confirmed(confirmată),completed(finalizată),cancelled(anulată),no_show(neprezentare).
Creează
POST /api/v1/appointments
{
"patient_id": "6c1f…",
"start": "2026-10-05T14:30:00",
"duration_minutes": 30,
"doctor_id": "d23…",
"service": "Urgență — durere dentară",
"notes": "Sunat la 02:10, durere 8/10"
}- Obligatorii:
patient_id,start(în viitor, cel mult 400 de zile înainte). Implicit:duration_minutes30 (între 5 și 600),service„Consultație”, fără medic. notesse salvează în programare (le vede medicul în Perlato), dar nu mai sunt întoarse de API.- Programarea apare imediat în calendar, cu sursa „Telefon”, și primește reminderul obișnuit (WhatsApp/SMS) cu o zi înainte.
- Dacă medicul (sau cabinetul) e ocupat în acel interval sau are o blocare în calendar →
409. - Sigur la reîncercare: aceeași cerere trimisă din nou (același pacient, aceeași oră, același medic, programarea încă activă) întoarce programarea existentă cu
200și"created": false, nu o a doua programare — chiar dacă durata sau serviciul diferă. Programarea nouă:201și"created": true.
curl -s -X POST "https://clinica.perlato.ro/api/v1/appointments" \
-H "Authorization: Bearer $PERLATO_KEY" -H "Content-Type: application/json" \
-d '{"patient_id": "6c1f…", "start": "2026-10-05T14:30:00", "duration_minutes": 30, "doctor_id": "d23…", "service": "Urgență"}'{"appointment": {"id": "a91d…", "patient_id": "6c1f…", "doctor_id": "d23…", "doctor_name": "Dr. Ionescu", "start": "2026-10-05T11:30:00Z", "start_local": "2026-10-05T14:30:00", "duration_minutes": 30, "service": "Urgență", "status": "scheduled", "source": "phone"}, "created": true}Flux tipic pentru o centrală telefonică
- La apel:
GET /patients?phone=<numărul apelantului>→ afișezi medicului de gardă numele și următoarea programare. - Număr necunoscut:
POST /patients {"phone": …}(sigur și dacă îl apelezi la fiecare apel). - Medicul programează:
POST /appointmentscupatient_iddin pasul 1 sau 2.
Webhook-uri
Perlato trimite un POST la adresa ta când se întâmplă un eveniment, indiferent de unde vine schimbarea: calendarul din Perlato, programarea online, confirmarea pe WhatsApp sau API-ul.
Evenimente
| Tip | Când |
|---|---|
patient.created | Pacient nou |
appointment.created | Programare nouă |
appointment.rescheduled | S-a schimbat ora, durata sau medicul |
appointment.confirmed | Pacientul a confirmat (sau recepția a marcat confirmarea) |
appointment.cancelled | Programare anulată |
appointment.deleted | Programare ștearsă din calendar |
ping | Testul din Setări → Integrări |
La configurare alegi evenimentele pe care le primești.
Cererea
POST https://adresa-ta/…
Content-Type: application/json
User-Agent: Perlato-Webhooks/1
X-Perlato-Event: appointment.created
X-Perlato-Delivery: 5b0e… (id unic al livrării — îl poți folosi ca să ignori dublurile)
X-Perlato-Signature: t=1759517460,v1=4f2a…{
"id": "5b0e…",
"type": "appointment.created",
"created_at": "2026-10-03T20:51:00Z",
"data": {
"appointment": {"id": "a91d…", "patient_id": "6c1f…", "doctor_id": "d23…", "doctor_name": "Dr. Ionescu", "start": "2026-10-05T11:30:00Z", "start_local": "2026-10-05T14:30:00", "duration_minutes": 30, "service": "Urgență", "status": "scheduled", "source": "online"},
"patient": {"id": "6c1f…", "name": "Maria Pop", "phone": "0722111222", "email": "", "created_at": "2026-09-12T08:15:00Z"}
}
}patient.createdare doardata.patient.appointment.deletedaredata.appointmentcuid,patient_id,start,doctor_id,service.- Conținutul (
data.appointment,data.patient) e starea de la momentul evenimentului; o reîncercare retrimite aceeași stare. - Observațiile programării (
notes) nu se trimit, pentru că pot conține informații medicale. - Importul în masă de pacienți nu generează câte un
patient.createdpentru fiecare rând.
Răspunsul tău și reîncercările
- Răspunde cu orice cod
2xx(ideal în sub 10 secunde). Restul se procesează la tine, asincron. - Orice alt răspuns, o eroare de rețea sau peste 10 secunde → reîncercăm după 30 s, 2 min, 10 min, 1 h și 6 h; apoi livrarea e marcată eșuată (vizibilă în Setări → Integrări → Livrări).
- Nu urmăm redirecționări (
3xx= eșec). - Ordinea nu e garantată între reîncercări — folosește
created_at. - Aceeași livrare poate sosi de două ori (de exemplu dacă răspunsul tău s-a pierdut): ignoră un
id(din corp) deja procesat. - Antetele
X-Perlato-EventșiX-Perlato-Deliverysunt doar ajutătoare și nu sunt semnate; după verificarea semnăturii, folosește valoriletypeșiiddin corpul cererii.
Verificarea semnăturii
Fiecare webhook are un secret whsec_…, afișat o singură dată la creare. Semnătura e HMAC-SHA256(secret, "<t>.<corpul exact al cererii>"), în hexazecimal, unde t e momentul trimiterii (secunde Unix) din același antet. Respinge cererile cu semnătura greșită sau mai vechi de 5 minute.
Python
import hashlib, hmac, time
def verifica(secret: str, antet: str, corp: bytes) -> bool:
try:
parti = dict(p.split("=", 1) for p in antet.split(","))
t, semnatura = int(parti["t"]), parti["v1"]
except (KeyError, ValueError):
return False
if abs(time.time() - t) > 300:
return False
asteptat = hmac.new(secret.encode(), f"{t}.".encode() + corp, hashlib.sha256).hexdigest()
return hmac.compare_digest(asteptat, semnatura)Node.js
const crypto = require("crypto");
function verifica(secret, antet, corp /* Buffer, corpul brut */) {
const parti = Object.fromEntries(String(antet || "").split(",").map((p) => p.split("=")));
const t = Number(parti.t);
if (!parti.v1 || !Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
const asteptat = Buffer.from(crypto.createHmac("sha256", secret).update(`${parti.t}.`).update(corp).digest("hex"));
const primit = Buffer.from(parti.v1);
return primit.length === asteptat.length && crypto.timingSafeEqual(asteptat, primit);
}Folosește corpul brut, așa cum a sosit — nu JSON-ul re-serializat.
Cerințe pentru adresă
- Doar
https://pe portul standard 443, cu certificat valid. - Adresa trebuie să fie publică: adresele din rețele interne (
10.x,192.168.x,localhostetc.) sunt refuzate.
Date personale
API-ul și webhook-urile transmit date ale pacienților (nume, telefon, programări), fără observațiile medicale. Când un pacient e șters definitiv din Perlato, copiile datelor lui din coada de webhook-uri se șterg și ele. Sistemul care le primește devine parte din prelucrarea datelor clinicii: păstrați cheile și secretele în siguranță, folosiți doar HTTPS și includeți furnizorul centralei în evidența GDPR a clinicii. Livrările păstrate în Perlato pentru diagnostic se șterg automat după 30 de zile.