Skilzy API for training organisations

The Skilzy REST API lets your organisation automatically pull your learners' data (activity, attendance, evaluations, documents, satisfaction) into your own tools: Airtable, spreadsheets, ETL, BI. Mostly read; a write operation also lets you enrol a learner (key with the “write” scope). Scoped to your organisation. These are the same figures as the PDF binders.

Quickstart

Three steps to your first call:

  1. Generate an API key from the “API & integrations” page in your dashboard (it starts with sk_live_).
  2. Make your first call, passing the key in the Authorization header:
    curl -H "Authorization: Bearer sk_live_xxx" \
      "https://skilzy.io/api/v1/learners?limit=100"
  3. You get a JSON response: rows are in data, with pagination in 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, … }
      ]
    }

Authentication

Every request must carry an organisation API key in the HTTP header. Create and manage your keys from the “API & integrations” page in your dashboard.

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx
  • The key is shown only once at creation; copy it right away.
  • A key only grants access to your organisation's data.
  • You can revoke a key at any time; it then returns a 401 error.
  • A key can have an expiry date (set at creation). After that date it returns 401 “API key expired”; generate a new one.
  • Never expose a key in a browser / public front-end.

Endpoints

Base URL: https://skilzy.io/api/v1

All resources are queried with GET:

EndpointContent
GET /api/v1/learnersPer-learner summary (1 row / learner)
GET /api/v1/usersOrg learners + assigned license (1 row / learner)
GET /api/v1/summaryCounters: learners + licenses by type (single object in data, not an array)
GET /api/v1/attendanceTutoring attendance (1 row / learner × session)
GET /api/v1/evaluationsQuiz attempts (1 row / attempt)
GET /api/v1/quiz-answersQuiz answers question by question (1 row / attempt × question)
GET /api/v1/documentsIssued PIF & certificates (1 row / document)
GET /api/v1/satisfactionSatisfaction surveys (1 row / survey)
GET /api/v1/enrollmentsProgress per learner × programme (1 row / enrolment)
GET /api/v1/progressDetailed progress (1 row / learner × programme × lesson)
GET /api/v1/loginsTimestamped logins (1 row / login)
GET /api/v1/tutoring-ratingsTutoring ratings (session + trainer) (1 row / rating)
GET /api/v1/lesson-ratingsMilestone / module-end lesson ratings (1 row / rating)

Query parameters

ParameterDescriptionDefault
formatOutput format: json or csvjson
yearFiltered calendar year (e.g. 2026)Current year
limitRows per page (max 1000, JSON)100
offsetPagination offset (JSON)0
updated_sinceOnly rows whose date ≥ value (YYYY-MM-DD). Incremental sync. Ignored for learners.
emailRestricts to ONE learner (exact email, case-insensitive). 404 if unknown in your organisation.
user_idLike email, but by the learner's technical identifier.
programmeRestricts to ONE programme (programme slug, e.g. marketing-contenus-rs7439). Ignored on resources without a programme (learners, logins).

Single-learner requests (email or user_id) are served through a direct path: they are not affected by the org-wide aggregation cap (meta.truncated) and return a meta.learner block.

# 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"

Response format

JSON responses contain metadata, pagination and rows in 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, … }
  ]
}

With ?format=csv, the response is a downloadable CSV file (UTF-8 with BOM, comma-separated), without pagination.

Exception: the summary resource returns a single object in data (not an array) and has no 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 }
    }
  }
}

Field reference

JSON keys are always in French regardless of language. Boolean values are returned as “Oui” / “Non”.

/api/v1/learners

FieldDescription
nomLearner name
emailEmail
invite_leInvitation date (YYYY-MM-DD)
inscrit_leEnrolment date
connexionsNumber of logins in the period
temps_actif_minTotal active time (minutes, deduplicated)
temps_cours_minOf which time on courses
temps_labia_minOf which AI Lab time
temps_tutorat_minOf which live tutoring time
programmes_suivisNumber of programmes followed
programmes_completesNumber of programmes 100% complete
evaluations_reussiesPassed evaluations
evaluations_totalEvaluations attempted (total)
score_moyenAverage score of passed evaluations (/100)
seances_tutoratTutoring sessions attended
seances_presentSessions with validated attendance
pif_emisNumber of PIFs issued
attestations_emisesNumber of certificates issued
satisfaction_noteLatest satisfaction rating (/5)
npsLatest NPS (/10)
See a request & response example
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

