Aller au contenu
Public API · free

YA-Planning API documentation

Integrate timetable generation and viewing, manage your establishments' configuration and receive real-time events. Full reference, examples and sandbox.

Introduction

The API is REST, JSON, versioned. All business resources live under the prefix /api/v1/pub/v1. Public demo endpoints are under /api/v1/public/api-demo.

  • Base URL: https://your-domain/api/v1/pub/v1
  • Format: requests and responses in application/json (except ICS/PDF exports).
  • Versioning: the version is in the path (/v1); backward-compatible additions do not change the version.
  • CORS: the public API is open to third-party browser applications (without cookies).

Authentication

Each request carries an API key, in one of two headers:

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

A key can be authorized for several establishments (enterprise key). In that case specify the target via the X-Etablissement-Id header; if only one establishment is authorized, it is inferred automatically. Keys are created, with scopes, in the console.

Environments & sandbox

Two environments, distinguished by the key prefix:

  • yap_live_… — production: your real data; generation counts against the weekly quota.
  • yap_test_… — sandbox: reads return a demo dataset (never your real data), generation is not counted, and configuration writes are rejected (SANDBOX_READONLY). Ideal for developing without risk.

Scopes

Each key carries a subset of scopes; an endpoint rejects (403 INSUFFICIENT_SCOPE) if the required scope is missing.

config:readRead cycles, classes, subjects, teachers, slots, hourly volumes.
config:writeCreate / edit / delete the establishment configuration.
timetables:readList jobs, read results and published timetables, export.
timetables:writeLaunch a generation and publish a timetable.
webhooks:manageCreate, list and delete webhook endpoints.
establishments:manageCreate and list your own establishments (account level, without X-Etablissement-Id).

Rate & quotas

Each key has a rate limit (60 req/min by default). Responses carry:

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

Exceeding it returns 429 RATE_LIMITED with the Retry-After header. Timetable generation is additionally subject to the plan's weekly quota (visible via /me).

Every API response also carries an X-Request-Id header (correlation id) — share it with support in case of an incident. Note: rate counting is currently applied per instance; on a multi-instance deployment the effective limit may be a multiple of the displayed value.

Errors

Uniform error envelope, with a requestId for support:

{ "error": "INSUFFICIENT_SCOPE", "message": "Scope requis : config:write.",
  "requestId": "6181762d-…", "requiredScope": "config:write" }
CodeerrorCause
400BAD_REQUESTMissing or invalid parameters.
401UNAUTHORIZED / INVALID_API_KEYMissing, invalid or revoked key.
403INSUFFICIENT_SCOPEThe key lacks the required scope.
403ETABLISSEMENT_NOT_GRANTEDThe key is not authorized for the requested establishment.
403SANDBOX_READONLYWriting forbidden with a sandbox key (read-only).
402PLAN_UPGRADE_REQUIREDFeature reserved for a paid plan (e.g. iCal export).
404NOT_FOUNDResource does not exist or is outside your establishment.
409WORKFLOW_ERRORWorkflow transition impossible (e.g. publishing a draft).
429RATE_LIMITEDRate exceeded — retry after the Retry-After header.

Test live — no sign-up

Public demo endpoints (no key required).

Download OpenAPI

Postman: “Import” → “Link” → paste /q/openapi?format=json.

Reference — Account & introspection

GET/me

Details of the current key: environment, target establishment, granted scopes and remaining generation quota. No scope required.

