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.
| No dashcam yet | You 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.
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.
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 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.
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 nullThe 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.
limitRows 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.
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_limitselects 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, thegeofence_*types,seatbeltandspeed_limitnever 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.
- 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 viaGET /v1/events/{eventId}/video(requires thevideo:readscope and consumes monthly quota; the URL is valid for 5 minutes). A thumbnail URL is available viaGET /v1/events/{eventId}/thumbnailwith just theevents:readscope — 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}/videoreturns a playable link in one call (media:recallscope, 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
429handling - Errors — RFC 9457
problem+json, branch on the stablecode - 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.