API Skilzy pour les organismes de formation

L'API REST Skilzy permet à ton organisme de récupérer automatiquement les données de tes apprenants (suivi, émargement, évaluations, documents, satisfaction) dans tes propres outils : Airtable, tableurs, ETL, BI. Principalement en lecture ; une opération d'écriture permet aussi d'inscrire un apprenant (clé avec scope « write »). Scopée à ton organisation. Ce sont les mêmes chiffres que les dossiers PDF.

Démarrage rapide

Trois étapes pour ton premier appel :

  1. Génère une clé API depuis la page « API & intégrations » de ton dashboard (elle commence par sk_live_).
  2. Fais ton premier appel en passant la clé dans l'en-tête Authorization :
    curl -H "Authorization: Bearer sk_live_xxx" \
      "https://skilzy.io/api/v1/learners?limit=100"
  3. Tu reçois une réponse JSON : les lignes sont dans data, avec la pagination dans pagination.
    {
      "resource": "learners",
      "meta": { "org_name": "Mon OF", "year": 2026, "generated_at": "..." },
      "pagination": { "limit": 100, "offset": 0, "count": 42, "total": 42, "has_more": false },
      "data": [
        { "nom": "Jean Dupont", "email": "jean@…", "temps_actif_min": 184, "evaluations_reussies": 12, … }
      ]
    }

Authentification

Chaque requête doit porter une clé API d'organisme dans l'en-tête HTTP. Génère et gère tes clés depuis la page « API & intégrations » de ton dashboard.

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx
  • La clé n'est affichée qu'une seule fois à sa création ; copie-la immédiatement.
  • Une clé ne donne accès qu'aux données de ton organisation.
  • Tu peux révoquer une clé à tout moment ; elle renvoie alors une erreur 401.
  • Une clé peut avoir une date d'expiration (définie à sa création). Passé cette date, elle renvoie 401 « Clé API expirée » ; génère-en une nouvelle.
  • Ne partage jamais une clé côté navigateur / front-end public.

Endpoints

URL de base : https://skilzy.io/api/v1

Toutes les ressources s'interrogent en GET :

EndpointContenu
GET /api/v1/learnersSynthèse par apprenant (1 ligne / apprenant)
GET /api/v1/usersApprenants de l'org + licence associée (1 ligne / apprenant)
GET /api/v1/summaryCompteurs : apprenants + licences par type (objet unique dans data, pas un tableau)
GET /api/v1/attendanceÉmargement tutorat (1 ligne / apprenant × séance)
GET /api/v1/evaluationsTentatives de quiz (1 ligne / tentative)
GET /api/v1/quiz-answersRéponses de quiz question par question (1 ligne / tentative × question)
GET /api/v1/documentsPIF & attestations émis (1 ligne / document)
GET /api/v1/satisfactionEnquêtes de satisfaction (1 ligne / enquête)
GET /api/v1/enrollmentsAvancement par apprenant × programme (1 ligne / inscription)
GET /api/v1/progressProgression détaillée (1 ligne / apprenant × programme × leçon)
GET /api/v1/loginsConnexions horodatées (1 ligne / connexion)
GET /api/v1/tutoring-ratingsNotes de tutorat (séance + formateur) (1 ligne / note)
GET /api/v1/lesson-ratingsNotes de leçon milestone / fin de module (1 ligne / note)

Paramètres de requête

ParamètreDescriptionDéfaut
formatFormat de sortie : json ou csvjson
yearAnnée civile filtrée (ex. 2026)Année en cours
limitNombre de lignes par page (max 1000, JSON)100
offsetDécalage de pagination (JSON)0
updated_sinceNe renvoie que les lignes dont la date ≥ valeur (YYYY-MM-DD). Sync incrémentale. Ignoré pour learners.
emailRestreint à UN apprenant (email exact, insensible à la casse). 404 si inconnu dans ton organisation.
user_idComme email, mais par identifiant technique de l'apprenant.
programmeRestreint à UNE formation (slug de programme, ex. marketing-contenus-rs7439). Ignoré sur les ressources sans programme (learners, logins).

Les requêtes mono-apprenant (email ou user_id) sont servies par un chemin direct : elles ne sont pas concernées par le plafond d'agrégation org-wide (meta.truncated) et renvoient un bloc meta.learner.

