Prohibit job combinations
In pick-up and delivery routing, Job pooling adds efficiencies that can result in all jobs being completed quicker and with fewer resources. However, there are times when two jobs should not be combined, for instance, if a customer had a bad experience traveling with another customer, they can request not to travel with that customer again.
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. Prohibited jobs
When a job must not overlap with another job for the same driver, add a prohibitedJobs array to the job with the job IDs listed:
{
"jobs": [
{
"id": "Job C",
"prohibitedJobs": ["Job B"],
"stops": [
{
"id": "C1",
"location": [33.78592, -84.46136],
"duration": "PT20M"
},
{
"id": "C2",
"location": [33.72757, -83.96354],
"duration": "PT20M",
"stopDependencies": [
{
"id": "jobC_dep1",
"precedingStop": "C1"
}
]
}
]
}
]
}
If the prohibitedJobs array is empty or not specified, the job doesn’t restrict how it can be assigned.
The Prohibit job combinations hard constraint penalizes solutions with a hard score if jobs overlap for the same driver and at least one of the jobs prohibits such a combination.
Jobs will be left unassigned if assigning them would break this constraint.
In the following example, Job C lists Job B as a prohibited job.
Although Job B and Job C share the same starting location, they are assigned sequentially.
-
Input
-
Output
Try this example in Timefold Platform by saving the JSON into a file called prohibited-jobs-input.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": "Prohibited jobs example"
}
},
"modelInput": {
"drivers": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startLocation": [33.68786, -84.18487],
"endLocation": [33.68786, -84.18487],
"minStartTime": "2027-02-01T09:00:00Z",
"maxEndTime": "2027-02-01T17:00:00Z"
}
]
}
],
"jobs": [
{
"id": "Job C",
"prohibitedJobs": ["Job B"],
"stops": [
{
"id": "C1",
"location": [33.78592, -84.46136],
"duration": "PT20M"
},
{
"id": "C2",
"location": [33.72757, -83.96354],
"duration": "PT20M",
"stopDependencies": [
{
"id": "jobC_dep1",
"precedingStop": "C1"
}
]
}
]
},
{
"id": "Job B",
"prohibitedJobs": [],
"stops": [
{
"id": "B1",
"location": [33.78592, -84.46136],
"duration": "PT20M"
},
{
"id": "B2",
"location": [33.72757, -83.96354],
"duration": "PT20M",
"stopDependencies": [
{
"id": "jobB_dep1",
"precedingStop": "B1"
}
]
}
]
}
]
}
}
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": "Prohibited jobs example",
"submitDateTime": "2025-09-23T11:40:55.994757+02:00",
"startDateTime": "2025-09-23T11:40:56.054933+02:00",
"activeDateTime": "2025-09-23T11:40:56.056582+02:00",
"completeDateTime": "2025-09-23T11:40:56.532302+02:00",
"shutdownDateTime": "2025-09-23T11:40:56.536063+02:00",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-13547soft",
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"drivers": [
{
"id": "Ann",
"shifts": [
{
"id": "Ann Mon",
"startTime": "2027-02-01T09:00:00Z",
"itinerary": [
{
"id": "B1",
"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": "B2",
"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
},
{
"id": "C1",
"arrivalTime": "2027-02-01T12:04:53Z",
"startServiceTime": "2027-02-01T12:04:53Z",
"departureTime": "2027-02-01T12:24:53Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT55M46S",
"travelDistanceMetersFromPreviousStandstill": 46477
},
{
"id": "C2",
"arrivalTime": "2027-02-01T13:20:39Z",
"startServiceTime": "2027-02-01T13:20:39Z",
"departureTime": "2027-02-01T13:40:39Z",
"effectiveServiceDuration": "PT20M",
"kind": "STOP",
"travelTimeFromPreviousStandstill": "PT55M46S",
"travelDistanceMetersFromPreviousStandstill": 46477
}
],
"metrics": {
"totalTravelTime": "PT3H45M47S",
"travelTimeFromStartLocationToFirstStop": "PT33M21S",
"travelTimeBetweenStops": "PT2H47M18S",
"travelTimeFromLastStopToEndLocation": "PT25M8S",
"totalTravelDistanceMeters": 188170,
"travelDistanceFromStartLocationToFirstStopMeters": 27795,
"travelDistanceBetweenStopsMeters": 139431,
"travelDistanceFromLastStopToEndLocationMeters": 20944,
"endLocationArrivalTime": "2027-02-01T14:05:47Z",
"overtime": "PT0S"
}
}
]
}
]
},
"inputMetrics": {
"stops": 4,
"drivers": 1,
"driverShifts": 1
},
"kpis": {
"totalTravelTime": "PT3H45M47S",
"travelTimeFromStartLocationToFirstStop": "PT33M21S",
"travelTimeBetweenStops": "PT2H47M18S",
"travelTimeFromLastStopToEndLocation": "PT25M8S",
"totalTravelDistanceMeters": 188170,
"travelDistanceFromStartLocationToFirstStopMeters": 27795,
"travelDistanceBetweenStopsMeters": 139431,
"travelDistanceFromLastStopToEndLocationMeters": 20944,
"totalUnassignedStops": 0,
"totalAssignedStops": 4,
"totalActivatedDrivers": 1,
"totalOvertime": "PT0S"
}
}
Next
-
See the full API spec or try the online API.