← Back to home

Integration · Geotab

Geotab + DirtFleet.

Geotab's telematics + DirtFleet's maintenance. A first-party Geotab adapter is on the roadmap (see /integrations/directory) — the patterns below are customer-side MyGeotab SDK scripts you can run today against your own database, calling the public DirtFleet API to push the data in. Same shape as Samsara: telematics on one side, the shop floor on the other, the bulk endpoints in between.

Pick a pattern

Effort vs. depth.

Most Geotab fleets start with Pattern 1 (one-shot roster sync) and graduate to Pattern 2 when they want maintenance triggers tied to real engine-hours. Pattern 3 is for shops already invested in the Geotab add-in ecosystem.

  1. Pattern 1 — Vehicle roster sync via the MyGeotab SDK

    Use Geotab's SDK to enumerate Devices on the database, then bulk-create matching DirtFleet assets. One-time or nightly cron. Same shape as the Samsara version.

    When to pick this

    First step in any Geotab → DirtFleet migration. Most fleets do this once at cutover and never re-run it.

    Show the setup
    # Python using the official mygeotab package
    # pip install mygeotab requests
    
    import mygeotab, requests, os
    
    api = mygeotab.API(
        username=os.environ["GEOTAB_USER"],
        password=os.environ["GEOTAB_PASSWORD"],
        database=os.environ["GEOTAB_DATABASE"],
    )
    api.authenticate()
    
    devices = api.get("Device", resultsLimit=2000)
    
    DF_KEY = os.environ["DIRTFLEET_API_KEY"]
    batch = []
    for d in devices:
        # Geotab's "name" is the friendly nickname; serialNumber + VIN
        # are surfaced as the device's identifying fields.
        batch.append({
            "nickname": d.get("name") or d.get("serialNumber"),
            "assetClass": "on-road",
            "vin": d.get("vehicleIdentificationNumber") or None,
            "serial": d.get("serialNumber") or None,
            "customerAssetNumber": d.get("licensePlate") or None,
        })
    
    for i in range(0, len(batch), 50):
        requests.post(
            "https://dirtfleet.app/api/v1/assets/batch",
            headers={"Authorization": f"Bearer {DF_KEY}"},
            json=batch[i:i+50],
            timeout=30,
        )
    print(f"Synced {len(batch)} Geotab devices into DirtFleet")
  2. Pattern 2 — Engine-hours capture (scheduled)

    Geotab's StatusData feed surfaces engine-hours. Pull on a schedule (15 min or hourly), post deltas to /api/v1/hours/batch. PM tracking and AUTO_PM flagging start firing on whichever interval the asset has configured.

    When to pick this

    When drivers don't log manually and Geotab is the canonical hours source. Same scheduling pattern as Samsara Pattern 2.

    Show the setup
    # Pull diagnostic IDs first (Geotab's engine-hours diagnostic id is
    # stable across databases but documented as DiagnosticIdEngineHours).
    
    import mygeotab, requests, os, json, pathlib
    
    api = mygeotab.API(username=..., password=..., database=...)
    api.authenticate()
    
    # Last-seen reading per device — persist between runs
    SEEN_FILE = pathlib.Path("last-seen.json")
    seen = json.loads(SEEN_FILE.read_text()) if SEEN_FILE.exists() else {}
    
    VEHICLE_MAP = json.loads(os.environ["GEOTAB_TO_DF_ASSETS"])  # { device_id: df_asset_id }
    
    # Latest status reading per device
    status_data = api.get(
        "StatusData",
        search={
            "diagnosticSearch": {"id": "DiagnosticEngineHoursAdjustmentId"},
            "fromDate": "2026-05-13T00:00:00Z",
        },
    )
    
    new_logs = []
    for s in status_data:
        device_id = s["device"]["id"]
        reading_hours = s["data"] / 3600.0   # Geotab returns seconds; convert
        if reading_hours <= seen.get(device_id, 0):
            continue
        df_asset = VEHICLE_MAP.get(device_id)
        if not df_asset: continue
        new_logs.append({
            "assetId": df_asset,
            "hoursReading": reading_hours,
            "note": "auto-synced from Geotab",
            "clientMutationId": f"geotab-{device_id}-{int(reading_hours)}",
        })
        seen[device_id] = reading_hours
    
    if new_logs:
        for i in range(0, len(new_logs), 100):
            requests.post(
                "https://dirtfleet.app/api/v1/hours/batch",
                headers={"Authorization": f"Bearer {os.environ['DIRTFLEET_API_KEY']}"},
                json=new_logs[i:i+100],
            )
    SEEN_FILE.write_text(json.dumps(seen))
  3. Pattern 3 — Geotab Marketplace add-in (long-term path)

    For shops that want DirtFleet inside the Geotab fleet manager's UI: build a Geotab add-in that surfaces DirtFleet flags + WO status directly in MyGeotab. Standard add-in JS bundle hits the DirtFleet REST API.

    When to pick this

    For larger fleets that already invest in Geotab's add-in ecosystem. Optional; the JSON-pull patterns above cover 95% of the value.

    Show the setup
    # Status: Roadmap — not shipped today.
    #
    # The Geotab Marketplace add-in framework takes a static JS bundle
    # that runs inside the MyGeotab iframe. The DirtFleet side is just
    # the REST API at /api/v1/* — same auth, same bearer-token shape,
    # same OpenAPI spec at /openapi.yaml. The add-in is the missing
    # piece on the Geotab side; we're not blocking on this.
    #
    # If you want this prioritized, email hello@dirtfleet.app — first
    # named customer who commits to using it is the unlock path.

Companion

Pairs with /integrations/samsara

Mixed-fleet shops sometimes have Geotab on the trucks and Samsara on the off-road equipment (or vice versa). Both integrations use the same DirtFleet bulk endpoints — /api/v1/assets/batch and /api/v1/hours/batch — so you can run both syncs against the same DirtFleet org with different VEHICLE MAPs. See /integrations/samsara for the matching Node-flavored version.

Want Pattern 3 prioritized? Email hello@dirtfleet.app.