# Un seul apprenant (toutes les ressources acceptent ?email= ou ?user_id=)
curl -H "Authorization: Bearer sk_live_xxx" \
  "https://skilzy.io/api/v1/learners?email=jean@orga.fr"

# Son avancement sur UNE formation (première/dernière connexion incluses)
curl -H "Authorization: Bearer sk_live_xxx" \
  "https://skilzy.io/api/v1/enrollments?email=jean@orga.fr&programme=marketing-contenus-rs7439"

# Ses réponses de quiz, question par question
curl -H "Authorization: Bearer sk_live_xxx" \
  "https://skilzy.io/api/v1/quiz-answers?email=jean@orga.fr"

Format de réponse

Les réponses JSON contiennent les métadonnées, la pagination et les lignes dans data :

{
  "resource": "learners",
  "meta": { "org_name": "Mon OF", "year": 2026, "generated_at": "..." },
  "pagination": { "limit": 100, "offset": 0, "count": 42, "total": 42, "has_more": false },
  "data": [
    { "nom": "Jean Dupont", "email": "jean@…", "temps_actif_min": 184, "evaluations_reussies": 12, … }
  ]
}

Avec ?format=csv, la réponse est un fichier CSV (UTF-8 avec BOM, séparateur virgule) téléchargeable, sans pagination.

Exception : la ressource summary renvoie un objet unique dans data (pas un tableau) et n'a pas de pagination :

{
  "resource": "summary",
  "meta": { "org_name": "Mon OF", "generated_at": "2026-06-10T09:00:00.000Z" },
  "data": {
    "utilisateurs": { "total": 38, "actifs": 31 },
    "licences": {
      "standard": { "total": 50, "disponibles": 12, "utilisees": 38 },
      "tutorat":  { "total": 10, "disponibles": 4,  "utilisees": 6 }
    }
  }
}

Référence des champs

Les clés JSON sont identiques en français quelle que soit la langue. Les valeurs booléennes sont renvoyées « Oui » / « Non ».

/api/v1/learners

ChampDescription
nomNom de l'apprenant
emailEmail
invite_leDate d'invitation (YYYY-MM-DD)
inscrit_leDate d'inscription
connexionsNombre de connexions sur la période
temps_actif_minTemps actif total (minutes, sans double comptage)
temps_cours_minDont temps sur les cours
temps_labia_minDont temps Lab IA
temps_tutorat_minDont temps en tutorat live
programmes_suivisNombre de programmes suivis
programmes_completesNombre de programmes complétés à 100 %
evaluations_reussiesÉvaluations réussies
evaluations_totalÉvaluations tentées (total)
score_moyenScore moyen des évaluations réussies (/100)
seances_tutoratSéances de tutorat suivies
seances_presentSéances où la présence est validée
pif_emisNombre de PIF émis
attestations_emisesNombre d'attestations émises
satisfaction_noteDernière note de satisfaction (/5)
npsDernier NPS (/10)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/learners"

# data[0] :
{
  "nom": "Jean Dupont",
  "email": "jean@orga.fr",
  "invite_le": "2026-01-05",
  "inscrit_le": "2026-01-06",
  "connexions": 46,
  "temps_actif_min": 184,
  "temps_cours_min": 150,
  "temps_labia_min": 22,
  "temps_tutorat_min": 12,
  "programmes_suivis": 1,
  "programmes_completes": 1,
  "evaluations_reussies": 12,
  "evaluations_total": 14,
  "score_moyen": 87,
  "seances_tutorat": 2,
  "seances_present": 2,
  "pif_emis": 1,
  "attestations_emises": 1,
  "satisfaction_note": 5,
  "nps": 9
}

/api/v1/users

ChampDescription
nomNom de l'apprenant
emailEmail
statutStatut du compte (actif / invité / suspendu)
licence_typeType de licence (standard / tutorat, vide si aucune)
licence_statutStatut de la licence (disponible / attribuée / activée / révoquée)
inscrit_leDate d'inscription ou d'invitation (YYYY-MM-DD)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/users"

# data[0] :
{
  "nom": "Jean Dupont",
  "email": "jean@orga.fr",
  "statut": "actif",
  "licence_type": "standard",
  "licence_statut": "activée",
  "inscrit_le": "2026-01-06"
}

/api/v1/summary

