Creating a Job
A Job needs an id and a location. Everything else — demands, time windows, skills, eligibility, mandatory/penalty/revenue — is optional and lets you express increasingly specific business rules. This page walks through each field group with realistic examples.
Location: index vs explicit
Every job needs exactly one of two ways to say where it is:
location_index— an integer row/column index into the request's distance/time matrices. Use this when you supply your owntravel.distance_matrix/time_matrix.location— an inline coordinate object (lat/lon), used when FG-API computes matrices for you from coordinates.
{ "id": "job-4821", "location_index": 3 }
{ "id": "job-4821", "location": { "lat": 52.3791, "lon": 4.9003 } }
Demands and service time
demands can be a plain integer (single default capacity dimension) or a dict of named dimensions matching the resource's capacities keys:
{
"id": "job-4821",
"location_index": 3,
"demands": { "weight_kg": 120, "volume_m3": 0.8 },
"service_time": 600,
"setup_time": 120
}
service_time is the time spent at the stop once the vehicle arrives (and after waiting, if any). setup_time is extra time charged before service can start (e.g. unloading equipment) and is tracked separately in the response.
Time windows
A job can carry one or more time_windows. Each window is { start, end, early_penalty?, late_penalty? }, in the same time unit as the rest of the request (commonly seconds from midnight).
"time_windows": [
{ "start": 32400, "end": 34200 }
]
With no penalty fields, a window is hard: the solver must arrive inside [start, end] or the job is dropped (reported as time_window_infeasible). Setting late_penalty makes the upper bound soft — the solver may arrive late and pays (arrival - end) × late_penalty in route cost instead of failing. Setting early_penalty does the same for the lower bound: the solver may arrive early and pays (start - arrival) × early_penalty. Setting both makes the window fully soft (a V-shaped cost curve with its minimum inside [start, end]), and the solver is free to arrive any time at a cost.
"time_windows": [
{ "start": 32400, "end": 34200, "late_penalty": 10 }
]
Arriving 5 minutes (300s) late here adds 300 × 10 = 3000 to route cost — the solver compares that against the cost of serving the job some other way, or dropping it and paying its penalty.
Required skills and skill levels
required_skills is a list of skill names a resource must have to be eligible. skill_levels raises the bar to a minimum proficiency per skill (matched against the resource's skill_levels):
{
"id": "job-4821",
"location_index": 3,
"required_skills": ["crane", "hazmat"],
"skill_levels": { "crane": 3 }
}
A resource is eligible only if it has every required skill at or above the requested level. On the resource side, skill_multipliers can scale this job's service_time up or down depending on which resource is assigned (e.g. a more proficient crew finishes faster).
Vehicle eligibility
allowed_resource_ids restricts a job to a specific subset of resources, independent of skills:
{ "id": "job-4821", "location_index": 3, "allowed_resource_ids": ["truck-1", "truck-2"] }
mandatory, penalty, and revenue
By default mandatory is true: the solver must serve the job or the request is infeasible. Setting mandatory: false lets the solver drop the job and pay penalty instead — useful when you'd rather get a partial solution than no solution at all.
{
"id": "job-4821",
"location_index": 3,
"mandatory": false,
"penalty": 50000,
"revenue": 80
}
Say penalty=50000 and revenue=80 on a job that would cost an extra 12,000 in detour distance/time to insert into the only feasible route. The solver compares: insert it (cost +12,000, but +80 revenue, net +11,920) versus drop it (cost +50,000 penalty, no revenue, net +50,000). Since 11,920 < 50,000, the solver inserts the job — the penalty is far larger than the actual detour cost, so it behaves almost like a hard requirement. If the detour cost were instead 60,000 (e.g. the job is geographically isolated), dropping and paying the 50,000 penalty becomes cheaper, and the solver drops it. revenue is a small offset, not typically the deciding factor — penalty is what governs whether a job survives when capacity or time is tight.
Day (multi-day pinning)
In a multi-day problem (problem.num_days > 1), a job can be pinned to a specific day with day (0-indexed). Omitting day lets the solver choose any day a feasible resource is available:
{ "id": "job-4821", "location_index": 3, "day": 1 }
departure_time: pinning a job as the first stop
departure_time lets an operator say "this vehicle leaves the depot for this specific job at this specific time" — effectively pinning the job as the very first stop of a resource's day. The mechanism has five steps:
- Set
departure_timeand exactly oneallowed_resource_idsentry.departure_timerequires the job to be restricted to a single resource — FG-API needs to know which vehicle is leaving, so ambiguity is rejected. - FG-API computes depot-to-job travel time. Using the assigned resource's depot location and the job's location, FG-API computes the real travel time between them.
- A tight
time_windowsentry is auto-injected. The window is built arounddeparture_time + travel_time, so the job's effective arrival window is pinned close to the computed value rather than left open. - The resource's
time_window.startshould matchdeparture_time. If the resource is also free to leave earlier, the solver may use that slack elsewhere; aligning the two avoids surprises. - The solver schedules everything else around the pin. The pinned job's arrival is now effectively fixed; the solver optimizes the rest of that resource's route (and every other resource) around it.
{
"id": "job-4821",
"location_index": 3,
"allowed_resource_ids": ["truck-1"],
"departure_time": 28800
}
departure_time only works when FG-API is computing travel from GPS coordinates (coordinate-based locations), since it needs to derive a real depot-to-job travel time. It is not meaningful when you supply your own precomputed distance/time matrices.
Full parameter table
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string | required | — | Unique job identifier. |
| location / location_index | object / int | required | — | Exactly one of the two must be set. |
| demands | dict | int | optional | 0 | Capacity consumed, single value or per-dimension dict. |
| service_time | int | optional | 0 | Seconds spent servicing the stop. |
| setup_time | int | optional | 0 | Extra seconds charged before service starts. |
| time_windows | list[TimeWindow] | optional | none | One or more allowed arrival windows; hard unless penalties set. |
| required_skills | list[string] | optional | [] | Skills a resource must have to be eligible. |
| skill_levels | dict | optional | {} | Minimum proficiency per required skill. |
| allowed_resource_ids | list[string] | optional | all | Restricts eligible resources. |
| mandatory | bool | optional | true | If false, solver may drop the job and pay penalty. |
| penalty | number | optional | 100000 | Cost charged if the job is dropped (only relevant when mandatory=false). |
| revenue | number | optional | 0 | Value credited when the job is served; offsets routing cost in scoring. |
| day | int | optional | any | Pins the job to a specific planning day in multi-day problems. |
| locked | bool | optional | false | Prevents the solver from moving this job during reoptimization. |
| lifo | bool | optional | false | Enforces last-in-first-out unloading order relative to other stops. |
| max_wait_before_service | int | optional | global | Overrides how long a vehicle may arrive early and wait for this job. |
| departure_time | int | optional | none | Pins the job as the first stop of a single resource's day; requires exactly one allowed_resource_ids entry. |
- Understanding the Response — see how served and unserved jobs show up in the result.
- Constraints — the full survey of constraint types beyond job-level fields.
- OptimizationRequest schema — exhaustive field reference.