Cimolace/Liri/Documentation développeur

Brancher Liri sur votre site

Liri est le moteur direct, rendez-vous et formation de Cimolace. Votre serveur crée et pilote les sessions par une API REST authentifiée par clé tenant ; le direct lui-même s'affiche dans votre page par une iframe dont vous demandez le jeton. Cette page ne documente que des routes vérifiées dans les contrôleurs.

Vue d'ensemble

Vous avez déjà un site. Liri lui ajoute des sessions en direct sans que vous ayez à gérer un serveur média, des jetons de salle ou un back-office. Le partage des rôles :

  • Votre serveur appelle l'API Liri avec une clé tenant (lk_live_…) pour créer, démarrer et terminer les sessions.
  • Votre serveur demande un jeton d'embed et reçoit une iframe_url prête à poser dans la page.
  • Le navigateur du visiteur n'affiche que cette iframe. Il ne voit jamais votre clé.
  • Liri notifie votre serveur par webhook quand une session démarre ou se termine.

Base de l'API : https://api.cimolace.space/v1/liri29 routes, toutes gardées par la même clé. Depuis le 19/09/2026, une clé lk_ est bornée à /v1/liri : elle n'ouvre plus /v1/crm. Pour le CRM il faut une clé lkr_ (lecture) ou lka_ (écriture), émise par POST /v1/liri/api-keys avec une session owner/admin.

⚠️ Liri n'utilise pas le même en-tête que les API Vimbo et MedOS. Celles-ci lisent Authorization: Bearer ; Liri lit X-Liri-Api-Key. Un site qui combine plusieurs moteurs envoie deux en-têtes différents.

Quickstart

1. Obtenir une clé

Une clé Liri a la forme lk_live_ suivie de 48 caractères hexadécimaux. Aucun écran ne permet aujourd'hui de créer votre première clé vous-même : la seule route qui en fabrique une est POST /v1/liri/api-keys, elle-même protégée par une clé. Demandez la première à Cimolace ; une fois en main, elle vous sert à en créer et en révoquer d'autres.

2. Créer une session

curl -X POST https://api.cimolace.space/v1/liri/sessions \
  -H "X-Liri-Api-Key: lk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Cours du mardi",
    "session_type": "class",
    "scheduled_at": "2026-09-08T18:00:00Z",
    "capacity": 40
  }'

3. Afficher le direct dans votre page

Demandez un jeton d'embed depuis votre serveur, puis posez l'iframe_url renvoyée. N'essayez pas de deviner cette URL : elle porte un jeton signé et expire.

bash
curl -X POST https://api.cimolace.space/v1/liri/sessions/<id>/embed-token \
  -H "X-Liri-Api-Key: lk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "role": "viewer", "display_name": "Awa N." }'

# → { "data": { "iframe_url": "https://…/embed/live/<id>?et=…&tenant=…&role=viewer",
#               "expires_in": 1800, ... } }

4. Démarrer, puis terminer

bash
curl -X POST https://api.cimolace.space/v1/liri/sessions/<id>/start -H "X-Liri-Api-Key: lk_live_xxxxxxxx"
curl -X POST https://api.cimolace.space/v1/liri/sessions/<id>/end   -H "X-Liri-Api-Key: lk_live_xxxxxxxx"

Ces deux appels sont les seuls qui déclenchent un webhook (session.started, session.ended).

Authentification

Toutes les routes /v1/liri exigent la clé tenant dans l'en-tête X-Liri-Api-Key. Il n'y a qu'un seul type de clé : pas de clé « publique » utilisable côté navigateur.