ChampDescription
utilisateurs.totalNombre total d'apprenants
utilisateurs.actifsApprenants au statut actif
licences.standard.totalLicences standard (total)
licences.standard.disponiblesLicences standard disponibles
licences.standard.utiliseesLicences standard utilisées
licences.tutorat.totalLicences tutorat (total)
licences.tutorat.disponiblesLicences tutorat disponibles
licences.tutorat.utiliseesLicences tutorat utilisées

/api/v1/attendance

ChampDescription
apprenantNom de l'apprenant
emailEmail
seanceTitre de la séance
programmeProgramme rattaché (titre)
programme_slugIdentifiant stable du programme (clé de jointure)
dateDate et heure de début (YYYY-MM-DD HH:MM)
duree_prevue_minDurée prévue de la séance (minutes)
presentPrésence validée (Oui / Non)
temps_connecte_minTemps réellement connecté (minutes)
signe_apprenantÉmargement signé par l'apprenant (Oui / Non)
signe_formateurÉmargement signé par le formateur (Oui / Non)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/attendance"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "seance": "Tutorat : bien démarrer",
  "programme": "Marketing de contenus IA : Préparation RS7439",
  "programme_slug": "marketing-contenus-rs7439",
  "date": "2026-03-12 14:00",
  "duree_prevue_min": 60,
  "present": "Oui",
  "temps_connecte_min": 58,
  "signe_apprenant": "Oui",
  "signe_formateur": "Oui"
}

/api/v1/evaluations

ChampDescription
apprenantNom de l'apprenant
emailEmail
programmeProgramme (titre)
programme_slugIdentifiant stable du programme (clé de jointure)
moduleModule
leconLeçon évaluée
tentativeNuméro de la tentative
scoreScore obtenu (/100)
reussiTentative réussie (Oui / Non)
dateDate de la tentative (YYYY-MM-DD)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/evaluations"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "programme": "Marketing de contenus IA : Préparation RS7439",
  "programme_slug": "marketing-contenus-rs7439",
  "module": "La méthode RMBC",
  "lecon": "Pourquoi RMBC",
  "tentative": 2,
  "score": 85,
  "reussi": "Oui",
  "date": "2026-03-15"
}

/api/v1/quiz-answers

ChampDescription
apprenantNom de l'apprenant
emailEmail
programmeProgramme (titre)
programme_slugIdentifiant stable du programme (clé de jointure)
moduleModule
leconLeçon contenant le quiz
tentativeNuméro de la tentative
score_tentativeScore de la tentative (/100)
reussiTentative réussie (Oui / Non)
questionIntitulé de la question posée
optionsOptions proposées, séparées par « | »
reponse_choisieRéponse choisie par l'apprenant
bonne_reponseBonne réponse attendue
correctRéponse correcte (Oui / Non)
repondu_leHorodatage de la réponse (YYYY-MM-DD HH:MM)
dateDate de la tentative (YYYY-MM-DD)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/quiz-answers"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "programme": "Marketing de contenus IA : Préparation RS7439",
  "programme_slug": "marketing-contenus-rs7439",
  "module": "La méthode RMBC",
  "lecon": "Pourquoi RMBC",
  "tentative": 2,
  "score_tentative": 85,
  "reussi": "Oui",
  "question": "Que garantit la méthode RMBC ?",
  "options": "Un angle clair | Des vues immédiates | Un script long",
  "reponse_choisie": "Un angle clair",
  "bonne_reponse": "Un angle clair",
  "correct": "Oui",
  "repondu_le": "2026-03-15 17:38",
  "date": "2026-03-15"
}

/api/v1/documents

ChampDescription
apprenantNom de l'apprenant
emailEmail
typeType de document (PIF | Attestation)
programmeProgramme concerné (titre)
programme_slugIdentifiant stable du programme (clé de jointure)
emis_leDate d'émission (YYYY-MM-DD)
heuresVolume horaire (PIF : estimé ; attestation : mesuré)
score_completionScore de complétion (attestations uniquement)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/documents"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "type": "Attestation",
  "programme": "Marketing de contenus IA : Préparation RS7439",
  "programme_slug": "marketing-contenus-rs7439",
  "emis_le": "2026-04-02",
  "heures": 21,
  "score_completion": 100
}

/api/v1/satisfaction

