Mileage and distance

How to calculate mileage and distance traveled.

When working with Samsara’s platform to track vehicle mileage, it’s important to understand the nuances of how mileage is measured and reported. You can obtain three key mileage-related data points from the Vehicle Stats History API:

  1. obdOdometerMeters – The odometer reading reported by the vehicle’s onboard diagnostics (ECU).
  2. gpsDistanceMeters – The distance traveled (in meters) since the Samsara Vehicle Gateway was installed, based on GPS calculations.
  3. gpsOdometerMeters – The GPS-based “odometer” reading, which can be manually edited by the customer in Samsara to correct odometer readings for vehicle maintenance and other use cases.

Recommendation: use obdOdometerMeters

We recommend using obdOdometerMeters as your primary mileage data source. Fleets commonly rely on the ECU odometer read to track usage and service intervals. However, there are scenarios where the vehicle's ECU does not provide diagnostic coverage for odometer readings. In these cases:

  1. Fallback to gpsDistanceMeters: If no obdOdometerMeters value is available, use gpsDistanceMeters to calculate distance traveled for the time period.
  2. Avoid gpsOdometerMeters for general mileage: This value is only used if (a) the ECU does not report odometer data and (b) a customer has manually edited in a "starting odometer" in Samsara. While useful for fleet maintenance intervals within Samsara, this value may not reflect the true mileage unless your use case specifically requires the manually edited reading.

How to retrieve distance data

The example below shows how to pull odometer and distance information for vehicles over a specified time period.

Example request

curl --request GET \
     --url "https://api.samsara.com/fleet/vehicles/stats/history?vehicleIds=123&types=obdOdometerMeters,gpsOdometerMeters,gpsDistanceMeters&endTime=2025-03-19T11:27:31Z&startTime=2025-03-19T11:25:31Z" \
     --header "Accept: application/json"
     --header "Authorization: Bearer <YOUR_API_TOKEN>"

Example response

{
  "data": [
    {
      "id": "123",
      "name": "Truck",
      "externalIds": {
        "samsara.serial": "G122SBGJT9",
        "samsara.vin": "111"
      },
      "obdOdometerMeters": {
        "time": "2025-03-20T12:13:38Z",
        "value": 188982000
      },
      "gpsOdometerMeters": {
        "time": "2025-03-20T16:14:20Z",
        "value": 190111382
      },
      "gpsDistanceMeters": {
        "time": "2025-03-20T16:14:20Z",
        "value": 1810088.2739995187
      }
    }
  ],
  "pagination": {
    "endCursor": "",
    "hasNextPage": false
  }
}

  • obdOdometerMeters.value: Odometer reading in meters from the onboard diagnostics system.
  • gpsDistanceMeters.value: The distance in meters traveled based on GPS since the Vehicle Gateway was installed.
  • gpsOdometerMeters.value: A GPS-based odometer that may have been manually overridden in the Samsara UI.

How to calculate distance for a time range

  1. Check obdOdometerMeters:
    If available, calculate the difference between the odometer readings at the start and end of the period.
  2. Use gpsDistanceMeters if obdOdometerMeters is missing:
    • If no ECU reading is present, you can use gpsDistanceMeters to see how much the vehicle traveled during the same period.
    • Keep in mind, gpsDistanceMeters accumulates from the time the gateway is installed, so typically you would calculate distance by comparing values from two timestamps.
  3. Do not rely on gpsOdometerMeters for general usage:
    • This is primarily for manual maintenance interval tracking. Unless your specific workflow requires the manual data entered by the fleet manager, avoid using gpsOdometerMeters as a general source of truth for mileage.

Handling potential data gaps

  • Missing odometer readings:
    If the ECU read is missing or invalid (e.g., certain vehicles do not report an odometer), always fall back on gpsDistanceMeters.
  • Jumps in odometer values:
    If you see a large jump in obdOdometerMeters, verify that the Vehicle Gateway was properly associated and configured for this vehicle, and that a different vehicle wasn’t accidentally assigned the same gateway.

Putting It All Together

Below is an example that might be used to calculate daily distance traveled:

import requests
import json

class MileageService:
    API_BASE = "https://api.samsara.com"

    def __init__(self, api_token):
        self.api_token = api_token

    def fetch_distance_for_vehicle(self, vehicle_id, start_time, end_time):
        url = f"{self.API_BASE}/fleet/vehicles/stats/history"
        params = {
            "vehicleIds": vehicle_id,
            "types": "obdOdometerMeters,gpsOdometerMeters,gpsDistanceMeters",
            "startTime": start_time,
            "endTime": end_time
        }
        headers = {
            "Accept": "application/json",
            "Authorization": f"Bearer {self.api_token}"
        }

        response = requests.get(url, params=params, headers=headers)
        response.raise_for_status()

        data = response.json().get("data")
        if not data:
            return 0

        vehicle_data = data[0]
        obd_value = vehicle_data.get("obdOdometerMeters", {}).get("value")
        gps_distance_value = vehicle_data.get("gpsDistanceMeters", {}).get("value")

        # Fallback to GPS distance if obd_value is missing or None
        if obd_value is not None:
            # In a real-world implementation, you’d compare the current OBD reading
            # to a previously recorded OBD reading to compute the difference over time.
            distance = "Use the difference between current and previous OBD reads"
        elif gps_distance_value is not None:
            distance = gps_distance_value
        else:
            distance = 0

        return distance

# app/services/mileage_service.rb
require 'net/http'
require 'uri'
require 'json'

class MileageService
  API_BASE = "https://api.samsara.com"

  def initialize(api_token)
    @api_token = api_token
  end

  def fetch_distance_for_vehicle(vehicle_id, start_time, end_time)
    uri = URI("#{API_BASE}/fleet/vehicles/stats/history")
    uri.query = URI.encode_www_form({
      vehicleIds: vehicle_id,
      types: "obdOdometerMeters,gpsOdometerMeters,gpsDistanceMeters",
      startTime: start_time,
      endTime: end_time
    })

    request = Net::HTTP::Get.new(uri)
    request["Accept"] = "application/json"
    request["Authorization"] = "Bearer #{@api_token}"

    response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    data = JSON.parse(response.body)["data"]&.first
    return 0 unless data

    obd_value = data.dig("obdOdometerMeters", "value")
    gps_distance_value = data.dig("gpsDistanceMeters", "value")

    # Fallback to GPS distance if obd_value is missing or nil
    distance = if obd_value
                 # Your logic for converting OBD odometer values into a period-specific distance
                 # Example: Subtract the start odometer from the end odometer to get the distance
                 # In a real implementation, you might store historical odometer readings
                 # and compute the delta between them for the time range.
                 "Use the difference between current and previous OBD reads"
               elsif gps_distance_value
                 gps_distance_value
               else
                 0
               end

    distance
  end
end

By combining these data fields, you can build robust mileage-tracking features in your integration:

  • Prefer ECU odometer (obdOdometerMeters.value) where available.
  • Fallback to GPS distance (gpsDistanceMeters.value) in the absence of ECU coverage.
  • Avoid manual “GPS odometer” (gpsOdometerMeters.value) unless your specific workflow requires it for maintenance logs.

This approach ensures that you produce accurate, consistent mileage calculations in most fleet management scenarios.