FieldDescription
nomLearner name
emailEmail
statutAccount status (actif / invité / suspendu)
licence_typeLicense type (standard / tutorat, empty if none)
licence_statutLicense status (disponible / attribuée / activée / révoquée)
inscrit_leEnrolment or invitation date (YYYY-MM-DD)
See a request & response example
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

FieldDescription
utilisateurs.totalTotal number of learners
utilisateurs.actifsActive learners
licences.standard.totalStandard licenses (total)
licences.standard.disponiblesAvailable standard licenses
licences.standard.utiliseesUsed standard licenses
licences.tutorat.totalTutoring licenses (total)
licences.tutorat.disponiblesAvailable tutoring licenses
licences.tutorat.utiliseesUsed tutoring licenses

/api/v1/attendance

FieldDescription
apprenantLearner name
emailEmail
seanceSession title
programmeLinked programme (title)
programme_slugStable programme identifier (join key)
dateStart date & time (YYYY-MM-DD HH:MM)
duree_prevue_minPlanned session duration (minutes)
presentAttendance validated (Oui / Non)
temps_connecte_minActual connected time (minutes)
signe_apprenantSigned by learner (Oui / Non)
signe_formateurSigned by trainer (Oui / Non)
See a request & response example
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

FieldDescription
apprenantLearner name
emailEmail
programmeProgramme (title)
programme_slugStable programme identifier (join key)
moduleModule
leconEvaluated lesson
tentativeAttempt number
scoreScore obtained (/100)
reussiAttempt passed (Oui / Non)
dateAttempt date (YYYY-MM-DD)
See a request & response example
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

FieldDescription
apprenantLearner name
emailEmail
programmeProgramme (title)
programme_slugStable programme identifier (join key)
moduleModule
leconLesson containing the quiz
tentativeAttempt number
score_tentativeAttempt score (/100)
reussiAttempt passed (Oui / Non)
questionQuestion wording
optionsOffered options, separated by “|”
reponse_choisieAnswer chosen by the learner
bonne_reponseExpected correct answer
correctAnswer correct (Oui / Non)
repondu_leAnswer timestamp (YYYY-MM-DD HH:MM)
dateAttempt date (YYYY-MM-DD)
See a request & response example
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

FieldDescription
apprenantLearner name
emailEmail
typeDocument type (PIF | Attestation)
programmeRelated programme (title)
programme_slugStable programme identifier (join key)
emis_leIssue date (YYYY-MM-DD)
heuresHours (PIF: estimated; certificate: measured)
score_completionCompletion score (certificates only)
See a request & response example
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

FieldDescription
apprenantLearner name
emailEmail
programmeEvaluated programme (title)
programme_slugStable programme identifier (join key)
noteSatisfaction rating (/5)
npsNet Promoter Score (/10)
verbatimFree-text learner comment
repondu_leResponse date (YYYY-MM-DD)
See a request & response example
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

FieldDescription
apprenantLearner name
emailEmail
programmeProgramme (title)
programme_slugStable programme identifier (join key)
pourcentageProgramme progress (%)
lecons_vuesLessons viewed
lecons_totalTotal lessons
completeProgramme completed (Oui / Non)
complete_leCompletion date (= certificate issuance, YYYY-MM-DD)
derniere_activite_leLast activity date on the programme (YYYY-MM-DD)
premiere_connexion_leLearner's first launch of the course, date & time (YYYY-MM-DD HH:MM)
derniere_connexion_leLast connection to the course, date & time (YYYY-MM-DD HH:MM)
See a request & response example
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

FieldDescription
apprenantLearner name
emailEmail
programmeProgramme (title)
programme_slugStable programme identifier (join key)
pourcentage_programmeOverall programme progress (%)
moduleModule
leconLesson
vueLesson viewed (Oui / Non)
visitesNumber of lesson visits
temps_minActive time on the lesson (minutes)
derniere_visite_leLast visit date (YYYY-MM-DD)
See a request & response example
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

FieldDescription
apprenantLearner name
emailEmail
debutLogin start (YYYY-MM-DD HH:MM)
finSession end (YYYY-MM-DD HH:MM)
duree_minSession duration (minutes)
ipLogin IP address
navigateurBrowser / device (user-agent)
See a request & response example
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

FieldDescription
apprenantLearner name
emailEmail
seanceTutoring session title
programmeLinked programme
programme_slugStable programme identifier (join key)
formateurRated trainer name
note_seanceSession rating (/5, empty for legacy imports)
note_formateurTrainer rating (/5)
commentaireFree-text comment
soumis_leRating date (YYYY-MM-DD)
See a request & response example
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