http
X-Liri-Api-Key: lk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
RègleDétail
Formatlk_live_ + 48 caractères hexadécimaux. Une clé qui ne commence pas par lk_ est refusée avant même la base.
StockageLa clé n'est jamais stockée en clair : seul son hash SHA-256 est comparé.
IsolationLa clé résout le tenant. Chaque requête est filtrée sur ce tenant — vous ne voyez jamais les sessions d'une autre organisation.
RévocationUne clé révoquée est refusée immédiatement (401), sans délai de cache.
Organisation suspendueSi le tenant n'est pas active, toutes ses clés sont refusées (401).
Auditlast_used_at est mis à jour à chaque appel, sans bloquer la réponse.

Serveur uniquement — ce n'est pas un choix de style

La politique CORS de l'API n'autorise que trois en-têtes : Content-Type, Authorization et X-Tenant-Slug. X-Liri-Api-Key n'en fait pas partie : un appel depuis un navigateur échoue au contrôle préalable (préflight), quelle que soit votre origine. Appelez l'API depuis votre backend et exposez à votre page uniquement l'iframe_url obtenue.

⛔ Une clé Liri crée des sessions et lit vos enregistrements. Elle ne gère plus vos webhooks et ne crée plus d'autres clés — une clé qui peut reconfigurer l'espace est une clé que la révoquer ne suffit plus à refermer. Ne la mettez jamais dans le JavaScript envoyé au navigateur.

Réponses et erreurs

Toutes les réponses de l'API sont enveloppées dans un objet data. Prévoyez-le dans votre client : la donnée utile est toujours d'un cran plus bas que ce que renvoie l'endpoint.

json
// GET /v1/liri/sessions
{
  "data": {
    "sessions": [
      {
        "id": "…", "title": "Cours du mardi", "status": "scheduled",
        "session_type": "class", "scheduled_at": "2026-09-08T18:00:00Z",
        "capacity": 40, "price_cents": 0, "currency": "EUR",
        "started_at": null, "ended_at": null, "replay_enabled": true
      }
    ],
    "total": 1
  }
}

Les erreurs suivent une forme unique :

json
{ "error": { "code": "UNAUTHORIZED", "message": "Clé API invalide ou révoquée" } }

Les 29 routes

La totalité de la surface /v1/liri. Rien de plus n'existe.

MéthodeCheminRôle
GET/sessionsLister (filtres status, type, limit, offset)
POST/sessionsCréer
GET/sessions/:idDétail complet
PATCH/sessions/:idModifier
DELETE/sessions/:idSupprimer (refusé si en cours)
POST/sessions/:id/startDémarrer → webhook
POST/sessions/:id/endTerminer → webhook
POST/sessions/:id/embed-tokenJeton + iframe_url
GET/sessions/:id/participantsÉtat de la salle
GET/sessions/:id/recordingsEnregistrements de la session
GET/sessions/:id/waiting-roomFile d'attente
POST/sessions/:id/waiting-room/:wid/admitAdmettre
POST/sessions/:id/waiting-room/:wid/rejectRefuser
GET/recordingsTous les enregistrements du tenant
POST/sessions/:id/transcribeTranscrire
POST/sessions/:id/summaryRésumer
POST/sessions/:id/translateTraduire
POST/sessions/:id/neuro-recall-deckCartes de révision
POST/masterclassesGénérer depuis un texte
GET/masterclassesLister
GET/masterclasses/:idDétail
GET/api-keysLister vos clés
POST/api-keysCréer une clé
DELETE/api-keys/:idRévoquer
GET/webhooksLister — session owner/admin, plus par clé
POST/webhooksCréer — session owner/admin
DELETE/webhooks/:idSupprimer — session owner/admin
PATCH/webhooks/:id/toggleActiver / désactiver — session owner/admin
GET/meTenant et identifiant de la clé

Sessions

POST /sessions

Seul title est obligatoire. Tout le reste a une valeur par défaut.

ChampDéfautDétail
titleRequis.
descriptionnullTexte libre.
session_typewebinarclass, webinar, workshop, consultation, debate, commercial, masterclass.
scheduled_atmaintenantISO 8601.
capacitynullIllimité si absent.
price_cents0Entier, en centimes.
currencyEURDevise affichée.
replay_enabledtrueRediffusion.
config{}Objet libre conservé tel quel.

