← Back to home

Integration · Samsara

Samsara + DirtFleet.

Samsara handles telematics + driver workflows; DirtFleet handles maintenance, PM, work orders, and tool tracking. Three patterns to wire them together — from a one-shot roster sync to fully automated engine-hours capture and DTC-to-flag routing. The first-party adapter is scaffolded (lib/oem/vendors/samsara.ts) and activates with the first paying customer's API token; the code samples below are the customer-side scripts you can run today against your Samsara account.

Why both

Different best-of-breed surfaces.

Samsara's telematics is the de-facto industry surface for on-road fleet visibility — location, hours, fuel, driver behavior. DirtFleet is built around the shop floor: maintenance scheduling, work orders, hour-based PM, tool tracking, repair-log P&L. Most heavy-equipment shops we talk to want both, with Samsara feeding telemetry into DirtFleet where the maintenance workflows live.

For pure off-road fleets (excavators, dozers, gensets) Samsara is overkill. For pure on-road compliance (HOS, IFTA) DirtFleet is the wrong tool — we're not an ELD. The interesting middle is the mixed fleet: trucks + trailers + yellow iron + tools, where Samsara on the trucks translates cleanly into DirtFleet for everything else.

Pick a pattern

From metadata sync to DTC routing.

  1. Pattern 1 — Vehicle metadata sync (works today via Samsara API)

    Pull your Samsara vehicle list and create matching DirtFleet assets. Keep names, VINs, license plates in sync so the same identifier works across both systems. One-way: Samsara is source-of-truth for the roster.

    When to pick this

    When you've standardized on Samsara for vehicle management but want DirtFleet to handle maintenance + PM + work orders + tool tracking. Most common pattern.

    Show the setup
    # Cron-driven sync (run nightly): read Samsara → upsert DirtFleet
    import requests
    
    SAMSARA_TOKEN = os.environ["SAMSARA_API_TOKEN"]
    DF_KEY = os.environ["DIRTFLEET_API_KEY"]
    
    # 1. Pull all vehicles from Samsara
    r = requests.get(
        "https://api.samsara.com/fleet/vehicles",
        headers={"Authorization": f"Bearer {SAMSARA_TOKEN}"},
        timeout=30,
    )
    r.raise_for_status()
    vehicles = r.json()["data"]
    
    # 2. Build a DirtFleet bulk-create batch
    batch = []
    for v in vehicles:
        batch.append({
            "nickname": v.get("name") or v.get("externalIds", {}).get("samsara.serial", "Unknown"),
            "assetClass": "on-road",                  # Samsara vehicles default to road
            "vin": v.get("vin"),
            "customerAssetNumber": v.get("externalIds", {}).get("customer.id"),
        })
    # Chunk to /api/v1/assets/batch's cap of 50
    for i in range(0, len(batch), 50):
        r = requests.post(
            "https://dirtfleet.app/api/v1/assets/batch",
            headers={"Authorization": f"Bearer {DF_KEY}"},
            json=batch[i:i+50],
            timeout=30,
        )
        print(r.json()["summary"])
  2. Pattern 2 — Engine-hours auto-log (works today, ~50 LOC)

    Poll Samsara's engine-hours feed every 15 minutes; for each delta, POST a DirtFleet hours log. Now PM tracking + auto-flag-on-threshold work without drivers having to log manually.

    When to pick this

    Fleets where mechanics are happy with DirtFleet but drivers won't reliably log meter readings. Samsara already has the hours from the telematics module; DirtFleet treats them as canonical.

    Show the setup
    # Sketch: 15-min cron that reads engine-hours and posts deltas
    const SAMSARA = process.env.SAMSARA_API_TOKEN;
    const DF = process.env.DIRTFLEET_API_KEY;
    const VEHICLE_MAP = JSON.parse(process.env.SAMSARA_TO_DF_ASSETS); // { samsaraId: dfAssetId }
    
    // 1. Pull current odometer + engine-hours snapshot from Samsara
    const r = await fetch(
      "https://api.samsara.com/fleet/vehicles/stats?types=engineHours",
      { headers: { Authorization: `Bearer ${SAMSARA}` } },
    );
    const snapshot = await r.json();
    
    // 2. Cross-reference with last-known reading per vehicle (you maintain
    //    this in a tiny key-value store — Redis, a JSON file on disk, etc).
    const lastSeen = await loadLastSeen();
    
    // 3. For each vehicle whose engine-hours advanced, POST a DirtFleet hours log
    const newLogs = [];
    for (const v of snapshot.data) {
      const reading = v.engineHours?.value;
      if (!reading) continue;
      if (reading <= (lastSeen[v.id] ?? 0)) continue;
      newLogs.push({
        assetId: VEHICLE_MAP[v.id],
        hoursReading: reading,
        note: "auto-synced from Samsara",
        clientMutationId: `samsara-${v.id}-${reading}`, // idempotency
      });
      lastSeen[v.id] = reading;
    }
    
    // 4. Bulk-import (partial success — bad rows surface per-entry)
    await fetch("https://dirtfleet.app/api/v1/hours/batch", {
      method: "POST",
      headers: { Authorization: `Bearer ${DF}`, "Content-Type": "application/json" },
      body: JSON.stringify(newLogs),
    });
    
    await saveLastSeen(lastSeen);
  3. Pattern 3 — DTC routing (scaffolded; activates with first customer)

    Samsara surfaces DTCs (diagnostic trouble codes) from the OBD/J1939 port; DirtFleet has a DTC narrative AI that can interpret them. The lib/integrations/samsara helpers route incoming DTC events into a flag-create with the AI-generated explanation.

    When to pick this

    Operations with mechanics who want fewer Slack pings and more pre-triaged information. The combo: Samsara catches the DTC, DirtFleet creates a RED flag with a human-readable cause + suggested next step.

    Show the setup
    # Status: SCAFFOLDED — repo has the OAuth + DTC normalization helpers,
    # the live wiring activates with the first paying customer's
    # Samsara API-token-bring-your-own flow.
    #
    # Pre-shipped:
    #   lib/integrations/samsara.ts — token storage + DTC parsing
    #   lib/ai-dtc-narrative.ts     — DTC code → plain English (Gemini)
    #   /api/integrations/samsara/connect  (token-paste form)
    #
    # When live, the flow is:
    #   1. Samsara webhook → DirtFleet /api/integrations/samsara/dtc
    #   2. We resolve the Samsara vehicle id → DirtFleet asset id
    #   3. AI generates "P0420 → catalyst efficiency low; common cause is..."
    #   4. We post a YELLOW flag with the narrative as the note
    #   5. Mechanic sees it in their queue with context already attached
    #
    # Email hello@dirtfleet.app to be the first-customer activator.