ChampDescription
apprenantNom de l'apprenant
emailEmail
programmeProgramme évalué (titre)
programme_slugIdentifiant stable du programme (clé de jointure)
noteNote de satisfaction (/5)
npsNet Promoter Score (/10)
verbatimCommentaire libre de l'apprenant
repondu_leDate de réponse (YYYY-MM-DD)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/satisfaction"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "programme": "Marketing de contenus IA : Préparation RS7439",
  "programme_slug": "marketing-contenus-rs7439",
  "note": 5,
  "nps": 9,
  "verbatim": "Formation très concrète, je recommande.",
  "repondu_le": "2026-04-02"
}

/api/v1/enrollments

ChampDescription
apprenantNom de l'apprenant
emailEmail
programmeProgramme (titre)
programme_slugIdentifiant stable du programme (clé de jointure)
pourcentageAvancement du programme (%)
lecons_vuesNombre de leçons vues
lecons_totalNombre total de leçons
completeProgramme complété (Oui / Non)
complete_leDate de complétion (= émission de l'attestation, YYYY-MM-DD)
derniere_activite_leDate de dernière activité sur le programme (YYYY-MM-DD)
premiere_connexion_lePremier lancement du cours par l'apprenant, date et heure (YYYY-MM-DD HH:MM)
derniere_connexion_leDernière connexion au cours, date et heure (YYYY-MM-DD HH:MM)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/enrollments"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "programme": "Marketing de contenus IA : Préparation RS7439",
  "programme_slug": "marketing-contenus-rs7439",
  "pourcentage": 100,
  "lecons_vues": 48,
  "lecons_total": 48,
  "complete": "Oui",
  "complete_le": "2026-04-02",
  "derniere_activite_le": "2026-04-01",
  "premiere_connexion_le": "2026-01-08 09:12",
  "derniere_connexion_le": "2026-04-01 18:47"
}

/api/v1/progress

ChampDescription
apprenantNom de l'apprenant
emailEmail
programmeProgramme (titre)
programme_slugIdentifiant stable du programme (clé de jointure)
pourcentage_programmeAvancement global du programme (%)
moduleModule
leconLeçon
vueLeçon vue (Oui / Non)
visitesNombre de visites de la leçon
temps_minTemps actif sur la leçon (minutes)
derniere_visite_leDate de dernière visite (YYYY-MM-DD)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/progress"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "programme": "Marketing de contenus IA : Préparation RS7439",
  "programme_slug": "marketing-contenus-rs7439",
  "pourcentage_programme": 100,
  "module": "La méthode RMBC",
  "lecon": "Pourquoi RMBC",
  "vue": "Oui",
  "visites": 3,
  "temps_min": 14,
  "derniere_visite_le": "2026-03-15"
}

/api/v1/logins

ChampDescription
apprenantNom de l'apprenant
emailEmail
debutDébut de connexion (YYYY-MM-DD HH:MM)
finFin de session (YYYY-MM-DD HH:MM)
duree_minDurée de la session (minutes)
ipAdresse IP de connexion
navigateurNavigateur / appareil (user-agent)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/logins"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "debut": "2026-03-15 17:02",
  "fin": "2026-03-15 17:58",
  "duree_min": 56,
  "ip": "92.184.100.10",
  "navigateur": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)…"
}

/api/v1/tutoring-ratings

ChampDescription
apprenantNom de l'apprenant
emailEmail
seanceTitre de la séance de tutorat
programmeProgramme rattaché
programme_slugIdentifiant stable du programme (clé de jointure)
formateurNom du formateur noté
note_seanceNote de la séance (/5, vide pour imports legacy)
note_formateurNote du formateur (/5)
commentaireCommentaire libre
soumis_leDate de la note (YYYY-MM-DD)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/tutoring-ratings"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "seance": "Tutorat : bien démarrer",
  "programme": "Marketing de contenus IA : Préparation RS7439",
  "programme_slug": "marketing-contenus-rs7439",
  "formateur": "Camille",
  "note_seance": 5,
  "note_formateur": 5,
  "commentaire": "Très clair.",
  "soumis_le": "2026-03-12"
}

/api/v1/lesson-ratings

ChampDescription
apprenantNom de l'apprenant
emailEmail
programmeProgramme (titre)
programme_slugIdentifiant stable du programme (clé de jointure)
moduleModule
leconLeçon notée (milestone / fin de module)
noteNote de la leçon (/5)
commentaireCommentaire libre
soumis_leDate de la note (YYYY-MM-DD)
Voir un exemple d'appel et de réponse
curl -H "Authorization: Bearer sk_live_xxx" "https://skilzy.io/api/v1/lesson-ratings"

