Loading...
Technical documentation and endpoint specifications
https://api.planifica.educationAll API endpoints are relative to this base URL. For example, to optimize a schedule, POST to https://api.planifica.education/api/v1/edt/optimize
All API requests require authentication using an API key. Include your API key in the request header.
X-API-Key: digi_xxxxxxxxxxxxxxxxxxxxxxxx Authorization: ApiKey digi_xxxxxxxxxxxxxxxxxxxxxxxx
Either header works — use whichever fits your HTTP client.
Keep your API key secure and never expose it in client-side code. Use environment variables in production.
In addition to your API key, every optimize, allocation, and validate request must include schoolExternalId and schoolNameas top-level fields identifying which school the request is for. This is enforced server-side — requests without them are rejected.
{
"schoolExternalId": "your-school-id",
"schoolName": "School Display Name"
}schoolExternalId is a stable identifier you choose (e.g. your own school ID) and is used as the grouping key for usage tracking and per-school rate limits.schoolName is a human-readable label. Both fields are required strings, max 255 characters.
https://api.planifica.education/api/v1/edt/optimizeStart an asynchronous optimization task
The request body is the EDT optimization payload, plus two required top-level fields, schoolExternalId and schoolName (identify the school for usage tracking, see Authentication). There is no wrapper object — if you already send EDTRequest bodies directly to the ML backend, just add these two fields.
{
"schoolExternalId": "your-school-id",
"schoolName": "School Display Name",
"requestid": "unique-request-identifier",
"config": {
"max_time_in_seconds": 600,
"objective_type": "AFFINITY",
"use_objective": false,
"debug_enabled": true,
"debug_level": "full"
},
"data": {
"requestid": "unique-request-identifier",
"config": {
"max_time_in_seconds": 600,
"objective_type": "AFFINITY",
"use_objective": false,
"debug_enabled": true,
"debug_level": "full"
},
{
"code": "MA",
"name": "Mathematics"
}
],
"grades": [
{
"code": "CP",
"groups": ["A", "B"],
"working_hours": [
["Monday", {
"available_hours": [["08:00", "12:00"], ["13:00", "17:00"]],
"unavailable_periods": [["10:00", "10:30"]]
}]
]
}
],
"classrooms": [
{
"id": 1,
"subjects": ["AR", "MA"]
}
],
"instructors": [
{
"id": 1,
"name": "John Doe",
"subjects": ["MA"],
"capacity": 24,
"grades": ["CP"],
"available_days": [
["Monday", {
"available_hours": [["08:00", "17:00"]],
"unavailable_periods": [["12:00", "13:00"]]
}]
]
}
],
"schemas": [
{
"grade": "CP",
"sessions": [
{
"id": "session_1",
"session": "MATH_001",
"grade": "CP",
"group": "A",
"duration": 2,
"subject": "MA"
},
{
"id": "session_2",
"session": "MATH_002",
"grade": "CP",
"group": "B",
"duration": 2,
"subject": "MA"
}
]
}
]
}Note: Requests missing schoolExternalId or schoolName are rejected with a 400 error (SCHOOL_REQUIRED).
{
"id": "task_123456",
"status": "queued",
"message": "Optimization task queued successfully"
}Control optimization behavior with intelligent stop conditions that balance solution quality, processing time, and resource consumption.
Set hard limits on processing time to ensure responsive applications.
max_time_in_seconds: 300Stop automatically when solution quality stops improving.
no_improvement_timeout_seconds: 60Key objects used across the optimization, allocation, and validation endpoints.
Top-level fields identifying the target school for the request, alongside your EDTRequest fields. Required on every optimize, allocation, and validate call.
| Field | Type | Description |
|---|---|---|
schoolExternalId | string (required, max 255) | Stable identifier you assign to the school. Used to group requests by school for usage and rate-limiting purposes. |
schoolName | string (required, max 255) | Human-readable school name. |
{
"schoolExternalId": "your-school-id",
"schoolName": "School Display Name"
}The endpoints above belong to a dedicated gateway for managed partner integrations: validate a payload, submit an optimization or allocation job, and poll the task by ID. Authentication and request shape are the same as described in Authentication above.
Include one of these headers on every request to the partner gateway.
X-API-Key: digi_xxxxxxxxxxxxxxxxxxxxxxxxAuthorization: ApiKey digi_xxxxxxxxxxxxxxxxxxxxxxxx/api/v1/edt/optimize/api/v1/edt/allocation/api/v1/edt/validate/api/v1/edt/{taskId}/api/v1/edt/{taskId}/solutions/api/v1/edt/{taskId}See Error Codes below for the optimizer's domain-level errors, and the dedicated error code reference for the partner gateway's own auth/HTTP error codes (invalid key, rate limit, etc.).
If you previously integrated directly against the optimization engine (without an API key, on a different host), use this section to move onto the partner gateway above. Two independent things change: where and how you call it, and what you send— the request schema itself has breaking changes versus an older combined-config shape some direct integrations still use.
| Legacy (direct) | Partner Gateway | |
|---|---|---|
| Host | Direct engine host, no gateway | api.planifica.education |
| Auth | None | API key required |
| Request body | Raw EDT payload, old combined subject config | Same payload + schoolExternalId/schoolName, new split config |
| Usage tracking | None | Per-school, per-API-key, automatic |
The gateway is a thin, mostly pass-through proxy in front of the same optimization engine — it adds auth, telemetry, and usage tracking, and strips two extra fields before forwarding your payload. It does not change endpoint semantics (async optimize, sync allocation, validate). If your old client called /allocate, the gateway path is /allocation — see API Endpoints above for the full path list.
Add schoolExternalId and schoolName as top-level siblings of your existing request — there is no wrapper object. See Authentication above for the exact shape and validation rules.
The old combined subject config (one object per subject mixing capacity, affinity, cardinality, and a single-day window) is not deprecated — it has been removed entirely. There is no combined model in the backend anymore; only the three split configs below exist.
Old (combined, single-day window)
{
"subject": "math",
"capacity": 30,
"affinity": 5,
"cardinality": 2,
"day": "monday",
"start": "08:00",
"end": "12:00"
}New (split into typed lists, multi-day window)
// schemas[].capacity_configs
{
"subject": "math",
"capacity": 30,
"start_day": "monday",
"start_time": "08:00",
"end_day": "monday",
"end_time": "12:00"
}
// affinity_configs / cardinality_configs
// follow the same start_day/start_time/
// end_day/end_time shapeschemas[] entry (one per grade) now carries separate arrays: capacity_configs, affinity_configs, cardinality_configs — instead of one combined object per subject.day / start / end → start_day / start_time / end_day / end_time. The window can now span multiple days.ordering_configs — session-ordering constraints (follows / avoid_after / distance_between) between two subjects, with min_gap_slots / max_gap_slots. Omit it if you don't need ordering constraints.classrooms[] gained an optional specialized: boolean (default false) to mark scarce/specialized rooms (labs, gyms) for capacity-aware scheduling.This fails silently, not loudly. The request body as a whole does not reject unrecognized fields at the schemas[] level, so if you still send the old combined-config shape under its old field name, the call will succeed (no validation error) but your capacity/affinity/cardinality constraints will be silently dropped from the solve — they simply aren't mapped to anything. Only session objects (schemas[].sessions[]) reject unknown fields; the constraint-config lists do not. A 2xx response does not mean your constraints were applied — after migrating, verify constraints actually show up in the result (e.g. check coverage_percent or solution statsagainst a known-good baseline), don't just check the HTTP status.
New error codes you may see from stricter validation and the features above: SESSION_ORDERING_CYCLE, DISTANCE_BETWEEN_INFEASIBLE, INVALID_DISTANCE_BETWEEN_CONFIG, SPECIALIZED_ROOM_CAPACITY_EXCEEDED, INSUFFICIENT_CONTINUOUS_SLOTS, among others — see Error Codes below.
optimize and allocation responses include a taskId generated by the gateway. Use this taskId — not any internal id in the response body — for all follow-up status, solutions, and cancel calls.PENDING / PROCESSING / COMPLETED / FAILED (see Task Status Values above).stats on an allocation response, stats now live per solution: allocations.solutions[].stats.schoolExternalId and schoolName as top-level sibling fields on every request.capacity_configs, affinity_configs, cardinality_configs under each schemas[] entry.day / start / end to start_day / start_time / end_day / end_time on every config window.schemas[].sessions[] objects (strictly validated).api.planifica.education and confirm paths match /api/v1/edt/* (note /allocate → /allocation if applicable).Authorization: ApiKey <key> (or X-API-Key) header to every request.taskId from the initial response, not any internal id.stats and the normalized status enum.validate, optimize, and allocation end-to-end against the new host before cutting over production traffic.Before — direct, old config, no auth
POST /api/v1/edt/optimize
Content-Type: application/json
{
"requestid": "req-001",
"timetable": { "time_granularity": 30 },
"schemas": [{
"grade": "G1",
"sessions": [],
"teachers_capacities": [{
"subject": "math", "capacity": 30,
"affinity": 5, "cardinality": 2,
"day": "monday",
"start": "08:00", "end": "12:00"
}]
}]
}After — gateway, split config, API key
POST /api/v1/edt/optimize
Content-Type: application/json
Authorization: ApiKey digi_xxxxxxxxxxxxxxxxxxxx
{
"schoolExternalId": "school-abc-001",
"schoolName": "ABC High School",
"requestid": "req-001",
"timetable": { "time_granularity": 30 },
"schemas": [{
"grade": "G1",
"sessions": [],
"capacity_configs": [{ "subject": "math", "capacity": 30,
"start_day": "monday", "start_time": "08:00",
"end_day": "monday", "end_time": "12:00" }],
"affinity_configs": [{ "subject": "math", "affinity": 5,
"start_day": "monday", "start_time": "08:00",
"end_day": "monday", "end_time": "12:00" }],
"cardinality_configs": [{ "subject": "math", "cardinality": 2,
"start_day": "monday", "start_time": "08:00",
"end_day": "monday", "end_time": "12:00" }]
}]
}The API returns specific error codes to help identify and resolve scheduling issues. Each errors[]/warnings[] entry has a stable code you should branch on, plus a human-readable message that's safe to show end users but may be reworded between releases.
A 200/202 response is not, by itself, success. Domain-level scheduling problems are returned inside the response body, not as HTTP error statuses. Always check status (/optimize, /allocation) or valid (/validate) and inspect errors[] before assuming a request succeeded. errors[] entries are fatal; warnings[]entries are non-fatal — the request still processed, but something is worth surfacing to the end user.
The full catalog — optimizer/scheduling error codes (data integrity, setup, availability, capacity, locked sessions, ordering, cardinality) plus the partner gateway's own auth/HTTP error codes — lives on a dedicated page so it doesn't crowd out the rest of this reference.
View all error codes →Success
Request completed successfully
Bad Request
Invalid request data or missing required fields
Unauthorized
Missing or invalid API key
Too Many Requests
Rate limit exceeded
Internal Server Error
Unexpected server error
{
"error": {
"code": "INVALID_REQUEST",
"message": "Missing required field: timetable.working_days",
"details": {
"field": "timetable.working_days",
"expected": "array",
"received": "undefined"
}
}
}Rate limit information is included in response headers:
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 99 X-RateLimit-Reset: 1640995200