Changelog
The API change history. Each entry carries one of the following labels.
| Label | Meaning |
|---|---|
| ADD | Endpoint / field / feature addition (backward compatible) |
| CHANGED | Change to existing behavior (backward compatibility preserved) |
| REMOVED | Endpoint / feature withdrawal — only before general availability, or after the BREAKING process |
| BREAKING | Breaks backward compatibility — applied only after advance notice plus a migration period |
/v1 follows an additive-only policy from general availability onward —
we only add fields and endpoints; we never remove, rename, or change types.
Implement clients to ignore unknown fields.
v1 — General availability
The first public release of the Fleeta Open API. Everything below is live on
https://openapi.fleeta.io/v1 and https://mcp.fleeta.io/.
-
ADD
GET /v1/safety-events/statscan count the hour buckets in your own time zone.byHourhas always been counted in UTC, leaving every caller outside UTC to shift all 24 buckets themselves — and a review of AI-assistant answers on 2026-09-20 found that shift going wrong often enough (off-by-one hours, am/pm flips) to misreport the daily peak. Passtzwith an IANA zone name (tz=Asia/Seoul) and the aggregation groups the buckets in that zone, applying daylight saving per event, so a period that crosses a transition is still counted correctly. A UTC offset (+09:00) is rejected with400 invalid_parameterfor exactly that reason — it cannot express the transition. The response now carrieshourTz, the zone the buckets were counted in, so a chart can be labelled from the payload alone. Omittingtzkeeps the previous behaviour byte for byte (hourTz: "UTC"), and nothing else moves:from/toand every timestamp in the response stay RFC 3339 UTC.@fleeta/sdkaddstztoevents.stats()andhourTztoSafetyEventStats; the MCPsafety_event_statstool takestzand is told to pass the viewer's zone whenever it will show an hour-of-day breakdown. See Safety events › Hour buckets and time zones. -
CHANGED Video is metered by volume, not by link count — the
video_urlquota bucket is replaced bytransfer_bytes.GET /v1/usageno longer reportsvideo_url; it reports{ bucket: "transfer_bytes", limit, used, period, unit: "bytes" }, and every bucket now carriesunit(bytesorcount). The allowance is the video delivered to you in a month: an event video URL counts at the clip's size (a flat 6 MB) and an SD-card recall (POST /v1/media/recall-jobs,GET /v1/devices/{psn}/sd-files/{filename}/video) at the recording's size (sizeBytes, or a flat 8 MBsub/ 150 MBmainwhen the dashcam does not report it), both when the link is issued; thumbnails, listings, metadata, job status and export files do not count. It is 0.8 GB per subscription dollar on Standard, 1 GB on Pro and 1.2 GB on Enterprise — 5.6 / 10 / 18 GB per camera and month. Until now the cap was a count of issued links (20,000 per camera) that treated a 5 MB clip and a 400 MB recording alike, and SD-card recalls had no monthly cap at all. Recalls are now checked before the dashcam is contacted: when the remaining allowance cannot cover the file, the call answers403 quota_exceededand no job is created. On that errorlimitandusedare bytes anddetailstates the limit in GB.export_jobmoves from 20,000 to 50 jobs per camera a month (Pro and above — the most any fleet has created is 25 a month). If your code switches on the bucket name or formatslimit/used, update it; the SDK types (UsageVolumeQuotaBucket,UsageVolumeQuota.unit) change with the next@fleeta/sdkrelease. See Plans & Limits and Rate Limits. -
ADD Road speed-limit violations are now delivered over webhooks.
GET /v1/eventshas returnedspeed_limit— the server's judgement that a vehicle exceeded the posted limit for the road it was on, made by comparing its GPS track against map data — since the type was introduced, but that event never reached webhook subscribers.safety_eventcarried only the dashcam's own fixed-thresholdoverspeed, so an integration built on webhooks saw a different set of speeding events than the same account saw through the read API, with nothing in the response to indicate the gap.safety_eventdeliveries now includesubType: "speed_limit", using the same valueGET /v1/eventsuses for the same event. The delivery carries adata.speedLimitobject —limitKmh,overKmh,durationSec,roadName, named exactly asEvent.speedLimitis on the read API so the two compare directly — anddata.location.speedKmhholds the peak speed reached. Fields the map data could not determine are omitted rather than sent asnull: a road with no posted limit has nolimitKmh, and without it there is nooverKmh. There are no coordinates on this subType;GET /v1/eventsremains the place to get the violation's location,endedAt,peakAt,countryandschoolZone.overspeedis unchanged and still arrives separately — the two are different events and a vehicle can trigger both on one drive, so route them separately if you act on them. Existing subscriptions receive the newsubTypewithout any change on your side; the webhook guide has always said to ignoresubTypevalues you do not recognise. -
FIXED A malformed request to a path that does not exist answered
500. A request whose method and path match no route already answers404 route_not_found— but if it also carried a body with a content type other than JSON (some HTTP clients set one by default), the gateway answered a bare500 {"message": "Internal server error"}with norequestId, so a typo in a URL looked like an outage on our side. It now answers the same404 route_not_foundregardless of the body or its content type. Nothing about valid requests changed, and no request body was ever echoed back. -
FIXED
GET /v1/events/feednever reported that you had caught up. The reference tells you to keep polling untilhasMoreisfalse; on a sandbox key it stayedtrueforever, because a tail feed keeps its resume cursor even when there is nothing left andhasMorewas derived from the cursor rather than from the backlog. A client that followed the instruction polled empty pages indefinitely.hasMoreis nowfalseonce the feed is caught up — the cursor is still returned, and you resume with it on the next poll, exactly asGET /v1/fleet/locations/feedalready behaved.start=latestalso returnshasMore: false, since attaching to the tail means there is no backlog by definition. -
FIXED Sandbox GPS exports ignored the requested format. A job created with
format: "gpx"reportedformat: "gpx"and then handed back the same CSV, and the finished job was missingdownloadFileNameanddownloadUrlExpiresIn— both of which live keys return. The sandbox now produces a zip archive in the requested format (csv/gpx/kml) laid out the way a live export is — oneexport.<ext>entry, the live CSV column set, the live GPX/KML structure — with the same suggested filename convention live keys use, and a running job carriescompletedAt/expiresAtasnullinstead of omitting the keys, so code that branches on them behaves the same in both places. -
BREAKING Trip coordinates use
latitude/longitudelike every other coordinate in the API.GET /v1/devices/{psn}/tripsreturnedstartLocationandendLocationas{lat, lng}— the only two fields in the whole API that used the short names. Readinglocation.latitudeworked everywhere except there, so code written against one endpoint broke on the other, and the response standard had specified the unification since the mock data was replaced with real data. Both fields are now{latitude, longitude}; nothing else about them changed (stillnullwhen the trip has no usable GPS fix, still no address).@fleeta/sdktypesTrip.startLocation/endLocationasGeoPoint, and the MCPlist_tripsdescription follows.LatLngremains in the SDK, deprecated, so existing type references keep compiling. This is the last coordinate-shape change before general availability. -
CHANGED The fleet summary and driving reports count cloud-connected dashcams only, and now say so.
GET /v1/fleet/summaryandGET /v1/reports/drivingleft Wi-Fi-only dashcams out — correctly, since those never reach the cloud and so have no status, location or driving data — but nothing in the reference said it, so a fleet of 12 dashcams read as 12 inGET /v1/devicesand 10 on the dashboard with no explanation. The exclusion is now documented onFleetSummary.fleet.totaland in the MCPfleet_summarytool. One real inconsistency went with it: a dashcam registered as Wi-Fi inside the main device registry was counted anyway, so the same kind of dashcam was included or excluded depending on where its record happened to live. It is now excluded everywhere, matching whatGET /v1/devicesreports as itscategory. Camera seats are unaffected — such a dashcam still consumes one, exactly as the Fleeta web viewer and the mobile apps count it (cameras.registereddocuments this). -
FIXED
POST /v1/devicesanswered500when apsnwas not a string. A JSON number, boolean ornullrow (an unquoted serial, most often) hit a type error instead of validation. That row now fails withinvalid_psninfailed[]like any other malformed serial, and the rest of the batch is unaffected. A numericpsnis rejected rather than silently accepted:psnis a string in the schema, and a missing pair of quotes should not register a device. -
CHANGED Sandbox writes are kept for 24 hours instead of living in one server's memory. Creating a geofence or a webhook and then reading it back could return
404— the write stayed on whichever instance served it, and the next request often landed elsewhere (measured: 60 reads of a just-created geofence answered20031 times and40429 times). Writes are now stored per key with a 24-hour expiry, so create → list → update → delete behaves as it does on a live key, and yesterday's experiments do not pile up.PUT /v1/devices/{psn}also persists in the sandbox now (it used to answer200with the new name and keep the old one everywhere else) and applies the live path's validation: empty body, Wi-Fi-only PSN,vehicle.yearrange, andvehicle.tagbeing read-only. -
CHANGED
simis now scoped to your organization and picked deterministically.sim_infocan hold more than one row for the same dashcam — two upstream writers use different upsert keys, so re-registering a dashcam under another account inserts a second row. The API used to look the SIM up by serial number alone, which meant the list and the detail view could pick different rows (reported 2026-09-09:GET /v1/devicesreturned{apn: null, iccid: null}whileGET /v1/devices/{psn}returned a SIM that had since moved to another dashcam), and a previous owner's APN and masked ICCID could be returned to the current one. Both views now filter by the owning account and take the most recently updated row, so they always agree and only ever show your own data. If a dashcam's SIM record still carries a previous owner,simnow readsnullinstead of that owner's values — the record is repaired the next time the dashcam reports its SIM.vehiclewas never affected: both views resolve the same reference stored on the dashcam. -
ADD
vehicle,simandbatteryon the device list — the list and the detail view now return the same fields.GET /v1/devicesused to omit them, so building a fleet table that shows a licence plate meant oneGET /v1/devices/{psn}per row: 101 calls to refresh a 100-dashcam view, which on a Standard plan with 10 cameras exceeded the monthly call limit at a five-minute refresh. The Fleeta web viewer has always shown plate, VIN and vehicle tag as columns of its camera list, fetched in one call — the API now does the same. Every row carriesvehicle(vin/plate/maker/model/year/tag,{}when no vehicle is assigned),sim,batteryandrequiredCameraLimit, joined once per page rather than once per row.vehicle.tagis new on both views — the free-text label the web viewer groups vehicles by; it is read-only,PUT /v1/devices/{psn}does not accept it. Nothing was removed and no field changed type, so existing clients are unaffected.DeviceDetailremains as an alias ofDevicein the spec and the SDK.qstill searches the dashcam name and PSN only — match a plate on the rows this endpoint returns. In MCP,list_devicescarries the vehicle on every row, so the assistant no longer callsget_deviceper candidate to resolve a plate. -
CHANGED Wi-Fi-registered dashcams answer
422 cloud_onlyinstead of an empty result. A dashcam can be registered as Wi-Fi-only while its record still lives in the cloud device collection. Cloud-only endpoints used to let those PSNs through and then return nothing, soGET /v1/devices/{psn}/tripson such a dashcam answered200with an empty list whileGET /v1/devicesdescribed it ascategory: wifi— the reference said one thing and the API did another. Trips, events filtered bypsn, geofence alerts and GPS export now answer422 cloud_onlyfor them, the same as dashcams registered through the Wi-Fi-only path. -
CHANGED
GET /v1/devices/{psn}/tripsrejects a reversed range.fromlater thantoused to return200with an empty list, which reads as "no trips in that period". It now answers400 invalid_parameter, matchingGET /v1/events, the event feed,GET /v1/safety-events/statsand the driving reports. (POST /v1/gps/export-jobskeeps422 invalid_fieldbecause there the range is a request-body field, not a query parameter.) -
ADD Camera seats —
camera_limit_exceeded,entitlement,cameraPosition, thecamerasblock and theentitlementfilter. A subscription covers a fixed number of cameras. An organization can end up with more cloud dashcams registered than that — normally only after a plan is downgraded or lapses, since neither the API nor the mobile app registers past the count — and then seats go to the most recently registered dashcams while the rest are over-limit, the dashcams the Fleeta web viewer shows greyed out. The API now says so instead of behaving as if the seat existed. Every dashcam row (GET /v1/devices, the detail,GET /v1/fleet/locationsand its feed, events, geofence alerts, driving reports, thetopDevices/disconnectedDeviceslists) carriesentitlement: covered | over_limit; the device list and detail addcameraPosition(1 = most recently registered) and an over-limit detail addsrequiredCameraLimit; and every organization-wide response —GET /v1/devices,GET /v1/fleet/locations,…/feed,GET /v1/fleet/summary,GET /v1/reports/driving,GET /v1/safety-events/stats,GET /v1/usage— returns acamerasblock besidedata(limit·registered·overLimit;/v1/usageaddsoverLimitPsns). An over-limit dashcam is readable, not hidden: location, trips, events, thumbnails, statistics, geofences and geofence alerts, GPS export, settings snapshot, battery and firmware all work as before. Only what pulls new media off the dashcam or moves it answers the new403 camera_limit_exceeded—GET /v1/events/{eventId}/video(before any quota is spent), the four SD-card routes (GET …/sd-files,…/{filename}/metadata,…/{filename}/video,DELETE …/{filename}),POST /v1/media/recall-jobsandPOST /v1/devices/{psn}/reboot— and so doesPOST /v1/deviceswhen the batch would exceed the count (all-or-nothing, withremainingSeats). The problem body carriescameraLimit,registeredCamerasandrequiredCameraLimit, pluspsn+cameraPositionon single-device routes orpsnson registration. Nothing needs re-configuring: raise the camera count in the Fleeta web viewer (Account › Subscription) and the same key picks it up within 5 minutes — no reissue.?entitlement=covered|over_limit|allnarrows any of those lists (defaultallon devices, locations, events and statistics). Sandbox test keys never lock a seat — every demo dashcam iscoveredandcameras.limitequalsregistered.@fleeta/sdklists the code inCODESand adds the fields toDevice; the MCP tools carry the same fields and explain the 403 (see the AI docs). Full rules in Rate Limits › Camera seats; the code in Errors ›camera_limit_exceeded. -
ADD
registeredAt,lastLoginAtandconnectivityon devices.GET /v1/devicesandGET /v1/devices/{psn}now carry when the dashcam was registered to the organization (registeredAt— the order camera seats are assigned in, newest first), its last cloud sign-in (lastLoginAt;lastConnectedAtis the last activity and falls back to this value when none is recorded), and how it reaches the network (connectivity—ltefor a built-in cellular module,wifiotherwise; separate fromcategory, which says whether the dashcam talks to the cloud at all). Each isnullwhen the record does not say, and all three arenullin the sandbox. -
CHANGED
GET /v1/fleet/summaryandGET /v1/reports/drivingcount covered dashcams by default. Both now default toentitlement=covered, so their numbers are the ones the Fleeta web viewer shows — the viewer's driving report and online count have always left over-limit dashcams out. An organization with over-limit dashcams therefore sees smaller totals and fewer report rows than before; passentitlement=allfor the previous figures (orover_limitfor the locked ones alone). Thecamerasblock on both responses always describes the whole organization regardless of the filter, andGET /v1/reports/driving/{psn}is unchanged — an over-limit dashcam still answers200, with itsentitlement. No other default changes. -
CHANGED A dashcam registered over Wi-Fi reads
category: wifi, notcloud. A dashcam whose cloud record was created through the Wi-Fi registration path (reg_category: wifi) used to be reported ascategory: cloud, indistinguishable from a cloud-connected unit although it has no cloud connectivity. It now readscategory: wifi, socategory=cloudleaves it out and every cloud-only feature — location, trips, events, video, SD-card access, recall, settings, reboot, GPS export — answers422 cloud_onlyfor its PSN, exactly as for a dashcam from the Wi-Fi-only inventory. Unlike those, it still holds a camera seat and acameraPosition. -
CHANGED
POST /v1/devicesdocuments everyfailed[].code. The per-row reason enum used to list onlyinvalid_psnandduplicated, while the registration backend can reject a row for reasons the reference did not name.unknown_psn(not a dashcam in the product database),blacklisted,backend_error(rejected for another reason) andcamera_limit_exceededare now declared and described. The last is the per-row form of the new seat check: a batch that would exceed the camera count is refused up front as403 camera_limit_exceededbefore anything is registered, so a row-levelcamera_limit_exceededappears only when the count changed within the last few minutes. Accepted rows behave as before. -
CHANGED Audit log outcomes use the API's lower-case vocabulary, and the filter accepts all of them.
GET /v1/audit-logsdeclaredstatusasSUCCESS | FAILwhile passing the source value straight through, so entries recorded by invite, password-reset, firmware and GPS-tracking actions carried values the schema did not allow. Both the response field and thestatusfilter now usesuccess | fail | request | accept | delete | start | enable | disable, in line with every other enum in the API. -
ADD
fwVersionon the device list.GET /v1/devicesnow carries the firmware version each dashcam last reported, so an outdated-firmware sweep no longer needs one detail call per device. -
CHANGED
validation_erroris gone — a malformedgroupIdanswersinvalid_parameter. Query-parameter format, range and enum failures all use one code now;validation_errorwas only ever raised forgroupIdandstart, which meant clients had to branch on two codes for the same class of error. -
ADD Firmware updates are actually reported.
GET /v1/devices/{psn}/firmwarereturnedlatest: nullandupdateAvailable: falsefor every dashcam because the model-to-latest catalogue was never configured. It is wired now, and the model lookup no longer matches a shorter name by accident (DR770X BOX PROused to be able to pick upDR770X's version). Models the catalogue does not list still answerlatest: null. -
CHANGED
recordingEventsis merged key by key on update.PUT /v1/geofences/{geofenceId}used to replace the whole object, so flipping one switch meant resending every block. SendingrecordingEvents: { enter: { sdCard: true } }now keepsenter.liveUploadand the entireexitblock as stored — the same rulestylealready followed. Adeviceslist still replaces wholesale. -
CHANGED Every
404names the code it returns, and400lists only the causes that apply to that endpoint. The reference used to print phrases like "Not found" while the body carriedevent_not_found, and endpoints with no cursor and no body still advertisedinvalid_cursorandinvalid_body. Both are generated from what each operation actually accepts now. -
CHANGED Geofence shapes that cannot be stored are rejected instead of quietly trimmed. A
Polygonwith interior rings (holes), arectanglesent as a multi-ring geometry, and apolylinesent as several rings used to be accepted with the extra rings silently dropped. They now answer422 invalid_fieldnaming the reason. Empty coordinates are reported as a point-count problem rather than a format problem. -
CHANGED Multi-ring geofences read back and round-trip correctly. A
polygonmade of several rings came back with empty coordinates —polygons: [[], []]— so sending aGETresponse straight back answered422. The read view now unwraps the multi-ring form properly, andrectangleno longer accepts a multi-ring geometry at all (it needs one ring of exactly 4 corners). -
CHANGED Geofence shapes are validated the same way in the sandbox and with a live key. A shape with empty or too-few coordinates used to be accepted by a sandbox key (
201) and rejected by a live one (422). The point-count and point-format rules now run before the store is touched, so both answer422 invalid_fieldwith the same message. Read-form points ({latitude, longitude}) are also accepted insideshape.geometry.coordinates, not only inshape.coordinates. -
ADD Dashcams that cannot do SD-card commands are named, not timed out. Older models —
DR750S-2CH,DR900S-2CH,DR590X-2CH Plusand the rest of that generation — have no cloud SD-card command channel, soGET /v1/devices/{psn}/sd-files, the per-recording metadata / video / delete routes andPOST /v1/media/recall-jobsused to wait out the device timeout and answer504. They now answer422 unsupported_deviceimmediately, carryingpsnandmodel. Everything that does not touch the SD card — the device list and detail, events, trips, GPS export — is unaffected on those dashcams. -
CHANGED Recalling a recording that is not on the card answers
404, and no longer spends a recall.POST /v1/media/recall-jobsandGET /v1/devices/{psn}/sd-files/{filename}/videoused to answer422 invalid_fieldfor a well-formed filename the card does not hold — after issuing a download token, which counts against the plan's recall allowance. The pre-check now confirms the file exists before the token is issued and answers404 file_not_found, matching what the reference always documented. -
CHANGED
includeon SD-card metadata is a query parameter, so it answers400.GET /v1/devices/{psn}/sd-files/{filename}/metadatarejected an unsupportedincludewith422 invalid_field; every other query parameter in the API answers400 invalid_parameterwith the accepted values inallowed, and this one now does too. Theinvalidextension field it used is gone — readallowedinstead. -
CHANGED SD-card recording types were mislabelled, and most of them were missing.
GET /v1/devices/{psn}/sd-filesdecoded the filename's category letter with a table of its own that held only seven letters. Twelve categories — harsh braking, overspeed, the driver-monitoring types (drowsy, distracted, undetected, calling, smoking, seatbelt) and the four geofence types — were not in it at all, so those clips came back withtype: nulland disappeared from everytypefilter while still being counted in the unfiltered total (a real card: 651 files, 624 across all type filters — the 27 missing ones were all hard-braking clips). Worse, two letters carried wrong names:automaticwas harsh acceleration andtimelapsewas a sharp turn. The endpoint now reads the same tableGET /v1/eventsreads, so a clip'stypeis the same word in both places:automatic→harsh_acceleration,timelapse→sharp_turn,event→driving_impactandimpact→parking_impact(all four old values are gone and are refused as filters, with the accepted values inallowed, rather than answering an empty list).automaticandtimelapsewere plainly wrong names;eventandimpactmeant the right thing but were a second set of words for the same clip — the SD-card list called iteventwhileGET /v1/eventscalled itdriving_impact— so one file needed two vocabularies and a translation table to move between them. There is now one word per recording across the whole API. Alongside that,harsh_braking,overspeed,drowsy,distracted,undetected,calling,smoking,seatbelt,geofence_enter,geofence_exit,geofence_passandgeofence_speedare now returned and filterable.normal,parkingandmanualkeep their names. Of those,normalandparkingare the only two recordings that are not safety events — amanualrecording (the driver pressed record) is published as an event as well, andGET /v1/eventshas used that same word for it since 2026-09-01. A category letter outside the table is now reported asunknowninstead ofnull— silence was what hid this — and the raw letters are exposed as the newtypeCode/directionCodefields.directiongainsoption(the 7-BOX option camera), which used to benulland so vanished fromdirectionfilters the same way.type,directionandstreamvalues outside their enum now answer400 invalid_parameterinstead of an empty list.@fleeta/sdkRecordingType/RecordingDirectionand the MCPlist_sd_recordingstool follow the same vocabulary. -
CHANGED SD-card metadata for a file that is not on the card answers
404.GET /v1/devices/{psn}/sd-files/{filename}/metadataused to return200for any well-formed filename, withrecordedAtLocal,type,directionandstreamsynthesized from the filename you sent and the restnull— a file that did not exist read as "it exists, it just has no thumbnail, GPS or sensor data". The dashcam does not report a missing file as an error (it answers success with an empty payload), so the API now treats an answer that carries no recording data at all as404 file_not_found, the response the specification and the sandbox already described. A clip that exists but genuinely has no samples for the part you asked for still answers200with that partnull. This makes the sandbox and production behave the same way, so error handling built against a test key now works with a live one. -
CHANGED
GET /v1/devices?status=rejects values outside the enum. An unrecognizedstatuswas silently ignored and the whole organization came back with200— includingstatus=online, the most natural way to ask for connected vehicles, which returned every device rather than none. The accepted values are unchanged (driving,parked,offline;drivingandparkedare both connected now, so add the two for "online"), and anything else answers400 invalid_parameterwith them inallowed, exactly as the siblingcategoryparameter already did. -
ADD Geofence map colour and opacity. Every geofence now carries
style—color(#RRGGBB) andopacity(0–1) — onGET /v1/geofences,GET /v1/geofences/{geofenceId}and the create/update responses. These are the values the Fleeta web viewer and the mobile apps actually draw the zone with, so a map you build yourself can match them; both arenullwhen the stored zone carries no value (the viewer then falls back to a palette colour chosen by list position, which this API will not guess).POST /v1/geofencesandPUT /v1/geofences/{geofenceId}accept the same object: each property is applied on its own, sostyle: { "color": "#F21212" }on an update keeps the stored opacity, and an omitted property takes the web viewer default (#6B2FD9,0.24) on create. Previously a geofence created through the API was always drawn in the default purple with no way to change it from the API.@fleeta/sdkaddsGeofenceStyle/GeofenceStyleInput, and the MCPcreate_geofencetool takes optionalcolorandopacity. An invalid value answers422 invalid_field. -
ADD Event thumbnails through MCP. The
list_eventstool takesincludeThumbnails: trueand puts athumbnailUrlon the rows it returns — the same presigned still imageGET /v1/events/{eventId}/thumbnailissues, valid 5 minutes, costing no extra requests and no quota. Because each presigned link is about 1.6 KB of text, the tool attaches images to the first 25 events of a call and says so insummary.thumbnails; the REST parameterinclude=thumbnailis uncapped and stays the right choice when you are assembling the gallery yourself. There is still no per-event thumbnail tool; assistants used to have no way to show event images at all. The AI docs gain a Not available through MCP section listing what genuinely has no tool. -
CHANGED The error schema no longer shows a phantom
propertyNamefield.Problem(and every4xx/5xxthat extends it) allows RFC 9457 extension members, which the API reference rendered as a row literally namedpropertyName— readers took it for a field of ours. It is now labelled extension and described. Five schema keys across three schemas, which a YAML quoting mistake had turned into nameless properties (one inProblemInvalidField.allowed, two inProblemInvalidParameter.allowed, two inProblemExportInProgress), are gone as well, and thealloweddescription — which said when the field is present — is no longer truncated. Spec and documentation only; responses are unchanged. -
ADD Geofence shapes accept the read form.
POST /v1/geofencesandPUT /v1/geofences/{geofenceId}now take the geometry exactly asGETreturns it —coordinatesas{latitude, longitude}points (closing point optional;polygonsfor a multi-polygon) andcenter+radiusMfor a circle — alongside the original write form (GeoJSONgeometry/circle). A geofence you fetched can be edited and sent back unchanged; the422 invalid_fieldmessage names both forms when neither is present. The Try-it panel also gained an Example ▾ picker with one body per shape (circle, polygon, rectangle, polyline) and, for the update endpoint, rename / colour / resize / replace-geometry presets — changingtypealone in the circle example used to leave the circle's centre in the body and answer 422. -
CHANGED Webhook test deliveries now appear in delivery history.
POST /v1/webhooks/{webhookId}/testused to leave no trace, so a subscription that had only been tested showed an emptyGET /v1/webhooks/{webhookId}/deliveries— which read as a fault when the receiver had clearly been called. Each test call is now recorded as one delivery flaggedtest: true(single attempt, status code and network error included), andWebhookDeliverygained thetestboolean (falseon real event deliveries and on records written before this change). Test records still never count towards automatic suspension — that rule is unchanged, see Webhooks › Automatic suspension. -
ADD Server-judged speed-limit events.
GET /v1/events,GET /v1/events/feed,GET /v1/events/{eventId}andGET /v1/safety-events/statsnow include road speed-limit violations that Fleeta judges from each vehicle's GPS track and map speed-limit data — the same events the web viewer's safety-event screen shows — by default, alongside uploaded recordings. They carry the newtypevaluespeed_limit(distinct fromoverspeed, the dashcam's own fixed-threshold alert with a clip), a reallocation(position at peak speed) andspeedKmh(peak speed), and a newspeedLimitobject —limitKmh,overKmh,durationSec,roadName,country,endedAt,peakAt,schoolZone— that isnullon every other event. They have no clip:channelsis empty,hasVideoisfalse, and the video / thumbnail endpoints answer404 video_not_availableas for any event without one.type=speed_limitselects them alone; atypelist without it leaves them out. Statistics merge them intokpi.totalEvents,kpi.activeDevices,byType(aspeed_limitrow),byHourandtopDevices, so counts grow for organizations with speeding vehicles. Existing cursors stay valid.@fleeta/sdkadds'speed_limit'toEventType,SafetyEvent.speedLimitand theSpeedLimitDetailtype; the MCPlist_eventsandsafety_event_statstools describe the new type. See Safety events › Server-judged speed-limit events. -
CHANGED Webhook deliveries no longer follow redirects. A
3xxanswer from your endpoint now counts as a failed delivery (it used to be followed). Point the subscription at the final URL. -
CHANGED Normal and parking recordings are not events.
GET /v1/events,GET /v1/events/feedandGET /v1/safety-events/statsexclude routine driving (N) and parking (P) recordings — they are not safety events, so counts drop for organizations whose dashcams upload them. -
CHANGED Two event populations, named.
GET /v1/reports/drivingandGET /v1/fleet/summarycount detections reported by the dashcam in its 1-minute telemetry (11 counter types —manual, thegeofence_*types andseatbeltnever appear there), whether or not a clip was uploaded.GET /v1/eventsandGET /v1/safety-events/statslist recordings uploaded to the cloud — a clip the dashcam sent because the event matched the device's event-upload settings (all 17 types, with video). The uploaded recordings are a subset of the detections, sobyTypediffers between the two families by design. No schema or behaviour change — descriptions only, plus the new Safety events guide; each MCP tool description now says which population it reads. -
CHANGED GPS Export splits per device by default. Omitting
packagingonPOST /v1/gps/export-jobsused to meansingle— every device and every day merged into oneexport.csvinside the zip. A CSV holds one row per GPS point and has no row cap, so a 70-vehicle, 90-day export ran to millions of rows and would not open in a spreadsheet (Excel stops at 1,048,576 rows and cuts the surplus off without saying so). The default is now the same rule the web console has always used:perDevicewhen the job targets more than one device,singlefor a single device. An explicitpackagingis honoured exactly as before, and the accepted values are unchanged. The GPS Export guide now carries the row-count arithmetic. -
ADD Speed in the exported files. GPS Export never carried speed, even though the same GPS records power
speedKmhonGET /v1/fleet/locationsandGET /v1/trips/{tripId}/track— customers had to call the track endpoint in bulk to fill the gap. CSV now ends with aspeedKmhcolumn (km/h, one decimal; empty when the device reported no speed —0means stationary and is a real value), appended after the existing columns so parsers that read by position are unaffected. GPX carries the same value in the standard Garmin extension<gpxtpx:TrackPointExtension><gpxtpx:speed>, whose unit is metres per second; points without a reported speed get no<extensions>block. KML has no standard place for a per-point scalar and therefore carries no speed. The archive's full field list — columns, units and the UTC basis — is now documented in GPS Export › What is inside the archive. -
CHANGED GPS Export ranges are UTC calendar days.
POST /v1/gps/export-jobsreducesfrom/toto the UTC calendar date of each instant and exports whole UTC days — the time of day is ignored — and the timestamps inside the exported files are UTC.GpsExportJob.from/toecho the job's own calendar basis (midnight UTC for jobs created through the API; jobs created in the web viewer keep the console's local calendar day). Previously the export backend applied a Korea-time calendar day to the same dates, so the exported window could be shifted by nine hours against the request. Every other timestamp in the API was already UTC; Getting started now states it once for all. -
ADD
404 group_not_foundfor agroupIdthat does not exist. A well-formedgroupIdthat no longer exists in your organization — a typo, or a group someone deleted — used to answer200with an empty list, so a mistake looked exactly like "this group has no vehicles" and aGET /v1/fleet/locations/feedpoller kept receiving zero events with a freshnextCursorforever. The nine operations that acceptgroupId(GET /v1/fleet/locations,…/feed,/v1/devices,/v1/events,/v1/events/feed,/v1/safety-events/stats,/v1/fleet/summary,/v1/reports/driving,/v1/geofence-alerts) now answer404 group_not_found, completing the three-way split with400 validation_error(wrong format) and403 group_not_allowed(outside the key's pinned groups). A group that exists but has no vehicles still answers200with an empty list, and nothing about other organizations is revealed — a group belonging to someone else is indistinguishable from one that never existed.@fleeta/sdklists the code inCODES; see Tenant Isolation. -
CHANGED
400is declared on every operation that can raise it. The shared router validates query parameters and request bodies (page size ranges, dates, cursors,groupIdformat, JSON shape) before an operation runs, so any operation that takes a query parameter or a body can answer400— but only 10 of them said so in the OpenAPI spec. All 22 remaining operations now declare it, referencing a sharedBadRequestresponse that coversinvalid_parameter,invalid_cursor,validation_errorandinvalid_body. The page-size parameters (limit,perPage) also declareminimum: 1, which the API always enforced —limit=0was legal per the spec and rejected in practice. Generated clients and contract tests stop treating a400as an unexpected 5xx. Behaviour is unchanged. -
CHANGED Common error responses on every operation. All 52 operations now declare
401 unauthorized,403(insufficient_scope,subscription_inactive),429(monthly_limit_exceeded,rate_limited, the API-edgequota_exceeded) and500 internal_errorin the OpenAPI spec — they could always occur, since the shared router raises them, but only a handful of operations listed them. Three operation-specific responses were missing as well and are now declared:400 invalid_parameteronGET /v1/reports/driving,404 device_not_foundonGET /v1/devices/{psn}/batteryand…/firmware, and409 geofence_unsupported_shapeonPUT /v1/geofences/{geofenceId}— the errors guide now documents 50 codes and@fleeta/sdk0.7.0 lists it inCODES.ProblemQuotaExceeded.bucket/limitare no longer marked required (the API-edge 429 variant carries neither). Behaviour is unchanged. -
CHANGED Webhook suspension and retries. A subscription is now suspended only when both hold: at least 10 consecutive events failed (each event counts once, on its first delivery burst) and the failures have lasted 15 minutes since the first one — a short outage can no longer suspend you (previously the counter was per burst, so ten failed bursts within seconds were enough). Events that arrive while a subscription is suspended are no longer dropped: they are parked in the 14-day dead-letter store, where Fleeta operations can replay them once you have reactivated. A receiver
408or429is no longer retried inside the burst; the event is returned to the queue and redelivered, honouring aRetry-Afterlonger than 60 s (up to 300 s). Suspension writes awebhook.suspendedentry to your audit log.X-Fleeta-Event-Keyis now derived from the event's own ID, so it also survives a dead-letter replay. Signatures and secrets are unchanged. See Webhooks › Automatic suspension. -
CHANGED Sandbox webhooks are per organization. The seeded subscriptions
wh_demo0001/wh_demo0002are copied for each sandbox key's organization on first access, so aDELETE,rotate-secretorreactivatefrom one key no longer changes what another key sees. -
ADD API-key actions in the audit log. Changes made with an API key — geofence and webhook changes, device renames and pre-registration, reboots, GPS exports, recording recalls, SD-card deletions, video URL issuance — are written to your organization's audit trail, the same timeline administrators see in the web viewer's Audit Log screen. Entries returned by
GET /v1/audit-logscarryactorType(api_key;systemfor an action the platform took on its own, such as a webhook suspension;nullfor actions people took) andactorId(the key IDkey_…); a category outside the documented enum is returned asnull. Writing is best-effort — an unreachable audit pipeline never fails the API call. Sandbox keys are not recorded. See API logs › API-key actions in the audit log. -
CHANGED Driving report dates.
GET /v1/reports/drivingacceptsfrom/toasYYYY-MM-DDor an RFC 3339 date-time with offset (the UTC calendar date of the instant is used) — the same form the event endpoints take. An offset-less date-time or a date that does not exist answers400 invalid_parameter.GET /v1/reports/driving/{psn}now answers400 invalid_parameterwhenfrom/toare passed (it takesdateonly); previously they were silently ignored and yesterday's report was returned. -
CHANGED MCP
play_sd_recordingis sub-stream only. The tool'squalityinput was removed; it always requests the low-resolution sub-stream. Full-resolution files go throughrecall_sd_recording, which asks for approval first. -
CHANGED One event is now one row. Safety events used to be returned per uploaded file: a single recording that uploads a front clip, a rear clip and their thumbnails produced four to six separate rows, each with its own
eventId— and two of those IDs could issue a presigned URL for the very same.mp4.GET /v1/events,GET /v1/events/feed,GET /v1/events/{eventId}andGET /v1/safety-events/statsnow group the files of one recording into a single item.eventIdis redefined — it identifies the event, not one of its files, and is stable for the life of the event (uploading another channel later never changes it); IDs stored before this release still resolve, but re-read them fromGET /v1/eventswhen you can.channelsis the union of the event's playable channels — a front + rear event returns["front","rear"]on one row, andoption(7-BOX option camera) was added to the enum.hasVideonow equalschannels.length > 0(a thumbnail-only file used to reporthasVideo: truewith an emptychannels, and every video request for it answered 404). Deleted events are excluded from the list, the feed and the statistics.GET /v1/safety-events/statscounts events, not files —totalEvents,byType,byHourandtopDevicesdrop by roughly 4-6x; this is a correction, not a reduction, andactiveDevicesis unchanged. Pagination: a page may hold fewer items thanlimit, or none, whilehasMoreistrue— always follownextCursorand stop onhasMore: false. Cursors issued before this release stay valid. Feed: an event is delivered once, when its first file arrives; channels uploaded seconds later are not re-delivered, so re-read the event when you need the fullchannels. -
CHANGED Event types were mislabelled.
typewas derived from a numeric table that one of the writers fills from a stale mapping: manual (emergency) recordings were reported asdistracted, andundetected,calling,seatbeltandgeofence_speedevents came back asunknown. The type is now read from the recording's own event code — the same source the BlackVue apps use. An event that used to arrive asdistractedmay now arrive asmanual.type=filters formanual,undetected,calling,seatbeltandgeofence_speed, which always returned an empty list, now work. -
ADD
include=thumbnailon the event list and feed — each row gainsthumbnailUrl, the same presigned URLGET /v1/events/{eventId}/thumbnailwould issue, valid for 5 minutes. It is built from data the row already loaded, so a page of 50 events costs no extra requests and no quota.GET /v1/events/{eventId}/thumbnailalso accepts an optionalchannel(defaults to the first entry ofchannels), so a rear-only event no longer answers with a front thumbnail. -
CHANGED
occurredAtis documented as the upload time of the event's first file — it can trail the moment of the incident by the device's upload delay (usually under a minute; 1–36 minutes measured when the vehicle was out of coverage). Behaviour is unchanged; only the documentation was wrong. -
ADD Webhook reactivation and secret rotation —
POST /v1/webhooks/{webhookId}/reactivateresumes a subscription the dispatcher suspended (same URL, events and secret;409 webhook_not_suspendedotherwise) — no more delete-and-re-register.POST /v1/webhooks/{webhookId}/rotate-secretissues a new signing secret, returned once, while the previous one keeps co-signing forgraceSeconds(default 24 h):X-Fleeta-Signature: v1=<new>,v1=<previous>— accept any matching part. Subscriptions that never rotate keep the single-signature header.WebhookgainssuspendedAtandpreviousSecretExpiresAt.@fleeta/sdk0.6.0:webhooks.reactivate()/webhooks.rotateSecret(). -
ADD Stable webhook event key — every delivery carries
X-Fleeta-Event-Key(alsoeventKeyin the body and inGET …/deliveries). UnlikedeliveryId, which is new for every burst, it does not change when the event is redelivered from the queue — dedupe on it. -
CHANGED Webhook retries — a receiver 4xx is final at both levels: the burst stops and the event is no longer returned to the delivery queue (it used to be redelivered up to 4 more times). The queue schedule is now documented: about 60 s between bursts, at most 5 bursts per event, then a 14-day dead-letter store. See Webhooks › Retries.
-
ADD Typed error extension fields — every error code that carries extension fields now has its own
Problem<Code>schema in the OpenAPI spec (ProblemCloudOnly,ProblemQuotaExceeded,ProblemInvalidField,ProblemMonthlyLimitExceeded, … 17 in total, listed under Models), referenced from the responses that produce them. Generated clients seepsns,bucket,requiredScope,resetsAt, … as typed fields instead of reading them off the examples.@fleeta/sdk0.6.0 addserror.is(code)/isKnown()on top of these types. -
CHANGED Internal diagnostics removed from error bodies —
502 delegate_failed,422 invalid_field(device command/settings),403 recall_limit_exceededand504 command_timeoutno longer include undocumented upstream values (upstreamStatus,statusCode,backendStatus,resultcode,message,waitedMs). They were never part of the contract; quoterequestIdand we match it to our logs. -
CHANGED GPS Export range over 90 days now answers
422 invalid_field—POST /v1/gps/export-jobsvalidates thefrom–tospan (UTC calendar days,toat most 90 days afterfrom) at the API edge and returnsinvalid_fieldwithmaxRangeDays: 90andrangeDays, before anyexport_jobquota is charged. Previously the export backend rejected it and the API surfaced a retryable-looking502 delegate_failed; sandbox keys accepted any span. No successful request changes. -
CHANGED Scopes follow the subscription at request time — after a plan change a key's scopes are recomputed from the current tier on the next authorization (≤ 5 minutes of gateway cache), instead of waiting for the usage-plan move to be stamped on the key. Explicit-scope keys remain capped by their stored list. Fixes the window where usage showed the new tier while
audit:readwas still refused. -
ADD
Retry-AfterandX-Request-Iddeclared as response headers in the OpenAPI spec (429, 409device_busyand the shared error responses), and exposed to browsers on edge responses viaAccess-Control-Expose-Headers. Behaviour is unchanged — the headers were already sent; generated clients and the Try-it panel can now see them. -
ADD Playable link for an SD-card recording in one call —
GET /v1/devices/{psn}/sd-files/{filename}/video?quality=main|sub(defaultsub, scopemedia:recall) returns{ url, expiresAt, jobId, sizeBytes, receivedBytes, quality }; the recall job behind it is created exactly as withPOST /v1/media/recall-jobsandurlequals that job'sdownloadUrl. Although aGET, it uploads over the vehicle's LTE and counts toward the recall allowance. Asking for the same file (samequality) again within 48 hours reuses the media server's copy when it is complete — the API now resends the size the device reported, so no second upload is needed. Afilenameending inS.mp4is rejected with422 invalid_fieldon both routes (select the sub-stream withquality), andRecallJob.sizeBytesis documented as the size of the requested stream. -
ADD Eight domains, 52 operations — devices, telemetry (live locations, trips, GPS tracks, GPS Export), safety events (18 event types, video and thumbnails), SD-card media (list, metadata, recall, delete), geofences and alerts, insights (fleet summary, driving reports), webhooks, and account (usage, API logs, audit logs). See the API Reference.
-
ADD Bearer API keys that follow your subscription — issue keys from Management › Open API in the web viewer; the plan tier, camera limit and scopes are read from your Fleeta subscription on every request, so a plan change never requires a new key. Scoped keys and per-organization tenant isolation throughout.
-
ADD Public sandbox keys — five test keys (one per plan) return a 12-vehicle demo fleet from the real endpoints, so you can build without a dashcam. See the Sandbox guide.
-
ADD Fleeta MCP — a remote MCP server at
mcp.fleeta.iowith 28 tools; connect ChatGPT, Claude, Cursor or any MCP client with a Bearer key or OAuth 2.1. Write actions sit behind a human approval step. See AI Assistants (MCP). -
ADD Webhooks — HMAC-signed push for safety events, device connectivity, geofence alerts and GPS Export completion, with delivery history and a test endpoint. See Webhooks.
-
ADD Official SDK —
@fleeta/sdkfor JavaScript/TypeScript with typed responses, automatic pagination andOpenApiErrormapping. See SDKs. -
ADD Uniform response standard — camelCase keys, RFC 3339 UTC timestamps, metric units, hybrid cursor/offset pagination, and RFC 9457
problem+jsonerrors with a stablecodeon every failure, including422 cloud_onlyfor Wi-Fi-only dashcams. -
ADD Device writes —
PUT /v1/devices/{psn}renames a dashcam and updates its vehicle profile,POST /v1/devicespre-registers dashcams (pending until they first connect), through the same backend the web viewer uses. -
ADD Geofence writes —
POST/PUT/DELETE /v1/geofencescreate, edit and remove geofences through the same backend the web viewer uses, and read the persisted result back. The MCPcreate_geofence/delete_geofencetools run through the approval gate. -
ADD Plan-based limits — rate limits, monthly call allowances and volume quotas derived from your plan and camera count; the API itself is included in the subscription. See Plans & Limits.
Upcoming
- SOON Dashcam Wi-Fi API — the overview is up; the endpoint reference and guides follow.
- SOON Python SDK · Postman collection.