Payroll
Calculate payroll by tracking hours or miles driven
Tracking Hours
You can track driving hours and on-duty hours using the ELD Compliance and Hours of Service features of the Samsara platform.
See the Daily Duty Status Summaries guide to learn how to pull a daily summary of on-duty and driving hours for each driver in a fleet.
See the Compliance & ELD page for an overview of other Hours of Service related endpoints.
Tracking Miles
You can track the miles driven using two APIs:
- Call the
/fleet/drivers/vehicle-assignments
API to get the list of driving segments that each driver made over a given time range. - Call the
/fleet/vehicles/stats/history
API to get the miles driven on for each driving segment returned in Step 1.
Step 1
The /fleet/drivers/vehicle-assignments
API returns the list of vehicles that each driver drove during a requested time range. It also returns the start and end times during which the driver drove each vehicle.
Example Request:
curl --request GET 'https://api.samsara.com/fleet/drivers/vehicle-assignments' \
-d startTime='2020-11-01T08:00:00Z' \
-d endTime='2020-11-02T08:00:00Z' \
-G \
--header 'Authorization: Bearer <<token>>'
The response will be in JSON format. The data
array will have an entry for each driver in the fleet. The vehicleAssignments
array will contain an entry for each block of time that the driver drove a vehicle. This is known as a "vehicle assignment". Each assignment will contain details about the vehicle
and the startTime
and endTime
of that driving segment.
Example Response:
{
"data": [
{
"id": "1654973",
"name": "Tyler Freckmann",
"driverActivationStatus": "active",
"externalIds": {
"payrollSys1": "[email protected]"
},
"vehicleAssignments": [
{
"isPassenger": false,
"vehicle": {
"id": "281474977075805",
"name": "Little Red",
"externalIds": {
"samsara.serial": "G9MTH7CNKZ",
"samsara.vin": "JTMBK32V895081147"
}
},
"startTime": "2020-11-01T18:16:17.152Z",
"endTime": "2020-11-01T18:54:07.996Z",
"assignmentType": "driverApp"
}
]
}
],
"pagination": {
"endCursor": "",
"hasNextPage": false
}
}
See the Pagination guide for details on working with the pagination
object.
🚧Time Limit
The
GET /fleet/drivers/vehicle-assignments
endpoint supports a maximum of 7 days for the query.
Step 2
Once you've retrieved the vehicle assignments and driving segments of each driver, you'll want to call the /fleet/vehicles/stats/history
endpoint to calculate the distance driven during each of those driving segments.
To calculate distance driven, you'll want to request both obdOdometerMeters
and gpsOdometerMeters
data. This allows you to get data on vehicles that Samsara can read directly from on-board diagnostics and for vehicles that require a GPS approximation. See this knowledge base article for more details on OBD vs GPS odometer readings.
Using the vehicle.id
, startTime
, and endTime
fields from the example response above, we can retrieve the odometer readings for that particular driving segment.
Example Request:
curl --request GET 'https://api.samsara.com/fleet/vehicles/stats/history' \
-d startTime='2020-11-01T18:16:17.152Z' \
-d endTime='2020-11-01T18:54:07.996Z' \
-d vehicleIds='281474977075805' \
-d types='obdOdometerMeters,gpsOdometerMeters' \
-G \
--header 'Authorization: Bearer <<token>>'
The response will be in JSON format. The data
array will contain an entry for each vehicle requested. In this case there is only one entry because we only requested data on the vehicle for the desired driving segment.
Depending on the type of odometer reading available from the vehicle, there may be an obdOdometerMeters
array, a gpsOdometerMeters
array, or both (if both are available). Using the values from these arrays, you can calculate the distance the vehicle was driven over the given driving segment.
Example Response:
{
"data": [
{
"obdOdometerMeters": [
{
"time": "2020-11-01T18:18:20Z",
"value": 241094660
},
...
...
...
{
"time": "2020-11-01T18:53:54Z",
"value": 241156687
}
],
"id": "281474977075805",
"name": "Little Red"
}
],
"pagination": {
"endCursor": "",
"hasNextPage": false
}
}
You can then use the first and last entry of the odometers array to calculate the distance driven during that driving segment.
🚧Accuracy
You may notice that the first timestamp in the example response above is
2020-11-01T18:18:20Z
, which is around 2 minutes after thestartTime
provided in the request (2020-11-01T18:16:17.152Z
).This is because the
GET /fleet/vehicle/stats/history
endpoint returns the vehicle odometer readings during the requested time range, so the first odometer reading during that time range may not line up exactly with thestartTime
parameter.This discrepancy should not cause too much of an issue, because the Samsara gateway reports odometer readings approximately every 30 seconds or 1000 meters of driving, which should cause only a slight underapproximation of meters driven during the request driving segment.
If you wish to get the most recent reading before the
startTime
(thereby calculating a slight overapproximation), you can use theGET /fleet/vehicles/stats
endpoint. See the Alternatives section for more details.Both strategies will be approximation due to the fact that the vehicle gateway collects odometer readings at specific intervals which do not always line up exactly with the driving time segments from the driver ↔️ vehicle assignments.
See the Pagination guide for details on working with the pagination
object.
Sample Code
import requests
import datetime
import time
from pprint import pprint
base_url = "https://api.samsara.com"
headers = {"Authorization": "Bearer <<token>>"}
def get_driver_meters_driven(start_time, end_time):
drivers = {}
params = {
"startTime": start_time,
"endTime": end_time
}
has_next_page = True
while has_next_page:
response = requests.request(
'GET',
base_url + "/fleet/drivers/vehicle-assignments",
headers=headers,
params=params
).json()
for driver in response["data"]:
meters_driven = 0
for vehicle_assignment in driver["vehicleAssignments"]:
if vehicle_assignment["startTime"] < start_time:
vehicle_assignment["startTime"] = start_time
if not vehicle_assignment["isPassenger"]:
meters_driven += get_vehicle_meters_driven(
vehicle_assignment["vehicle"]["id"],
vehicle_assignment["startTime"],
vehicle_assignment["endTime"]
)
drivers[driver["name"]] = meters_driven
has_next_page = response["pagination"]["hasNextPage"]
params["after"] = response["pagination"]["endCursor"]
return drivers
def get_vehicle_meters_driven(vehicle_id, start_time, end_time):
start_odometer = 0
end_odometer = 0
if end_time == "":
end_time = datetime.datetime.utcnow().isoformat() + "Z"
params = {
"startTime": start_time,
"endTime": end_time,
"vehicleIds": vehicle_id,
"types": "obdOdometerMeters,gpsOdometerMeters"
}
has_next_page = True
while has_next_page:
response = requests.request(
'GET',
base_url + "/fleet/vehicles/stats/history",
headers=headers,
params=params
).json()
has_next_page = response["pagination"]["hasNextPage"]
if len(response["data"]) != 0:
vehicle_data = response["data"][0]
if "obdOdometerMeters" in vehicle_data:
odometer_readings = vehicle_data["obdOdometerMeters"]
else:
odometer_readings = vehicle_data["gpsOdometerMeters"]
if "after" not in params:
start_odometer = odometer_readings[0]["value"]
if not has_next_page:
end_odometer = odometer_readings[-1]["value"]
params["after"] = response["pagination"]["endCursor"]
return end_odometer - start_odometer
if __name__ == "__main__":
driver_meters = get_driver_meters_driven("2020-11-18T08:00:00Z", "2020-11-19T08:00:00Z")
pprint(driver_meters)
Alternatives
The Samsara gateway reports odometer readings approximately every 30 seconds or 1000 meters of driving. This means that the odometer reading timestamps will not line up exactly with the startTime
and endTime
of the driving segments from the driver ↔️ vehicle assignments.
Calculating meters driven using the GET /fleet/vehicles/stats/history
endpoint will always result in a slight underapproximation because the first odometer reading returned in the array will be slightly after the startTime
provided in the request.
If you wish to get the most recent reading before the startTime
(thereby calculating a slight overapproximation), you can use the GET /fleet/vehicles/stats
endpoint:
curl --request GET 'https://api.samsara.com/fleet/vehicles/stats/history' \
-d time='2020-11-01T18:16:17.152Z' \
-d vehicleIds='281474977075805' \
-d types='obdOdometerMeters,gpsOdometerMeters' \
-G \
--header 'Authorization: Bearer <<token>>'
The response will contain the most recent odometer reading that the vehicle reported before the time
parameter. (Note that this timestamp may be well before the time
parameter if the vehicle was not active for a while before the requested time
.)
{
"data": [
{
"id": "281474977075805",
"name": "Little Red",
"gpsOdometerMeters": {
"time": "2020-10-31T20:08:02Z",
"value": 241093646
}
}
],
"pagination": {
"endCursor": "",
"hasNextPage": false
}
}
Alternative Solution Sample Code
import requests
import datetime
import time
from pprint import pprint
base_url = "https://api.samsara.com"
headers = {"Authorization": "Bearer <<token>>"}
def get_driver_meters_driven(start_time, end_time):
drivers = {}
params = {
"startTime": start_time,
"endTime": end_time
}
has_next_page = True
while has_next_page:
response = requests.request(
'GET',
base_url + "/fleet/drivers/vehicle-assignments",
headers=headers,
params=params
).json()
for driver in response["data"]:
meters_driven = 0
for vehicle_assignment in driver["vehicleAssignments"]:
if vehicle_assignment["startTime"] < start_time:
vehicle_assignment["startTime"] = start_time
if not vehicle_assignment["isPassenger"]:
meters_driven += get_odometer(
vehicle_assignment["vehicle"]["id"],
vehicle_assignment["endTime"]
) - get_odometer(
vehicle_assignment["vehicle"]["id"],
vehicle_assignment["startTime"]
)
drivers[driver["name"]] = meters_driven
has_next_page = response["pagination"]["hasNextPage"]
params["after"] = response["pagination"]["endCursor"]
return drivers
def get_odometer(vehicle_id, time):
odometer = 0
if time == "":
time = datetime.datetime.utcnow().isoformat() + "Z"
params = {
"time": time,
"vehicleIds": vehicle_id,
"types": "obdOdometerMeters,gpsOdometerMeters"
}
response = requests.request(
'GET',
base_url + "/fleet/vehicles/stats",
headers=headers,
params=params
).json()
if len(response["data"]) != 0:
vehicle_data = response["data"][0]
if "obdOdometerMeters" in vehicle_data:
odometer = vehicle_data["obdOdometerMeters"]["value"]
else:
odometer = vehicle_data["gpsOdometerMeters"]["value"]
return odometer
if __name__ == "__main__":
driver_meters = get_driver_meters_driven("2020-11-18T08:00:00Z", "2020-11-19T08:00:00Z")
pprint(driver_meters)
Updated over 3 years ago