# data[0] :
{
  "apprenant": "Jean Dupont",
  "email": "jean@orga.fr",
  "programme": "Marketing de contenus IA : Préparation RS7439",
  "programme_slug": "marketing-contenus-rs7439",
  "module": "La méthode RMBC",
  "lecon": "Bilan du module",
  "note": 5,
  "commentaire": "Super module.",
  "soumis_le": "2026-03-16"
}

Rapport d'activité PDF

L'endpoint /api/v1/activity-report renvoie le dossier d'audit PDF d'un apprenant (le même document que le dashboard : connexions, temps par leçon, évaluations, émargement, documents). Réponse binaire application/pdf.

curl -H "Authorization: Bearer sk_live_xxx" \
  -o rapport-jean.pdf \
  "https://skilzy.io/api/v1/activity-report?email=jean@orga.fr&year=2026"

# Optionnel : limiter le rapport à une formation
#   &programme=marketing-contenus-rs7439
ParamètreDescription
email / user_idL'apprenant ciblé (requis, l'un des deux)
yearAnnée du rapport (défaut : année en cours)
programmeLimite le rapport à une formation (slug, optionnel)

Inscrire un apprenant

En plus de la lecture, l'API permet d'inscrire un apprenant dans ton organisation. Cette opération est réservée aux clés disposant du scope « write » (à cocher « Autoriser l'écriture » à la création de la clé). Elle attribue une licence disponible de ton organisation.

curl -X POST \
  -H "Authorization: Bearer sk_live_write_xxx" \
  -H "Content-Type: application/json" \
  -d '{"email":"apprenant@orga.fr","programme_slugs":["marketing-contenus-rs7439"]}' \
  "https://skilzy.io/api/v1/users"

Corps de la requête (JSON)

ChampDescription
emailEmail de l'apprenant (requis)
programme_slugsTableau de slugs de programmes à attribuer (optionnel ; ex. ["marketing-contenus-rs7439"])

Réponses

CodeSignification
201Inscrit (status active si le compte existait déjà, pending sinon, invitation envoyée)
403Clé en lecture seule (scope write requis)
409Aucune licence disponible, ou apprenant déjà membre
400Email manquant, programme inconnu, ou email hors domaine autorisé

Inscription en lot (bulk)

Envoie un tableau d'apprenants via le champ users (max 50 par appel). Le traitement est best-effort : chaque ligne réussit ou échoue indépendamment (déjà membre, plus de licence…), et la réponse renvoie un récapitulatif summary + un détail results ligne par ligne. Réexécuter le même lot est sans danger : les apprenants déjà inscrits sont ignorés sans reconsommer de licence.

Option atomic: true → l'API pré-compte les licences disponibles et rejette tout le lot (409) si elles sont insuffisantes, sans inscrire personne.

# Inscription en lot (max 50 / appel)
curl -X POST \
  -H "Authorization: Bearer sk_live_write_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "users": [
      { "email": "a@orga.fr", "programme_slugs": ["marketing-contenus-rs7439"] },
      { "email": "b@orga.fr" }
    ],
    "atomic": false
  }' \
  "https://skilzy.io/api/v1/users"

# Réponse : { "summary": { "requested": 2, "enrolled": 2, "failed": 0 },
#            "results": [ { "email": "a@orga.fr", "status": "active", "member_id": "…" }, … ] }

Webhooks

Plutôt que d'interroger l'API en boucle, tu peux recevoir les événements importants en temps quasi réel (moins d'une minute) sur ton serveur. Configure tes endpoints (URL https + choix des événements) depuis la page « API & intégrations » de ton dashboard. Un secret de signature (whsec_…) est affiché une seule fois à la création.

Événements disponibles

TypeDéclencheur
quiz.completedQuiz terminé : score, réussite et détail question par question (answers)
course.startedPremier lancement d'une formation par un apprenant (première connexion au cours)
programme.completedFormation complétée : l'attestation de fin vient d'être émise
learner.enrolledApprenant inscrit dans ton organisation
learner.activatedL'apprenant a activé son compte (première connexion à l'app)
satisfaction.submittedEnquête de satisfaction reçue (note, NPS, verbatim)

Format de livraison

