Your first solve¶
This page solves a two-week roster for fifteen employees, using the demo dataset the employee
scheduling model ships with. It needs a running service with a key, which
Run it on your machine gives you as sk_dev on port 9000. Set two variables once:
export KEY=sk_dev
export API=http://localhost:9000/api/models/employee-scheduling/v1
Every model lives under /api/models/{model}/{version}, and every call carries the key in the
X-API-KEY header.
Look at the model¶
The model describes itself: its name, its constraints and their default weights, the KPIs it reports, and the JSON schemas of its input and output.
curl -s -H "X-API-KEY: $KEY" $API/model | head -c 600
{"id":"employee-scheduling","version":"v1","entity":"schedules","name":"Employee Shift Scheduling",
"description":"Assign employees to shifts: every shift gets an employee with the skill it needs, …",
"maturity":"STABLE","features":["pinning","real-time planning","weights"], …
The entity is the name of the thing a request submits, so this model's datasets live under
$API/schedules.
Submit the demo dataset¶
demo-data/SMALL is a complete request: the input plus a configuration with the recommended thirty
second limit. Pipe it straight into a submit:
curl -s -H "X-API-KEY: $KEY" $API/demo-data/SMALL \
| curl -s -X POST -H "X-API-KEY: $KEY" -H 'Content-Type: application/json' \
--data-binary @- "$API/schedules?name=first"
{"id":"ds_01K6…","parentId":null,"originId":null,"name":"first","tags":[],
"solverStatus":"DATASET_CREATED","score":null,"submitDateTime":"2026-09-28T10:15:02Z", … "seq":1}
The answer is 202 with the dataset's metadata. Nothing has been solved yet: the dataset is in the
queue, and a worker will pick it up within a second. Keep the id:
export ID=ds_01K6…
Watch it improve¶
The dataset's event stream sends one frame per improvement, carrying the status and the score, and closes when the solve ends:
curl -sN -H "X-API-KEY: $KEY" $API/schedules/$ID/events
data: "{\"seq\":2,\"metadata\":{\"id\":\"ds_01K6…\",\"solverStatus\":\"SOLVING_SCHEDULED\", …}}"
data: "{\"seq\":4,\"metadata\":{\"id\":\"ds_01K6…\",\"solverStatus\":\"DATASET_COMPUTED\",\"score\":\"0hard/0soft\", …}}"
data: "{\"seq\":7,\"metadata\":{\"id\":\"ds_01K6…\",\"solverStatus\":\"SOLVING_ACTIVE\",\"score\":\"-3hard/-2310soft\", …}}"
data: "{\"seq\":8,\"metadata\":{\"id\":\"ds_01K6…\",\"solverStatus\":\"SOLVING_ACTIVE\",\"score\":\"0hard/-2085soft\", …}}"
data: "{\"heartbeat\":true}"
data: "{\"seq\":19,\"metadata\":{\"id\":\"ds_01K6…\",\"solverStatus\":\"SOLVING_COMPLETED\",\"score\":\"0hard/-1830soft\", …}}"
Each data: payload is a JSON string holding the frame; a client parses it twice. The
DATASET_COMPUTED frame carries the input's own score, before any solving. From SOLVING_ACTIVE on,
each frame is a strictly better score than the one before. A score whose hard part is 0 is feasible:
no hard constraint is broken, and the soft part then measures how well preferences are met. The
stream ends after SOLVING_COMPLETED, and opening it again on a finished dataset answers 410.
Fetch the solution¶
curl -s -H "X-API-KEY: $KEY" $API/schedules/$ID | head -c 500
{"metadata":{"id":"ds_01K6…","solverStatus":"SOLVING_COMPLETED","score":"0hard/-1830soft", …},
"modelOutput":{"employees":[…],"shifts":[{"id":"2026-10-05-Ambulatory-0","start":"2026-10-05T06:00:00",
"end":"2026-10-05T14:00:00","location":"Ambulatory care","requiredSkill":"Doctor","employee":"Amy Cole","pinned":false}, …]},
"inputMetrics":{"employees":15,"shifts":…,"pinnedShifts":0,"locations":3},
"kpis":{"assignedShifts":…,"unassignedShifts":0,"workingTimeFairnessPercentage":97.4,"disruptionPercentage":0.0}}
modelOutput is the input with every shift's employee filled in. It is also valid input, which is how
a solved roster is continued or patched. The metrics are the model's: what it measured about the
problem, and what it measured about the solution.
Read the score¶
The score analysis says what each constraint contributed:
curl -s -H "X-API-KEY: $KEY" $API/schedules/$ID/score-analysis
{"score":"0hard/-1830soft","constraints":[
{"name":"Missing required skill","weight":"1hard/0soft","score":"0hard/0soft"},
{"name":"Undesired day for employee","weight":"0hard/1soft","score":"0hard/-960soft"},
{"name":"Balance employee shift assignments","weight":"0hard/1soft","score":"0hard/-870soft"}, …]}
Each constraint's contribution is what the score changes by when that constraint is switched off, and the contributions sum to the total.
Solve it again with different weights¶
A dataset is never changed in place. To weigh undesired days more heavily, derive a new dataset from this one's input with a different configuration:
curl -s -X POST -H "X-API-KEY: $KEY" -H 'Content-Type: application/json' \
"$API/schedules/$ID/from-input?name=second" \
-d '{"config":{"run":{"termination":{"spentLimit":"PT20S"}},"model":{"overrides":{"undesiredDayForEmployeeWeight":5}}}}'
The reply is a new dataset whose parentId is the first. Lineage instead of mutation
explains the three ways to derive one, and Submit and poll every field
of the request.