SDKs
Package: @fleeta/sdk on npm ·
Source: fleeta-io/sdk-js on GitHub
The official client handles authentication, pagination, and error handling
automatically. It is a zero-dependency JS/TS library that works in both
Node 18+ and browsers, and ships with type declarations (index.d.ts).
Installation
npm install @fleeta/sdk
The current release is v0.4.0. bv.media (SD card recordings / recall),
devices.getSettings, devices.reboot, and webhooks.deliveries require
v0.3.0 or later; older versions have every other method listed below.
Fixed in the next release (0.5.0): in v0.4.0 devices.getSettings() exists
at runtime but is missing from the bundled TypeScript declarations — until
then, TypeScript callers can reach it with
bv.request('GET', '/v1/devices/' + psn + '/settings'). 0.5.0 also
synchronizes every declaration with the OpenAPI schemas (typed
webhooks.create().secret, devices.create() result, GeofenceAlert,
Usage, GPS export jobs) and wraps all 52 operations (the 52nd,
media.sdFileVideo(), ships in 0.7.0, published 2026-09-02).
Initialization
const { FleetaClient } = require('@fleeta/sdk');
const bv = new FleetaClient({
apiKey: 'flt_live_...', // issued from Management > Open API
baseUrl: 'https://openapi.fleeta.io', // host root (without /v1)
});
| Option | Required | Default | Description |
|---|---|---|---|
apiKey | Yes | — | API key. Automatically attached to every request as the Authorization: Bearer header |
baseUrl | Yes | — | API host root. The SDK appends the /v1/... path, so omit /v1 |
fetch | No | global fetch | Custom fetch implementation (e.g., a polyfill for Node < 18) |
timeoutMs | No | 30000 | Request timeout (ms). Requests are aborted when exceeded |
Pass a public test key as apiKey and the SDK works against a demo fleet —
no dashcam, no key issuance:
const fleeta = new FleetaClient({
apiKey: 'flt_test_ent_0000000000000000001',
baseUrl: 'https://openapi.fleeta.io',
});
For live data, issue a key from Management → Open API in the Fleeta web viewer. Everything else stays the same — see the Sandbox guide.
Methods by domain
The resource accessors follow the tag structure of the
API Reference. From v0.5.0 every operation has a wrapper;
on v0.4.0 two endpoints do not — GET /v1/api-logs and GET /v1/groups —
reach those with bv.request('GET', '/v1/groups'). Methods marked
v0.5.0+ below are new in that release.
bv.devices — devices (offset)
| Method | Endpoint |
|---|---|
devices.list(query) | GET /v1/devices |
devices.iterate(query) | Auto-iterate all pages (offset) |
devices.create(body) | POST /v1/devices |
devices.get(psn) | GET /v1/devices/{psn} |
devices.update(psn, body) | PUT /v1/devices/{psn} |
devices.battery(psn) | GET /v1/devices/{psn}/battery |
devices.firmware(psn) | GET /v1/devices/{psn}/firmware |
devices.getSettings(psn) | GET /v1/devices/{psn}/settings — read-only (settings changes are made in the viewer app) |
devices.reboot(psn) | POST /v1/devices/{psn}/reboot — the device must be online (409 otherwise) |
devices.groups() | GET /v1/groups — organization groups, no pagination (v0.5.0+) |
bv.telemetry — locations · trips · GPS Export (cursor)
| Method | Endpoint |
|---|---|
telemetry.fleetLocations(query) | GET /v1/fleet/locations — latest location snapshot per device |
telemetry.fleetLocationsFeed(query) | GET /v1/fleet/locations/feed — tail polling for location changes |
telemetry.trips(psn, query) | GET /v1/devices/{psn}/trips |
telemetry.track(tripId, query) | GET /v1/trips/{tripId}/track |
telemetry.createExportJob(body) | POST /v1/gps/export-jobs |
telemetry.exportJobs(query) | GET /v1/gps/export-jobs |
telemetry.exportJob(jobId) | GET /v1/gps/export-jobs/{jobId} — real-time progress; presigned downloadUrl when completed |
telemetry.cancelExportJob(jobId) | DELETE /v1/gps/export-jobs/{jobId} |
For the full export workflow — real-time progress, 1-hour presigned
downloads, and the export_completed webhook pattern — see the
GPS Export guide.
bv.events — safety events (cursor)
| Method | Endpoint |
|---|---|
events.list(query) | GET /v1/events |
events.iterate(query) | Automatic cursor traversal |
events.feed(query) | GET /v1/events/feed |
events.get(eventId) | GET /v1/events/{eventId} |
events.video(eventId, query) | GET /v1/events/{eventId}/video |
events.thumbnail(eventId) | GET /v1/events/{eventId}/thumbnail |
events.stats(query) | GET /v1/safety-events/stats |
bv.geofences — geofences (offset)
| Method | Endpoint |
|---|---|
geofences.list(query) | GET /v1/geofences |
geofences.iterate(query) | Auto-iterate all pages (offset) |
geofences.create(body) | POST /v1/geofences |
geofences.get(id) | GET /v1/geofences/{id} |
geofences.update(id, body) | PUT /v1/geofences/{id} |
geofences.delete(id) | DELETE /v1/geofences/{id} |
geofences.alerts(query) | GET /v1/geofence-alerts (cursor) |
geofences.iterateAlerts(query) | Automatic cursor traversal of alerts (v0.5.0+) |
bv.insights — reports
| Method | Endpoint |
|---|---|
insights.fleetSummary(query) | GET /v1/fleet/summary |
insights.drivingReports(query) | GET /v1/reports/driving |
insights.drivingReport(psn, query) | GET /v1/reports/driving/{psn} |
bv.admin — audit logs · usage (cursor)
| Method | Endpoint |
|---|---|
admin.auditLogs(query) | GET /v1/audit-logs |
admin.iterateAuditLogs(query) | Automatic cursor traversal |
admin.auditLog(id) | GET /v1/audit-logs/{id} |
admin.apiLogs(query) | GET /v1/api-logs — API call log, cursor (v0.5.0+) |
admin.iterateApiLogs(query) | Automatic cursor traversal of API logs (v0.5.0+) |
admin.usage() | GET /v1/usage |
bv.webhooks — webhook subscriptions (offset)
| Method | Endpoint |
|---|---|
webhooks.list(query) | GET /v1/webhooks |
webhooks.create(body) | POST /v1/webhooks |
webhooks.get(id) | GET /v1/webhooks/{id} |
webhooks.delete(id) | DELETE /v1/webhooks/{id} |
webhooks.test(id) | POST /v1/webhooks/{id}/test |
webhooks.deliveries(id, query) | GET /v1/webhooks/{id}/deliveries — last 30 days |
webhooks.reactivate(id) | POST /v1/webhooks/{id}/reactivate — resume a suspended subscription (0.6.0+) |
webhooks.rotateSecret(id, body?) | POST /v1/webhooks/{id}/rotate-secret — new secret, previous one co-signs for graceSeconds (0.6.0+) |
bv.media — SD card recordings · recall (offset)
Reading the SD card sends an IoT command to the dashcam, so the device must be online. Playing or recalling a recording uploads it over the vehicle's mobile data — see SD Recordings before using it.
| Method | Endpoint |
|---|---|
media.sdFiles(psn, query) | GET /v1/devices/{psn}/sd-files |
media.iterateSdFiles(psn, query) | Auto-iterate all pages (offset) |
media.sdFileMetadata(psn, filename, query) | GET /v1/devices/{psn}/sd-files/{filename}/metadata |
media.deleteSdFile(psn, filename) | DELETE /v1/devices/{psn}/sd-files/{filename} — permanent |
media.sdFileVideo(psn, filename, query?) | GET /v1/devices/{psn}/sd-files/{filename}/video — playable link in one call ({ url, expiresAt, jobId, sizeBytes, receivedBytes, quality }). query.quality defaults to sub. Uses vehicle mobile data and counts toward the recall allowance, exactly like createRecallJob. Answers the same 409 device_busy — retry it yourself after e.retryAfter seconds (0.7.0+) |
media.createRecallJob(body, opts?) | POST /v1/media/recall-jobs — same transfer as a job resource (quality defaults to main). Uses vehicle mobile data. opts.retryOnBusy handles 409 device_busy for you (see below) |
media.recallJobs(query) | GET /v1/media/recall-jobs |
media.iterateRecallJobs(query) | Auto-iterate all pages (offset) |
media.recallJob(jobId) | GET /v1/media/recall-jobs/{jobId} |
media.cancelRecallJob(jobId) | DELETE /v1/media/recall-jobs/{jobId} |
In addition, bv.ping() (GET /v1/ping) is provided for health checks,
along with the low-level bv.request(method, path, { query, body }) for
calling endpoints that are not wrapped yet.
Waiting out a busy device
A dashcam handles one transfer at a time, so a recall issued while
another transfer is running returns 409 device_busy and creates no job.
Pass retryOnBusy and the SDK runs the wait loop for you: it honours each
Retry-After (5 seconds when the header is absent) and re-throws the last
409 if the overall deadline passes — 60 seconds by default.
// Playable link (0.7.0+) — the recall job behind it is returned as `jobId`.
// quality defaults to 'sub'; on 409 device_busy retry after e.retryAfter seconds.
const { data: clip } = await bv.media.sdFileVideo('7XBPK0BE00000001', '20260727_131445_NF.mp4');
player.src = clip.url;
// Job form — same transfer, returned as a recall job resource (retryOnBusy available here)
const job = await bv.media.createRecallJob(
{ psn: '7XBPK0BE00000001', filename: '20260727_131445_NF.mp4', quality: 'sub' },
{ retryOnBusy: { timeoutMs: 5 * 60_000 } }, // or just `retryOnBusy: true`
);
Size the deadline to the transfer you are waiting behind: a sub transfer
clears in seconds, while a full-resolution main transfer legitimately holds
the device for several minutes. See
SD Recordings.
Automatic pagination
The iterate() helper handles
hybrid pagination (cursor / offset)
automatically. You never have to think about page boundaries.
Cursor resources (events · telemetry · admin) — iterates by following
nextCursor / hasMore:
// iterate all safety events for the month of June
for await (const ev of bv.events.iterate({ from: '2026-06-01T00:00:00Z' })) {
console.log(ev.eventId, ev.type, ev.occurredAt);
}
Offset resources (devices · geofences) — iterates by following
page / totalPages:
// iterate all dashcams disconnected for 30+ days
for await (const d of bv.devices.iterate({ lastConnectedBefore: '30d', perPage: 100 })) {
console.log(d.psn, d.name, d.lastConnectedAt);
}
If you only need a single page, call list() directly — the response has
the { data, pagination } shape.
Error handling and automatic 429 retry
Every error response (application/problem+json,
RFC 9457) is thrown as an OpenApiError. It carries
the status · code · detail · requestId fields plus the raw
problem+json (body); branch on the code value.
const { FleetaClient, OpenApiError } = require('@fleeta/sdk');
try {
// the event tells you which channel to ask for - do not assume front (some are rear-only)
const { data } = await bv.events.video(ev.eventId, { channel: ev.channels[0] });
console.log(data.url);
} catch (e) {
if (e instanceof OpenApiError) {
console.error(e.status, e.code, e.detail, e.requestId);
// e.body — raw RFC 9457 problem+json
if (e.status === 429) {
// still 429 after the automatic retry (code: rate_limited) — back off and call again
}
// v0.6.0+: is(code) narrows e.body to that code's Problem<Code> shape — extension fields are typed
if (e.is('cloud_only')) console.warn('Wi-Fi-only dashcams:', e.body.psns);
if (e.is('quota_exceeded')) console.warn(`${e.body.bucket} quota used up (${e.body.used}/${e.body.limit})`);
} else {
throw e; // network/timeout, etc.
}
}
On a 429 Too Many Requests response, the SDK waits for the Retry-After
header and retries once automatically. If it is still 429 after the
retry, an OpenApiError is thrown — handle it with the pattern above. See
the Rate Limits guide for the limit policy.
Real-time locations — feed tail polling
For high-volume GPS data, the canonical real-time mechanism is cursor
tail polling, not webhook push (there is no WebSocket API — by design).
fleetLocationsFeed() returns only the devices whose locations changed
since the last cursor.
Call first without after → receive a current-state snapshot plus a
nextCursor; then pass nextCursor as after on subsequent calls to
receive only the changes.
let cursor = null;
for (;;) {
const { data, pagination } = await bv.telemetry.fleetLocationsFeed(
cursor ? { after: cursor, limit: 200 } : { limit: 200 }
);
for (const loc of data) {
console.log(loc.psn, loc.status, loc.speedKmh, loc.at);
}
if (pagination && pagination.nextCursor) cursor = pagination.nextCursor;
// if changes remain (hasMore), call again immediately to drain; otherwise wait
if (!(pagination && pagination.hasMore)) {
await new Promise((r) => setTimeout(r, 30000)); // recommended polling interval: 30 seconds or more
}
}
For low-volume, latency-sensitive data such as safety events, use Webhooks push instead of polling.
Supported languages
| Language | Status | Notes |
|---|---|---|
| JavaScript / TypeScript | Available | @fleeta/sdk — zero-dependency, works in browsers and Node, includes type declarations |
| Python | Not scheduled | Generate a client from the spec with openapi-generator (python generator) |
| Postman / Insomnia | Available now | Import https://developers.fleeta.io/spec/openapi.yaml directly — no separate collection is published |
For other languages, use the per-language code samples (including curl) in
the API Reference, or generate a client from
openapi.yaml.