Intégrer MedOS dans votre application
Le moteur médical de Cimolace s'intègre dans n'importe quel site existant via widget JS, iframe, ou API REST. Aucun framework propriétaire. Cette doc couvre tout : quickstart, authentification, modes d'intégration, référence API, sécurité.
Vue d'ensemble
MedOS est le moteur santé de Cimolace : dossier patient (EHR), notes SOAP, prescriptions, formulaires, journal santé, téléconsultation. Vous l'activez sur votre tenant Cimolace, et vous l'exposez à vos utilisateurs via 3 canaux :
- Widget JS — 6 lignes HTML collées dans votre site.
- iframe — pour les CMS qui bloquent les scripts tiers (Webflow strict).
- API REST — pour reconstruire votre propre UI au-dessus de MedOS.
Vous gardez votre domaine, votre branding, votre frontend. Vos clients ne quittent jamais votre site. Cimolace tourne en coulisse, invisible.
💡 Commencez par le Quickstart pour avoir un widget qui marche en 5 minutes. Allez ensuite voir Niveau 2 SSO pour le brancher à votre système d'auth existant.
Quickstart (5 minutes)
Trois étapes pour afficher le portail patient sur votre site en mode anonyme.
1. Whitelister votre domaine
Demandez au staff Cimolace d'ajouter votredomaine.com dans tenant_domains (usage embed_origin). Sans ça, votre Origin sera refusée par CORS.
2. Activer les moteurs MedOS
Votre tenant doit avoir au moins med_ehr et med_notes activés dans tenant_services.
3. Coller le snippet
<!-- N'importe quelle page de votre site -->
<div id="medos-portal"></div>
<script
src="https://cimolace.space/medos/v1/embed.js"
data-tenant="votre-tenant-slug"
data-mode="patient-portal"
data-primary-color="#10b981"
async
></script>Le widget se monte dans le <div>, charge un JWT court, affiche le portail patient. Les visiteurs s'authentifient via magic link Supabase intégré.
Architecture
Cimolace est un SaaS multi-tenant. MedOS est un moteur de ce SaaS. Vous activez MedOS pour vos utilisateurs.
┌──────────────────────────────────────────────────────────────┐
│ Navigateur du visiteur final │
│ │
│ Votre site (ex: zahirwellness.com) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ votre header / nav / branding │ │
│ ├────────────────────────────────────────────────────────┤ │
│ │ <div id="medos-portal"></div> ← widget Cimolace ici │ │
│ ├────────────────────────────────────────────────────────┤ │
│ │ votre footer │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
↓
↓ widget appelle (CORS validé Origin)
↓
┌──────────────────────────────────────────────────────────────┐
│ api.cimolace.space (invisible) │
│ • Vérifie votredomaine.com whitelisté │
│ • Délivre un JWT court (15 min) │
│ • Renvoie les données patient │
└──────────────────────────────────────────────────────────────┘3 modes officiels
Hébergé
Vous n'avez pas de site. Cimolace vous héberge sur *.medos.cimolace.space
Domaine personnalisé
Vous avez un domaine acheté mais pas de site. Cimolace sert sous ce domaine. SSL auto.
Embedded
Vous avez déjà un site. Vous y collez le widget MedOS. Le cas le plus courant.
Identités & rôles
| Identité | Auth | Cas d'usage |
|---|---|---|
| Visiteur anonyme | POST /embed/token | Widget se monte, visiteur se connecte via magic link |
| Patient identifié (Niveau 2) | POST /embed/server-token | Votre backend appelle Cimolace, obtient un JWT lié au patient |
| Backend tenant | Bearer mdk_* | Server-to-server vers /med/* |
| Praticien | JWT Supabase | App med-app (pas le widget) |
Niveau 1 — Widget anonyme
Le plus simple. Vous collez le snippet HTML, le widget gère l'auth visiteur lui-même (magic link).
Fonctionnement
- Le widget lit
data-tenantetdata-mode - Il appelle
POST /v1/medos/embed/tokenavec son Origin - Cimolace vérifie l'Origin contre
tenant_domains, signe un JWT 15 min - Le widget appelle
/v1/medos/embed/me/*avec ce JWT - Si le visiteur n'est pas connecté, magic link Supabase
<div id="medos-portal"></div>
<script
src="https://cimolace.space/medos/v1/embed.js"
data-tenant="votre-slug"
data-mode="patient-portal"
async
></script>Niveau 2 — SSO server-to-server
Votre user est déjà connecté sur votre site. Vous voulez que le widget affiche directement son dossier, sans login dans le widget.
Votre backend appelle Cimolace avec sa clé API tenant, obtient un JWT “identifié” (sub = patient_user_id), l'injecte dans la page via data-embed-token.
Étape 1 — Backend : récupérer un token
// pages/api/medos-token.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "next-auth/react";
const CIMOLACE_API = "https://api.cimolace.space";
const TENANT_KEY = process.env.CIMOLACE_TENANT_API_KEY!;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({ req });
if (!session?.user?.email) return res.status(401).end();
const r = await fetch(`${CIMOLACE_API}/v1/medos/embed/server-token`, {
method: "POST",
headers: {
Authorization: `Bearer ${TENANT_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
patient_email: session.user.email,
patient_first_name: session.user.firstName,
patient_last_name: session.user.lastName,
mode: "patient-portal",
}),
});
const { data } = await r.json();
res.json({ token: data.token });
}Étape 2 — Frontend : injecter le token
<!-- /mon-espace-sante -->
<div id="medos-portal"></div>
<script>
fetch("/api/medos-token")
.then(r => r.json())
.then(({ token }) => {
const s = document.createElement("script");
s.src = "https://cimolace.space/medos/v1/embed.js";
s.setAttribute("data-tenant", "votre-slug");
s.setAttribute("data-mode", "patient-portal");
s.setAttribute("data-embed-token", token);
s.async = true;
document.body.appendChild(s);
});
</script>⚠️ Sécurité : la clé CIMOLACE_TENANT_API_KEY reste UNIQUEMENT côté serveur. Jamais dans un public env var (NEXT_PUBLIC_*), jamais dans un fichier versionné, jamais dans le bundle JS livré au navigateur.
Attributs du widget JS
| Attribut | Requis | Description |
|---|---|---|
data-tenant | requis | Slug de votre tenant Cimolace |
data-mode | défaut: patient-portal | patient-portal, consent-form, intake-form, health-tracker, appointment-booker |
data-embed-token | optionnel | Pour Niveau 2 SSO. Si fourni, le widget l'utilise directement. |
data-api-base | défaut: prod | URL API Cimolace (pour dev/staging) |
data-target | défaut: #medos-portal | Sélecteur CSS du conteneur DOM |
data-primary-color | optionnel | Hex color, ex: #10b981 |
Mode iframe (Mode C.2)
Si votre CMS bloque les scripts tiers (Webflow strict, certains setups Squarespace), utilisez l'iframe — aucun JS à charger, juste une balise :
<iframe
id="medos"
src="https://cimolace.space/embed/patient-portal?tenant=votre-slug&primary=10b981"
width="100%"
height="600"
frameborder="0"
style="border:0"
></iframe>Paramètres d'URL
/embed/<mode>—patient-portal,appointment-booker,health-tracker,consent-form,intake-form?tenant=— slug de votre tenant (requis)?primary=— couleur hex sans#(ex :10b981)
⚠️ Aucun token dans l'URL (évite les fuites dans les logs). L'iframe s'authentifie elle-même via POST /embed/token, validé par l'Origin HTTP — votre domaine doit donc être whitelisté dans tenant_domains.
Communication parent ↔ iframe (postMessage)
| Sens | Message | Effet |
|---|---|---|
| iframe → parent | { type: 'medos:ready' } | l'iframe est chargée et prête |
| iframe → parent | { type: 'medos:height', height } | hauteur du contenu (auto-resize) |
| iframe → parent | { type: 'medos:event', name, payload } | événement utilisateur (ex : note-read) |
| parent → iframe | { type: 'medos:theme', primary } | changer la couleur à chaud |
Auto-resize (recommandé)
L'iframe émet sa hauteur en continu (via ResizeObserver). Ajustez-la côté parent pour éviter le double scroll :
<script>
window.addEventListener("message", (e) => {
if (e.origin !== "https://cimolace.space") return; // sécurité : vérifier l'origine
const msg = e.data || {};
if (msg.type === "medos:height") {
document.getElementById("medos").style.height = msg.height + "px";
}
});
</script>Authentification API
Tous les endpoints /v1/medos/embed/* et /med/* demandent un JWT en Authorization: Bearer.
| Type | Préfixe | Durée | Obtenu via |
|---|---|---|---|
| Clé API tenant | mdk_* | jusqu'à revoke | Admin Cimolace |
| Embed-token anonyme | eyJ... | 15 min | POST /embed/token |
| Embed-token identifié | eyJ... | 15 min | POST /embed/server-token |
POST /v1/medos/embed/token
Émet un JWT court anonyme. Appelé par le widget JS, validé via l'Origin HTTP contre tenant_domains.
Request
curl -X POST https://api.cimolace.space/v1/medos/embed/token \
-H "Origin: https://votredomaine.com" \
-H "Content-Type: application/json" \
-d '{"tenant_slug":"votre-slug","mode":"patient-portal"}'Response 200
{
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 900,
"api_base": "https://api.cimolace.space",
"mode": "patient-portal",
"scope": ["med:me:read", "med:notes:read"]
}
}Erreurs
403Origin non whitelisté404Tenant inconnu ou inactif400Mode invalide
POST /v1/medos/embed/server-token
Émet un JWT court “identifié” lié à un patient précis. Crée le user Supabase + patient record automatiquement si absents.
Authentification
Clé API tenant en Authorization: Bearer mdk_*.
Request
curl -X POST https://api.cimolace.space/v1/medos/embed/server-token \
-H "Authorization: Bearer mdk_votre-slug_xxx" \
-H "Content-Type: application/json" \
-d '{
"patient_email": "client@example.com",
"patient_first_name": "Marie",
"patient_last_name": "Dupont",
"mode": "patient-portal",
"external_user_id": "user_42"
}'Response 201
{
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 900,
"api_base": "https://api.cimolace.space",
"mode": "patient-portal",
"scope": ["med:me:read", "med:notes:read"],
"patient_user_id": "27d9260d-635c-4995-9524-48008fb7d5da",
"patient_record_id": "029e5194-1813-4611-8220-1951c5e70531",
"created": true
}
}created: true au premier appel pour ce patient, false aux suivants (idempotent par email).
Endpoints data — votre app, votre UI
Vous avez déjà votre application ? C'est le mode le plus puissant : votre backend obtient un embed-token via /embed/server-token, puis votre front appelle ces endpoints avec Authorization: Bearer <token> et reconstruit sa propre interface par-dessus MedOS. Toutes les réponses sont enveloppées dans { "data": ... }. Toutes les routes sont scopées au seul patient du token.
| Endpoint | Méthode | Scope |
|---|---|---|
| /v1/medos/embed/me/whoami | GET | — |
| /v1/medos/embed/me/appointments | GET | med:appointments:read |
| /v1/medos/embed/me/appointments | POST | med:appointments:write |
| /v1/medos/embed/me/prescriptions | GET | med:prescriptions:read |
| /v1/medos/embed/me/threads | GET | med:messages:read |
| /v1/medos/embed/me/threads/:id/messages | GET | med:messages:read |
| /v1/medos/embed/me/threads/:id/messages | POST | med:messages:write |
| /v1/medos/embed/me/teleconsult/appointment/:id/join | POST | med:teleconsult:join |
| /v1/medos/embed/me/notes | GET | med:notes:read |
| /v1/medos/embed/me/notes/:id/read | POST | med:notes:read |
| /v1/medos/embed/forms | GET | med:forms:read |
| /v1/medos/embed/forms/:id | GET | med:forms:read |
| /v1/medos/embed/me/health | POST | med:health:write |
Le mode patient-portal couvre tous ces scopes. Les données médicales restent chez Cimolace (multi-tenant + RLS) ; votre app n'affiche que celles du patient courant.
Clés API tenant
Les clés API sont créées par le staff Cimolace, ou via app.cimolace.space (panneau admin tenant).
- Format :
mdk_<slug>_<48 chars hex> - Stockée hashée SHA-256 — irrécupérable une fois affichée. Si vous la perdez, créez-en une nouvelle.
- Révocation immédiate via
DELETE /admin/tenants/:id/api-keys/:keyId - Stat d'usage : champ
last_used_atà chaque requête
Sécurité
- JWT court 15 min — pas de refresh, votre backend réémet à chaque page load.
- CORS strict — seuls les domaines whitelist dans
tenant_domainspeuvent appeler/embed/token. - Clé API hashée — SHA-256, jamais stockée en clair.
- Scope par mode — un token
consent-formne peut PAS lire les notes. - RBAC patient — un embed-token ne peut accéder qu'aux données de SON patient.
- Audit log automatique — chaque accès est tracé dans
med_audit_log.
RGPD & audit
MedOS est nativement RGPD :
med_audit_log— chaque lecture/écriture de note est loggée (qui, quand, depuis quelle IP)med_consent_records— chaque consentement granulaire avec preuve (IP, user-agent, signature optionnelle, version du texte)med_gdpr_exports— droit d'accès / portabilité (export JSON ou PDF)med_gdpr_anonymizations— droit à l'oubli (pseudonymisation déterministe SHA-256)
Troubleshooting
Le widget ne s'affiche pas
- Vérifier que
<div id="medos-portal">existe AVANT le script - Console : chercher les erreurs CORS / 403
- Tester en curl :
curl -X POST .../embed/token -H "Origin: votre-domaine"
Origin non autorisé (403)
Votre domaine n'est pas dans tenant_domains. Contactez le staff Cimolace.
Tenant inactif
Le tenant doit avoir status = 'active' ET au moins un service MedOS activé.
Token expired
Les JWT durent 15 min. Le widget les réémet automatiquement. En Niveau 2, votre backend doit réémettre à chaque page load.
Aucune note partagée
Le patient n'a pas encore de note signée + partagée par un praticien.
Changelog
- 2026-05-30 v1.2 — API patient complète : rendez-vous, ordonnances, messagerie et téléconsultation exposés via
/embed/me/*(scopesappointments,prescriptions,messages,teleconsult). Mode “votre app, votre UI”. - 2026-05-28 v1.1 — Niveau 2 SSO via
/embed/server-token. Supportdata-embed-token. Endpoints data/embed/me/*. - 2026-05-28 v1.0 — Widget JS embed.js. Modes patient-portal, consent-form, health-tracker. Iframe
/embed/[mode]. ApiKeyGuard.
Prêt à intégrer ?
Demandez votre clé API tenant et activez MedOS sur votre site en quelques minutes.