L'hôte est le propriétaire de l'organisation, sauf si vous passez un host_user_id. La session naît en scheduled.

Cycle de vie

  • POST /start — passe en live. Rejouer l'appel sur une session déjà en direct la renvoie telle quelle ; sur une session terminée, il échoue en 400.
  • POST /end — passe en ended et horodate ended_at.
  • DELETE — refusé en 400 tant que la session est live. Terminez-la d'abord.

Salle d'attente

GET /sessions/:id/waiting-room renvoie les personnes en attente, les plus anciennes en premier. Vous les admettez ou les refusez une par une. Cette file n'émet aucun webhook : interrogez-la pendant la session.

Jeton d'embed

POST /sessions/:id/embed-token renvoie un jeton signé et l'URL d'iframe correspondante.

json
// requête
{ "role": "viewer", "display_name": "Awa N." }

// réponse
{
  "data": {
    "embed_token": "eyJhbGciOi…",
    "iframe_url": "https://…/embed/live/<id>?et=…&tenant=<slug>&role=viewer&display=Awa%20N.",
    "expires_in": 1800,
    "session_title": "Cours du mardi",
    "session_status": "live",
    "role": "viewer"
  }
}
RôleDroitsDurée du jeton
viewer (défaut)Regarde.30 minutes
co_hostCaméra et micro.30 minutes
hostContrôle complet de la salle.4 heures

Posez l'iframe_url telle quelle. Elle est construite à partir de l'origine de l'application, qui est une configuration serveur : ne la codez pas en dur, et regénérez le jeton plutôt que de le mettre en cache au-delà de son expiration.

html
<iframe
  src="<iframe_url renvoyée par l'API>"
  style="width:100%;height:560px;border:none;border-radius:12px"
  allow="camera; microphone; autoplay; fullscreen; display-capture"
  allowfullscreen
></iframe>

Transcription et IA

Ces routes travaillent sur l'enregistrement d'une session terminée. Elles s'enchaînent dans cet ordre : sans transcription, le résumé et la traduction n'ont pas de matière.

RouteCorpsEffet
POST /sessions/:id/transcribelanguage, forceTranscrit l'enregistrement (Whisper).
POST /sessions/:id/summarylength (short/medium/long), format (paragraph/bullets)Résume le transcript. Un résumé déjà calculé est renvoyé tel quel avec cached: true.
POST /sessions/:id/translatetarget_langs (défaut ["en"]), mode (live/video)Traduit le transcript.
POST /sessions/:id/neuro-recall-deckcount (1–50), difficulty, save, user_idFabrique des cartes question / réponse et les enregistre, sauf save: false.
bash
curl -X POST https://api.cimolace.space/v1/liri/sessions/<id>/transcribe \
  -H "X-Liri-Api-Key: lk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "language": "fr" }'

Appeler /translate avant /transcribe ne lève pas d'erreur HTTP : la réponse contient { "error": "NOT_TRANSCRIBED" }. Testez ce cas, il arrive en 200.

Masterclasses

POST /masterclasses transforme un texte source en cours structuré. source_text est obligatoire — vide, la réponse est un 400.

bash
curl -X POST https://api.cimolace.space/v1/liri/masterclasses \
  -H "X-Liri-Api-Key: lk_live_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Introduction à la comptabilité",
    "source_text": "…votre texte source…"
  }'

GET /masterclasses liste celles de votre organisation, GET /masterclasses/:id en donne le détail.

Clés API

POST /api-keys attend un label et renvoie la clé en clair une seule fois. Elle n'est stockée que hachée : personne, y compris Cimolace, ne peut vous la relire ensuite.

json
{
  "data": {
    "id": "…",
    "label": "Serveur production",
    "key_prefix": "lk_live_a1b2c3d4e5",
    "key": "lk_live_a1b2c3…",
    "_warning": "Copiez cette clé maintenant — elle ne sera plus affichée."
  }
}

