Aller au contenu principal
QualiForma
CatalogueDevenir formateurQualiopiIADéveloppeurs
Connexion
  • Catalogue
  • Devenir formateur
  • Qualiopi
  • IA
  • Développeurs
  • Connexion
  • Vue d'ensemble

Démarrage

  • Quickstart
  • Authentification

Référence

  • API Reference (Scalar)

Endpoints

  • Tous les endpoints

Cœur LMS

  • Tenants
  • Utilisateurs
  • Formations
  • Inscriptions
  • Sessions live

Conformité Qualiopi

  • Dashboard Qualiopi
  • Conformité
  • Émargement
  • Questionnaires
  • Réclamations
  • Plans d'amélioration
  • Parcours adaptatifs
  • Compétences formateurs
  • BPF
  • Médiateurs

Paiements & Facturation

  • Paiements
  • Facturation Factur-X
  • Webhooks

Design System

  • Vue d'ensemble
  • Couleurs
  • Typographie
  • Espacement
  • Elevation
  • Motion
  • Radius
  • Composants
  • · Formulaires
  • · Feedback
  • · Navigation
  • · Progression
  • · Données
Swagger UI (s'ouvre dans un nouvel onglet)
  1. Développeurs
  2. Endpoints
  3. Emargement

Emargement

L'émargement numérique QualiForma répond aux exigences Qualiopi (indicateurs 11 et 12) avec cinq méthodes complémentaires, un horodatage qualifié et un scellement eIDAS via Scell.io.

Les 5 méthodes de signature

E1

QR code

L'apprenant scanne un QR généré dynamiquement à l'écran (5 min de validité).

E2

Code PIN

Code à 6 chiffres communiqué oralement, valide pour toute la session.

E3

Canvas manuscrit

Signature au doigt ou stylet, stockée chiffrée et scellée SHA-256.

E4

Auto LiveKit

Webhook LiveKit qui détecte la présence et calcule la durée de connexion.

E5

Override admin

Saisie manuelle par un ADMIN avec justification tracée en audit log.

Endpoints

post/api/v1/emargement/sign/qr

E1 — Signature par QR code

L'apprenant scanne un QR code généré dynamiquement et affiché par le formateur. Le code contient un token signé valide 5 minutes et lié à une session précise.

  • Emargement
  • E1

Corps de requête

Content-Type : application/json

Body · JSON
{
  "qrToken": "qrt_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "location": {
    "lat": 48.8566,
    "lng": 2.3522,
    "accuracy": 12
  }
}

Réponses

  • 201Émargement enregistré
    Réponse 201 · JSON
    {
      "data": {
        "id": "emg_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
        "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "method": "QR",
        "signedAt": "2026-05-22T13:35:14.000Z",
        "sealedHash": "sha256:9f2b8e1c3d4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c"
      }
    }
  • 410QR token expiré
    Réponse 410 · JSON
    {
      "statusCode": 410,
      "message": "QR token expired"
    }
  • 422QR token invalide ou déjà utilisé
    Réponse 422 · JSON
    {
      "statusCode": 422,
      "message": "QR token already used"
    }

Exemples

POST /api/v1/emargement/sign/qr · cURL / Shell
curl -X POST https://api.qualiforma.site/api/v1/emargement/sign/qr \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Tenant-ID: qualiforma-demo" \
  -H "Content-Type: application/json" \
  -d '{
  "qrToken": "qrt_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "location": {
    "lat": 48.8566,
    "lng": 2.3522,
    "accuracy": 12
  }
}'
POST /api/v1/emargement/sign/qr · JavaScript
const response = await fetch('https://api.qualiforma.site/api/v1/emargement/sign/qr', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'X-Tenant-ID': 'qualiforma-demo',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "qrToken": "qrt_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "location": {
      "lat": 48.8566,
      "lng": 2.3522,
      "accuracy": 12
    }
  }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const data = await response.json();
console.log(data);
POST /api/v1/emargement/sign/qr · Python
import requests

