Priority jobs and optional jobs
The urgency of jobs varies.
Some jobs are high priority while others are low. You can give individual jobs priority levels to specify the importance of assigning one job instead of another job.
When you are planning in pick-up and delivery routing, you can introduce mandatory jobs and optional jobs to help prioritize which jobs should be assigned as soon as possible and which jobs can wait until later.
Mandatory jobs are jobs that are penalized with a medium-level penalty when unassigned. This means assigning a mandatory job will always take precedence over satisfying soft constraints.
Optional jobs are jobs that are penalized with a soft-level penalty when unassigned. This means assigning an optional job will compete with all other soft constraints, so an optional job may be left unassigned in favor of satisfying other soft constraints, such as matching job or driver preferences.
This guide explains how to manage resource-limited planning and optional jobs with the following examples:
Prerequisites
Learn how to configure an API Key to run the examples in this guide:
-
Log in to Timefold Platform: app.timefold.ai
-
From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.
-
Create a new API key or use an existing one. Ensure the list of models for the API key contains the Pick-up and Delivery Routing model.
In the examples, replace <API_KEY> with the API Key you just copied.
1. Mandatory and optional jobs
The job’s priority defines if the job is considered mandatory (default if priority not specified) or optional.
In the following example, there are 2 jobs:
Job A defines priority "1" which makes it a mandatory job. Mandatory jobs are jobs that are penalized with a medium-level penalty when unassigned.
Job B defines priority "opt-1", making this an optional job. Optional jobs are jobs that are penalized with a soft-level penalty when unassigned.
You can read more about priorities in Job priorities.
{
"jobs": [
{
"id": "Job A",
"priority": "1",
"stops": [
{
"id": "A1",
"location": [33.78592, -84.46136],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "A2",
"location": [33.72757, -83.96354],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobA_dep1",
"precedingStop": "A1"
}
]
}
]
},
{
"id": "Job B",
"priority": "opt-1",
"stops": [
{
"id": "B1",
"location": [34.11110, -84.43002],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-02T17:00:00Z"
}
]
},
{
"id": "B2",
"location": [33.48594, -84.26560],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-02T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobB_dep1",
"precedingStop": "B1"
}
]
}
]
}
]
}
Carl is working between 09:00 and 13:00 and only has time to complete one job.
Job A is a mandatory job and is assigned to Carl. Because Job B is an optional job, it is left unassigned.
-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/pickup-delivery-routing/v1/route-plans [email protected]
{
"config": {
"run": {
"name": "Assign mandatory and optional jobs example"
}
},
"modelInput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startLocation": [33.68786, -84.18487],
"endLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:00:00Z"
}
]
}
],
"jobs": [
{
"id": "Job A",
"priority": "1",
"stops": [
{
"id": "A1",
"location": [33.78592, -84.46136],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "A2",
"location": [33.72757, -83.96354],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobA_dep1",
"precedingStop": "A1"
}
]
}
]
},
{
"id": "Job B",
"priority": "opt-1",
"stops": [
{
"id": "B1",
"location": [34.11110, -84.43002],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "B2",
"location": [33.48594, -84.26560],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobB_dep1",
"precedingStop": "B1"
}
]
}
]
}
],
"planningWindow": {
"startDate": "2027-02-01T00:00:00Z",
"endDate": "2027-02-02T00:00:00Z"
}
}
}
To request the solution, locate the ID from the response to the post operation and append it to the following API call:
|
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/pickup-delivery-routing/v1/route-plans/<ID>
{
"metadata": {
"id": "ID",
"originId": "ID",
"name": "Assign mandatory and optional jobs example",
"submitDateTime": "2025-11-07T21:34:01.088550366+01:00",
"startDateTime": "2025-11-07T21:34:01.325779948+01:00",
"activeDateTime": "2025-11-07T21:34:01.328985773+01:00",
"completeDateTime": "2025-11-07T21:34:31.451704108+01:00",
"shutdownDateTime": "2025-11-07T21:34:31.451715727+01:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-2000006855soft",
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "A1",
"arrivalTime": "2027-02-01T09:33:21Z",
"startServiceTime": "2027-02-01T09:33:21Z",
"departureTime": "2027-02-01T09:53:21Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT33M21S",
"travelDistanceMetersFromPreviousStandstill": 27795
},
{
"id": "A2",
"arrivalTime": "2027-02-01T10:49:07Z",
"startServiceTime": "2027-02-01T10:49:07Z",
"departureTime": "2027-02-01T11:09:07Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT55M46S",
"travelDistanceMetersFromPreviousStandstill": 46477
}
],
"metrics": {
"totalTravelTime": "PT1H54M15S",
"travelTimeFromStartLocationToFirstStop": "PT33M21S",
"travelTimeBetweenStops": "PT55M46S",
"travelTimeFromLastStopToEndLocation": "PT25M8S",
"totalTravelDistanceMeters": 95216,
"travelDistanceFromStartLocationToFirstStopMeters": 27795,
"travelDistanceBetweenStopsMeters": 46477,
"travelDistanceFromLastStopToEndLocationMeters": 20944,
"endLocationArrivalTime": "2027-02-01T11:34:15Z",
"overtime": "PT0S"
}
}
]
}
]
},
"inputMetrics": {
"jobs": 2,
"stops": 4,
"drivers": 1,
"driverShifts": 1
},
"kpis": {
"totalTravelTime": "PT1H54M15S",
"totalTravelDistanceMeters": 95216,
"totalActivatedDrivers": 1,
"totalUnassignedJobs": 1,
"totalAssignedJobs": 1,
"assignedMandatoryJobs": 1,
"assignedOptionalJobs": 0,
"totalUnassignedStops": 2,
"totalAssignedStops": 2,
"assignedMandatoryStops": 2,
"assignedOptionalStops": 0,
"totalOvertime": "PT0S",
"travelTimeFromStartLocationToFirstStop": "PT33M21S",
"travelTimeBetweenStops": "PT55M46S",
"travelTimeFromLastStopToEndLocation": "PT25M8S",
"travelDistanceFromStartLocationToFirstStopMeters": 27795,
"travelDistanceBetweenStopsMeters": 46477,
"travelDistanceFromLastStopToEndLocationMeters": 20944
}
}
2. Too many mandatory jobs
When there are too many mandatory jobs to assign during the planning window, mandatory jobs will be left unassigned.
The Minimize unassigned stops medium constraint adds a medium penalty to solutions with mandatory jobs with stops that are left unassigned.
From the original example, Job B’s priority has been changed from "opt-1" to "1", making it a mandatory job.
{
"planningWindow": {
"startDate": "2027-02-01T00:00:00Z",
"endDate": "2027-02-02T00:00:00Z"
},
"jobs": [
{
"id": "Job B",
"priority": "1",
"stops": [
{
"id": "B1",
"location": [34.11110, -84.43002],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "B2",
"location": [33.48594, -84.26560],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobB_dep1",
"precedingStop": "B1"
}
]
}
]
},
...
]
}
Again, mandatory Job A is assigned while mandatory Job B is unassigned.
This time, the medium score includes the penalty (-2000medium) for leaving one mandatory job (with 2 stops) unassigned.
-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/pickup-delivery-routing/v1/route-plans [email protected]
{
"config": {
"run": {
"name": "Assign too many mandatory jobs example"
}
},
"modelInput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startLocation": [33.68786, -84.18487],
"endLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T13:00:00Z"
}
]
}
],
"jobs": [
{
"id": "Job A",
"priority": "1",
"stops": [
{
"id": "A1",
"location": [33.78592, -84.46136],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "A2",
"location": [33.72757, -83.96354],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobA_dep1",
"precedingStop": "A1"
}
]
}
]
},
{
"id": "Job B",
"priority": "1",
"stops": [
{
"id": "B1",
"location": [34.11110, -84.43002],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "B2",
"location": [33.48594, -84.26560],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobB_dep1",
"precedingStop": "B1"
}
]
}
]
}
],
"planningWindow": {
"startDate": "2027-02-01T00:00:00Z",
"endDate": "2027-02-02T00:00:00Z"
}
}
}
To request the solution, locate the ID from the response to the post operation and append it to the following API call:
|
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/pickup-delivery-routing/v1/route-plans/<ID>
{
"metadata": {
"id": "ID",
"originId": "ID",
"name": "Assign too many mandatory jobs example",
"submitDateTime": "2025-11-07T21:39:55.717524976+01:00",
"startDateTime": "2025-11-07T21:39:55.740454259+01:00",
"activeDateTime": "2025-11-07T21:39:55.741069272+01:00",
"completeDateTime": "2025-11-07T21:40:25.765007594+01:00",
"shutdownDateTime": "2025-11-07T21:40:25.765013306+01:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-2000000000medium/-6855soft",
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "A1",
"arrivalTime": "2027-02-01T09:33:21Z",
"startServiceTime": "2027-02-01T09:33:21Z",
"departureTime": "2027-02-01T09:53:21Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT33M21S",
"travelDistanceMetersFromPreviousStandstill": 27795
},
{
"id": "A2",
"arrivalTime": "2027-02-01T10:49:07Z",
"startServiceTime": "2027-02-01T10:49:07Z",
"departureTime": "2027-02-01T11:09:07Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT55M46S",
"travelDistanceMetersFromPreviousStandstill": 46477
}
],
"metrics": {
"totalTravelTime": "PT1H54M15S",
"travelTimeFromStartLocationToFirstStop": "PT33M21S",
"travelTimeBetweenStops": "PT55M46S",
"travelTimeFromLastStopToEndLocation": "PT25M8S",
"totalTravelDistanceMeters": 95216,
"travelDistanceFromStartLocationToFirstStopMeters": 27795,
"travelDistanceBetweenStopsMeters": 46477,
"travelDistanceFromLastStopToEndLocationMeters": 20944,
"endLocationArrivalTime": "2027-02-01T11:34:15Z",
"overtime": "PT0S"
}
}
]
}
]
},
"inputMetrics": {
"jobs": 2,
"stops": 4,
"drivers": 1,
"driverShifts": 1
},
"kpis": {
"totalTravelTime": "PT1H54M15S",
"totalTravelDistanceMeters": 95216,
"totalActivatedDrivers": 1,
"totalUnassignedJobs": 1,
"totalAssignedJobs": 1,
"assignedMandatoryJobs": 1,
"totalUnassignedStops": 2,
"totalAssignedStops": 2,
"assignedMandatoryStops": 2,
"totalOvertime": "PT0S",
"travelTimeFromStartLocationToFirstStop": "PT33M21S",
"travelTimeBetweenStops": "PT55M46S",
"travelTimeFromLastStopToEndLocation": "PT25M8S",
"travelDistanceFromStartLocationToFirstStopMeters": 27795,
"travelDistanceBetweenStopsMeters": 46477,
"travelDistanceFromLastStopToEndLocationMeters": 20944
}
}
3. Job priorities
There are 10 built-in mandatory priorities. "1" is the highest priority, and "10" is the lowest priority.
The priority also defines if the job is considered mandatory (default) or optional.
There are also 10 built-in optional priorities. "opt-1" is the highest priority for optional jobs, and "opt-10" is the lowest priority.
The default built-in priority is "6". These built-in priorities use exponential penalty weights, where priority "1" is 10 times more important than priority "2", priority "2" is 10 times more important than priority "3" and so on.
Optionally, you can override these weights or define custom priorities in the model input priorityWeights attribute:
{
"jobs": [
{
"id": "job1",
"priority": "HIGH",
...
}
],
"drivers": [
...
],
"priorityWeights": [
{
"priority": "HIGH",
"weight": 10000
},
{
"priority": "LOW",
"weight": 10,
"assignment": "OPTIONAL"
}
]
}
The following example uses 5 jobs with the following built-in priorities:
-
Job A: 5
-
Job B: opt-2
-
Job C: 5
-
Job D: 10
-
Job E: 1
{
"jobs": [
{
"id": "Job A",
"priority": "5",
"prohibitJobPooling": true,
"stops": [
{
"id": "A1",
"location": [33.70592, -84.26136],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "A2",
"location": [33.72757, -83.96354],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobA_dep1",
"precedingStop": "A1"
}
]
}
]
},
{
"id": "Job B",
"priority": "opt-2",
"prohibitJobPooling": true,
"stops": [
{
"id": "B1",
"location": [33.75757, -83.76354],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-04T17:00:00Z"
}
]
},
{
"id": "B2",
"location": [33.95594, -83.26560],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-04T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobB_dep1",
"precedingStop": "B1"
}
]
}
]
},
{
"id": "Job C",
"priority": "5",
"prohibitJobPooling": true,
"stops": [
{
"id": "C1",
"location": [34.01, -83.563],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "C2",
"location": [33.978, -83.860],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobC_dep1",
"precedingStop": "C1"
}
]
}
]
},
{
"id": "Job D",
"priority": "10",
"prohibitJobPooling": true,
"stops": [
{
"id": "D1",
"location": [33.901, -83.990],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "D2",
"location": [33.955, -83.650],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobD_dep1",
"precedingStop": "D1"
}
]
}
]
},
{
"id": "Job E",
"priority": "1",
"prohibitJobPooling": true,
"stops": [
{
"id": "E1",
"location": [33.695, -83.950],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "E2",
"location": [33.698, -83.650],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobE_dep1",
"precedingStop": "E1"
}
]
}
]
}
]
}
Job E has the highest priority (1) and is assigned. Job C and Job A both have a priority of 5 and are assigned.
Job D is a mandatory job, but its priority is lower than the other priority jobs and is left unassigned. Job B is an optional job and is left unassigned.
-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/pickup-delivery-routing/v1/route-plans [email protected]
{
"config": {
"run": {
"name": "Assign high priority jobs example"
}
},
"modelInput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T15:00:00Z"
}
]
}
],
"jobs": [
{
"id": "Job A",
"priority": "5",
"prohibitJobPooling": true,
"stops": [
{
"id": "A1",
"location": [33.70592, -84.26136],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "A2",
"location": [33.72757, -83.96354],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobA_dep1",
"precedingStop": "A1"
}
]
}
]
},
{
"id": "Job B",
"priority": "opt-2",
"prohibitJobPooling": true,
"stops": [
{
"id": "B1",
"location": [33.75757, -83.76354],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "B2",
"location": [33.95594, -83.26560],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobB_dep1",
"precedingStop": "B1"
}
]
}
]
},
{
"id": "Job C",
"priority": "5",
"prohibitJobPooling": true,
"stops": [
{
"id": "C1",
"location": [34.01, -83.563],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "C2",
"location": [33.978, -83.860],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobC_dep1",
"precedingStop": "C1"
}
]
}
]
},
{
"id": "Job D",
"priority": "10",
"prohibitJobPooling": true,
"stops": [
{
"id": "D1",
"location": [33.901, -83.990],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "D2",
"location": [33.955, -83.650],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobD_dep1",
"precedingStop": "D1"
}
]
}
]
},
{
"id": "Job E",
"priority": "1",
"prohibitJobPooling": true,
"stops": [
{
"id": "E1",
"location": [33.695, -83.950],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
},
{
"id": "E2",
"location": [33.698, -83.650],
"duration": "PT20M",
"timeWindows": [
{
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
],
"stopDependencies": [
{
"id": "jobE_dep1",
"precedingStop": "E1"
}
]
}
]
}
],
"planningWindow": {
"startDate": "2027-02-01T00:00:00Z",
"endDate": "2027-02-02T00:00:00Z"
}
}
}
To request the solution, locate the ID from the response to the post operation and append it to the following API call:
|
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/pickup-delivery-routing/v1/route-plans/<ID>
{
"metadata": {
"id": "ID",
"originId": "ID",
"name": "Assign high priority jobs example",
"submitDateTime": "2025-11-07T22:11:43.478483371+01:00",
"startDateTime": "2025-11-07T22:11:43.602634399+01:00",
"activeDateTime": "2025-11-07T22:11:43.603719291+01:00",
"completeDateTime": "2025-11-07T22:12:13.688311682+01:00",
"shutdownDateTime": "2025-11-07T22:12:13.688319497+01:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-2medium/-200012519soft",
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"drivers": [
{
"id": "Carl",
"shifts": [
{
"id": "Carl Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "A1",
"arrivalTime": "2027-02-01T09:08:50Z",
"startServiceTime": "2027-02-01T09:08:50Z",
"departureTime": "2027-02-01T09:28:50Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT8M50S",
"travelDistanceMetersFromPreviousStandstill": 7356
},
{
"id": "A2",
"arrivalTime": "2027-02-01T10:02:01Z",
"startServiceTime": "2027-02-01T10:02:01Z",
"departureTime": "2027-02-01T10:22:01Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT33M11S",
"travelDistanceMetersFromPreviousStandstill": 27651
},
{
"id": "E1",
"arrivalTime": "2027-02-01T10:26:37Z",
"startServiceTime": "2027-02-01T10:26:37Z",
"departureTime": "2027-02-01T10:46:37Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT4M36S",
"travelDistanceMetersFromPreviousStandstill": 3832
},
{
"id": "E2",
"arrivalTime": "2027-02-01T11:19:55Z",
"startServiceTime": "2027-02-01T11:19:55Z",
"departureTime": "2027-02-01T11:39:55Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT33M18S",
"travelDistanceMetersFromPreviousStandstill": 27756
},
{
"id": "C1",
"arrivalTime": "2027-02-01T12:22:39Z",
"startServiceTime": "2027-02-01T12:22:39Z",
"departureTime": "2027-02-01T12:42:39Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT42M44S",
"travelDistanceMetersFromPreviousStandstill": 35611
},
{
"id": "C2",
"arrivalTime": "2027-02-01T13:15:47Z",
"startServiceTime": "2027-02-01T13:15:47Z",
"departureTime": "2027-02-01T13:35:47Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT33M8S",
"travelDistanceMetersFromPreviousStandstill": 27611
}
],
"metrics": {
"totalTravelTime": "PT3H28M39S",
"travelTimeFromStartLocationToFirstStop": "PT8M50S",
"travelTimeBetweenStops": "PT2H26M57S",
"travelTimeFromLastStopToEndLocation": "PT52M52S",
"totalTravelDistanceMeters": 173877,
"travelDistanceFromStartLocationToFirstStopMeters": 7356,
"travelDistanceBetweenStopsMeters": 122461,
"travelDistanceFromLastStopToEndLocationMeters": 44060,
"endLocationArrivalTime": "2027-02-01T14:28:39Z",
"overtime": "PT0S"
}
}
]
}
]
},
"inputMetrics": {
"jobs": 5,
"stops": 10,
"drivers": 1,
"driverShifts": 1
},
"kpis": {
"totalTravelTime": "PT3H28M39S",
"totalTravelDistanceMeters": 173877,
"totalActivatedDrivers": 1,
"totalUnassignedJobs": 2,
"totalAssignedJobs": 3,
"assignedMandatoryJobs": 3,
"assignedOptionalJobs": 0,
"totalUnassignedStops": 4,
"totalAssignedStops": 6,
"assignedMandatoryStops": 6,
"assignedOptionalStops": 0,
"totalOvertime": "PT0S",
"travelTimeFromStartLocationToFirstStop": "PT8M50S",
"travelTimeBetweenStops": "PT2H26M57S",
"travelTimeFromLastStopToEndLocation": "PT52M52S",
"travelDistanceFromStartLocationToFirstStopMeters": 7356,
"travelDistanceBetweenStopsMeters": 122461,
"travelDistanceFromLastStopToEndLocationMeters": 44060
}
}
Next
-
See the full API spec or try the online API.