GET /api-keys ne renvoie que les préfixes et les dates de dernier usage, jamais les clés. DELETE /api-keys/:id révoque immédiatement.

Pour changer de clé sans coupure : créez la nouvelle, déployez-la, vérifiez sur GET /me qu'elle répond, puis révoquez l'ancienne.

SDK JavaScript

Le SDK est servi par l'application Cimolace à /liri-sdk.js et expose le global LiriSDK. Il déduit tout seul les adresses de l'API et de l'application à partir de l'URL du script — inutile de les configurer.

html
<div id="live"></div>
<script src="https://app.cimolace.space/liri-sdk.js"></script>
<script>
  const liri = LiriSDK.init({ tenant: "mon-ecole" });
  liri.viewer("#live", { session: "SESSION_ID" });
</script>

Sans clé, le SDK passe par le flux navigateur décrit ci-dessous : l'origine de votre site doit être déclarée. Les méthodes d'affichage sont viewer(), host(), coHost() et embed() (rôle explicite).

Montage automatique

Utile pour un CMS où vous ne pouvez coller que du HTML : tout élément de classe liri-live est monté au chargement.

html
<div class="liri-live"
     data-tenant="mon-ecole"
     data-session="SESSION_ID"
     data-role="viewer"
     data-height="560"
     data-theme="dark"></div>
<script src="https://app.cimolace.space/liri-sdk.js" async></script>

Événements

L'iframe communique avec votre page. Cinq événements sont disponibles :

javascript
const live = liri.viewer("#live", { session: "SESSION_ID" });

live.on("session:joined",     (i) => console.log("connecté", i.sessionId, i.room));
live.on("session:ended",      (i) => console.log("terminé", i.sessionId));
live.on("participant:joined", (p) => console.log("arrivée", p));
live.on("participant:left",   (p) => console.log("départ", p));
live.on("error",              (e) => console.error(e.message));

live.resize(720);   // change la hauteur
live.unmount();     // retire l'iframe et les écouteurs

⚠️ Le SDK accepte une apiKey, et sa documentation interne le dit clairement : ne l'utilisez que dans un contexte serveur (Node), jamais dans une page publique. Depuis un navigateur, la politique CORS de l'API rejette de toute façon l'en-tête — l'appel échouera au lieu de fuiter, mais ne comptez pas là-dessus comme protection.

Flux navigateur (sans clé)

Ce flux existe pour les pages publiques. Il ne s'authentifie pas par clé mais par l'origine du site appelant, comparée aux domaines déclarés par votre organisation. Vous déclarez un domaine depuis votre espace Cimolace ; il devient une origine autorisée une fois son DNS vérifié.

RouteRôle
POST /lives/embed/token{ tenant, session, role? } → jeton + iframe_url, valable 30 minutes.
GET /lives/embed/:sessionId/info?tenant=…Titre, statut et horaire — sans authentification, pour afficher un aperçu avant de rejoindre.
POST /lives/embed/:sessionId/joinAppelée par l'iframe elle-même : échange le jeton contre un accès à la salle.

🔒 Le rôle demandé par ce flux est ignoré et forcé à viewer. L'origine est partagée par tous les visiteurs du site : sans cette règle, n'importe quel spectateur pourrait réclamer co_host et publier caméra et micro dans votre direct. Pour un vrai co-présentateur, passez par POST /v1/liri/sessions/:id/embed-token depuis votre serveur, où le rôle est honoré.

Rendez-vous publics

Le moteur de rendez-vous expose une entrée entièrement publique, sans clé ni compte : un visiteur anonyme consulte les disponibilités et demande un rendez-vous. Votre organisation est désignée par son slug dans le chemin. La demande arrive dans la file du secrétariat.

