Skip to main content

Getting Started

List dashcams → iterate safety events with a cursor. Your first call takes no more than 3 minutes — and you do not need a dashcam to make it.

Before you start — two ways in
No dashcam yetYou have dashcams
Use a public test key (flt_test_…). It returns a full demo fleet from the real endpoints — no sign-up, no billing, no hardware. Everything on this page works with it.You need an active Fleeta cloud subscription — the same plan that gives you the web viewer and live dashcam monitoring. Issue an API key from Management › Open API in the web viewer. The API and the AI assistant (MCP) connection are included in the subscription — there is no separate API fee.

The Free plan counts as a subscription too (inventory only); GPS, events and video come with the paid plans. What each plan unlocks is on Plans & Limits.

What a Fleeta subscription includes — web viewer, live dashcam monitoring, Open API, AI assistants (MCP)

One subscription, four ways in: the web viewer, live dashcam monitoring, the Open API (REST, webhooks, SDK) and AI assistants over MCP — all from the same plan, with no separate API charge.

All examples use the following Base URL.

https://openapi.fleeta.io/v1

Start here — no key issuance, no hardware

Copy this and run it. It works right now:

curl "https://openapi.fleeta.io/v1/devices?perPage=5" \
-H "Authorization: Bearer flt_test_ent_0000000000000000001"

That is a public test key. It returns a demo fleet — 12 vehicles, safety events, trips, GPS tracks, geofences and playable video — from the same endpoints, in the same shapes, that live keys use. It reaches no customer data, so it is safe to paste into a terminal, a shared repo or a support ticket.

Build the whole integration against it, then swap one string when your hardware arrives. There are five test keys, one per tier, so you can develop against the plan you are actually buying — see the Sandbox guide.

Run requests from these docs

Every endpoint on the API Reference page has a Try it panel beside it that calls the live API from your browser. Paste a test key into the panel's key field and send — the documentation stays on screen the whole time, and the key carries over to the other endpoints for that session.

When you are ready — issue a live API key

Issue an API key from Management > Open API in the Fleeta web viewer. Keys use the flt_live_<random> format and are bound to the issuing company (masterEmail). One key = one company — data from other organizations is structurally inaccessible.

Two properties are set when a key is issued. The tier is the plan level (free / starter / standard / pro / enterprise) that determines the requests per second (rate limit) and monthly quotas (video URL issuance, GPS Export, etc.). You do not choose it — the tier is derived from your Fleeta subscription plan at issuance, together with your camera count, and a subscription with no Open API mapping cannot issue a key (403). The API itself costs nothing extra — allowances simply grow with the plan and the number of cameras on it (each plan has a maximum camera count: 1 / 1 / 10 / 99 / 999) — see Plans & Limits for the full table. Scopes are permission units such as devices:read and events:read; we recommend granting only the minimum scopes needed at issuance. Requests lacking a required scope receive a 403 problem response with the insufficient_scope code. See the Authentication and Rate Limits guides for details.

The key plaintext is shown only once, right after issuance

The server stores only a hash of the key — never the plaintext. Once you leave the issuance screen you cannot view it again, so store it in an environment variable or a secrets manager. If you suspect a leak, revoke the key immediately and issue a new one.

A live key reaches real vehicles

Unlike a test key, a live key runs against your own company's data and its write operations reach physical hardware. Grant only the scopes you need, and keep using the sandbox for anything you are still iterating on.

STEP 1 — first call: list my cameras

Put the issued key in the Authorization: Bearer header and call GET /v1/devices (requires the devices:read scope).

curl "https://openapi.fleeta.io/v1/devices" \
-H "Authorization: Bearer flt_live_..."
{
"data": [
{
"psn": "7XBPK0BE00000001",
"name": "Truck-01",
"model": "DR770X BOX PRO",
"category": "cloud",
"status": "driving",
"lastConnectedAt": "2026-07-13T02:10:00Z",
"groupId": "grp_12",
"fwVersion": "1.013",
"registeredAt": "2026-06-01T00:00:00Z",
"lastLoginAt": "2026-07-13T01:58:00Z",
"connectivity": "lte",
"entitlement": "covered",
"cameraPosition": 1
},
{
"psn": "7XBPK0BE00000003",
"name": "Van-01",
"model": "DR750X-2CH",
"category": "wifi",
"status": "offline",
"lastConnectedAt": "2026-05-30T02:00:00Z",
"groupId": "grp_13",
"fwVersion": null,
"registeredAt": "2025-01-15T03:00:00Z",
"lastLoginAt": null,
"connectivity": "wifi",
"entitlement": "covered",
"cameraPosition": null
}
],
"pagination": { "page": 1, "perPage": 20, "total": 5, "totalPages": 1 },
"cameras": { "limit": 10, "registered": 4, "overLimit": 0 }
}

