Aller au contenu
API publique · gratuite

Documentation de l'API YA-Planning

Intégrez la génération et la consultation d'emplois du temps, gérez la configuration de vos établissements et recevez des événements en temps réel. Référence complète, exemples et bac à sable.

Introduction

L'API est REST, en JSON, versionnée. Toutes les ressources métier vivent sous le préfixe /api/v1/pub/v1. Les endpoints de démonstration publics sont sous /api/v1/public/api-demo.

  • Base URL : https://votre-domaine/api/v1/pub/v1
  • Format : requêtes et réponses en application/json (sauf exports ICS/PDF).
  • Versionnage : la version est dans le chemin (/v1) ; les ajouts rétrocompatibles ne changent pas la version.
  • CORS : l'API publique est ouverte aux applications navigateur tierces (sans cookies).

Authentification

Chaque requête porte une clé API, dans l'un des deux en-têtes :

X-API-Key: yap_live_xxxxxxxxxxxxxxxxxxxx
# ou
Authorization: Bearer yap_live_xxxxxxxxxxxxxxxxxxxx

Une clé peut être autorisée pour plusieurs établissements (clé entreprise). Précisez alors la cible via l'en-tête X-Etablissement-Id ; s'il n'y a qu'un seul établissement autorisé, il est déduit automatiquement. Les clés sont créées, à scopes, dans la console.

Environnements & bac à sable

Deux environnements, distingués par le préfixe de la clé :

  • yap_live_… — production : vos données réelles ; la génération décompte le quota hebdomadaire.
  • yap_test_… — sandbox : les lectures renvoient un jeu de données de démonstration (jamais vos vraies données), la génération n'est pas décomptée, et l'écriture de configuration est refusée (SANDBOX_READONLY). Idéal pour développer sans risque.

Scopes

Chaque clé porte un sous-ensemble de scopes ; un endpoint refuse (403 INSUFFICIENT_SCOPE) si le scope requis manque.

config:readLire cycles, classes, matières, professeurs, créneaux, volumes horaires.
config:writeCréer / modifier / supprimer la configuration de l'établissement.
timetables:readLister les jobs, lire les résultats et les emplois du temps publiés, exporter.
timetables:writeLancer une génération et publier un emploi du temps.
webhooks:manageCréer, lister et supprimer des endpoints webhook.
establishments:manageCréer et lister ses propres établissements (niveau compte, sans X-Etablissement-Id).

Débit & quotas

Chaque clé a une limite de débit (par défaut 60 req/min). Les réponses portent :

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1783919311   # epoch (s) de réinitialisation

Un dépassement renvoie 429 RATE_LIMITED avec l'en-tête Retry-After. La génération d'emploi du temps est en plus soumise au quota hebdomadaire du forfait (visible via /me).

Toute réponse de l'API porte aussi un en-tête X-Request-Id (identifiant de corrélation) — communiquez-le au support en cas d'incident. Note : le comptage de débit est actuellement appliqué par instance ; sur un déploiement multi-instances la limite effective peut être un multiple de la valeur affichée.

Erreurs

Enveloppe d'erreur uniforme, avec un requestId pour le support :

{ "error": "INSUFFICIENT_SCOPE", "message": "Scope requis : config:write.",
  "requestId": "6181762d-…", "requiredScope": "config:write" }
CodeerrorCause
400BAD_REQUESTParamètres manquants ou invalides.
401UNAUTHORIZED / INVALID_API_KEYClé absente, invalide ou révoquée.
403INSUFFICIENT_SCOPELa clé ne possède pas le scope requis.
403ETABLISSEMENT_NOT_GRANTEDLa clé n'est pas autorisée pour l'établissement demandé.
403SANDBOX_READONLYÉcriture interdite avec une clé sandbox (lecture seule).
402PLAN_UPGRADE_REQUIREDFonction réservée à un forfait payant (ex. export iCal).
404NOT_FOUNDRessource inexistante ou hors de votre établissement.
409WORKFLOW_ERRORTransition de workflow impossible (ex. publier un brouillon).
429RATE_LIMITEDDébit dépassé — réessayez après l'en-tête Retry-After.

Tester en direct — sans inscription

Endpoints de démonstration publics (aucune clé requise).

Télécharger OpenAPI

Postman : « Import » → « Link » → collez /q/openapi?format=json.

Référence — Compte & introspection

GET/me

Détails de la clé courante : environnement, établissement cible, scopes accordés et quota de génération restant. Aucun scope requis.