FieldDescription
apprenantLearner name
emailEmail
programmeProgramme (title)
programme_slugStable programme identifier (join key)
moduleModule
leconRated lesson (milestone / module-end)
noteLesson rating (/5)
commentaireFree-text comment
soumis_leRating date (YYYY-MM-DD)
See a request & response example
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"
}

Activity report PDF

The /api/v1/activity-report endpoint returns a learner's audit binder PDF (the same document as the dashboard: logins, per-lesson time, evaluations, attendance, documents). Binary application/pdf response.

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
ParameterDescription
email / user_idTarget learner (required, either one)
yearReport year (default: current year)
programmeLimits the report to one programme (slug, optional)

Enrol a learner

Beyond reading, the API can enrol a learner into your organisation. This operation requires a key with the “write” scope (tick “Allow writing” when creating the key). It assigns an available license from your 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"

Request body (JSON)

FieldDescription
emailLearner email (required)
programme_slugsArray of programme slugs to assign (optional; e.g. ["marketing-contenus-rs7439"])

Responses

CodeMeaning
201Enrolled (status active if the account already existed, pending otherwise, invitation sent)
403Read-only key (write scope required)
409No license available, or learner already a member
400Missing email, unknown programme, or email outside allowed domain

Bulk enrolment

Send an array of learners via the users field (max 50 per call). Processing is best-effort: each row succeeds or fails independently (already a member, out of licenses…), and the response returns a summary plus per-row results. Re-running the same batch is safe: already-enrolled learners are skipped without consuming another license.

Option atomic: true → the API pre-counts available licenses and rejects the whole batch (409) if there aren't enough, enrolling no one.

# 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

Instead of polling the API, you can receive important events in near real time (under a minute) on your server. Configure your endpoints (https URL + event selection) from the “API & integrations” page in your dashboard. A signing secret (whsec_…) is shown once at creation.

Available events

TypeTrigger
quiz.completedQuiz completed: score, pass status and question-by-question detail (answers)
course.startedA learner's first launch of a course (first connection to the course)
programme.completedProgramme completed: the end certificate has just been issued
learner.enrolledLearner enrolled in your organisation
learner.activatedThe learner activated their account (first app login)
satisfaction.submittedSatisfaction survey received (rating, NPS, verbatim)

Delivery format

Each event is POSTed as JSON to your URL, with three headers: X-Skilzy-Event (type), X-Skilzy-Delivery (stable delivery id, identical on every retry: use it to deduplicate) and X-Skilzy-Signature (HMAC signature).

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"
      }
    ]
  }
}

Verify the signature

The signature covers the timestamp and the raw body: v1 = HMAC-SHA256(secret, "t.body"). Recompute it server-side and compare (constant-time comparison), rejecting stale timestamps.

// 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))
}

Expected responses & retries

  • Respond with a 2xx code within 10 seconds (process asynchronously if needed).
  • On failure (other code, timeout), delivery is retried: 1 min, 10 min, 1 h, 6 h then 24 h. After 6 attempts it is abandoned (“dead” status in the log).
  • 50 consecutive failures automatically disable the endpoint (re-enable it from the dashboard).
  • Delivery order is not guaranteed: use the payload's created_at to order events.
  • The dashboard's “Test” button sends a signed ping event, visible in the delivery log.

Examples & integrations

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

Error codes

CodeMeaning
401Missing, invalid, revoked or expired API key
404Unknown resource (check the URL)
405Method not allowed (use GET)
429Too many requests; honour the Retry-After header
500Internal error; retry or contact support

Limits & guarantees

  • Limited writes: keys are read-only by default; only learner enrolment (POST /users) is possible, and only with a “write”-scoped key.
  • Scope: a key only sees and acts on its own organisation's learners.
  • Volume: up to 50 learners per (bulk) enrolment call; paginate your calls beyond that.
  • Rate: 120 requests per minute per key. Beyond that, a 429 response with a Retry-After header.
  • Consistency: figures exactly match the PDF binders.

OpenAPI & Postman

Import the API into your client (Postman, Insomnia, Bruno…) or generate a client from the OpenAPI 3.1 spec:

In Postman: Import → Link, and paste the URL https://skilzy.io/openapi.json (or the file). Then set the collection's apiKey variable to your sk_live_… key.