Every response follows the { data, pagination } envelope with camelCase fields, RFC 3339 UTC (Z) timestamps, and metric units. All timestamps in the API are UTC — in query filters such as from / to, in responses, and in webhook payloads. Date-based ranges are UTC calendar days (a GPS export covers whole UTC days), and the timestamps inside exported files are UTC too; convert to local time on your side. Small, static lists such as devices use offset pagination (page / perPage, default 20, max 100).

The list is your whole inventory: category tells the two kinds of dashcam apart. cloud is a cloud-connected dashcam — every feature of this API applies to it. wifi is a Wi-Fi-only dashcam: it is listed so the count matches what you own, but it has no cloud connectivity, so it is always offline here, and cloud-only features — location, trips, events, video, SD-card access, recall, settings, reboot, GPS export — answer 422 cloud_only for its PSN rather than 404. Pass category=cloud when you only want the dashcams those features apply to. The list returns the whole record for every dashcam — the same fields GET /v1/devices/{psn} returns, including fwVersion, sim, battery and the assigned vehicle object (vin / plate / maker / model / year / tag — an empty object {} when no vehicle is assigned). A fleet table with licence plates therefore takes one call, not one per dashcam. Fetch the detail endpoint only when you already have a single PSN.

q searches the dashcam name and PSN, not vehicle fields; match a plate or a vehicle.tag on the rows the list returns.

Field-by-field semantics — what vehicle: {} means, why battery omits level instead of reporting 0, when sim reads null, and what PUT /v1/devices/{psn} will and will not change — are in Vehicles & Dashcams.

The cameras block beside data and each row's entitlement describe the organization's camera seats: a subscription covers cameras.limit dashcams, assigned newest-registration first (cameraPosition). A dashcam beyond that count is over_limit — still listed and readable, but video, SD-card access, recall and reboot answer 403 camera_limit_exceeded until the camera count is raised in the web viewer (picked up within 5 minutes, no new key). Most organizations never see one; the rules are in Rate Limits › Camera seats.

STEP 2 — filter long-disconnected cameras

Use the lastConnectedBefore parameter to have the server filter cameras that have not connected for a long time. Both relative notation like 30d and absolute ISO-8601 timestamps are supported.

curl "https://openapi.fleeta.io/v1/devices?lastConnectedBefore=30d" \
-H "Authorization: Bearer flt_live_..."
{
"data": [
{
"psn": "7XBPK0BE00000003",
"name": "Van-01",
"model": "DR750X-2CH",
"category": "wifi",
"status": "offline",
"lastConnectedAt": "2026-05-30T02:00:00Z",
"groupId": "grp_13"
},
{
"psn": "7XBPK0BE00000004",
"name": "Van-02",
"model": "DR770X BOX",
"category": "cloud",
"status": "offline",
"lastConnectedAt": "2026-05-05T02:00:00Z",
"groupId": "grp_13"
}
],
"pagination": { "page": 1, "perPage": 20, "total": 2, "totalPages": 1 }
}

The response always includes the raw lastConnectedAt value, so you can apply your own threshold ("we use 45 days") on the client side. You can also combine the q (name/PSN search), status (driving / parked / offline), and groupId filters.

STEP 3 — safety events and cursor pagination

Query safety events with GET /v1/events (requires the events:read scope). Filter with from / to (ISO-8601 UTC), type (comma-separated for multiple values), and psn; results are sorted by occurredAt in descending order (newest first).

curl "https://openapi.fleeta.io/v1/events?from=2026-07-01T00:00:00Z&limit=2" \
-H "Authorization: Bearer flt_live_..."
{
"data": [
{
"eventId": "evt_0032",
"psn": "7XBPK0BE00000001",
"type": "harsh_braking",
"occurredAt": "2026-07-09T02:11:00Z",
"location": null,
"speedKmh": null,
"channels": ["front", "rear"],
"hasVideo": true
},
{
"eventId": "evt_0031",
"psn": "7XBPK0BE00000005",
"type": "overspeed",
"occurredAt": "2026-07-08T22:47:00Z",
"location": null,
"speedKmh": null,
"channels": ["front"],
"hasVideo": true
}
],
"pagination": {
"nextCursor": "eyJsYXN0SWQiOiJldnRfMDAzMSJ9",
"hasMore": true
}
}
location and speedKmh are currently always null

The safety-event records do not carry coordinates or speed, so the API reports those fields as null rather than estimating them from other sources. The same applies to kpi.totalDistanceKm, kpi.avgSpeedKmh and hotspots on GET /v1/safety-events/stats. When a positional source is joined in, the fields will start returning values without any breaking change to the response shape.