Réponse
{
  "environment": "LIVE",
  "etablissementId": "61a9…8cb",
  "scopes": ["config:read", "timetables:write"],
  "entitlements": {
    "plan": "PRO",
    "generationsRestantes": -1,
    "resetLe": "2026-07-14T00:00:00Z",
    "maxClassesExport": -1
  }
}
GET/me/etablissement

Profil public de l'établissement cible : nom, sigle, ville, pays, type, site web. Aucun scope requis.

Réponse
{"id":"…","nom":"Groupe Scolaire Les Palmiers","slug":"gs-palmiers","sigle":"GSP","ville":"Abidjan","pays":"CI","type":"PRIVE","siteWeb":"https://…"}
GET/me/usage

Usage récent de la clé courante (appels et erreurs par jour). Aucun scope requis.

Paramètres :
  • days — fenêtre en jours (défaut 30, max 90)
Réponse
{"apiKeyId":"…","days":30,"daily":[{"day":"2026-07-12","calls":128,"errors":3}]}

Référence — Compte — établissements

Provisioning niveau-compte : créez vos propres établissements SANS en-tête X-Etablissement-Id (le chemin /account n'est pas lié à un établissement). Scope establishments:manage. À la création, la clé appelante reçoit automatiquement un grant ACTIVE sur le nouvel établissement — utilisez ensuite X-Etablissement-Id pour le configurer et générer.

POST/account/etablissementsestablishments:manage

Créer un établissement (bucket développeur, soumis au plafond de votre compte).

Corps
{"nom":"Groupe Scolaire Les Palmiers","type":"PRIVE_LAIC","pays":"CI"}
Réponse
{"id":"…","nom":"Groupe Scolaire Les Palmiers","slug":"gs-les-palmiers","pays":"CI","type":"PRIVE_LAIC"}
GET/account/etablissementsestablishments:manage

Lister les établissements que vous avez créés.

Réponse
[{"id":"…","nom":"…","slug":"…","pays":"CI","type":"PRIVE_LAIC"}]

Référence — Configuration

Structure pédagogique de l'établissement. Lecture : scope config:read. Écriture : scope config:write (interdite en sandbox). Un établissement fraîchement créé contient déjà un « Cycle principal ».

GET/cyclesconfig:read

Liste des cycles.

Réponse
[{"id":"…","libelle":"Cycle principal","ordre":0}]
POST/cyclesconfig:write

Créer un cycle.

Corps
{"libelle":"Second cycle","ordre":1}
DELETE/cycles/{id}config:write

Supprimer un cycle.

GET/niveauxconfig:read

Liste des classes/niveaux.

Réponse
[{"id":"…","libelle":"6ème 1","cycleId":"…","effectif":42,"ordre":1}]
POST/niveauxconfig:write

Créer une classe.

Corps
{"libelle":"6ème 3","cycleId":"<uuid-cycle>","effectif":40,"ordre":3}
PUT/niveaux/{id}config:write

Modifier une classe (champs partiels).

DELETE/niveaux/{id}config:write

Supprimer une classe.

GET/disciplinesconfig:read

Liste des matières.

Réponse
[{"id":"…","libelle":"Mathématiques","code":"MATH","couleur":"#6C5CE7","ordre":1}]
POST/disciplinesconfig:write

Créer une matière.

Corps
{"libelle":"Physique-Chimie","code":"PC","couleur":"#00B894","ordre":5}
PUT/disciplines/{id}config:write

Modifier une matière.

DELETE/disciplines/{id}config:write

Supprimer une matière.

GET/professeursconfig:read

Liste des professeurs.

Réponse
[{"id":"…","nom":"Koné","prenoms":"Awa","matricule":"P-0142","email":"…","telephone":"…","nombreHeureAutorise":18,"statut":"TITULAIRE","actif":true}]
POST/professeursconfig:write

Créer un professeur.

Corps
{"nom":"Diarra","prenoms":"Sékou","matricule":"P-0210","email":"s.diarra@ecole.ci","nombreHeureAutorise":18,"statut":"VACATAIRE","actif":true}
PUT/professeurs/{id}config:write

Modifier un professeur.

DELETE/professeurs/{id}config:write

Supprimer un professeur.

GET/horairesconfig:read

Créneaux horaires de la grille.

Réponse
[{"id":"…","jour":"MONDAY","heureDebut":"08:00","heureFin":"09:00","estPause":false,"libelle":null}]
POST/horairesconfig:write

Créer un créneau (jour, HH:mm).

Corps
{"jour":"MONDAY","heureDebut":"08:00","heureFin":"09:00","estPause":false,"libelle":"M1"}
DELETE/horaires/{id}config:write

Supprimer un créneau.

GET/volumesconfig:read

Volumes horaires (matière × cycle).

Réponse
[{"id":"…","cycleId":"…","disciplineId":"…","nombreHeuresObligatoire":4,"minHeuresConsecutives":1,"maxHeuresConsecutives":2}]
POST/volumesconfig:write

Créer un volume horaire.

Corps
{"cycleId":"<uuid>","disciplineId":"<uuid>","nombreHeuresObligatoire":4,"minHeuresConsecutives":1,"maxHeuresConsecutives":2}
DELETE/volumes/{id}config:write

Supprimer un volume horaire.

Référence — Emplois du temps

Génération asynchrone (le solveur tourne en tâche de fond — interrogez le statut), publication et consultation. En sandbox, la génération utilise un jeu de démonstration et n'est pas décomptée du quota.

POST/timetables/generatetimetables:write

Lance une génération. Renvoie 202 + un jobId à interroger. LIVE décompte le quota hebdomadaire. En-tête optionnel Idempotency-Key : un même identifiant rejoue la réponse mémorisée (réponse marquée Idempotency-Replayed: true) au lieu de relancer une génération.

Paramètres :
  • Idempotency-Key — en-tête (optionnel) — clé d'idempotence côté LIVE
Corps
{"durationMinutes": 5}
Réponse
{"jobId":"…","status":"SOLVING_SCHEDULED"}
GET/timetables/jobstimetables:read

Liste paginée des jobs.

Paramètres :
  • page — index de page (défaut 0)
  • size — taille (défaut 25, max 100)
Réponse
{"items":[{"jobId":"…","name":"…","status":"SOLVING_ENDED","workflowStatus":"BROUILLON","feasible":true,"hardScore":0,"softScore":-42,"createdAt":"…"}],"total":7,"page":0,"size":25}
GET/timetables/jobs/{jobId}timetables:read

Statut d'un job.

GET/timetables/jobs/{jobId}/resulttimetables:read

Résultat : liste des leçons (une fois status = SOLVING_ENDED).

Réponse
{"jobId":"…","status":"SOLVING_ENDED","feasible":true,"lessons":[{"id":"1","matiere":"Maths","niveau":"6ème 1","professeur":"M. Koné","jour":"MONDAY","debut":"08:00:00","fin":"09:00:00","couleur":"#6C5CE7","niveauOrdre":1,"verrouille":false}]}
POST/timetables/jobs/{jobId}/publishtimetables:write

Publie l'emploi du temps (workflow → PUBLIE). Déclenche l'événement webhook timetable.published.

GET/timetables/published/latesttimetables:read

Dernier emploi du temps publié de l'établissement.

GET/timetables/published/{jobId}/classestimetables:read

Libellés des classes présentes dans un emploi du temps publié.

Réponse
{"classes":["6ème 1","6ème 2","5ème 1"]}
GET/timetables/published/{jobId}/classe/{niveau}timetables:read

Leçons d'une classe d'un emploi du temps publié.

GET/timetables/jobs/{jobId}/export.icstimetables:read

Export iCalendar (text/calendar). Réservé au forfait Pro → 402 sinon.

Paramètres :
  • niveau — filtrer sur une classe
  • professeur — filtrer sur un prof
GET/timetables/jobs/{jobId}/export.pdftimetables:read

Export PDF (application/pdf) — toute la promotion, par classe ou par professeur.

Paramètres :
  • mode — all | class | teacher
  • niveau — requis si mode=class
  • professeur — requis si mode=teacher

Référence — Webhooks

Recevez des événements signés. Scope webhooks:manage. Le secret n'est renvoyé qu'à la création (ou après rotation). Événements : timetable.job.completed, timetable.job.failed, timetable.published, webhook.test. Chaque livraison est signée (HMAC-SHA256) et rejouée jusqu'à 5 fois en cas d'échec. L'URL est validée contre le SSRF (cibles internes/privées refusées).

GET/webhookswebhooks:manage

Liste des endpoints.

POST/webhookswebhooks:manage

Créer un endpoint. Renvoie le secret de signature (à conserver).

Corps
{"url":"https://exemple.com/webhooks/yap","events":["timetable.published","timetable.job.completed"]}
Réponse
{"endpoint":{"id":"…","url":"…","events":["timetable.published"],"active":true},"secret":"whsec_…"}
DELETE/webhooks/{id}webhooks:manage

Supprimer un endpoint.

GET/webhooks/{id}/deliverieswebhooks:manage

Journal des livraisons (statut, code HTTP, tentatives).

POST/webhooks/{id}/rotatewebhooks:manage

Régénérer le secret de signature (renvoyé une seule fois).

Réponse
{"endpoint":{"id":"…","url":"…"},"secret":"whsec_…"}
POST/webhooks/{id}/testwebhooks:manage

Envoyer un événement webhook.test à l'endpoint (202 + status queued).

POST/webhooks/deliveries/{deliveryId}/replaywebhooks:manage

Rejouer une livraison passée (202 + status queued).

Référence — Démo (public, sans clé)

Endpoints publics non authentifiés pour découvrir la forme des réponses.

GET/api/v1/public/api-demo/preview

Aperçu d'un établissement de démonstration (cycles, classes, matières, profs, créneaux).

GET/api/v1/public/api-demo/sample-timetable

Exemple de leçons (forme d'un emploi du temps généré).

Webhooks — vérifier une signature

Chaque livraison est un POST JSON avec les en-têtes X-Webhook-Event, X-Webhook-Timestamp et X-Webhook-Signature = HMAC-SHA256(timestamp + "." + body, secret). Vérifiez la signature et la fraîcheur du timestamp (moins de 5 min) pour éviter le rejeu. Événements : timetable.published, timetable.job.completed, timetable.job.failed, webhook.test.

POST https://votre-app/webhook
X-Webhook-Event: timetable.published
X-Webhook-Timestamp: 1712345678
X-Webhook-Signature: 9f86d081…

{ "event":"timetable.published", "jobId":"…", "etablissementId":"…", "occurredAt":"…" }
import crypto from "crypto";

// Express — vérifier la signature d'un webhook YA-Planning
app.post("/webhooks/yap", express.raw({ type: "*/*" }), (req, res) => {
  const ts  = req.header("X-Webhook-Timestamp");
  const sig = req.header("X-Webhook-Signature");
  const body = req.body.toString("utf8");

  const expected = crypto.createHmac("sha256", process.env.YAP_WEBHOOK_SECRET)
    .update(`${ts}.${body}`).digest("hex");

  // fraîcheur (anti-rejeu) : < 5 min
  const fresh = Math.abs(Date.now()/1000 - Number(ts)) < 300;
  if (!fresh || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)))
    return res.sendStatus(400);

  const event = JSON.parse(body); // { event, jobId, etablissementId, occurredAt }
  res.sendStatus(200);
});