Field mapping reference

Samsara → DirtFleet.

Samsara fieldDirtFleet fieldNotes
externalIds.samsara.serialAsset.serialSamsara's serial maps cleanly to DirtFleet's "serial" field. Use this as the primary cross-reference key for off-road equipment.
Vehicle.vinAsset.vinOn-road only. NHTSA VIN format. DirtFleet's VIN decoder works against either source.
Vehicle.nameAsset.nicknameBoth have a human-readable "friendly name". Same field, different name.
Vehicle.externalIds.customer.idAsset.customerAssetNumberCustomer-side asset numbers (your asset tag system). Map this both ways if you maintain it in both systems.
Vehicle.engineHours.valueHoursLog.hoursReadingThe headline data point for Pattern 2. Samsara reports cumulative; DirtFleet stores cumulative; the delta is computed at the DirtFleet end.

Honest scope

What we don't do.

  • HOS / IFTA / ELD compliance. Stays in Samsara. DirtFleet is not an FMCSA-registered ELD; the integration explicitly skips the regulatory data path.
  • Driver-facing Samsara dashboards. Drivers keep using the Samsara Driver app for HOS + workflows; DirtFleet's mobile is for the maintenance side. Two apps, two purposes.
  • Real-time GPS streaming. The integration uses Samsara's polling API on a 15-min cadence — fine for hours, plates, and DTCs. If you need second-by-second tracking that's Samsara's product, not ours.

Want to activate Pattern 3 (DTC routing) early? Email hello@dirtfleet.app — first-customer activation is the unlock path. Full integrations directory.