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:

  1. Call the /fleet/drivers/vehicle-assignments API to get the list of driving segments that each driver made over a given time range.
  2. 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 the startTime 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 the startTime 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 the GET /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)