Exemples de code

# Vérifier ses droits
curl https://votre-domaine/api/v1/pub/v1/me \
  -H "X-API-Key: yap_live_xxxxxxxxxxxx"

# Générer un EDT puis suivre le job
JOB=$(curl -s -X POST .../api/v1/pub/v1/timetables/generate \
  -H "X-API-Key: yap_live_xxxx" -H "Content-Type: application/json" \
  -d '{"durationMinutes":5}' | jq -r .jobId)

curl .../api/v1/pub/v1/timetables/jobs/$JOB \
  -H "X-API-Key: yap_live_xxxx"

Versions & dépréciation

L'API est versionnée dans le chemin (/api/v1/pub/v1/…). Nous ajoutons des champs et des endpoints de façon rétrocompatible ; ces ajouts ne constituent pas une rupture, alors codez de façon tolérante (ignorez les champs inconnus). Toute rupture donnera lieu à une nouvelle version majeure et à un préavis de 90 jours minimum, annoncé via l'en-tête de réponse Deprecation et le journal des modifications ci-dessous.

Journal des modifications
  • 2026-07 — Comptes développeurs & dashboard ; provisioning niveau-compte (POST /account/etablissements, scope establishments:manage) ; endpoints /cycles ; export PDF mode=teacher.
  • 2026-07 — Idempotency-Key sur la génération ; endpoints /me/etablissement, /me/usage ; rotation / test / rejeu de webhooks ; événement timetable.job.failed ; en-tête X-Request-Id ; pagination des listes de configuration ; expiration et restriction IP des clés ; validation anti-SSRF des URLs de webhook.
  • 2026-06 — Lancement de l'API publique v1 : clés à scopes, configuration, génération & publication d'emploi du temps, webhooks signés, bac à sable, en-têtes de quota.

Prêt à intégrer ?

Créez une clé sandbox en 30 secondes et faites votre premier appel.

Ouvrir la console API