Aller au contenu
API عامة · مجانية

توثيق API الخاص بـ YA-Planning

ادمج توليد الجداول الزمنية والاطّلاع عليها، وأدِر إعداد مؤسساتك، وتلقَّ الأحداث في الوقت الفعلي. مرجع كامل، وأمثلة، وبيئة اختبار.

مقدّمة

واجهة API من نوع REST، بصيغة JSON، ومُصدَرة بإصدارات. تعيش جميع موارد الأعمال تحت البادئة /api/v1/pub/v1. نقاط النهاية التجريبية العامة تحت /api/v1/public/api-demo.

  • عنوان URL الأساسي : https://votre-domaine/api/v1/pub/v1
  • الصيغة : الطلبات والاستجابات بـ application/json (عدا تصديرات ICS/PDF).
  • الإصدارات : الإصدار موجود في المسار (/v1)؛ الإضافات المتوافقة مع الإصدارات السابقة لا تغيّر الإصدار.
  • CORS : واجهة API العامة مفتوحة لتطبيقات المتصفّح الخارجية (دون ملفات تعريف ارتباط).

المصادقة

يحمل كل طلب مفتاح API، في أحد ترويسين اثنين:

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

يمكن أن يُصرَّح لمفتاح بالوصول إلى عدّة مؤسسات (مفتاح شركات). حدِّد عندئذٍ الهدف عبر الترويسة X-Etablissement-Id؛ وإن كانت مؤسسة واحدة فقط مُصرَّحًا بها، فتُستنتَج تلقائيًا. تُنشأ المفاتيح، بنطاقات، من وحدة التحكم.

البيئات وبيئة الاختبار

بيئتان، تتميّزان ببادئة المفتاح:

  • yap_live_… — الإنتاج : بياناتك الحقيقية؛ ويخصم التوليد من الحصة الأسبوعية.
  • yap_test_… — sandbox : تُعيد عمليات القراءة مجموعة بيانات تجريبية (وليست بياناتك الحقيقية أبدًا)، ولا يُخصَم التوليد، ويُرفض كتابة الإعداد (SANDBOX_READONLY). مثالي للتطوير دون مخاطرة.

النطاقات

يحمل كل مفتاح مجموعة فرعية من النطاقات؛ وترفض نقطة النهاية (403 INSUFFICIENT_SCOPE) إذا كان النطاق المطلوب مفقودًا.

config:readقراءة الأطوار والأقسام والمواد والأساتذة والفترات الزمنية والأحجام الزمنية.
config:writeإنشاء / تعديل / حذف إعداد المؤسسة.
timetables:readسرد المهام، وقراءة النتائج والجداول الزمنية المنشورة، والتصدير.
timetables:writeبدء توليد ونشر جدول زمني.
webhooks:manageإنشاء وسرد وحذف نقاط نهاية webhook.
establishments:manageإنشاء وسرد مؤسساتك الخاصة (على مستوى الحساب، دون X-Etablissement-Id).

المعدّل والحصص

لكل مفتاح حدّ للمعدّل (افتراضيًا 60 req/min). تحمل الاستجابات:

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

يُعيد التجاوز 429 RATE_LIMITED مع الترويسة Retry-After. ويخضع توليد الجداول الزمنية إضافةً إلى ذلك لـ الحصة الأسبوعية للخطة (تظهر عبر /me).

تحمل كل استجابة من الواجهة أيضًا ترويسة X-Request-Id (معرّف ارتباط) — أبلِغ به الدعم عند حدوث عطل. ملاحظة : يُطبَّق حساب المعدّل حاليًا لكل نسخة؛ وفي نشر متعدّد النسخ قد يكون الحدّ الفعلي مضاعفًا للقيمة المعروضة.

الأخطاء

غلاف خطأ موحّد، مع requestId للدعم:

{ "error": "INSUFFICIENT_SCOPE", "message": "Scope requis : config:write.",
  "requestId": "6181762d-…", "requiredScope": "config:write" }