MéthodeCheminDétail
GET/booking-public/:slug/availabilityParamètres windowStart, windowEnd, timezone, country.
POST/booking-public/:slug/appointment-requestsubject, description, email, whatsapp, preferredIso.
GET/booking-public/:slug/servicesCatalogue des prestations réservables.
GET/booking-public/:slug/vitrine-navLiens de navigation publiés par l'organisation.
GET/booking-public/reschedule/:tokenContexte d'un report par lien.
POST/booking-public/reschedule/:tokenApplique le nouveau créneau.
bash
curl -X POST https://api.cimolace.space/booking-public/mon-ecole/appointment-request \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Première consultation",
    "email": "visiteur@example.com",
    "whatsapp": "+241060000000",
    "preferredIso": "2026-09-10T14:00:00Z"
  }'

La création de demande est limitée à 4 requêtes par adresse IP par tranche de 10 minutes (et 50 toutes origines confondues). Au-delà, l'API répond 429. Ces routes étant ouvertes, mettez un contrôle anti-robot devant votre formulaire.

Les routes /booking/* (sans -public) sont réservées au back-office : elles exigent une session utilisateur et ne répondent pas à une clé API.

Webhooks — configuration

⚠️ Depuis le 19/09/2026, les webhooks ne se configurent plus avec une clé API : il faut une session owner/admin. Une clé qui peut détourner le flux de vos contacts vers une autre adresse est une clé dont la révocation ne suffit plus à réparer les dégâts. Utilisez le jeton de session (Authorization: Bearer), pas X-Liri-Api-Key.

bash
curl -X POST https://api.cimolace.space/v1/liri/webhooks \
  -H "Authorization: Bearer <jeton de session owner/admin>" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Mon backend",
    "url": "https://mon-site.example/hooks/liri",
    "events": ["session.started", "session.ended"]
  }'

La réponse contient signing_secret : c'est le secret de signature. Conservez-le, il ne réapparaît pas. Vous pouvez aussi fournir le vôtre via secret. La valeur "*" dans events souscrit à tout.

ContrainteDétail
HTTPS obligatoireUne URL en http:// est refusée à la création.
Pas d'adresse interneLes adresses privées, locales et non résolubles sont refusées, à la création comme à chaque envoi. La résolution DNS utilisée pour la vérification est celle utilisée pour la connexion.
Pas de redirectionUne réponse 3xx n'est pas suivie et compte comme un échec.
Délai8 secondes. Répondez vite et traitez en tâche de fond.
SuccèsSeul un code 2xx remet failure_count à zéro.
Pas de réessaiUne livraison échouée n'est pas rejouée. Si vous ne pouvez pas perdre un événement, réconciliez périodiquement avec GET /sessions.

Vérifier la signature

Chaque livraison porte un HMAC-SHA256 du corps brut, calculé avec votre secret et encodé en hexadécimal.

En-têteContenu
X-Cimolace-Signaturesha256=<hex> — en-tête canonique.
X-Cimolace-DeliveryIdentifiant unique de la livraison.
X-Liri-SignatureMême valeur que X-Cimolace-Signature — alias historique.
X-Liri-DeliveryAlias historique de X-Cimolace-Delivery.

La signature ne couvre que le corps : elle ne contient pas d'horodatage et il n'y a donc pas de fenêtre anti-rejeu. Si le rejeu vous expose, dédupliquez sur liri_delivery_id, que le corps transporte.

import { createHmac, timingSafeEqual } from "crypto";

// Attention : il faut le corps BRUT, pas l'objet déjà analysé.
export function verifyLiriSignature(rawBody, header, secret) {
  if (typeof header !== "string" || !header.startsWith("sha256=")) return false;
  const received = Buffer.from(header.slice(7), "hex");
  const expected = createHmac("sha256", secret).update(rawBody).digest();
  if (received.length !== expected.length) return false;
  return timingSafeEqual(received, expected);
}

Corps reçu

json
{
  "event": "session.ended",
  "tenant_id": "…",
  "session_id": "…",
  "data": { "session_id": "…" },
  "liri_delivery_id": "3f2a…",
  "timestamp": "2026-08-28T18:42:11.004Z"
}

Le contenu de data est volontairement mince : session.started porte session_id et title ; session.ended ne porte que session_id. Pour la durée, le nombre de participants ou l'enregistrement, relisez la session avec GET /sessions/:id.

Événements réellement émis

L'API accepte de vous abonner à toute la liste ci-dessous. Douze événements sont réellement envoyés aujourd'hui ; six sont déclarés mais aucun code ne les déclenche. Ils sont signalés pour que vous ne construisiez rien dessus.

ÉvénementÉmis ?Déclencheur
session.startedPOST /sessions/:id/start
session.endedPOST /sessions/:id/end
session.cancelledAucun émetteur.
participant.joinedAucun émetteur.
participant.leftAucun émetteur.
recording.startedAucun émetteur.
recording.completedAucun émetteur.
waiting_room.knockAucun émetteur — interrogez /waiting-room.
billing.subscription.activatedAbonnement activé.
billing.invoice.paidFacture payée.
billing.subscription.past_duePaiement en retard.
billing.subscription.canceledAbonnement résilié.
crm.contact.createdContact créé.
crm.deal.createdAffaire créée.
crm.deal.wonAffaire gagnée.
crm.deal.lostAffaire perdue.
crm.deal.stage_movedChangement d'étape.
crm.task.createdTâche créée.

Les événements de facturation et de CRM arrivent sur le même endpoint : filtrez sur le champ event.

Sécurité

  • Clé côté serveur uniquement — la politique CORS n'autorise pas X-Liri-Api-Key depuis un navigateur ; exposez l'iframe_url, jamais la clé.
  • Isolation par organisation — la clé résout le tenant et chaque requête est filtrée dessus.
  • Révocation immédiate — une clé compromise se coupe en un appel, sans délai de cache.
  • Jetons d'embed courts — 30 minutes, 4 heures pour un hôte. Regénérez plutôt que de prolonger.
  • Pas d'escalade depuis le navigateur — le flux sans clé ne délivre que le rôle viewer.
  • Webhooks sortants bridés — HTTPS obligatoire, adresses internes refusées, redirections non suivies.
  • Portée réelle de la clé — une clé lk_ ouvre /v1/liri et rien d'autre : ni le CRM, ni les webhooks, ni l'émission de clés. Traitez-la tout de même comme un identifiant de service, pas comme un jeton public.

Codes d'erreur

SymptômeCause probable
401 « Header X-Liri-Api-Key manquant ou invalide »En-tête absent, ou clé qui ne commence pas par lk_. Vérifiez que vous n'envoyez pas Authorization: Bearer — c'est l'en-tête de Vimbo et MedOS, pas de Liri.
401 « Clé API invalide ou révoquée »Clé inconnue ou révoquée.
401 « Tenant inactif »L'organisation n'est plus active.
Échec CORS avant tout appelVous appelez /v1/liri depuis un navigateur. Passez par votre serveur.
403 sur /lives/embed/tokenL'origine du site n'est pas déclarée pour cette organisation.
404 « Session introuvable »Identifiant erroné, ou session appartenant à une autre organisation.
400 « La session est déjà terminée »/start sur une session ended.
400 « Impossible de supprimer une session en cours »Appelez /end avant DELETE.
200 avec NOT_TRANSCRIBED/translate appelé avant /transcribe.
Transcription entre crochetsLe service de transcription n'est pas configuré côté serveur ; un texte de remplacement est renvoyé au lieu d'une erreur.
429 sur /booking-publicPlus de 4 demandes de rendez-vous depuis la même IP en 10 minutes.
Webhook jamais reçuURL en http://, hôte non public, redirection, ou réponse au-delà de 8 secondes. Aucune de ces livraisons n'est rejouée.