channels is the union of the event's playable channels. One recording that uploaded a front clip and a rear clip is one row with ["front", "rear"] — not two rows. A channel is never guessed, so a file whose direction cannot be read from its name is left out, and an event with no readable channel at all returns [] with hasVideo: false.

A page can be shorter than limit

Rows are emitted at event boundaries, so a page may hold fewer items than limit — even zero — while hasMore is still true. Stop when hasMore is false, never because a page looked short, and always follow nextCursor.

High-volume, time-series data such as events uses cursor pagination. While hasMore is true, pass the response's nextCursor unchanged as the after parameter of the next request to continue.

curl "https://openapi.fleeta.io/v1/events?from=2026-07-01T00:00:00Z&limit=2&after=eyJsYXN0SWQiOiJldnRfMDAzMSJ9" \
-H "Authorization: Bearer flt_live_..."

When nextCursor is null (= hasMore: false), you are on the last page. Cursors are opaque strings — do not parse or construct them yourself.

Event types — naming convention

Across the API, JSON keys are camelCase while enum values — event types included — are lower snake_case (harsh_braking, not harshBraking). The canonical event type values are these 18:

driving_impact parking_impact overspeed harsh_braking harsh_acceleration
sharp_turn drowsy distracted seatbelt undetected
calling smoking geofence_enter geofence_exit geofence_pass
geofence_speed manual speed_limit

Event types are only ever used as values, never as object keys — for example, the per-type aggregations in insights responses (events.byType in GET /v1/reports/driving) are [{ "type": "harsh_braking", "count": 6 }] arrays, not objects keyed by type. The same canonical values appear as data.subType in webhook safety_event deliveries.

One vocabulary, several populations

The 18 above are the canonical vocabulary, shared across events, insights, and webhooks. Each surface publishes a subset of it:

  • GET /v1/events?type=… — every value is a working filter. The 17 recording types select uploaded clips by their event code; speed_limit selects the server-judged road speed-limit violations, which have no clip (see Safety events).
  • GET /v1/reports/driving / GET /v1/fleet/summary (events.byType) — 11 detection counters from the dashcam's telemetry; manual, the geofence_* types, seatbelt and speed_limit never appear there.
  • Webhook data.subType — 11 values; see Webhooks.

The vocabulary is deliberately the union of all of them, so a value that returns nothing on one surface today can start returning data there without a breaking change.

Learn more
  • To receive new events in near real time, use GET /v1/events/feed (cursor tail polling) or Webhooks push.
  • For events with hasVideo: true, you can issue a live presigned video URL via GET /v1/events/{eventId}/video (requires the video:read scope and consumes monthly quota; the URL is valid for 5 minutes). A thumbnail URL is available via GET /v1/events/{eventId}/thumbnail with just the events:read scope — no quota consumed.
  • Recordings that never left the dashcam's SD card can be played the same way: GET /v1/devices/{psn}/sd-files/{filename}/video returns a playable link in one call (media:recall scope, pro plan and up). The dashcam uploads the clip over its own LTE behind that link, so it spends the vehicle's mobile data — see SD Recordings.

Doing the same with the SDK

The official JS/TS client @fleeta/sdk automates auth header attachment and cursor traversal.

npm install @fleeta/sdk
const { FleetaClient } = require('@fleeta/sdk');
const bv = new FleetaClient({
apiKey: process.env.FLEETA_API_KEY,
baseUrl: 'https://openapi.fleeta.io', // host root — the SDK appends the /v1 path, so omit /v1
});

// STEP 1 & 2 — dashcams disconnected for 30+ days (server-side filter)
const { data } = await bv.devices.list({ lastConnectedBefore: '30d' });

// STEP 3 — automatic cursor traversal (no pagination handling needed)
for await (const ev of bv.events.iterate({ from: '2026-07-01T00:00:00Z' })) {
console.log(ev.eventId, ev.type, ev.occurredAt);
}

STEP 4 — next steps

  • Authentication — key scopes and the company isolation model
  • Pagination — cursor / offset hybrid
  • Rate Limits — per-tier rate limits, monthly quotas, and 429 handling
  • Errors — RFC 9457 problem+json, branch on the stable code
  • Webhooks — HMAC-SHA256 signed push
  • API Reference — full endpoint reference (inline Try it panel — runnable with your own key)
  • SDKs — official JS/TS client

Next — see it as a working app

The same test key runs a nine-screen reference app: dashboard, live fleet map, trips and tracks, safety events, driving reports, geofences and API usage. Clone it and read the BFF handler sitting next to each screen — every one is a worked example of a specific call pattern (both pagination styles, tail feeds, parallel fan-out, inline thumbnails, signed media with job polling).

It also answers the question this page does not: where does the API key live? It stays in the server process and never reaches the browser.

fleeta-io/openapi-demo — MIT, runs with no key at all in mock mode.