الرمزerrorالسبب
400BAD_REQUESTمعاملات مفقودة أو غير صالحة.
401UNAUTHORIZED / INVALID_API_KEYمفتاح مفقود أو غير صالح أو مُبطَل.
403INSUFFICIENT_SCOPEالمفتاح لا يملك النطاق المطلوب.
403ETABLISSEMENT_NOT_GRANTEDالمفتاح غير مُصرَّح له بالمؤسسة المطلوبة.
403SANDBOX_READONLYالكتابة ممنوعة بمفتاح sandbox (قراءة فقط).
402PLAN_UPGRADE_REQUIREDوظيفة محصورة بخطة مدفوعة (مثل تصدير iCal).
404NOT_FOUNDمورد غير موجود أو خارج مؤسستك.
409WORKFLOW_ERRORانتقال في سير العمل غير ممكن (مثل نشر مسودة).
429RATE_LIMITEDتجاوز المعدّل — أعد المحاولة بعد ترويسة Retry-After.

اختبر مباشرةً — دون تسجيل

نقاط نهاية تجريبية عامة (لا يلزم مفتاح).

تنزيل OpenAPI

Postman : « Import » → « Link » → الصق /q/openapi?format=json.

مرجع — الحساب والاستبطان

GET/me

تفاصيل المفتاح الحالي: البيئة، المؤسسة الهدف، النطاقات الممنوحة، والحصة المتبقية من التوليد. لا يلزم أي نطاق.

