Trois étapes pour intégrer.
Récupérez votre clé
Demandez un accès via /demo?source=api. Vous recevez votre client_id et client_secret en moins de 24 h ouvrées.
Demandez un accès via /demo?source=api. Vous recevez votre client_id et client_secret en moins de 24 h ouvrées.
Échangez vos identifiants contre un access_token via /auth/token. Renouvelable toutes les heures.
Listez les médecins de votre wilaya, créez votre premier rendez-vous, écoutez vos premiers webhooks.
curl -X POST https://api.jivox.ma/api/appointments \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"doctorId": "usr_8f3a92",
"patientId": "usr_4c7b91",
"startTime": "2026-05-12T10:30:00+01:00",
"endTime": "2026-05-12T11:00:00+01:00"
}'Jivox utilise OAuth 2.0 avec le grant client_credentials pour les intégrations serveur-à-serveur. Les tokens sont valides 1 heure et peuvent être renouvelés via un refresh_token de 30 jours. Chaque token est limité à un ensemble de scopes.
POST /api/auth/token
{
"grant_type": "client_credentials",
"client_id": "ck_live_…",
"client_secret": "cs_live_…",
"scope": "appointments:read appointments:write webhooks:manage"
}Tous les appels suivants doivent inclure l'en-tête Authorization: Bearer <access_token>. En cas de token expiré l'API renvoie 401 avec le code token_expired.
users:readdoctors:readappointments:readappointments:writeprescriptions:readprescriptions:writeorders:readorders:writenotifications:writepdf:generatevideo:joinwebhooks:manage12 groupes, 35 endpoints. Toutes les requêtes sont JSON UTF-8 ; les dates en ISO 8601 ; les montants en centimes MAD entiers. Cliquez sur un endpoint pour voir l'exemple.
Sign-in, sign-out, refresh, profil — basé NextAuth + JWT.
publicPOST /api/auth/callback/credentials
Content-Type: application/x-www-form-urlencoded
email=jane%40clinic.ma&password=…&csrfToken=…{
"url": "https://app.jivox.ma/dashboard"
}{
"error": "CredentialsSignin"
}public{
"email": "[email protected]",
"password": "S3cret!Passw0rd",
"name": "Dr. Jane",
"role": "DOCTOR"
}{
"ok": true,
"user": { "id": "usr_…", "email": "[email protected]", "role": "DOCTOR" }
}{
"error": { "code": "CONFLICT", "message": "Email already registered" }
}users:read{
"id": "usr_8f3a92",
"email": "[email protected]",
"name": "Dr. Jane",
"role": "DOCTOR",
"tenantId": "ten_…"
}{
"error": { "code": "UNAUTHORIZED", "message": "Authentication required" }
}Annuaire public, sans authentification, mis en cache.
public{
"doctors": [{
"id": "usr_…", "slug": "dr-elhassani",
"name": "Dr. Younes El Hassani",
"specialty": "Cardiologie",
"city": "Casablanca",
"rating": 4.8,
"isTele": true
}],
"pagination": { "page": 1, "pageSize": 20, "total": 142, "totalPages": 8 }
}Création, listing, mise à jour, annulation. Conflits détectés serveur.
appointments:read{
"appointments": [{
"id": "apt_…",
"startTime": "2026-05-12T10:30:00.000Z",
"endTime": "2026-05-12T11:00:00.000Z",
"status": "SCHEDULED",
"videoRoomId": "room_…",
"patient": { "id": "usr_…", "name": "Patient X" },
"doctor": { "id": "usr_…", "name": "Dr. Jane" }
}],
"pagination": { "page": 1, "pageSize": 20, "total": 5, "totalPages": 1 }
}Émission et listing d'ordonnances numériques signées.
prescriptions:read{
"prescriptions": [{
"id": "rx_…",
"medication": "Metformine 500 mg",
"dosage": "1 cp x 2/j",
"doctor": { "id": "usr_…", "name": "Dr. Jane" },
"patient": { "id": "usr_…", "name": "Patient X" },
"issuedAt": "2026-05-02T08:00:00.000Z"
}]
}Création, statuts, dispensation des commandes pharmaceutiques.
orders:read{
"orders": [{
"id": "ord_…",
"status": "READY",
"deliveryRequested": true,
"prescription": { "id": "rx_…", "medication": "…" },
"pharmacy": { "id": "usr_…", "name": "Pharmacie Centrale" }
}]
}Capture des prospects depuis le site marketing (formulaires, démo).
public{
"name": "Dr. Karim",
"email": "[email protected]",
"phone": "+212600000000",
"source": "DEMO_FORM",
"metadata": { "specialty": "Cardiologie", "city": "Rabat" }
}{ "ok": true, "leadId": "lead_…" }Envoi de SMS/WhatsApp/Email transactionnels via les providers ChannelProvider.
notifications:write{
"recipientUserId": "usr_…",
"channel": "WHATSAPP",
"templateCode": "appointment_reminder",
"variables": { "doctorName": "Dr. Jane", "datetime": "12 mai à 10h30" }
}Rendu serveur d'ordonnances, certificats, factures, attestations.
pdf:generate{
"templateId": "rx_default",
"data": { "prescriptionId": "rx_…" }
}{
"ok": true,
"url": "https://cdn.jivox.ma/pdf/rx_…?sig=…",
"expiresAt": "2026-05-02T11:30:00.000Z"
}Délivre un access token JWT pour rejoindre une room LiveKit.
video:join{
"appointmentId": "apt_…",
"identity": "usr_…"
}{
"ok": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"url": "wss://livekit.jivox.ma",
"roomId": "room_…",
"expiresAt": "2026-05-12T10:45:00.000Z"
}Sondes de disponibilité — utilisées par le load balancer et le statut public.
public{
"ok": true,
"timestamp": "2026-05-02T10:30:00.000Z"
}{ "ok": false, "error": "service_unavailable" }Workers planifiés. Auth via en-tête X-Cron-Secret. Idempotents.
cron:run{ "ok": true, "processed": 42, "succeeded": 41, "failed": 1 }{ "error": "forbidden" }Inter-app événements signés. Auth via X-Internal-Token.
internal{ "appointmentId": "apt_…" }{
"ok": true,
"timestamp": "2026-05-02T10:30:00.000Z"
}Toutes les erreurs renvoient une enveloppe normalisée avec un code, un message lisible et des détails optionnels. Les codes 4xx ne sont jamais retentés automatiquement, les 5xx oui (backoff exponentiel).
{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}Pour les codes 429, 5xx et timeouts réseau : retentez avec un backoff exponentiel (250 ms × 2ⁿ, max 5 tentatives). Respectez l'en-tête Retry-After quand il est présent.
Les listes utilisent un schéma page + pageSize (max 100). La réponse contient un objet pagination avec total et totalPages.
GET /api/appointments?status=SCHEDULED&page=2&pageSize=50
Authorization: Bearer YOUR_ACCESS_TOKEN{
"appointments": [ /* … 50 rows … */ ],
"pagination": {
"page": 2,
"pageSize": 50,
"total": 327,
"totalPages": 7
}
}Chaque token reçoit ses quotas selon le forfait de l'organisation. Les en-têtes X-RateLimit-Limit et X-RateLimit-Remaining sont renvoyés à chaque appel ; Retry-After est renvoyé en cas de 429.
Chaque webhook contient un en-tête X-MC-Signature calculé avec votre webhook_secret en HMAC SHA-256 sur le corps brut. Les requêtes échouées sont retentées 3 fois (1 min, 10 min, 1 h).
Politique de retry : 3 tentatives avec backoff exponentiel (1 min, 10 min, 1 h). Au-delà, la livraison est marquée failed et l'événement reste consultable dans le tableau de bord.
import crypto from "node:crypto";
export function verify(rawBody: string, header: string, secret: string) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody, "utf8")
.digest("hex");
// Constant-time comparison to defeat timing attacks.
const a = Buffer.from(expected, "hex");
const b = Buffer.from(header, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}appointment.createdUn nouveau rendez-vous a été créé.{
"event": "appointment.created",
"id": "evt_…",
"createdAt": "2026-05-02T10:30:00.000Z",
"data": { "appointmentId": "apt_…", "doctorId": "usr_…", "patientId": "usr_…", "startTime": "…" }
}appointment.updatedStatut, horaire ou notes mis à jour.{ "event": "appointment.updated", "data": { "appointmentId": "apt_…", "changes": ["status"] } }appointment.cancelledRendez-vous annulé (par le patient, le médecin ou l'auto-cancel).{ "event": "appointment.cancelled", "data": { "appointmentId": "apt_…", "reason": "patient_request" } }appointment.completedConsultation marquée terminée.{ "event": "appointment.completed", "data": { "appointmentId": "apt_…", "durationSec": 1620 } }prescription.createdNouvelle ordonnance émise.{ "event": "prescription.created", "data": { "prescriptionId": "rx_…", "medication": "…" } }prescription.signedOrdonnance signée numériquement.{ "event": "prescription.signed", "data": { "prescriptionId": "rx_…", "signature": "sha256:…" } }lab_order.createdCommande d'analyses soumise.{ "event": "lab_order.created", "data": { "labOrderId": "lab_…", "tests": ["NFS","Glycémie"] } }lab_order.status_changedStatut LabOrder mis à jour.{ "event": "lab_order.status_changed", "data": { "labOrderId": "lab_…", "status": "RESULTS_READY" } }lab_result.publishedRésultat publié et visible par le patient.{ "event": "lab_result.published", "data": { "labResultId": "res_…", "url": "https://…" } }lab_result.critical_alertValeur critique détectée — alerte haute priorité.{ "event": "lab_result.critical_alert", "data": { "labResultId": "res_…", "param": "K+", "value": 6.2 } }payment.succeededPaiement CMI ou cash comptabilisé.{ "event": "payment.succeeded", "data": { "invoiceId": "inv_…", "amountMAD": 25000 } }payment.refundedRemboursement émis.{ "event": "payment.refunded", "data": { "invoiceId": "inv_…", "amountMAD": 25000 } }review.createdAvis vérifié publié.{ "event": "review.created", "data": { "reviewId": "rev_…", "rating": 5 } }patient.createdNouveau patient enregistré.{ "event": "patient.created", "data": { "patientId": "usr_…" } }patient.consent_updatedLe patient a modifié son consentement CNDP.{ "event": "patient.consent_updated", "data": { "patientId": "usr_…", "consents": ["MARKETING"] } }npm i @medconnect/sdkCompatible Node 20+, Next.js, Bun, Deno. Types embarqués, ESM + CJS.
pip install medconnectCompatible Python 3.10+. Async via httpx, synchronisé via requests.
composer require medconnect/sdkCompatible PHP 8.2+. Guzzle, PSR-7, autoloading PSR-4.
Cinq opérations courantes en JavaScript, Python et PHP. Toutes les opérations passent par un client SDK initialisé avec votre access_token.
import { Jivox } from "@medconnect/sdk";
const client = new Jivox({
accessToken: process.env.JIVOX_TOKEN,
});
const { appointments, pagination } = await client.appointments.list({
status: "SCHEDULED",
page: 1,
pageSize: 50,
});from medconnect import Jivox
client = Jivox(access_token=os.environ["JIVOX_TOKEN"])
resp = client.appointments.list(status="SCHEDULED", page=1, page_size=50)
for appt in resp["appointments"]:
print(appt["id"], appt["startTime"])<?php
use Jivox\Client;
$client = new Client(['access_token' => getenv('JIVOX_TOKEN')]);
$result = $client->appointments->list(['status' => 'SCHEDULED', 'pageSize' => 50]);
foreach ($result['appointments'] as $appt) {
echo $appt['id'] . PHP_EOL;
}const appointment = await client.appointments.create({
doctorId: "usr_8f3a92",
patientId: "usr_4c7b91",
startTime: "2026-05-12T10:30:00+01:00",
endTime: "2026-05-12T11:00:00+01:00",
});
console.log("Réservé :", appointment.id);appt = client.appointments.create(
doctor_id="usr_8f3a92",
patient_id="usr_4c7b91",
start_time="2026-05-12T10:30:00+01:00",
end_time="2026-05-12T11:00:00+01:00",
)$appt = $client->appointments->create([
'doctorId' => 'usr_8f3a92',
'patientId' => 'usr_4c7b91',
'startTime' => '2026-05-12T10:30:00+01:00',
'endTime' => '2026-05-12T11:00:00+01:00',
]);const { prescriptions } = await client.prescriptions.list({
patientId: "usr_4c7b91",
});rxs = client.prescriptions.list(patient_id="usr_4c7b91")["prescriptions"]$rxs = $client->prescriptions->list(['patientId' => 'usr_4c7b91']);await client.notifications.send({
recipientUserId: "usr_4c7b91",
channel: "WHATSAPP",
templateCode: "appointment_reminder",
variables: { doctorName: "Dr. Jane", datetime: "12 mai à 10h30" },
});client.notifications.send(
recipient_user_id="usr_4c7b91",
channel="WHATSAPP",
template_code="appointment_reminder",
variables={"doctorName": "Dr. Jane", "datetime": "12 mai à 10h30"},
)$client->notifications->send([
'recipientUserId' => 'usr_4c7b91',
'channel' => 'WHATSAPP',
'templateCode' => 'appointment_reminder',
'variables' => ['doctorName' => 'Dr. Jane', 'datetime' => '12 mai à 10h30'],
]);const health = await client.health.check();
console.log(health.ok); // truehealth = client.health.check()
assert health["ok"]$health = $client->health->check();
assert($health['ok']);L'environnement sandbox réplique l'API de production avec des données fictives. Aucune notification réelle n'est envoyée ; les paiements simulent CMI.
curl -X POST https://api-sandbox.jivox.ma/api/sandbox/reset \
-H "Authorization: Bearer SANDBOX_TOKEN"Pour explorer l'API et bâtir un prototype.
Pour les éditeurs SaaS et les cabinets en production.
Volumes illimités, isolation dédiée, contrats sur-mesure.
{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}doctors:read{
"doctors": [{ "id": "usr_…", "name": "Dr. Jane", "specialty": "…" }]
}{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}appointments:write{
"doctorId": "usr_…",
"patientId": "usr_…",
"startTime": "2026-05-12T10:30:00.000Z",
"endTime": "2026-05-12T11:00:00.000Z",
"notes": "Suivi diabète"
}{
"appointment": { "id": "apt_…", "status": "SCHEDULED", "videoRoomId": "room_…" }
}{
"error": {
"code": "CONFLICT",
"message": "Doctor already has an appointment in that window",
"details": { "overlap": { "id": "apt_…" } }
}
}appointments:read{
"appointment": { "id": "apt_…", "status": "SCHEDULED", "notes": "…" }
}{ "error": { "code": "NOT_FOUND", "message": "Appointment not found" } }appointments:write{ "status": "CANCELLED", "notes": "Patient indisponible" }{ "appointment": { "id": "apt_…", "status": "CANCELLED" } }{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}appointments:writeadmin{ "ok": true }{ "error": { "code": "FORBIDDEN", "message": "Insufficient permissions" } }{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}prescriptions:write{
"patientId": "usr_…",
"appointmentId": "apt_…",
"medication": "Amoxicilline 1g",
"dosage": "1 cp x 3/j pendant 7 jours",
"instructions": "À prendre au milieu du repas."
}{
"prescription": { "id": "rx_…", "medication": "Amoxicilline 1g" }
}{
"error": { "code": "FORBIDDEN", "message": "Only doctors can issue prescriptions" }
}{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}orders:write{
"prescriptionId": "rx_…",
"pharmacyId": "usr_…",
"deliveryRequested": false
}{
"order": { "id": "ord_…", "status": "PENDING" }
}{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}orders:read{ "order": { "id": "ord_…", "status": "READY" } }{ "error": { "code": "NOT_FOUND", "message": "Order not found" } }orders:write{ "status": "READY" }{ "order": { "id": "ord_…", "status": "READY" } }{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}{
"ok": true,
"messageId": "msg_…",
"providerMessageId": "wa_…",
"status": "QUEUED"
}{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}{
"error": {
"code": "UNPROCESSABLE",
"message": "Validation failed",
"details": [
{ "path": ["startTime"], "message": "Invalid datetime" }
]
}
}{ "error": { "code": "FORBIDDEN", "message": "User not part of this appointment" } }public{ "ok": true, "service": "postgres", "latencyMs": 12 }{ "ok": false, "service": "postgres", "error": "connection_refused" }public{ "ok": true, "service": "redis", "latencyMs": 4 }{ "ok": false, "service": "redis", "error": "timeout" }public{ "ok": true, "service": "livekit", "latencyMs": 87 }{ "ok": false, "service": "livekit", "error": "auth_failed" }cron:run{ "ok": true, "cancelled": 7 }{ "error": "forbidden" }cron:run{ "ok": true, "processed": 18, "succeeded": 16, "failed": 2 }{ "error": "forbidden" }cron:run{ "ok": true, "advanced": 124 }{ "error": "forbidden" }cron:run{ "ok": true, "warnedItems": 36 }{ "error": "forbidden" }cron:run{ "ok": true, "applied": 3 }{ "error": "forbidden" }{ "error": "forbidden" }internal{ "prescriptionId": "rx_…", "pharmacyId": "usr_…", "deliveryRequested": false }{ "ok": true, "prescriptionId": "rx_…", "routedOrderId": "ord_…" }{ "error": "rx_not_found" }internal{ "orderId": "ord_…", "status": "READY" }{
"ok": true,
"timestamp": "2026-05-02T10:30:00.000Z"
}{ "error": "order_not_found" }internal{ "labOrderId": "lab_…" }{
"ok": true,
"timestamp": "2026-05-02T10:30:00.000Z"
}{ "error": "lab_order_not_found" }internal{ "followUpId": "fu_…" }{
"ok": true,
"timestamp": "2026-05-02T10:30:00.000Z"
}{ "error": "follow_up_not_found" }© 2026 Jivox. Conçu au Maroc.
ICE 001234567000089 · TVA TVA-CAS-2024-0142 · RC Casablanca 487291 · IF 12345678