Chaque événement est POSTé en JSON sur ton URL, avec trois en-têtes : X-Skilzy-Event (type), X-Skilzy-Delivery (id stable de la livraison, identique à chaque nouvel essai : sers-t'en pour dédupliquer) et X-Skilzy-Signature (signature HMAC).

POST https://ton-serveur.fr/webhooks/skilzy
Content-Type: application/json
X-Skilzy-Event: quiz.completed
X-Skilzy-Delivery: 5f0c…            # id stable, identique à chaque retry
X-Skilzy-Signature: t=1751980800,v1=a1b2c3…

{
  "id": "5f0c…",
  "type": "quiz.completed",
  "created_at": "2026-07-09T14:32:11.000Z",
  "data": {
    "learner": { "user_id": "…", "email": "jean@orga.fr", "name": "Jean Dupont" },
    "programme": { "slug": "marketing-contenus-rs7439", "title": "Marketing & contenus…" },
    "lesson": { "key": "…", "title": "…", "module_title": "…", "course_id": "…" },
    "attempt_number": 1,
    "score": 85,
    "passed": true,
    "completed_at": "2026-07-09T14:32:08.000Z",
    "answers": [
      {
        "question": "Que fait la méthode RMBC ?",
        "options": ["…", "…", "…"],
        "chosen_label": "…",
        "correct_label": "…",
        "is_correct": true,
        "attempt_no": 1,
        "answered_at": "2026-07-09T14:31:52.000Z"
      }
    ]
  }
}

Vérifier la signature

La signature couvre l'horodatage et le corps brut : v1 = HMAC-SHA256(secret, "t.corps"). Recalcule-la côté serveur et compare (comparaison en temps constant), en rejetant les horodatages trop anciens.

// Node / Express : vérifier X-Skilzy-Signature
import crypto from 'node:crypto'

function verifySkilzySignature(req, secret) {
  const header = req.get('X-Skilzy-Signature') || ''      // t=1751980800,v1=abc…
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  if (!t || !v1) return false

  // Anti-rejeu : refuse les livraisons de plus de 5 minutes.
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false

  const expected = crypto
    .createHmac('sha256', secret)                          // secret whsec_…
    .update(`${t}.${req.rawBody}`)                        // corps BRUT, pas re-sérialisé
    .digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
}

Réponses attendues & relances

  • Réponds un code 2xx en moins de 10 secondes (traite en asynchrone si besoin).
  • En cas d'échec (autre code, timeout), la livraison est retentée : 1 min, 10 min, 1 h, 6 h puis 24 h. Après 6 tentatives, elle est abandonnée (statut « dead » dans le journal).
  • 50 échecs consécutifs désactivent automatiquement l'endpoint (réactivable depuis le dashboard).
  • L'ordre de livraison n'est pas garanti : utilise created_at du payload pour ordonner.
  • Le bouton « Tester » du dashboard envoie un événement ping signé, visible dans le journal des livraisons.

Exemples & intégrations

curl -H "Authorization: Bearer sk_live_xxx" \
  "https://skilzy.io/api/v1/learners?limit=100"

Codes d'erreur

CodeSignification
401Clé API manquante, invalide, révoquée ou expirée
404Ressource inconnue (vérifie l'URL)
405Méthode non autorisée (utilise GET)
429Trop de requêtes ; respecte l'en-tête Retry-After
500Erreur interne ; réessaie ou contacte le support

Limites & garanties

  • Écriture limitée : par défaut les clés sont en lecture seule ; seule l'inscription d'apprenant (POST /users) est possible, et uniquement avec une clé au scope « write ».
  • Périmètre : une clé ne voit et n'agit que sur les apprenants de son organisation.
  • Volume : jusqu'à 50 apprenants par appel en inscription (bulk) ; pagine tes appels au-delà.
  • Débit : 120 requêtes par minute et par clé. Au-delà, réponse 429 avec en-tête Retry-After.
  • Cohérence : les chiffres correspondent exactement aux dossiers PDF.

OpenAPI & Postman

Importe l'API dans ton client (Postman, Insomnia, Bruno…) ou génère un client depuis la spec OpenAPI 3.1 :

Dans Postman : Import → Link, et colle l'URL https://skilzy.io/openapi.json (ou le fichier). Renseigne ensuite la variable apiKey de la collection avec ta clé sk_live_….