الاستجابة
{
  "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

الملف العام للمؤسسة الهدف: الاسم، الاختصار، المدينة، البلد، النوع، الموقع الإلكتروني. لا يلزم أي نطاق.

الاستجابة
{"id":"…","nom":"Groupe Scolaire Les Palmiers","slug":"gs-palmiers","sigle":"GSP","ville":"Abidjan","pays":"CI","type":"PRIVE","siteWeb":"https://…"}
GET/me/usage

الاستخدام الحديث للمفتاح الحالي (الطلبات والأخطاء يوميًا). لا يلزم أي نطاق.

المعاملات:
  • days — نافذة بالأيام (افتراضيًا 30، بحدّ أقصى 90)
الاستجابة
{"apiKeyId":"…","days":30,"daily":[{"day":"2026-07-12","calls":128,"errors":3}]}

مرجع — الحساب — المؤسسات

التجهيز على مستوى الحساب: أنشئ مؤسساتك الخاصة دون ترويسة X-Etablissement-Id (المسار /account غير مرتبط بمؤسسة). النطاق establishments:manage. عند الإنشاء، يحصل المفتاح المُنادِي تلقائيًا على منح ACTIVE على المؤسسة الجديدة — ثم استخدم X-Etablissement-Id لإعدادها والتوليد.

POST/account/etablissementsestablishments:manage

إنشاء مؤسسة (حصّة المطوّر، خاضعة لسقف حسابك).

المتن
{"nom":"Groupe Scolaire Les Palmiers","type":"PRIVE_LAIC","pays":"CI"}
الاستجابة
{"id":"…","nom":"Groupe Scolaire Les Palmiers","slug":"gs-les-palmiers","pays":"CI","type":"PRIVE_LAIC"}
GET/account/etablissementsestablishments:manage

سرد المؤسسات التي أنشأتها.

الاستجابة
[{"id":"…","nom":"…","slug":"…","pays":"CI","type":"PRIVE_LAIC"}]

مرجع — الإعداد

البنية التربوية للمؤسسة. القراءة: النطاق config:read. الكتابة: النطاق config:write (ممنوعة في sandbox). تحتوي المؤسسة المُنشأة حديثًا بالفعل على « طور رئيسي ».

GET/cyclesconfig:read

قائمة الأطوار.

الاستجابة
[{"id":"…","libelle":"Cycle principal","ordre":0}]
POST/cyclesconfig:write

إنشاء طور.

المتن
{"libelle":"Second cycle","ordre":1}
DELETE/cycles/{id}config:write

حذف طور.

GET/niveauxconfig:read

قائمة الأقسام/المستويات.

الاستجابة
[{"id":"…","libelle":"6ème 1","cycleId":"…","effectif":42,"ordre":1}]
POST/niveauxconfig:write

إنشاء قسم.

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

تعديل قسم (حقول جزئية).

DELETE/niveaux/{id}config:write

حذف قسم.

GET/disciplinesconfig:read

قائمة المواد.

الاستجابة
[{"id":"…","libelle":"Mathématiques","code":"MATH","couleur":"#6C5CE7","ordre":1}]
POST/disciplinesconfig:write

إنشاء مادة.

المتن
{"libelle":"Physique-Chimie","code":"PC","couleur":"#00B894","ordre":5}
PUT/disciplines/{id}config:write

تعديل مادة.

DELETE/disciplines/{id}config:write

حذف مادة.

GET/professeursconfig:read

قائمة الأساتذة.

الاستجابة
[{"id":"…","nom":"Koné","prenoms":"Awa","matricule":"P-0142","email":"…","telephone":"…","nombreHeureAutorise":18,"statut":"TITULAIRE","actif":true}]
POST/professeursconfig:write

إنشاء أستاذ.

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

تعديل أستاذ.

DELETE/professeurs/{id}config:write

حذف أستاذ.

GET/horairesconfig:read

الفترات الزمنية للشبكة.

الاستجابة
[{"id":"…","jour":"MONDAY","heureDebut":"08:00","heureFin":"09:00","estPause":false,"libelle":null}]
POST/horairesconfig:write

إنشاء فترة زمنية (يوم، HH:mm).

المتن
{"jour":"MONDAY","heureDebut":"08:00","heureFin":"09:00","estPause":false,"libelle":"M1"}
DELETE/horaires/{id}config:write

حذف فترة زمنية.

GET/volumesconfig:read

الأحجام الزمنية (المادة × الطور).

الاستجابة
[{"id":"…","cycleId":"…","disciplineId":"…","nombreHeuresObligatoire":4,"minHeuresConsecutives":1,"maxHeuresConsecutives":2}]
POST/volumesconfig:write

إنشاء حجم زمني.

المتن
{"cycleId":"<uuid>","disciplineId":"<uuid>","nombreHeuresObligatoire":4,"minHeuresConsecutives":1,"maxHeuresConsecutives":2}
DELETE/volumes/{id}config:write

حذف حجم زمني.

مرجع — الجداول الزمنية

توليد غير متزامن (يعمل المحرّك في الخلفية — استعلم عن الحالة)، ونشر واطّلاع. في sandbox، يستخدم التوليد مجموعة بيانات تجريبية ولا يُخصَم من الحصة.

POST/timetables/generatetimetables:write

يبدأ توليدًا. يُعيد 202 + jobId للاستعلام عنه. تخصم LIVE من الحصة الأسبوعية. ترويسة اختيارية Idempotency-Key : يُعيد المعرّف نفسه تشغيل الاستجابة المحفوظة (استجابة موسومة بـ Idempotency-Replayed: true) بدلًا من بدء توليد جديد.

المعاملات:
  • Idempotency-Key — ترويسة (اختيارية) — مفتاح idempotence على جانب LIVE
المتن
{"durationMinutes": 5}
الاستجابة
{"jobId":"…","status":"SOLVING_SCHEDULED"}
GET/timetables/jobstimetables:read

قائمة مقسّمة إلى صفحات للمهام.

المعاملات:
  • page — فهرس الصفحة (افتراضيًا 0)
  • size — الحجم (افتراضيًا 25، بحدّ أقصى 100)
الاستجابة
{"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

حالة مهمة.

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

النتيجة: قائمة الحصص (بمجرّد status = SOLVING_ENDED).

الاستجابة
{"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). يُطلق حدث webhook المسمّى timetable.published.

GET/timetables/published/latesttimetables:read

آخر جدول زمني منشور للمؤسسة.

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

تسميات الأقسام الموجودة في جدول زمني منشور.

الاستجابة
{"classes":["6ème 1","6ème 2","5ème 1"]}
GET/timetables/published/{jobId}/classe/{niveau}timetables:read

حصص قسم في جدول زمني منشور.

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

تصدير iCalendar (text/calendar). محصور بخطة Pro → 402 خلاف ذلك.

المعاملات:
  • niveau — التصفية حسب قسم
  • professeur — التصفية حسب أستاذ
GET/timetables/jobs/{jobId}/export.pdftimetables:read

تصدير PDF (application/pdf) — الدفعة كاملة، حسب القسم أو حسب الأستاذ.

المعاملات:
  • mode — all | class | teacher
  • niveau — مطلوب إذا mode=class
  • professeur — مطلوب إذا mode=teacher

مرجع — Webhooks

تلقَّ أحداثًا موقّعة. النطاق webhooks:manage. لا يُعاد السر إلا عند الإنشاء (أو بعد التدوير). الأحداث : timetable.job.completed، timetable.job.failed، timetable.published، webhook.test. كل عملية تسليم موقّعة (HMAC-SHA256) وتُعاد حتى 5 مرات عند الفشل. يُتحقَّق من عنوان URL ضدّ SSRF (تُرفض الأهداف الداخلية/الخاصة).

GET/webhookswebhooks:manage

قائمة نقاط النهاية.

POST/webhookswebhooks:manage

إنشاء نقطة نهاية. يُعيد سر التوقيع (احتفظ به).

المتن
{"url":"https://exemple.com/webhooks/yap","events":["timetable.published","timetable.job.completed"]}
الاستجابة
{"endpoint":{"id":"…","url":"…","events":["timetable.published"],"active":true},"secret":"whsec_…"}
DELETE/webhooks/{id}webhooks:manage

حذف نقطة نهاية.

GET/webhooks/{id}/deliverieswebhooks:manage

سجل عمليات التسليم (الحالة، رمز HTTP، المحاولات).

POST/webhooks/{id}/rotatewebhooks:manage

إعادة توليد سر التوقيع (يُعاد مرة واحدة فقط).

الاستجابة
{"endpoint":{"id":"…","url":"…"},"secret":"whsec_…"}
POST/webhooks/{id}/testwebhooks:manage

إرسال حدث webhook.test إلى نقطة النهاية (202 + status queued).

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

إعادة تشغيل عملية تسليم سابقة (202 + status queued).

مرجع — عرض (عام، دون مفتاح)

نقاط نهاية عامة غير مصادَق عليها لاكتشاف شكل الاستجابات.

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

معاينة مؤسسة تجريبية (أطوار، أقسام، مواد، أساتذة، فترات زمنية).

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

مثال على الحصص (شكل جدول زمني مُولَّد).

Webhooks — التحقّق من توقيع

كل عملية تسليم هي POST بصيغة JSON مع الترويسات X-Webhook-Event وX-Webhook-Timestamp وX-Webhook-Signature = HMAC-SHA256(timestamp + "." + body, secret). تحقّق من التوقيع ومن حداثة الطابع الزمني (أقل من 5 دقائق) لتفادي إعادة التشغيل. الأحداث : 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);
});