response = requests.post(
    'https://api.qualiforma.site/api/v1/emargement/sign/qr',
    headers={
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
        'X-Tenant-ID': 'qualiforma-demo',
        'Content-Type': 'application/json',
    },
    json={
        'qrToken': 'qrt_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6',
        'location': {
            'lat': 48.8566,
            'lng': 2.3522,
            'accuracy': 12,
        },
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
POST /api/v1/emargement/sign/qr · PHP
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

try {
    $response = $client->post('emargement/sign/qr', [
        'headers' => [
            'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
            'X-Tenant-ID' => 'qualiforma-demo',
        ],
        'json' => [
            'qrToken' => 'qrt_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6',
            'location' => [
                'lat' => 48.8566,
                'lng' => 2.3522,
                'accuracy' => 12,
            ],
        ],
    ]);
    $data = json_decode($response->getBody()->getContents(), true);
    var_dump($data);
} catch (GuzzleException $e) {
    fwrite(STDERR, $e->getMessage());
}
post/api/v1/emargement/sign/pin

E2 — Signature par code PIN

L'apprenant saisit le code PIN à 6 chiffres communiqué oralement par le formateur. Le PIN est valide pour toute la durée de la session et limite à 5 tentatives par utilisateur.

  • Emargement
  • E2

Corps de requête

Content-Type : application/json

Body · JSON
{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "pin": "482915"
}

Réponses

  • 201Émargement enregistré
    Réponse 201 · JSON
    {
      "data": {
        "id": "emg_b2c3d4e5-f6a7-8901-bcde-f23456789012",
        "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
        "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "method": "PIN",
        "signedAt": "2026-05-22T13:36:02.000Z"
      }
    }
  • 401PIN incorrect
    Réponse 401 · JSON
    {
      "statusCode": 401,
      "message": "Invalid PIN"
    }
  • 429Trop de tentatives
    Réponse 429 · JSON
    {
      "statusCode": 429,
      "message": "Too many attempts — wait 15 minutes"
    }

Exemples

POST /api/v1/emargement/sign/pin · cURL / Shell
curl -X POST https://api.qualiforma.site/api/v1/emargement/sign/pin \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Tenant-ID: qualiforma-demo" \
  -H "Content-Type: application/json" \
  -d '{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "pin": "482915"
}'
POST /api/v1/emargement/sign/pin · JavaScript
const response = await fetch('https://api.qualiforma.site/api/v1/emargement/sign/pin', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'X-Tenant-ID': 'qualiforma-demo',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
    "pin": "482915"
  }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const data = await response.json();
console.log(data);
POST /api/v1/emargement/sign/pin · Python
import requests

response = requests.post(
    'https://api.qualiforma.site/api/v1/emargement/sign/pin',
    headers={
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
        'X-Tenant-ID': 'qualiforma-demo',
        'Content-Type': 'application/json',
    },
    json={
        'sessionId': 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
        'pin': '482915',
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
POST /api/v1/emargement/sign/pin · PHP
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

try {
    $response = $client->post('emargement/sign/pin', [
        'headers' => [
            'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
            'X-Tenant-ID' => 'qualiforma-demo',
        ],
        'json' => [
            'sessionId' => 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
            'pin' => '482915',
        ],
    ]);
    $data = json_decode($response->getBody()->getContents(), true);
    var_dump($data);
} catch (GuzzleException $e) {
    fwrite(STDERR, $e->getMessage());
}
post/api/v1/emargement/sign/canvas

E3 — Signature manuscrite (canvas)

L'apprenant trace sa signature au doigt ou au stylet sur un canvas tactile. La signature est enregistrée en base64 (PNG transparent) et stockée chiffrée. Une géolocalisation optionnelle renforce la preuve.

  • Emargement
  • E3

Corps de requête

Content-Type : application/json

Body · JSON
{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "signatureBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAACWCAYAAAB6dx+VAAAAAXNSR0IArs4c6QAA...",
  "location": {
    "lat": 48.8566,
    "lng": 2.3522,
    "accuracy": 8
  }
}

Réponses

  • 201Émargement avec signature enregistré
    Réponse 201 · JSON
    {
      "data": {
        "id": "emg_c3d4e5f6-a7b8-9012-cdef-345678901234",
        "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
        "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "method": "CANVAS",
        "signedAt": "2026-05-22T13:37:48.000Z",
        "signatureUrl": "https://s3.eu-west-4.idrivee2.com/qualiforma-prod/signatures/emg_c3d4.png?...",
        "sealedHash": "sha256:1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b"
      }
    }
  • 422Signature trop courte (moins de 30 points tracés)
    Réponse 422 · JSON
    {
      "statusCode": 422,
      "message": "Signature too short or empty"
    }

Exemples

POST /api/v1/emargement/sign/canvas · cURL / Shell
curl -X POST https://api.qualiforma.site/api/v1/emargement/sign/canvas \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Tenant-ID: qualiforma-demo" \
  -H "Content-Type: application/json" \
  -d '{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "signatureBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAACWCAYAAAB6dx+VAAAAAXNSR0IArs4c6QAA...",
  "location": {
    "lat": 48.8566,
    "lng": 2.3522,
    "accuracy": 8
  }
}'
POST /api/v1/emargement/sign/canvas · JavaScript
const response = await fetch('https://api.qualiforma.site/api/v1/emargement/sign/canvas', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'X-Tenant-ID': 'qualiforma-demo',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
    "signatureBase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAACWCAYAAAB6dx+VAAAAAXNSR0IArs4c6QAA...",
    "location": {
      "lat": 48.8566,
      "lng": 2.3522,
      "accuracy": 8
    }
  }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const data = await response.json();
console.log(data);
POST /api/v1/emargement/sign/canvas · Python
import requests

response = requests.post(
    'https://api.qualiforma.site/api/v1/emargement/sign/canvas',
    headers={
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
        'X-Tenant-ID': 'qualiforma-demo',
        'Content-Type': 'application/json',
    },
    json={
        'sessionId': 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
        'signatureBase64': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAACWCAYAAAB6dx+VAAAAAXNSR0IArs4c6QAA...',
        'location': {
            'lat': 48.8566,
            'lng': 2.3522,
            'accuracy': 8,
        },
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
POST /api/v1/emargement/sign/canvas · PHP
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

try {
    $response = $client->post('emargement/sign/canvas', [
        'headers' => [
            'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
            'X-Tenant-ID' => 'qualiforma-demo',
        ],
        'json' => [
            'sessionId' => 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
            'signatureBase64' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAACWCAYAAAB6dx+VAAAAAXNSR0IArs4c6QAA...',
            'location' => [
                'lat' => 48.8566,
                'lng' => 2.3522,
                'accuracy' => 8,
            ],
        ],
    ]);
    $data = json_decode($response->getBody()->getContents(), true);
    var_dump($data);
} catch (GuzzleException $e) {
    fwrite(STDERR, $e->getMessage());
}
post/api/v1/emargement/auto

E4 — Émargement automatique LiveKit

Endpoint webhook appelé par le serveur LiveKit lorsqu'un participant rejoint la session. La présence est enregistrée automatiquement avec la durée de connexion mesurée côté LiveKit (sealed via HMAC).

  • Emargement
  • E4
  • Webhooks

Corps de requête

Content-Type : application/json

Body · JSON
{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "joinedAt": "2026-05-22T13:31:55.000Z",
  "leftAt": "2026-05-22T15:30:42.000Z",
  "connectionDurationSeconds": 7127,
  "livekitParticipantSid": "PA_a1b2c3d4e5f6",
  "hmacSignature": "sha256=2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b"
}

Réponses

  • 201Émargement auto enregistré
    Réponse 201 · JSON
    {
      "data": {
        "id": "emg_d4e5f6a7-b8c9-0123-defa-456789012345",
        "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
        "method": "AUTO",
        "attendanceMinutes": 119,
        "attendanceRate": 0.992,
        "signedAt": "2026-05-22T15:30:50.000Z"
      }
    }
  • 401Signature HMAC invalide
    Réponse 401 · JSON
    {
      "statusCode": 401,
      "message": "Invalid HMAC signature"
    }

Exemples

POST /api/v1/emargement/auto · cURL / Shell
curl -X POST https://api.qualiforma.site/api/v1/emargement/auto \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Tenant-ID: qualiforma-demo" \
  -H "Content-Type: application/json" \
  -d '{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "joinedAt": "2026-05-22T13:31:55.000Z",
  "leftAt": "2026-05-22T15:30:42.000Z",
  "connectionDurationSeconds": 7127,
  "livekitParticipantSid": "PA_a1b2c3d4e5f6",
  "hmacSignature": "sha256=2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b"
}'
POST /api/v1/emargement/auto · JavaScript
const response = await fetch('https://api.qualiforma.site/api/v1/emargement/auto', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'X-Tenant-ID': 'qualiforma-demo',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
    "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "joinedAt": "2026-05-22T13:31:55.000Z",
    "leftAt": "2026-05-22T15:30:42.000Z",
    "connectionDurationSeconds": 7127,
    "livekitParticipantSid": "PA_a1b2c3d4e5f6",
    "hmacSignature": "sha256=2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b"
  }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const data = await response.json();
console.log(data);
POST /api/v1/emargement/auto · Python
import requests

response = requests.post(
    'https://api.qualiforma.site/api/v1/emargement/auto',
    headers={
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
        'X-Tenant-ID': 'qualiforma-demo',
        'Content-Type': 'application/json',
    },
    json={
        'sessionId': 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
        'userId': 'usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890',
        'joinedAt': '2026-05-22T13:31:55.000Z',
        'leftAt': '2026-05-22T15:30:42.000Z',
        'connectionDurationSeconds': 7127,
        'livekitParticipantSid': 'PA_a1b2c3d4e5f6',
        'hmacSignature': 'sha256=2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b',
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
POST /api/v1/emargement/auto · PHP
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

try {
    $response = $client->post('emargement/auto', [
        'headers' => [
            'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
            'X-Tenant-ID' => 'qualiforma-demo',
        ],
        'json' => [
            'sessionId' => 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
            'userId' => 'usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890',
            'joinedAt' => '2026-05-22T13:31:55.000Z',
            'leftAt' => '2026-05-22T15:30:42.000Z',
            'connectionDurationSeconds' => 7127,
            'livekitParticipantSid' => 'PA_a1b2c3d4e5f6',
            'hmacSignature' => 'sha256=2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b',
        ],
    ]);
    $data = json_decode($response->getBody()->getContents(), true);
    var_dump($data);
} catch (GuzzleException $e) {
    fwrite(STDERR, $e->getMessage());
}
post/api/v1/emargement/override

E5 — Override manuel (admin)

Un administrateur force la signature pour un apprenant (cas d'absence du système numérique : panne réseau, oubli, etc.). L'override est tracé en audit avec la raison obligatoire. Réservé au rôle ADMIN.

  • Emargement
  • E5

Corps de requête

Content-Type : application/json

Body · JSON
{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "reason": "Panne réseau de l'apprenant pendant la session — confirmation orale et envoi du PDF signé hors ligne.",
  "attendanceMinutes": 115
}

Réponses

  • 201Override enregistré
    Réponse 201 · JSON
    {
      "data": {
        "id": "emg_e5f6a7b8-c9d0-1234-efab-567890123456",
        "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
        "method": "OVERRIDE",
        "overrideBy": "usr_admin01-2345-6789-abcd-ef0123456789",
        "reason": "Panne réseau de l'apprenant",
        "signedAt": "2026-05-22T15:45:00.000Z",
        "auditLogId": "aud_f6a7b8c9-d0e1-2345-fabc-678901234567"
      }
    }
  • 403Rôle ADMIN requis
    Réponse 403 · JSON
    {
      "statusCode": 403,
      "message": "Forbidden — ADMIN role required"
    }
  • 422Raison manquante ou trop courte
    Réponse 422 · JSON
    {
      "statusCode": 422,
      "message": "Validation failed",
      "errors": {
        "reason": [
          "reason must be at least 20 characters"
        ]
      }
    }

Exemples

POST /api/v1/emargement/override · cURL / Shell
curl -X POST https://api.qualiforma.site/api/v1/emargement/override \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Tenant-ID: qualiforma-demo" \
  -H "Content-Type: application/json" \
  -d '{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "reason": "Panne réseau de l'apprenant pendant la session — confirmation orale et envoi du PDF signé hors ligne.",
  "attendanceMinutes": 115
}'
POST /api/v1/emargement/override · JavaScript
const response = await fetch('https://api.qualiforma.site/api/v1/emargement/override', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'X-Tenant-ID': 'qualiforma-demo',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
    "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "reason": "Panne réseau de l'apprenant pendant la session — confirmation orale et envoi du PDF signé hors ligne.",
    "attendanceMinutes": 115
  }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const data = await response.json();
console.log(data);
POST /api/v1/emargement/override · Python
import requests

response = requests.post(
    'https://api.qualiforma.site/api/v1/emargement/override',
    headers={
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
        'X-Tenant-ID': 'qualiforma-demo',
        'Content-Type': 'application/json',
    },
    json={
        'sessionId': 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
        'userId': 'usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890',
        'reason': 'Panne réseau de l\'apprenant pendant la session — confirmation orale et envoi du PDF signé hors ligne.',
        'attendanceMinutes': 115,
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
POST /api/v1/emargement/override · PHP
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

try {
    $response = $client->post('emargement/override', [
        'headers' => [
            'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
            'X-Tenant-ID' => 'qualiforma-demo',
        ],
        'json' => [
            'sessionId' => 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
            'userId' => 'usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890',
            'reason' => 'Panne réseau de l\'apprenant pendant la session — confirmation orale et envoi du PDF signé hors ligne.',
            'attendanceMinutes' => 115,
        ],
    ]);
    $data = json_decode($response->getBody()->getContents(), true);
    var_dump($data);
} catch (GuzzleException $e) {
    fwrite(STDERR, $e->getMessage());
}
get/api/v1/emargement/session/:sessionId

Statut d'émargement d'une session

Retourne la liste des inscrits avec leur statut d'émargement (signé / non signé), la méthode utilisée et l'heure de signature. Utile pour le suivi en temps réel par le formateur.

  • Emargement

Paramètres

NomTypeRequisExempleDescription
sessionIdpathouises_e5f6a7b8-c9d0-4123-ef45-678901234567Identifiant UUID de la session

Réponses

  • 200Statut détaillé
    Réponse 200 · JSON
    {
      "data": {
        "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
        "totalRegistered": 18,
        "totalSigned": 17,
        "completionRate": 0.944,
        "signatures": [
          {
            "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "userEmail": "sophie.durand@example.fr",
            "method": "CANVAS",
            "signedAt": "2026-05-22T13:37:48.000Z"
          },
          {
            "userId": "usr_b2c3d4e5-f6a7-8901-bcde-f23456789012",
            "userEmail": "thomas.bernard@example.fr",
            "method": null,
            "signedAt": null
          }
        ]
      }
    }

Exemples

GET /api/v1/emargement/session/:sessionId · cURL / Shell
curl -X GET https://api.qualiforma.site/api/v1/emargement/session/ses_e5f6a7b8-c9d0-4123-ef45-678901234567 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Tenant-ID: qualiforma-demo"
GET /api/v1/emargement/session/:sessionId · JavaScript
const response = await fetch('https://api.qualiforma.site/api/v1/emargement/session/ses_e5f6a7b8-c9d0-4123-ef45-678901234567', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'X-Tenant-ID': 'qualiforma-demo',
  },
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const data = await response.json();
console.log(data);
GET /api/v1/emargement/session/:sessionId · Python
import requests

response = requests.get(
    'https://api.qualiforma.site/api/v1/emargement/session/ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
    headers={
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
        'X-Tenant-ID': 'qualiforma-demo',
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
GET /api/v1/emargement/session/:sessionId · PHP
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

try {
    $response = $client->get('emargement/session/ses_e5f6a7b8-c9d0-4123-ef45-678901234567', [
        'headers' => [
            'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
            'X-Tenant-ID' => 'qualiforma-demo',
        ],
    ]);
    $data = json_decode($response->getBody()->getContents(), true);
    var_dump($data);
} catch (GuzzleException $e) {
    fwrite(STDERR, $e->getMessage());
}
get/api/v1/emargement/session/:sessionId/export

Exporter la feuille d'émargement (PDF eIDAS)

Génère un PDF de la feuille d'émargement scellé eIDAS (signature électronique qualifiée). Inclut toutes les signatures, horodatages, hashes SHA-256 et le QR code de vérification. Format compatible audit Qualiopi.

  • Emargement
  • Export
  • Qualiopi

Paramètres

NomTypeRequisExempleDescription
sessionIdpathouises_e5f6a7b8-c9d0-4123-ef45-678901234567Identifiant UUID de la session

Réponses

  • 200URL présignée vers le PDF généré
    Réponse 200 · JSON
    {
      "data": {
        "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
        "pdfUrl": "https://s3.eu-west-4.idrivee2.com/qualiforma-prod/emargements/ses_e5f6.pdf?X-Amz-Signature=...",
        "sealedAt": "2026-05-22T16:00:00.000Z",
        "eidasCertificateId": "cert_scellio_eu1_a1b2c3",
        "expiresAt": "2026-05-22T17:00:00.000Z"
      }
    }
  • 409Session non terminée — l'export n'est disponible qu'après /end
    Réponse 409 · JSON
    {
      "statusCode": 409,
      "message": "Session must be ENDED before export"
    }

Exemples

GET /api/v1/emargement/session/:sessionId/export · cURL / Shell
curl -X GET https://api.qualiforma.site/api/v1/emargement/session/ses_e5f6a7b8-c9d0-4123-ef45-678901234567/export \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Tenant-ID: qualiforma-demo"
GET /api/v1/emargement/session/:sessionId/export · JavaScript
const response = await fetch('https://api.qualiforma.site/api/v1/emargement/session/ses_e5f6a7b8-c9d0-4123-ef45-678901234567/export', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'X-Tenant-ID': 'qualiforma-demo',
  },
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const data = await response.json();
console.log(data);
GET /api/v1/emargement/session/:sessionId/export · Python
import requests

response = requests.get(
    'https://api.qualiforma.site/api/v1/emargement/session/ses_e5f6a7b8-c9d0-4123-ef45-678901234567/export',
    headers={
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
        'X-Tenant-ID': 'qualiforma-demo',
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
GET /api/v1/emargement/session/:sessionId/export · PHP
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

try {
    $response = $client->get('emargement/session/ses_e5f6a7b8-c9d0-4123-ef45-678901234567/export', [
        'headers' => [
            'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
            'X-Tenant-ID' => 'qualiforma-demo',
        ],
    ]);
    $data = json_decode($response->getBody()->getContents(), true);
    var_dump($data);
} catch (GuzzleException $e) {
    fwrite(STDERR, $e->getMessage());
}
post/api/v1/emargement/sign-link

Générer un magic link de signature

Génère un lien magique signé permettant à un apprenant de signer à distance (par exemple par email après une session). Le lien expire après 48 heures et déclenche la méthode CANVAS au clic.

  • Emargement

Corps de requête

Content-Type : application/json

Body · JSON
{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "sendByEmail": true
}

Réponses

  • 201Lien généré
    Réponse 201 · JSON
    {
      "data": {
        "link": "https://qualiforma.site/emargement/sign?token=mlt_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8",
        "expiresAt": "2026-05-24T13:35:00.000Z",
        "emailSent": true
      }
    }

Exemples

POST /api/v1/emargement/sign-link · cURL / Shell
curl -X POST https://api.qualiforma.site/api/v1/emargement/sign-link \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "X-Tenant-ID: qualiforma-demo" \
  -H "Content-Type: application/json" \
  -d '{
  "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
  "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "sendByEmail": true
}'
POST /api/v1/emargement/sign-link · JavaScript
const response = await fetch('https://api.qualiforma.site/api/v1/emargement/sign-link', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
    'X-Tenant-ID': 'qualiforma-demo',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "sessionId": "ses_e5f6a7b8-c9d0-4123-ef45-678901234567",
    "userId": "usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "sendByEmail": true
  }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}

const data = await response.json();
console.log(data);
POST /api/v1/emargement/sign-link · Python
import requests

response = requests.post(
    'https://api.qualiforma.site/api/v1/emargement/sign-link',
    headers={
        'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
        'X-Tenant-ID': 'qualiforma-demo',
        'Content-Type': 'application/json',
    },
    json={
        'sessionId': 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
        'userId': 'usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890',
        'sendByEmail': True,
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()
print(data)
POST /api/v1/emargement/sign-link · PHP
<?php

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

$client = new Client(['base_uri' => 'https://api.qualiforma.site/api/v1/']);

try {
    $response = $client->post('emargement/sign-link', [
        'headers' => [
            'Authorization' => 'Bearer YOUR_ACCESS_TOKEN',
            'X-Tenant-ID' => 'qualiforma-demo',
        ],
        'json' => [
            'sessionId' => 'ses_e5f6a7b8-c9d0-4123-ef45-678901234567',
            'userId' => 'usr_a1b2c3d4-e5f6-7890-abcd-ef1234567890',
            'sendByEmail' => true,
        ],
    ]);
    $data = json_decode($response->getBody()->getContents(), true);
    var_dump($data);
} catch (GuzzleException $e) {
    fwrite(STDERR, $e->getMessage());
}
QualiForma

La plateforme de formation professionnelle certifiee Qualiopi.

Plateforme

  • Catalogue
  • Espace formateur
  • Étude de besoin

Societe

  • A propos
  • Contact

Ressources

  • Développeurs
  • Référence API
  • Webhooks

Legal

  • CGV
  • Mentions legales
  • Confidentialite

Catalogue par catégorie

  • Management
  • Digital
  • Communication
  • Langues
  • Sécurité
  • Gestion

Comparatifs

  • QualiForma vs Didask
  • QualiForma vs Edusign
  • QualiForma vs Klaxoon
  • QualiForma vs 360Learning

Glossaire Qualiopi

  • I01 — Conditions d'information
  • I05 — Adaptation des prestations
  • I11 — Évaluations en cours
  • I22 — Compétences des intervenants
  • I30 — Recueil des appréciations
  • Voir les 32 indicateurs →

© 2026 QualiForma. Tous droits reserves.

Certifie Qualiopi