Introduction
The Solvers API offers you the power of the MoviSmart planning engine through direct integration with your services.
The MoviSmart planning engine solves a complex variant of the vehicle routing problem. You can provide the algorithm with a set of drivers (and respective vehicles) and tasks which need to be done. The drivers and tasks have several attributes, such as time windows, skills, capacities, etc.
In the remainder of this document you can find instructions on how to use the API key you were provided to call our different API endpoints, as well as the meaning of each and every parameter and their influence on the MoviSmart planning engine.
Functionality and Concepts
In this section we will give you an overview of the functionality provided by the API and the concepts you will need to better understand the optimization process.
The MoviSmart planning engine implements a complex variant of the Vehicle Routing Problem with Time Windows (VRPTW) problem. At its core, the problem involves matching a set of drivers or human resources to a set of task that need to be performed. These tasks can involve simple services, done at the location of the tasks (e.g. "install a router", "cleanup a room") or they can involve the carrying of a kind of item or entity (e.g. "transport a person from Lisbon to Coimbra" or "carry 500 kg of cargo from Porto to Faro". The latter kind of tasks are usually called Shipments, and involve a Pickup (of the specified item/entity) and a Delivery (of the same item/entity). The solution to the problem is a set of routes which detail which tasks each driver must do, in which order, and at what time.
Our variant of the problem is characterized by several different entities, which we will now briefly explain.
Location
Every geographical place in the algorithm is referred to as a Location. For example, every driver/human resource has a location from where they depart when their working day start.
Driver
A driver (sometimes referred to as "user" or "human resource") represents an entity capable of performing tasks. If you are an employer, the drivers are typically your employees, which go on the road with their vehicles. Drivers have several characteristics which influence how they are used in the context of the optimization problem.
Schedule
The driver has a specific availability dictating when they start and end their day's work. These are fixed time windows with no margin for delays or anticipations. A driver with a tight schedule will therefore probably be able to perform less tasks than a driver with more relaxed schedule.
Cost per hour
A driver can have a cost per hour, specified in €/hour. The optimization algorithm prefers to pick drivers with lower costs per hour. Therefore, it is expected that "cheap" drivers usually perform the longest tasks or a larger number of tasks.
Vehicle cost per km
Every driver has their own vehicle, and each vehicle has its own cost per km. While the cost per hour influences the cost of the driver with relation to the length/duration of its work, the cost per km influences the relation between the driver and the distance made in the context of a task. A driver can have a very low cost per hour but still be a poor choice for a task if that task requires them to drive a lot and drive the price up based on the cost per km.
Vehicle capacities and comfort settings
Another property of a driver's vehicle is the set of the vehicle's capacities.
Typical real-world problems involve dealing with sets and compartments of items, people or other entities. For example, depending on your business area, you may need to: (i) carry a number of passengers; (ii) carry a number of bags; (iii) carry a maximum weight, (iv) carry a limited number of produce, etc. In the language of the MoviSmart planning system, these are known as "capacities". A vehicle can have any number of capacities with fixed sizes, and each task can have capacity requirements.
In addition to having fixed sizes for their compartments/capacities (e.g. "This vehicle has room for 4 people" or "This room can carry up to 400kg"), our system allows for specification of comfort values. A comfort value allows you to set the preferred ocupation of a given compartment, which is typically used in the transport area when transporting passengers. Your vehicle may have 4 seats, but it is more comfortable or desirable to keep its usage below 2 seats. On the other hand, for business reasons, it may not be profitable for a vehicle with 8 seats to only fill 1 of them for a long period of time. Thus, you can specify a minimum comfort value and a maximum comfort value which pass this domain-specific knowledge to the algorithm. Note that these examples are not limited to seats, and can apply to any kind of capacity.
Start and end Locations
The algorithm must know where each driver starts and ends their day. The location you provide as a "start" location is where the driver is considered to be at the start of their schedule. So, for example, if you set your drivers in China, but create all your tasks in Portugal, it is very likely that no tasks will be performed, because there is no time to travel the distance between China and Portugal. The start location is mandatory, because every driver must start somewhere.
The "end" location, on the other hand, is an optional attribute which allows you to specify a mandatory place where your driver must end their route. If it is not specified, then the algorithm assumes that the driver's workday ends wherever they were in their last task or stop. If the end is indeed specified, then the algorithm will force it to happen within the driver's schedule. As such, if you set the start to Portugal, and all tasks are in Portugal, but you set the end to China, it is very likely that the driver will have no tasks, because it cannot go to China during the provided schedule.
Limit on the number of working hours
Our version of the VRPTW includes a way to limit the number of hours that a driver can work. This can be extremely useful if your drivers have flexible schedules, or if they do indeed have a limit on the number of hours they can work.
While in theory companies and drivers have an agreement on a fixed number of hours their workers can have (e.g. 8 hours in a day), in practice, and in the real world, this value is often considered in a "softer" approach. In other words, it is usually agreed upon that occasionally the driver can work 8h30m or even 9h. For example, it may happen that the driver is the only one who could perform a crucial task at a given time, and so they could perform it even at the expense of working a bit longer that day. Our algorithm supports this by supplying two kinds of limits:
Target: This is the value in the "theorized" world. If you specify it (e.g. 8h), the algorithm will try to enforce it, by preferring solutions to the problem which do not violate this number. However, this is not a strict enforcement, and the driver may violate this window if there is no other option or if it provides a particularly better solution. For example, it may make sense to incur the penalty of violating this target value if the driver has a significantly lower salary than the other drivers.
Max allowed: This is the "hard" limit which cannot possibly be violated. Typically you might set this to the real maximum hours you believe your driver can perform. It would not be uncommon, for example, to set a target value of 8 and a max allowed value of 10. Therefore, the algorithm will try to minimize any work done after 8 hours and completely prevent it from being done after 10 hours.
Note that these limits can also be used as a way to balance a solution: if you force all drivers to work a maximum number of hours, you are more likely to achieve balance and appropriate distribution of work. Note, however, that it is usually recommended to try to use the Limit on the number of jobs first as a solution to unbalancing issues, and only later turn to the limit on the number of working hours.
Limit on the number of maximum driving hours
Apart from allowing to limit the number of hours that a driver can work, one can impose a maximum total number of driving hours. For instance, many companies desire to balance the number of driving hours in order to avoid risky excess of driving hours. Moreover, nearly all countries have laws to impose a maximum number of driving hours within a workday.
Limit on the number of jobs
Similarly to the Limit on the number of working hours, it may make sense, business-wise, to establish a limit on the number of tasks that a driver can perform. This restriction can also be used as a way of achieving an approximation of a balanced solution. If you specify that a user can perform at most 30 jobs, then, regardless of a job's value, associated km and/or time, it will only be done by the driver if they have not done 30 jobs already.
Skills
A driver may possess certain skills that other drivers do not. Similarly, certain tasks may require a set of skills. The application of the skill concept varies from business area to business area. For example, if you work in the IT services area, your drivers can have different skills such as "can repair CISCO router" or "can deal with high-profile clients". Effectively, skills work as a way to filter out the number and kinds of drivers that can perform a given task. You may want to have a task be performed by a set of drivers known as the "Northern Region Driver Team", so you would create a skill with that name and mark each of the drivers having it. You may also have certain operations which require drivers with a specific kind of drivers license, etc.
Task whitelist
It may make sense to directly inform the algorithm of the tasks that a driver can perform. As such, you can specify a task whitelist which explicitly tells the algorithm which tasks the driver can perform. If this parameter is not passed, the algorithm assumes that every task can be done by the driver (after taking into account any schedule, skill and capacity restriction).
Shifts
Some companies operate in a shifts logic, which in turn means that a vehicle returns to depot at the middle of the working day in order to switch driver. Our optimization system supports this, by forcing the first shift's driver to return to depot not later than a maximum hour. As this shift hour is an upper bound, the shift can occur earlier.
When planning with shifts, the driver's Schedule corresponds to the first shift start hour and the second shift end hour. For instance, a schedule from 8am until 8pm means that the first shift starts not earlier than 8am and the second shift ends not later than 8pm. The driver shift occurs within this period of time, fulfilling the maximum allowed shift time.
Task
Generally speaking, a Task represents some work that must be done, somewhere, at some given point in time, with a given length and with certain restrictions on who can do it and which kinds of resources (e.g. litres, kg, seats) are required to do it. In practice, a task represents the work that your drivers have to do, such as picking up people and taking them somewhere, or simply performing a service at some destination.
Like drivers, tasks have different properties.
Type
There are two main types of tasks: shipment and service. Shipments are composed of a pickup and a delivery. Many of the properties of a service exist in the pickup and in the delivery, such as the time-window, the service time an the location. As such, we will refer to a subtask as any of pickup, delivery or service.
As a rule of thumb to knowing which kind of type you should use, ask yourself the following questions:
Do my tasks involve going somewhere and doing some work, such as a repair or a maintenance intervention? If so, then they are services
Do my tasks involve departing from some common place and dropping off merchandise throughout the day? If so, they might be modelled as services
Do my tasks involve picking merchandise or people at one place and delivering it at another? If so, they are shipments
Service
A service is the "simplest" kind of task, with a single location. There may be a set of required capacities. For example, in order for a driver to perform a routine maintenace, they may need to have a number of parts in their car.
Shipment
A shipment is "similar" to two services that are bound together. It is consisted by a pickup and a delivery. The pickup happens in some location, in some time window, and the delivery happens in another location, at another time window. Any cargo/capacitiy that is picked up at the pickup is kept in the vehicle until the delivery is performed. If either the pickup or the delivery cannot be performed, then neither is done, and the task is left unassigned. Evidently, the pickup and delivery will belong to the same route - it would make no sense for a driver to do the pickup and then have another driver "magically" do the delivery.
Time-window
Tasks can have associated time-windows. In the case of a task of the service type, it has a single time-window. In the case of the shipment, it has two time-windows: one for the pickup and one for the delivery.
The time window dictates the period during which a subtask can be started. Note that it does not specify the window during which it can be performed. For example, you may have a time-window between 08:00 and 08:15 for a task that takes 3 hours to complete. What this means is that it must be started in that 15-minute span, but once it is started, it will take 3 hours to be done.
Service time
Any subtask (service, pickup or delivery) has an associated service time. This is the duration of the subtask, i.e., the time it takes to complete once it has been started. Depending on your business area, this can typically represent the load time at some place, or the time that passengers take to enter the car and put their bags or, even, the time it takes to perform a repair at a given client.
Location
A subtask (service, pickup or delivery) takes place somewhere. You cannot have tasks without location(s). Note that a pickup and delivery can happen in the same place.
Capacities
Capacities are explained in a broader way here. A task can have a set of capacities that the driver must have room for in their vehicle. For example, if you run a taxi service, you could have a task of the type shipment, with a pickup of 3 people (your fares) and the delivery of the same people. The amount of cargo/capacities that are "picked up" is the same amount that is delivered. Note also that several pickups can happen in sequence. For example, if you run a package distribution system, you may pickup several packages along the way and drop them off as you go.
Skills
Skills are explained in a broader way here. A task can have a required set of skills that the driver must have. If the task is a shipment, the skills are the same for the pickup and the delivery. A typical example of a skill is that of a specific driver's license that is required to perform a job, or, for example, a skill such as "is suited for high-profile clients"
Payment
Our version of the VRPTW algorithm supports the (optional) concept of income or payment associated with the conclusion of a task. This allows you to express the monetary gain of a task. While you can provide the algorithm with nothing but the tasks, it might make sense to include their relative "worth" in the form of the "payment". If this value is not specified, the system may not prioritize tasks which have high-income value, for example, because they would take longer to perform or because they would require a lot of traveling.
Priority
You can provide a priority associated with your task. There are two kinds of priority: normal and high. This parameter is used for the algorithm to decide which tasks are more important to assign. For example, if the algorithm has to choose between leaving a normal task unassigned or a high task unassigned, it will prefer to leave the normal one unassigned. This evidently means that the more "high" priority tasks you provide, the less their priority matters - no tasks are prioritary if all tasks are prioritary.
Other optimization settings
There are some additional settings that can be provided to the MoviSmart algorithm that influence the results of the optimization.
Task atomicity (all-or-nothing tasks)
It may make sense to have tasks that work in an "all-or-nothing" fashion, which we call "atomic tasks". For example, you may decide that you have a set of services that must all be performed, and that if one of them is not performed, then neither should the others. This can be achieved by specifying all of these tasks in the here.
Speed factor
Our system has a builtin route optimizer which determines the distance and time between locations, based on the coordinates you provide. However, in practice, your drivers may be able to go faster or slower than the values we calculated. For example, they may go slightly above the legal value, or they may be forced to go below it because they drive mostly slower, heavy-load trucks. To deal with this, you can provide a global speed factor parameter that determines if we should consider the time between two points as actually smaller or larger than reported by our route optimizar.
This value is applied as an inverse multiplier with regards to time. If you want your drivers to go at twice the speed they would otherwise go, then you should pass a speed factor of 0.5 (because they now take half the time to traverse their route). If you want your drivers to go at half the speed, you would pass a speed factor of 2 (because they now take double the time to traverse their route).
Constraint balance and trade-offs
As can be seen in this documentation, the optimization algorithm has to deal with a delicate balance of many different constraints and weights. For example, it must balance the importance of travelling larger distances vs the importance of fulfilling high-rewarding jobs. Similarly, it must also balance the penalty to apply to violations of the target number of maximum working hours or the influence of the violation of comfort settings.
Althought you should not normally have to manually influence these values, you can do so, and we hope to improve upon this fine-tuning in future revisions of the API. At the moment, you can associate a certain weight to three constraints:
- PaybackSoft - The influence of the monetary gain of a task in the overall algorithm. Higher values will make the payback of tasks take a higher influence in the algorithm. Defaults to 1.0
- ComfortSoft - The influence of the comfort violations in the overall algorithm. High values will force the algorithm to pick vehicles that are "more comfortable", whereas lower values make this less relevant (e.g. a value of 0 effectively makes the comfort irrelevant). Defaults to 1.0
- MaxWorkingHours - The influence of violating the maximum number of working hours. High values will force the algorithm to distribute the work in a way that minimizes the "overwork" of your drivers, whereas lower values make this less relevant (e.g. a value of 0 effectively makes the comfort irrelevant). Defaults to 1.0
Number of iterations
Though you should generally not need to change the number of iterations that our algorithm runs for, you can specify this to try and limit the amount of time it takes to process, which might be particularly useful while you are developing your integration with the API. Please note that we may clamp your number of iterations, to prevent you from providing a value that is too large to process and that could otherwise disrupt the normal operation of the system.
API Endpoints Overview
To make the optimization process simple, the API only provides you with two API endpoints that will be summarized next.
This is the endpoint to start a new optimization process, sending to the API a Plan Object as input. To allow our users to better control the way they can access planning results, we support 3 different types of planning:
- Sync - The HTTP request will wait until the optimization process is finished and will return the results of the optimization in a synchronous way. This is generally not recommended except while testing with very small problems, as there is a fixed timeout (~3 minutes).
- Async with polling - When calling the method, an ID will be returned by the API immediately. With this ID you can access the Check Plan endpoint to know the status of the optimization process as well as its results (when they are calculated).
- Async with Web Hook - Works like the async with polling but also adds the possibility to pass an url that will be used to post the results of the optimization process when it ends. You can poll the detail URL of the returned plan even though we will return the results back to you with a web hook.
Please note that each time you invoke this algorithm (with valid data), the amount of available calls you have in the current billing period (a month) is decreased by one. This topic is covered in greater depth in the authentication section
This endpoint lets you check the status of the optimization process for a given plan and view the results when they are calculated.
Date Formats
There are several objects that require you to pass dates (e.g. start and end date of a task). The dates and times are to be passed as UTC Zulu strings (ie. "2018-06-06T13:43:41Z").
Authentication
The authentication in the API is performed using an API key. This means that you will need a valid API key in order to access all the endpoints. The API key is issued by us and you are responsible to keep it private.
Our users typically have a monthly request limit. That limit is only taken into account when starting new plan optimizations.
You can always check plan status and results as long as you have a valid key independently if you exceeded the montly request limit or not.
Using the API key
Any request that you perform requires that you send the key in the query parameter key. For example:
POST /api/plan/?key=<YOUR_API_KEY>
Authentication related errors
Example error response:
{
"detail": "API key does not exist."
}
When there is any error related to request authentication, you will be presented with an error message and the HTTP status code 401 (Unauthorized). Possible errors are:
- API key does not exist.
- API key expired.
- Monthly request limit exceeded.
Input Objects
In this section we present you the several data objects that compose the input of the optimization API. We present this in a top down way, where we will start by the entry point of a plan object and go through all deeper objects.
Plan
The Plan object contains all the data required by the API to perform the optimization.
Example Plan object:
{
"api_version": 1,
"tasks": [<Task>, <Task>, ...],
"drivers": [<Driver>, <Driver>, ...],
"atomic_task_ids": [[1,2], ...],
"settings": <Settings>
}
| Field | Type | Required | Description |
|---|---|---|---|
| api_version | Integer | yes | Version of the API. For now, the only possible version is 1. |
| tasks | Task array | yes | List of tasks to be optimized |
| drivers | Driver array | yes | List of drivers available to perform the tasks |
| atomic_task_ids | Array of arrays of integers | no | List of dependency arrays (atomic tasks). Each dependency array must contain the ids of tasks that must be performed together. |
| settings | Settings | no | Object containing settings used by the optimizer |
Task
The Task object contains all the information related to a single task as previously described here.
Example Task object
{
"id": 1,
"name": "Example Task 1",
"type": "shipment",
"priority": "normal",
"payment": 20.410547032375042,
"capacities": [<Capacity>, <Capacity>, ...],
"skills": [<Skill>, ...],
"pickup_data": <SubTask>,
"delivery_data": <SubTask>
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | Integer | yes | Identifier of the task |
| type | String | yes | Type of the task, as seen before. One of: service, shipment |
| name | String | no | Name of the task. Used to easily identify a task in human readable form. |
| priority | String | no | Priority of the task. One of: normal, high |
| payment | Float | no | Payment for performing the task |
| capacities | Capacity array | yes | Capacities needed to perform the task, including the value needed for each capacity |
| skills | Skill array | no | Skills needed to perform the task |
| driver_id | Integer | no | Id of a driver assigned to the task (it can only be done by them) |
| pickup_data | Sub Task instance | if task type is shipment | Data related to the pickup |
| delivery_data | Sub Task instance | if task type is shipment | Data related to the delivery |
| service_data | Sub Task instance | if task type is service | Data related to the service |
Sub Task
Example Sub Task object
{
"time_window": <TimeWindow>,
"location": <Location>,
"service_time": 420
}
Additional data related to a task.
| Field | Type | Required | Description |
|---|---|---|---|
| time_window | Time Window | yes | Time period during which the task can be performed |
| location | Location | yes | Location where the sub task must be performed |
| service_time | Integer | yes | Duration of the subtask in seconds |
Time Window
Example Time Window object
{
"start": "2018-05-16T12:00:00.000Z",
"end": "2018-05-16T15:00:00.000Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
| start | Datetime | yes | Start datetime of the time window |
| end | Datetime | yes | End datetime of the time window |
Driver
Example Driver object
{
"id": 1,
"name": "John",
"cost_per_hour": 5,
"start_location": <Location>,
"end_location": null,
"task_id_whitelist": [],
"schedule": [<TimeWindow>],
"shift_max_timestamp": "2018-05-16T12:00:00.000Z",
"skills": [<Skill>, <Skill>, ...],
"vehicle": <Vehicle>,
"max_working_hours": {
"target": 4,
"max_allowed": 8
},
"max_number_of_jobs": 4
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | Integer | yes | Identifier of the driver |
| name | String | no | Name of the driver |
| cost_per_hour | Float | yes | Cost per hour of work of the driver |
| start_location | Location | yes | Location where the driver will start |
| end_location | Location | no | Location where the driver will end |
| schedule | Time Window array | yes | List of time windows that represent the time periods when the driver is available to perform tasks |
| shift_max_timestamp | Datetime | no | Defines a maximum timestamp for the user return to depot in order to give the vehicle to the second shift driver |
| vehicle | Vehicle | yes | Vehicle used by the driver to perform tasks |
| skills | Skill array | yes | List of skills that the driver has |
| max_working_hours | object | yes | Object containing two float/number attributes: target and max_allowed. The target is the ideal number of hours the driver will spend performing tasks and the max_allowed is the maximum number of hours. For more information see here. |
| max_driving_hours | Float/Number | no | Maximum total number of hours that the user can drive |
| max_number_of_jobs | Integer | no | Maximum amount of tasks the driver can perform as documented here |
Vehicle
Example Vehicle object
{
"id": 1,
"name": "John's car",
"capacities": [<Vehicle Capacity>, <Vehicle Capacity>, ...],
"cost_per_km": 3.5
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | Integer | yes | Identifier of the vehicle |
| name | String | no | Name of the vehicle |
| capacities | Vehicle Capacity array | yes | Capacities the vehicle has. |
| cost_per_km | Float or null | yes | Cost per km of the vehicle. Can be null, but if one vehicle specifies the cost_per_km, all others must also do so. |
Settings
Example Settings object
{
"constraints": <Constraints>,
"speed_factor": 2.3,
"num_iterations": 300
}
| Field | Type | Required | Description |
|---|---|---|---|
| constraints | Constraints | no | Relative algorithm trade-off weights |
| speed_factor | Float | no | Described here |
| num_iterations | Integer | no | Number of iterations the optimizer algorithm will perform as described here |
Constraints
Example Constraints object
{
"PaybackSoft": 1.0,
"ComfortSoft": 2.0,
"MaxWorkingHours": 1.0
}
| Field | Type | Required | Description |
|---|---|---|---|
| PaybackSoft | Float | no | Described here |
| ComfortSoft | Float | no | Described here |
| MaxWorkingHours | Float | no | Described here |
Location
Example Location object
{
"id": 3,
"name": "Fórum Coimbra",
"latitude": 40.2116461,
"longitude": -8.4454925
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | Integer | yes | Identifier of the location |
| name | String | no | Name of the location |
| latitude | Float | yes | Latitude of the location between -85.05112878 and 85.05112878 |
| longitude | Float | yes | Longitude of the location between -180 and 180 |
Skill
Example Skill object
{
"id": 1,
"name": "Heavy vehicle licence"
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | Integer | yes | Identifier of the skill |
| name | String | yes | Name of the skill |
| required_in_consecutive_pickups | Boolean | false | Indicates whether consecutive tasks require this skill |
Capacity
Example Capacity object
{
"id": 1,
"name": "Seats",
"value": 2,
}
| Field | Type | Required | Description |
|---|---|---|---|
| id | Integer | yes | Identifier of the capacity |
| name | String | no | Name of the capacity |
| value | String | yes | Value of the capacity |
Vehicle Capacity
Example Vehicle Capacity object
{
"id": 1,
"name": "Seats",
"value": 1,
"comfort_min": 2,
"comfort_max": 4
}
Has the same attributes as the Capacity plus some external attributes to configure the comfort needs for a vehicle.
| Field | Type | Required | Description |
|---|---|---|---|
| id | Integer | yes | Identifier of the capacity |
| name | String | no | Name of the capacity |
| value | String | yes | Value of the capacity |
| comfort_min | integer | no | Described here |
| comfort_max | integer | no | Described here |
Complete input example
{
"api_version": 1,
"drivers": [
{
"id": 1,
"name": "John",
"cost_per_hour": 5,
"start_location": {
"id": 1,
"name": "IPN",
"latitude": 40.192167,
"longitude": -8.4163527
},
"end_location": null,
"task_id_whitelist": [],
"schedule": [
{
"start": "2018-05-16T09:00:00.000Z",
"end": "2018-05-16T18:00:00.000Z"
}
],
"skills": [
{
"id": 1,
"name": "Heavy vehicle licence"
},
{
"id": 2,
"name": "Speak english"
}
],
"vehicle": {
"id": 1,
"name": "John's car",
"capacities": [
{
"id": 1,
"name": "Seats",
"value": 1,
"comfort_min": 1
},
{
"id": 2,
"name": "Luggage places",
"value": 1
}
],
"cost_per_km": null
},
"max_working_hours": {
"target": 4,
"max_allowed": 8
},
"max_number_of_jobs": 4
}
],
"tasks": [
{
"id": 1,
"name": "Example Task 1",
"type": "shipment",
"priority": "normal",
"payment": 20.410547032375042,
"capacities": [
{
"id": 1,
"name": "Seats",
"value": 1
},
{
"id": 2,
"name": "Luggage places",
"value": 1
}
],
"skills": [
{
"id": 2,
"name": "Speak english"
}
],
"pickup_data": {
"time_window": {
"start": "2018-05-16T09:00:00.000Z",
"end": "2018-05-16T12:00:00.000Z"
},
"location": {
"id": 3,
"name": "Fórum Coimbra",
"latitude": 40.2116461,
"longitude": -8.4454925
},
"service_time": 720
},
"delivery_data": {
"time_window": {
"start": "2018-05-16T12:00:00.000Z",
"end": "2018-05-16T15:00:00.000Z"
},
"location": {
"id": 4,
"name": "Alma Shopping Coimbra",
"latitude": 40.2046885,
"longitude": -8.4097995
},
"service_time": 420
}
}
],
"atomic_task_ids": [],
"settings": {
"constraints": {
},
"speed_factor": 2
}
}
Output Objects
Plan Result
Example Plan Result object
{
"drivers": [<Driver>, <Driver>, ...],
"unassigned_tasks": [<Task>, <Task>, ...]
}
| Field | Type | Description |
|---|---|---|
| drivers | Driver | List of drivers and its assigned tasks |
| unassigned_tasks | Task | Array of Task which were not assigned to any driver |
Task
Example Task object
{
"id": 1,
"type": "shipment",
"name": "Example Task 1",
"subtype": "pickup",
"start_timestamp": null,
"end_timestamp": null,
"location": <Location>,
"cargo":
"failed_reasons": [<Fail Reason>, ...]
}
| Field | Type | Description |
|---|---|---|
| id | Integer | Identification of the task |
| name | String | Name of the task |
| type | String | Type of the task. One of (start, end, break, shipment, service) |
| subtype | String | Subtype of the task. If the task type is shipment the subtype can be pickup or delivery. |
| location | Location | Location where the task is performed |
| failed_reasons | Fail Reason array | Statistics from the algorithm which may help explain why a task was left unassigned |
| start_timestamp | Datetime | When the task starts to be performed |
| end_timestamp | Datetime | When the task is completed |
Location
This contains exactly the same properties as the input Location object.
Example Location object
{
"id": 3,
"name": "Fórum Coimbra",
"latitude": 40.2116461,
"longitude": -8.4454925
}
| Field | Type | Description |
|---|---|---|
| id | Integer | yes |
| name | String | no |
| latitude | Float | yes |
| longitude | Float | yes |
Vehicle
Example Vehicle object
{
"id": 3,
}
| Field | Type | Description |
|---|---|---|
| id | Integer | Identification of the vehicle |
Fail Reason
Example Fail Reason object
{
"reason": "Task time window(s) do not sufficiently overlap driver free time windows",
"count": 153,
"percentage": 100,
"cumulative_percentage": 100
}
| Field | Type | Description |
|---|---|---|
| reason | String | Human readable description of the reason |
| count | Integer | How many times the task could not be assigned, during the optimization, because of this reason |
| percentage | Integer | The percentage of total failures that are due to this reason |
| cumulative_percentage | Integer | A cumulative percentage of the percent of total failures (when in an array) |
Full output example
{
"drivers": [
{
"id": 1,
"name": "John",
"vehicle": {
"id": 1
},
"tasks": [
{
"id": -1,
"type": "start",
"name": null,
"subtype": null,
"start_timestamp": null,
"end_timestamp": "2018-05-16T09:00:00.000Z",
"location": {
"id": 1,
"name": "IPN",
"latitude": 40.192167,
"longitude": -8.4163527
}
},
{
"id": 2,
"type": "shipment",
"name": "Example Task 2",
"subtype": "pickup",
"start_timestamp": "2018-05-16T10:00:00.000Z",
"end_timestamp": "2018-05-16T10:12:00.000Z",
"location": {
"id": 3,
"name": "Fórum Coimbra",
"latitude": 40.2116461,
"longitude": -8.4454925
},
"failed_reasons": [
{
"reason": "Vehicle cannot fulfill capacities (shipment)",
"count": 30,
"percentage": 40,
"cumulative_percentage": 40
},
{
"reason": "Task time window(s) do not sufficiently overlap driver free time windows",
"count": 45,
"percentage": 60,
"cumulative_percentage": 100
}
]
},
{
"id": -1,
"type": "break",
"name": null,
"subtype": null,
"start_timestamp": "2018-05-16T13:00:00.000Z",
"end_timestamp": "2018-05-16T14:00:00.000Z",
"location": null
},
{
"id": 2,
"type": "shipment",
"name": "Example Task 2",
"subtype": "delivery",
"start_timestamp": "2018-05-16T14:00:00.000Z",
"end_timestamp": "2018-05-16T14:07:00.000Z",
"location": {
"id": 4,
"name": "Alma Shopping Coimbra",
"latitude": 40.2046885,
"longitude": -8.4097995
},
"failed_reasons": [
{
"reason": "Vehicle cannot fulfill capacities (shipment)",
"count": 30,
"percentage": 40,
"cumulative_percentage": 40
},
{
"reason": "Task time window(s) do not sufficiently overlap driver free time windows",
"count": 45,
"percentage": 60,
"cumulative_percentage": 100
}
]
},
{
"id": -1,
"type": "end",
"name": null,
"subtype": null,
"start_timestamp": "2018-05-16T14:07:00.000Z",
"end_timestamp": "2018-05-16T14:14:50.000Z",
"location": null
}
]
}
],
"unassigned_tasks": [
{
"id": 1,
"type": "shipment",
"name": "Example Task 1",
"subtype": "pickup",
"start_timestamp": null,
"end_timestamp": null,
"location": {
"id": 3,
"name": "Fórum Coimbra",
"latitude": 40.2116461,
"longitude": -8.4454925
},
"failed_reasons": [
{
"reason": "Vehicle cannot fulfill capacities (shipment)",
"count": 108,
"percentage": 60,
"cumulative_percentage": 60
},
{
"reason": "Task time window(s) do not sufficiently overlap driver free time windows",
"count": 72,
"percentage": 40,
"cumulative_percentage": 100
}
]
}
]
}
API Endpoints
In this section we describe the two endpoints that our API provides. One for creating a new plan optimization and another to check its progress and result after being optimized. When invoking these endpoints, some errors may occur. We document them here.
Create plan
HTTP Request
POST /api/plans
Query Parameters
| Parameter | Required | Description |
|---|---|---|
| key | yes | API key of the client performing the request |
| type | yes | Type of the plan: sync, polling or webhook |
| callback_url | if type is webhook | URL to post the result of the planning |
Body
The body is an object of type Plan Object in JSON format.
Response
Example response to sync planning
{
"id": "837da5d5a27542afbc911855cfcc582d",
"started_at": "2018-06-04T10:45:01.529Z",
"status": "In queue",
"finished_at": "2018-06-04T10:55:02.622Z",
"result": <Output Plan Object>
}
Example response to async planning
{
"id": "837da5d5a27542afbc911855cfcc582d",
"started_at": "2018-06-04T10:45:01.529Z",
"status": "In queue",
"finished_at": null,
"result": null
}
| Field | Description |
|---|---|
| id | Identification of the plan |
| started_at | When the plan was created |
| status | The status of the plan, as described here |
| finished_at | An error occurred while planning. |
| result | Result returned by the solvers algorithm. Typically a Plan Result object if the plan was successfully planed, or a object containing an error message if the plan was not successfully planed. |
In case of a polling and webhook plan types, only id, started_at and status will be sent. finished_at and result will be available as soon as the plan is completed and by accessing the Check Plan endpoint.
In case of the webhook type, the response body, in full, after planning, is posted to the Callback URL.
Possible planing status
| Status | Description |
|---|---|
| In queue | The plan was submitted and is queued to be solved |
| Planning | The plan is being planned |
| Planning success | The plan was successfully planned |
| Planning error | An error occurred while planning. |
| Timeout | The planning took too long to process and resulted in a timeout |
Get plan details
HTTP Request
GET /api/plans/<plan_id>
Query Parameters
| Parameter | Description |
|---|---|
| plan_id | Id of the plan previously returned by the Create Plan endpoint |
| key | API key of the client performing the request |
| Field | Description |
|---|---|
| id | Identification of the plan |
| started_at | When the plan was created |
| status | The status of the plan, as described here |
| finished_at | An error occurred while planning. |
| result | Result returned by the solvers algorithm. Typically a Plan Result object if the plan was successfully planed, or an object containing an error message if the plan was not successfully planed. |
API Errors
When making requests to the API endpoints (creating or checking a plan), the response can be an error (due to invalid input, planning errors, network errors, etc). In this section we describe the different types of errors that can occur.
When an error happens, we send a response in the format of:
{
"status": "Error",
"error": ...,
"detail": ...,
}
Possible errors are presented in the next table. Some are tangent to all the endpoints, others are specific to a endpoint.
| Error | HTTP Status | Observations |
|---|---|---|
| Authentication Error | 401 | Ensure you have a valid key and you not exceeded your monthly request limit |
| Invalid JSON object | 400 | The JSON you submited is not valid JSON |
| Invalid input | 422 | The JSON you submitted is valid, but does not implement the specification defined here |
| Invalid plan type | 400 | You did not specify one of the available plan types (sync, polling or webhook) |
| Callback url not provided | 400 | Plan type is webhook but you did not pass callback_url |
| Invalid callback url | 400 | callback_url is not a valid url |
Validation Errors
Example of a response with validation errors
{
"status": "Error",
"errors": [
{
"error": "'123' is not a 'date-time'",
"field": "plan.tasks[0].pickup_data.time_window.start"
},
{
"error": "140.2116461 is greater than the maximum of 85.05112878",
"field": "plan.tasks[0].pickup_data.location.latitude"
}
]
}
While processing your input plan, the API will validate the data to ensure it matches our Plan Object specification. In case it detects any violation of the specification it will return an object with status = Error and a list of error objects.
An error object is composed by the error message and the field it refers to:
{
"error": ERROR_MESSAGE
"field": FIELD_NAME
}
Planning Errors
Sometimes an unexpected planning error can occur. When that happens, the plan status will be Planning error and the plan result will contain an object similar to this:
{
"error": "An unexpected pre-algorithm error has occured. Please contact the development team."
}