أمثلة برمجية

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

الإصدارات والإهمال

واجهة API مُصدَرة في المسار (/api/v1/pub/v1/…). نضيف حقولًا ونقاط نهاية بطريقة متوافقة مع الإصدارات السابقة؛ ولا تشكّل هذه الإضافات كسرًا، لذا برمِج بتسامح (تجاهل الحقول غير المعروفة). أي كسر سيؤدّي إلى إصدار رئيسي جديد وإلى إشعار مسبق لا يقل عن 90 يومًا، يُعلَن عبر ترويسة الاستجابة Deprecation وسجل التغييرات أدناه.

سجل التغييرات
  • 2026-07 — حسابات المطوّرين ولوحة التحكم؛ تجهيز على مستوى الحساب (POST /account/etablissements، النطاق establishments:manage)؛ نقاط النهاية /cycles؛ تصدير PDF mode=teacher.
  • 2026-07 — Idempotency-Key على التوليد؛ نقاط النهاية /me/etablissement و/me/usage؛ تدوير / اختبار / إعادة تشغيل webhooks؛ الحدث timetable.job.failed؛ الترويسة X-Request-Id؛ ترقيم صفحات قوائم الإعداد؛ انتهاء صلاحية المفاتيح وتقييدها بعنوان IP؛ تحقّق مضاد لـ SSRF من عناوين URL الخاصة بـ webhook.
  • 2026-06 — إطلاق واجهة API العامة v1 : مفاتيح بنطاقات، إعداد، توليد ونشر الجداول الزمنية، webhooks موقّعة، بيئة اختبار، ترويسات الحصص.

جاهز للدمج؟

أنشئ مفتاح sandbox في 30 ثانية وقم بأول طلب لك.

افتح وحدة تحكم API