Response
{
  "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

Public profile of the target establishment: name, acronym, city, country, type, website. No scope required.

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

Recent usage of the current key (calls and errors per day). No scope required.

Parameters:
  • days — window in days (default 30, max 90)
Response
{"apiKeyId":"…","days":30,"daily":[{"day":"2026-07-12","calls":128,"errors":3}]}

Reference — Account — establishments

Account-level provisioning: create your own establishments WITHOUT the X-Etablissement-Id header (the /account path is not tied to an establishment). Scope establishments:manage. On creation, the calling key automatically receives an ACTIVE grant on the new establishment — then use X-Etablissement-Id to configure and generate.

POST/account/etablissementsestablishments:manage

Create an establishment (developer bucket, subject to your account's cap).

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

List the establishments you have created.

Response
[{"id":"…","nom":"…","slug":"…","pays":"CI","type":"PRIVE_LAIC"}]

Reference — Configuration

Pedagogical structure of the establishment. Read: scope config:read. Write: scope config:write (forbidden in sandbox). A freshly created establishment already contains a “Main cycle”.

GET/cyclesconfig:read

List of cycles.

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

Create a cycle.

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

Delete a cycle.

GET/niveauxconfig:read

List of classes/levels.

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

Create a class.

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

Edit a class (partial fields).

DELETE/niveaux/{id}config:write

Delete a class.

GET/disciplinesconfig:read

List of subjects.

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

Create a subject.

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

Edit a subject.

DELETE/disciplines/{id}config:write

Delete a subject.

GET/professeursconfig:read

List of teachers.

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

Create a teacher.

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

Edit a teacher.

DELETE/professeurs/{id}config:write

Delete a teacher.

GET/horairesconfig:read

Time slots of the grid.

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

Create a slot (day, HH:mm).

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

Delete a slot.

GET/volumesconfig:read

Hourly volumes (subject × cycle).

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

Create an hourly volume.

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

Delete an hourly volume.

Reference — Timetables

Asynchronous generation (the solver runs in the background — poll the status), publication and viewing. In sandbox, generation uses a demo dataset and is not counted against the quota.

POST/timetables/generatetimetables:write

Launches a generation. Returns 202 + a jobId to poll. LIVE counts against the weekly quota. Optional Idempotency-Key header: the same identifier replays the stored response (response marked Idempotency-Replayed: true) instead of launching a new generation.

Parameters:
  • Idempotency-Key — header (optional) — idempotency key on the LIVE side
Body
{"durationMinutes": 5}
Response
{"jobId":"…","status":"SOLVING_SCHEDULED"}
GET/timetables/jobstimetables:read

Paginated list of jobs.

Parameters:
  • page — page index (default 0)
  • size — size (default 25, max 100)
Response
{"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

Status of a job.

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

Result: list of lessons (once status = SOLVING_ENDED).

Response
{"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

Publishes the timetable (workflow → PUBLIE). Triggers the timetable.published webhook event.

GET/timetables/published/latesttimetables:read

Latest published timetable of the establishment.

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

Labels of the classes present in a published timetable.

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

Lessons of a class in a published timetable.

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

iCalendar export (text/calendar). Reserved for the Pro plan → 402 otherwise.

Parameters:
  • niveau — filter on a class
  • professeur — filter on a teacher
GET/timetables/jobs/{jobId}/export.pdftimetables:read

PDF export (application/pdf) — the whole cohort, by class or by teacher.

Parameters:
  • mode — all | class | teacher
  • niveau — required if mode=class
  • professeur — required if mode=teacher

Reference — Webhooks

Receive signed events. Scope webhooks:manage. The secret is only returned on creation (or after rotation). Events: timetable.job.completed, timetable.job.failed, timetable.published, webhook.test. Each delivery is signed (HMAC-SHA256) and replayed up to 5 times on failure. The URL is validated against SSRF (internal/private targets rejected).

GET/webhookswebhooks:manage

List of endpoints.

POST/webhookswebhooks:manage

Create an endpoint. Returns the signing secret (keep it).

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

Delete an endpoint.

GET/webhooks/{id}/deliverieswebhooks:manage

Delivery log (status, HTTP code, attempts).

POST/webhooks/{id}/rotatewebhooks:manage

Regenerate the signing secret (returned only once).

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

Send a webhook.test event to the endpoint (202 + status queued).

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

Replay a past delivery (202 + status queued).

Reference — Demo (public, no key)

Public unauthenticated endpoints to discover the shape of the responses.

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

Preview of a demo establishment (cycles, classes, subjects, teachers, slots).

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

Sample lessons (shape of a generated timetable).

Webhooks — verify a signature

Each delivery is a JSON POST with the headers X-Webhook-Event, X-Webhook-Timestamp and X-Webhook-Signature = HMAC-SHA256(timestamp + "." + body, secret). Verify the signature and the freshness of the timestamp (under 5 min) to prevent replay. Events: 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);
});

Code examples

# 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 & deprecation

The API is versioned in the path (/api/v1/pub/v1/…). We add fields and endpoints in a backward-compatible way; these additions are not a breaking change, so code tolerantly (ignore unknown fields). Any breaking change will result in a new major version and at least 90 days notice, announced via the Deprecation response header and the changelog below.

Changelog
  • 2026-07 — Developer accounts & dashboard; account-level provisioning (POST /account/etablissements, scope establishments:manage); /cycles endpoints; PDF export mode=teacher.
  • 2026-07 — Idempotency-Key on generation; /me/etablissement, /me/usage endpoints; webhook rotation / test / replay; timetable.job.failed event; X-Request-Id header; pagination of configuration lists; key expiration and IP restriction; anti-SSRF validation of webhook URLs.
  • 2026-06 — Launch of public API v1: scoped keys, configuration, timetable generation & publication, signed webhooks, sandbox, quota headers.

Ready to integrate?

Create a sandbox key in 30 seconds and make your first call.

Open the API console