Aller au contenu
API pública · gratuita

Documentación de la API de YA-Planning

Integre la generación y consulta de horarios, gestione la configuración de sus centros y reciba eventos en tiempo real. Referencia completa, ejemplos y entorno de pruebas.

Introducción

La API es REST, en JSON, versionada. Todos los recursos de negocio viven bajo el prefijo /api/v1/pub/v1. Los endpoints de demostración públicos están en /api/v1/public/api-demo.

  • Base URL: https://su-dominio/api/v1/pub/v1
  • Formato: solicitudes y respuestas en application/json (salvo exportaciones ICS/PDF).
  • Versionado: la versión está en la ruta (/v1); las adiciones retrocompatibles no cambian la versión.
  • CORS: la API pública está abierta a aplicaciones de navegador de terceros (sin cookies).

Autenticación

Cada solicitud lleva una clave API, en una de las dos cabeceras:

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

Una clave puede estar autorizada para varios centros (clave empresa). Indique entonces el destino mediante la cabecera X-Etablissement-Id; si solo hay un centro autorizado, se deduce automáticamente. Las claves se crean, con ámbitos, en la consola.

Entornos y entorno de pruebas

Dos entornos, distinguidos por el prefijo de la clave:

  • yap_live_… — producción: sus datos reales; la generación descuenta de la cuota semanal.
  • yap_test_… — sandbox: las lecturas devuelven un conjunto de datos de demostración (nunca sus datos reales), la generación no se descuenta, y la escritura de configuración se rechaza (SANDBOX_READONLY). Ideal para desarrollar sin riesgo.

Ámbitos

Cada clave lleva un subconjunto de ámbitos; un endpoint rechaza (403 INSUFFICIENT_SCOPE) si falta el ámbito requerido.

config:readLeer ciclos, clases, asignaturas, profesores, franjas, volúmenes horarios.
config:writeCrear / modificar / eliminar la configuración del centro.
timetables:readListar los trabajos, leer los resultados y los horarios publicados, exportar.
timetables:writeLanzar una generación y publicar un horario.
webhooks:manageCrear, listar y eliminar endpoints webhook.
establishments:manageCrear y listar sus propios centros (nivel cuenta, sin X-Etablissement-Id).

Límite y cuotas

Cada clave tiene un límite de velocidad (60 req/min por defecto). Las respuestas llevan:

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

Superarlo devuelve 429 RATE_LIMITED con la cabecera Retry-After. La generación de horarios está además sujeta a la cuota semanal del plan (visible mediante /me).

Toda respuesta de la API lleva también una cabecera X-Request-Id (identificador de correlación) — comuníquelo al soporte en caso de incidencia. Nota: el conteo de velocidad se aplica actualmente por instancia; en un despliegue multiinstancia el límite efectivo puede ser un múltiplo del valor mostrado.

Errores

Envoltura de error uniforme, con un requestId para el soporte:

{ "error": "INSUFFICIENT_SCOPE", "message": "Scope requis : config:write.",
  "requestId": "6181762d-…", "requiredScope": "config:write" }
CódigoerrorCausa
400BAD_REQUESTParámetros ausentes o inválidos.
401UNAUTHORIZED / INVALID_API_KEYClave ausente, inválida o revocada.
403INSUFFICIENT_SCOPELa clave no posee el ámbito requerido.
403ETABLISSEMENT_NOT_GRANTEDLa clave no está autorizada para el centro solicitado.
403SANDBOX_READONLYEscritura prohibida con una clave sandbox (solo lectura).
402PLAN_UPGRADE_REQUIREDFunción reservada a un plan de pago (p. ej. exportación iCal).
404NOT_FOUNDRecurso inexistente o fuera de su centro.
409WORKFLOW_ERRORTransición de flujo imposible (p. ej. publicar un borrador).
429RATE_LIMITEDVelocidad superada — reintente tras la cabecera Retry-After.

Probar en directo — sin registro

Endpoints de demostración públicos (no se requiere clave).

Descargar OpenAPI

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

Referencia — Cuenta e introspección

GET/me

Detalles de la clave actual: entorno, centro destino, ámbitos concedidos y cuota de generación restante. No requiere ámbito.

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

Perfil público del centro destino: nombre, sigla, ciudad, país, tipo, sitio web. No requiere ámbito.

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

Uso reciente de la clave actual (llamadas y errores por día). No requiere ámbito.

Parámetros:
  • days — ventana en días (por defecto 30, máx 90)
Respuesta
{"apiKeyId":"…","days":30,"daily":[{"day":"2026-07-12","calls":128,"errors":3}]}

Referencia — Cuenta — centros

Aprovisionamiento a nivel de cuenta: cree sus propios centros SIN la cabecera X-Etablissement-Id (la ruta /account no está vinculada a un centro). Ámbito establishments:manage. Al crear, la clave llamante recibe automáticamente una concesión ACTIVE sobre el nuevo centro — luego use X-Etablissement-Id para configurarlo y generar.

POST/account/etablissementsestablishments:manage

Crear un centro (bucket de desarrollador, sujeto al tope de su cuenta).

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

Listar los centros que ha creado.

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

Referencia — Configuración

Estructura pedagógica del centro. Lectura: ámbito config:read. Escritura: ámbito config:write (prohibida en sandbox). Un centro recién creado ya contiene un «Ciclo principal».

GET/cyclesconfig:read

Lista de ciclos.

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

Crear un ciclo.

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

Eliminar un ciclo.

GET/niveauxconfig:read

Lista de clases/niveles.

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

Crear una clase.

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

