CimolaceDémarrer
MedOS/Documentation développeur

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.

txt
┌──────────────────────────────────────────────────────────────┐
│  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

Mode A

Hébergé

Vous n'avez pas de site. Cimolace vous héberge sur *.medos.cimolace.space

Mode B

Domaine personnalisé

Vous avez un domaine acheté mais pas de site. Cimolace sert sous ce domaine. SSL auto.

Mode C · Recommandé

Embedded

Vous avez déjà un site. Vous y collez le widget MedOS. Le cas le plus courant.

Identités & rôles

IdentitéAuthCas d'usage
Visiteur anonymePOST /embed/tokenWidget se monte, visiteur se connecte via magic link
Patient identifié (Niveau 2)POST /embed/server-tokenVotre backend appelle Cimolace, obtient un JWT lié au patient
Backend tenantBearer mdk_*Server-to-server vers /med/*
PraticienJWT SupabaseApp 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

  1. Le widget lit data-tenant et data-mode
  2. Il appelle POST /v1/medos/embed/token avec son Origin
  3. Cimolace vérifie l'Origin contre tenant_domains, signe un JWT 15 min
  4. Le widget appelle /v1/medos/embed/me/* avec ce JWT
  5. 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

html
<!-- /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

AttributRequisDescription
data-tenantrequisSlug de votre tenant Cimolace
data-modedéfaut: patient-portalpatient-portal, consent-form, intake-form, health-tracker, appointment-booker
data-embed-tokenoptionnelPour Niveau 2 SSO. Si fourni, le widget l'utilise directement.
data-api-basedéfaut: prodURL API Cimolace (pour dev/staging)
data-targetdéfaut: #medos-portalSélecteur CSS du conteneur DOM
data-primary-coloroptionnelHex 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 :

html
<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)

SensMessageEffet
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 :

html
<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.

TypePréfixeDuréeObtenu via
Clé API tenantmdk_*jusqu'à revokeAdmin Cimolace
Embed-token anonymeeyJ...15 minPOST /embed/token
Embed-token identifiéeyJ...15 minPOST /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

bash
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

json
{
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "expires_in": 900,
    "api_base": "https://api.cimolace.space",
    "mode": "patient-portal",
    "scope": ["med:me:read", "med:notes:read"]
  }
}

Erreurs

  • 403 Origin non whitelisté
  • 404 Tenant inconnu ou inactif
  • 400 Mode 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

bash
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

json
{
  "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.

EndpointMéthodeScope
/v1/medos/embed/me/whoamiGET
/v1/medos/embed/me/appointmentsGETmed:appointments:read
/v1/medos/embed/me/appointmentsPOSTmed:appointments:write
/v1/medos/embed/me/prescriptionsGETmed:prescriptions:read
/v1/medos/embed/me/threadsGETmed:messages:read
/v1/medos/embed/me/threads/:id/messagesGETmed:messages:read
/v1/medos/embed/me/threads/:id/messagesPOSTmed:messages:write
/v1/medos/embed/me/teleconsult/appointment/:id/joinPOSTmed:teleconsult:join
/v1/medos/embed/me/notesGETmed:notes:read
/v1/medos/embed/me/notes/:id/readPOSTmed:notes:read
/v1/medos/embed/formsGETmed:forms:read
/v1/medos/embed/forms/:idGETmed:forms:read
/v1/medos/embed/me/healthPOSTmed: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_domains peuvent appeler /embed/token.
  • Clé API hashée — SHA-256, jamais stockée en clair.
  • Scope par mode — un token consent-form ne 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/* (scopes appointments, prescriptions, messages, teleconsult). Mode “votre app, votre UI”.
  • 2026-05-28 v1.1 — Niveau 2 SSO via /embed/server-token. Support data-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.