Skip to main content

SDKs

npm version Source on GitHub license

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
Version

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)
});
OptionRequiredDefaultDescription
apiKeyYesAPI key. Automatically attached to every request as the Authorization: Bearer header
baseUrlYesAPI host root. The SDK appends the /v1/... path, so omit /v1
fetchNoglobal fetchCustom fetch implementation (e.g., a polyfill for Node < 18)
timeoutMsNo30000Request timeout (ms). Requests are aborted when exceeded
Try it without a key of your own

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)

MethodEndpoint
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)

MethodEndpoint
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)

MethodEndpoint
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)

MethodEndpoint
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

MethodEndpoint
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)

MethodEndpoint
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)

MethodEndpoint
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.

MethodEndpoint
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.
}
}
Automatic 429 retry

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

LanguageStatusNotes
JavaScript / TypeScriptAvailable@fleeta/sdk — zero-dependency, works in browsers and Node, includes type declarations
PythonNot scheduledGenerate a client from the spec with openapi-generator (python generator)
Postman / InsomniaAvailable nowImport 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.