Modificar una clase (campos parciales).

DELETE/niveaux/{id}config:write

Eliminar una clase.

GET/disciplinesconfig:read

Lista de asignaturas.

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

Crear una asignatura.

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

Modificar una asignatura.

DELETE/disciplines/{id}config:write

Eliminar una asignatura.

GET/professeursconfig:read

Lista de profesores.

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

Crear un profesor.

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

Modificar un profesor.

DELETE/professeurs/{id}config:write

Eliminar un profesor.

GET/horairesconfig:read

Franjas horarias de la cuadrícula.

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

Crear una franja (día, HH:mm).

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

Eliminar una franja.

GET/volumesconfig:read

Volúmenes horarios (asignatura × ciclo).

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

Crear un volumen horario.

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

Eliminar un volumen horario.

Referencia — Horarios

Generación asíncrona (el solver corre en segundo plano — consulte el estado), publicación y consulta. En sandbox, la generación usa un conjunto de demostración y no se descuenta de la cuota.

POST/timetables/generatetimetables:write

Lanza una generación. Devuelve 202 + un jobId a consultar. LIVE descuenta de la cuota semanal. Cabecera opcional Idempotency-Key: un mismo identificador reproduce la respuesta memorizada (respuesta marcada Idempotency-Replayed: true) en lugar de relanzar una generación.

Parámetros:
  • Idempotency-Key — cabecera (opcional) — clave de idempotencia en LIVE
Cuerpo
{"durationMinutes": 5}
Respuesta
{"jobId":"…","status":"SOLVING_SCHEDULED"}
GET/timetables/jobstimetables:read

Lista paginada de los trabajos.

Parámetros:
  • page — índice de página (por defecto 0)
  • size — tamaño (por defecto 25, máx 100)
Respuesta
{"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

Estado de un trabajo.

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

Resultado: lista de lecciones (una vez status = SOLVING_ENDED).

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

Publica el horario (workflow → PUBLIE). Dispara el evento webhook timetable.published.

GET/timetables/published/latesttimetables:read

Último horario publicado del centro.

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

Etiquetas de las clases presentes en un horario publicado.

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

Lecciones de una clase de un horario publicado.

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

Exportación iCalendar (text/calendar). Reservada al plan Pro → 402 en caso contrario.

Parámetros:
  • niveau — filtrar por una clase
  • professeur — filtrar por un profesor
GET/timetables/jobs/{jobId}/export.pdftimetables:read

Exportación PDF (application/pdf) — toda la promoción, por clase o por profesor.

Parámetros:
  • mode — all | class | teacher
  • niveau — requerido si mode=class
  • professeur — requerido si mode=teacher

Referencia — Webhooks

Reciba eventos firmados. Ámbito webhooks:manage. El secreto solo se devuelve en la creación (o tras la rotación). Eventos: timetable.job.completed, timetable.job.failed, timetable.published, webhook.test. Cada entrega se firma (HMAC-SHA256) y se reproduce hasta 5 veces en caso de fallo. La URL se valida contra SSRF (destinos internos/privados rechazados).

GET/webhookswebhooks:manage

Lista de endpoints.

POST/webhookswebhooks:manage

Crear un endpoint. Devuelve el secreto de firma (consérvelo).

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

Eliminar un endpoint.

GET/webhooks/{id}/deliverieswebhooks:manage

Registro de entregas (estado, código HTTP, intentos).

POST/webhooks/{id}/rotatewebhooks:manage

Regenerar el secreto de firma (devuelto una sola vez).

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

Enviar un evento webhook.test al endpoint (202 + status queued).

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

Reproducir una entrega pasada (202 + status queued).

Referencia — Demo (pública, sin clave)

Endpoints públicos no autenticados para descubrir la forma de las respuestas.

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

Vista previa de un centro de demostración (ciclos, clases, asignaturas, profesores, franjas).

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

Ejemplo de lecciones (forma de un horario generado).

Webhooks — verificar una firma

Cada entrega es un POST JSON con las cabeceras X-Webhook-Event, X-Webhook-Timestamp y X-Webhook-Signature = HMAC-SHA256(timestamp + "." + body, secret). Verifique la firma y la frescura del timestamp (menos de 5 min) para evitar la reproducción. Eventos: 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);
});

Ejemplos de código

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

Versiones y obsolescencia

La API está versionada en la ruta (/api/v1/pub/v1/…). Añadimos campos y endpoints de forma retrocompatible; estas adiciones no constituyen una ruptura, así que programe de forma tolerante (ignore los campos desconocidos). Toda ruptura dará lugar a una nueva versión mayor y a un preaviso de 90 días mínimo, anunciado mediante la cabecera de respuesta Deprecation y el registro de cambios a continuación.

Registro de cambios
  • 2026-07 — Cuentas de desarrollador y panel; aprovisionamiento a nivel de cuenta (POST /account/etablissements, ámbito establishments:manage); endpoints /cycles; exportación PDF mode=teacher.
  • 2026-07 — Idempotency-Key en la generación; endpoints /me/etablissement, /me/usage; rotación / prueba / reproducción de webhooks; evento timetable.job.failed; cabecera X-Request-Id; paginación de las listas de configuración; expiración y restricción de IP de las claves; validación anti-SSRF de las URL de webhook.
  • 2026-06 — Lanzamiento de la API pública v1: claves con ámbitos, configuración, generación y publicación de horarios, webhooks firmados, sandbox, cabeceras de cuota.

¿Listo para integrar?

Cree una clave sandbox en 30 segundos y haga su primera llamada.

Abrir la consola API