API reference
Base URL https://api.operex.eu · contract version v1. Send your key as Authorization: Bearer <key>.
Field, enum and symbol names are the contract and stay English in every locale; only prose is localized. Schemas track the shipping engine (apps/shift-engine).
⬇ OpenAPI spec (JSON)OpenAPI specification ↓
Endpoints
| POST | /v1/schedule:build | Build one month’s roster; optionally evaluate compliance |
| POST | /v1/schedule:build-range | Build a roster over any date range; optionally evaluate compliance |
| GET | /v1/version | Engine + rule-data versions (pin for reproducibility) |
| GET | /v1/patterns | List the available shift patterns |
| GET | /v1/holidays?year= | Base national public holidays for a year (editable seed data) |
| GET | /v1/rules | Base compliance rules with their editable thresholds |
| GET | /health | Liveness probe |
Authentication
Every request carries an API key as a bearer token: Authorization: Bearer <key>. Keys are per-account and can be rotated. GET /health needs no key.
POST /v1/schedule:build
Builds one month for a site and, when evaluate is true, runs the compliance evaluator.
POST /v1/schedule:build
Authorization: Bearer sk_…
Content-Type: application/json
{
"site": {
"site_id": "plant-01",
"shift_pattern_key": "12h-2-2-4",
"anchor": "2022-08-09",
"n_crews": 4,
"dayworker_anchor": "2022-08-08",
"min_headcount": { "safety_total": 8 },
"employees": [
{ "staff_id": "1000001", "name": "…", "role": "Operator", "kind": "shift", "crew": 0 },
{ "staff_id": "9000001", "name": "…", "role": "Engineer", "kind": "dayworker" }
]
},
"year": 2025,
"month": 12,
"evaluate": true
}Request — top level
| site | Site | The site configuration (below). |
| year | int | Target year (1900–2200). |
| month | int | Target month (1–12). |
| evaluate | bool | Run the compliance evaluator (default true). |
| holidays | date[]? | Effective holiday calendar (edited base data). Omit to compute national holidays; [] means none. |
| rule_overrides | object? | Per-rule threshold / on-off overrides: rule_id → { param: value, "enabled": 0|1 }. Unknown ids ignored. |
| assignments | object? | Per-cell manual plan overrides: staff_id → { "YYYY-MM-DD": symbol }. Replaces that person’s symbol on that day; coverage and compliance reflect the edited plan. Unknown staff ids / out-of-range days ignored. |
Site
| site_id | string | Stable identifier for the site. |
| name | string? | Human-readable site name. |
| shift_pattern_key | string | Pattern for the shift crews, from GET /v1/patterns (e.g. "12h-2-2-4"). |
| anchor | date | Fixed base date (Alapnap) that phases the rotation. ISO YYYY-MM-DD. |
| n_crews | int | Number of shift crews (1–12). The pattern cycle must be divisible by this. |
| dayworker_pattern_key | string? | Pattern for dayworkers (default "nappalos"). |
| dayworker_anchor | date | Base date for the weekly dayworker pattern (a Monday aligns weekends). |
| employees | Employee[] | The roster (see below). |
| min_headcount | MinHeadcount? | Operational safety minimums (see below). |
| timezone | string? | IANA zone the clock-times are anchored in (default "Europe/Budapest"). Drives DST-correct worked hours. |
Employee
| staff_id | string | Unique per person; keys the response rows. |
| name | string | Display name. |
| role | string? | Job role (e.g. Operator, Shift lead). |
| kind | enum | One of: shift, dayworker, standby_crew, standby_day, lean. |
| crew | int? | Required for shift and standby_crew — the 0-based crew index. |
| lean_pattern_key | string? | Required for lean — the LEAN pattern key. |
MinHeadcount
| safety_total | int | Minimum headcount per staffed shift (Biztonsági / BLTSZ). 0 disables. |
| per_role | object | Optional map of role → minimum-per-shift. |
Response
{
"engine_version": "0.6.2",
"schedule": {
"site_id": "plant-01", "year": 2025, "month": 12,
"days": [ "2025-12-01", … ],
"rows": { "1000001": [ "É","É","-","-", … ], "9000001": [ "N","N", …, "Üp","Üp" ] },
"coverage": [ { "day": "2025-12-01", "morning": 1, "afternoon": 0, "night": 1, "dayworker": 1, "understaffed": [] }, … ],
"statistics": { "1000001": { "worked_hours": 192, "night_count": 8, "shift_count": 16, "vacation_days": 0, "flex_days": 0, "holiday_rest_days": 0, "expected_hours": 176, "balance": 16 }, … }
},
"compliance": {
"rulebook_version": "2026-09-03-draft",
"violations": [],
"notes": [ "This report signals and justifies; it does not guarantee compliance. …" ],
"any_needs_review": false
}
}Top level: engine_version, schedule, and compliance (null when evaluate is false).
schedule
| site_id | string | Echoed from the request. |
| year | int | Echoed. |
| month | int | Echoed (1–12). |
| days | date[] | Every calendar day of the month, in order. |
| rows | object | staff_id → array of symbols, aligned with days. |
| coverage | DayCoverage[] | Per-day working headcount by shift (see below). |
| statistics | object | staff_id → PersonStats month summary (see below). |
coverage[]
| day | date | The calendar day. |
| morning | int | People on a morning/day shift (r + R). |
| afternoon | int | People on an afternoon shift (d). |
| night | int | People on a night shift (é + É). |
| dayworker | int | People on the dayworker pattern (N). |
| understaffed | string[] | Shift categories below safety_total, if any. |
statistics (PersonStats)
| worked_hours | int | Engine-authoritative worked hours from the intrinsic Jelek durations. |
| night_count | int | Number of night shifts (é + É). |
| shift_count | int | Number of worked shifts (standby excluded). |
| vacation_days | int | Annual-leave days (Sz). |
| flex_days | int | Working-time-frame flex days off (CS, csúszó). |
| holiday_rest_days | int | Paid holiday-rest days (Üp, ünnepi pihenő). |
| expected_hours | int | The month's obligation (general workdays × weekly_hours / 5). |
| balance | int | Working-time-frame balance for the month (worked − expected). |
POST /v1/schedule:build-range
Same as POST /v1/schedule:build, but over an arbitrary inclusive date range instead of one calendar month — one call for a fortnight, a quarter or a full year. The rotation, holidays, rule_overrides and per-cell assignments behave identically; compliance rolling windows span the whole range, and statistics aggregate over the span. schedule is a RangeSchedule: the same shape as schedule but anchored by start and end instead of year and month.
Request — top level
| site | Site | The site configuration (as in build). |
| start | date | First day of the range, inclusive (ISO YYYY-MM-DD). |
| end | date | Last day, inclusive. At most 366 days after start (422 above). |
| evaluate | bool | Run the compliance evaluator (default true). |
| holidays | date[]? | Effective holiday calendar; omit to compute national holidays for every year the range touches. |
| rule_overrides | object? | As in build (rule_id → { param: value, "enabled": 0|1 }). |
| assignments | object? | As in build; the per-staff day cap widens to the whole span (≤ 366 days). |
Shift symbols
Each cell in rows is one symbol:
| r / d / é | 8h morning / afternoon / night |
| R / É | 12h day / night |
| N | dayworker |
| - | off / rest day |
| Sz | annual leave (szabadság) |
| CS | flex / working-time-frame day off (csúszó) |
| Üp | paid holiday rest (ünnepi pihenő) |
| B | standby / on-call |
Worker kinds
| shift | Runs the crew rotation pattern for its crew. |
| dayworker | Weekly dayworker pattern (N), off on weekends. |
| standby_crew | Standby derived from the crew pattern (night → day variant). |
| standby_day | Standby derived from the dayworker pattern. |
| lean | A named LEAN standby pattern. |
Compliance report
When evaluate is true, compliance holds the findings. It signals and justifies; it does not guarantee compliance. Each violation cites its statute section; findings from a draft rule carry needs_review: true. Rules checked: daily rest, daily and weekly maximum hours, night-work limit, weekly rest day.
violation
| staff_id | string | The affected person. |
| rule_id | string | The rule that fired (e.g. "daily-rest-11h"). |
| rule_type | enum | daily_rest | max_daily_hours | max_weekly_hours | night_work | weekly_rest. |
| day | date | The day the finding anchors to. |
| measured | number | The measured value (hours or days). |
| limit | number | The rule threshold. |
| statute_section | string | The cited statute section (e.g. "104. § (1)"). |
| message | string | Rendered Hungarian message. |
| severity | string | e.g. "warning". |
| needs_review | bool | True while the rule threshold awaits legal confirmation. |
GET /v1/patterns
Lists the available shift patterns: key, name, hours, cycle_length. Use a key as shift_pattern_key or dayworker_pattern_key.
GET /v1/holidays?year=
Returns the base national public holidays for a year (fixed dates + Easter-derived movable feasts). This is the seed data an editor shows: override it (rename, add, remove) and pass the effective set back in the build request’s holidays field. The engine only computes the default set; it never stores edits.
| day | date | The holiday date (ISO YYYY-MM-DD). |
| name | string | Hungarian holiday name (e.g. "Karácsony", "Húsvéthétfő"). |
GET /v1/rules
Returns the base compliance rules with their editable thresholds. This is the seed data a legal-compliance editor shows: adjust the numeric thresholds (min rest hours, max hours…) and the on/off flag, then pass the deltas back in the build request’s rule_overrides field. Which rules exist and their statute references are engine-owned and not editable.
| id | string | Rule identifier (e.g. "daily-rest-11h") — the key for rule_overrides. |
| rule_type | enum | daily_rest | max_daily_hours | max_weekly_hours | night_work | weekly_rest | work_time_frame. |
| enabled | bool | Whether the rule is active. |
| params | object | The editable numeric thresholds (e.g. { min_rest_hours: 11 }). |
| statute_section | string | The cited statute section (engine-owned). |
| message_hu | string | The Hungarian message template. |
| severity | string | e.g. "warning". |
| needs_review | bool | True while the threshold awaits legal confirmation. |
GET /v1/version
Returns the engine and rule-data versions. Pin these to reproduce a roster.
{
"engine_version": "0.6.2",
"pattern_library_version": "2026-08-28",
"rulebook_version": "2026-09-03-draft",
"shift_times_version": "2026-08-28"
}Determinism, timezones & versioning
The same request plus the same engine_version and rulebook_version always returns the same roster. The rotation is a pure function of the date relative to the site’s anchor; there is no hidden state and no randomness. Worked hours are timezone-aware (the site’s IANA zone, Europe/Budapest by default): a night shift spanning a daylight-saving change is measured as its true 11h (spring) or 13h (autumn), and the autumn 13h night trips the daily-hours rule. Rule data (patterns, shift times, holidays, the labour-law rulebook) is versioned and overridable per request, so a schedule can be reproduced and audited later.
Errors
| 422 | Unprocessable Entity | Invalid request: unknown pattern key, cycle length not divisible by n_crews, a crew index outside 0..n_crews-1, a duplicate staff_id, an unsupported kind (lean), a non-finite rule_overrides value (NaN/Infinity), or an unknown timezone. |
| 404 | Not Found | Unknown path. |
| 500 | Internal Server Error | Unexpected engine error. |
OpenAPI specification
The full machine-readable contract is served at /engine/shift-scheduling/openapi.json (OpenAPI 3.1). The hosted engine also exposes it at /openapi.json with interactive docs at /docs.