Skip to main content

Safety events — two populations

The API reports safety events in two places, and they answer two different questions. Both are correct; they are simply not the same set of things.

GET /v1/events · GET /v1/safety-events/statsGET /v1/reports/driving · GET /v1/fleet/summary
What is countedRecordings uploaded to the cloud — a clip the dashcam sent because the event matched the device's event-upload settingsDetections counted by the dashcam in its 1-minute telemetry — every event the camera detected, whether or not a clip was uploaded
Depends on upload settingsYes — an event type that is switched off for upload never appearsNo
Event typesAll 17 recording types (driving_impactmanual, geofence_*, seatbelt) plus speed_limit — server-judged road speed-limit violations, see below11 DMS counters — manual, the four geofence_* types, seatbelt and speed_limit never appear
Has a videoYes for recordings (hasVideo, channels, GET /v1/events/{eventId}/video); never for speed_limitNo — counts only
Answers"Which events have footage?", "show me the impact at 14:07", "where did Van 07 break the speed limit?""How many harsh-braking events did Van 07 have last week?", "riskiest vehicles"
MCP toolslist_events, safety_event_stats, get_event_videodriving_report, fleet_summary

The uploaded recordings are always a subset of the detections: a detection becomes an event in /v1/events only when the device's event-upload settings told the dashcam to upload it. Comparing byType across the two families is therefore not an error check — the report side is expected to be larger.

Camera seats

Dashcams registered beyond the subscription's camera count (entitlement: over_limit — see Rate Limits › Camera seats) appear in both families, with different defaults. GET /v1/events, its feed and GET /v1/safety-events/stats include them (entitlement=all); GET /v1/reports/driving and GET /v1/fleet/summary leave them out (entitlement=covered, the web viewer's report numbers) — pass entitlement=all to count them. Their events, thumbnails and statistics stay readable; only GET /v1/events/{eventId}/video answers 403 camera_limit_exceeded for such a dashcam.

Server-judged speed-limit events (speed_limit)

GET /v1/events, GET /v1/events/feed, GET /v1/events/{eventId} and GET /v1/safety-events/stats include a second kind of event by default, exactly as the Fleeta web viewer's safety-event screen does: a road speed-limit violation judged by the server. Fleeta evaluates each vehicle's GPS track against map speed-limit data for the road it was on and records one event per violation — from the moment the vehicle went over the limit until it dropped back under it.

  • What you get. type is speed_limit. occurredAt is when the vehicle first exceeded the limit (not an upload time), location is the position at peak speed and speedKmh the peak GPS speed. The speedLimit object carries the rest: limitKmh (the posted limit that applied), overKmh (how far above it the vehicle got), durationSec, roadName, country, endedAt, peakAt and schoolZone. schoolZone is true only when a school-zone limit was actually in force at the time — a school-zone road outside its hours reports false. For every other event type speedLimit is null.
  • Why there is no video. The judgement comes from the GPS track, not from the dashcam, so nothing was recorded: channels is empty, hasVideo is false, thumbnailUrl is null, and the video / thumbnail endpoints answer 404 video_not_available — the same answer as for any event without a clip.
  • speed_limit vs overspeed. overspeed is the dashcam's own speed alert: a fixed threshold configured on the device, uploaded as a recording with a clip. speed_limit compares the vehicle against the legal limit of the road it was actually on, road segment by road segment, and needs no device setting. A single burst of speeding can therefore appear as both — one overspeed recording and one speed_limit event — and they keep separate eventIds.
  • Filtering. Leave type out to get both kinds. type=speed_limit returns the violations alone; any type list without speed_limit (for example type=harsh_braking,overspeed) leaves them out. from/to, psn and groupId apply to both kinds. The statistics endpoint has no type filter and always merges both: kpi.totalEvents adds them, byType carries a speed_limit row, byHour uses the moment the violation began and topDevices ranks devices on both kinds together.
  • Hour buckets and time zones. byHour counts by hour of day, in UTC by default. Pass tz with an IANA zone name (tz=Asia/Seoul) and the aggregation groups the buckets in that zone instead, applying daylight saving per event. A UTC offset (+09:00) is rejected with 400 invalid_parameter precisely because it cannot do that — use the zone name. The response echoes the zone as hourTz, so you can label the chart from the payload alone rather than shifting 24 buckets yourself. Nothing else changes: from/to and every timestamp in the response stay RFC 3339 UTC.
  • Pagination. The two kinds come from different stores and are merged in occurredAt order. A page can end early — fewer rows than limit, or none — while hasMore is true; follow nextCursor and stop on hasMore: false, as for every cursor-paginated list. Cursors issued before this addition stay valid and simply include the violations from the beginning of history.
  • Feed. In GET /v1/events/feed a violation is delivered once the server has recorded it, which happens shortly after it ends; its position in the feed is occurredAt (when it began). As with a recording that is still uploading, a poll that already moved past that moment can skip it.

Which one to read

  • Counting — how often something happened, per vehicle or per type: use the driving reports. events.total and events.byType[] on GET /v1/reports/driving (ranking, fromto) and GET /v1/reports/driving/{psn} (one day, with hourly[].events), plus eventTrend and topDevicesByEvents on GET /v1/fleet/summary.
  • Looking at evidence — a clip, a thumbnail, when exactly a recorded event happened: use GET /v1/events and issue the video URL from there. GET /v1/safety-events/stats aggregates that same population.
  • Both families use the same byType vocabulary (type values in lower snake_case, the same names as Event.type). The shape is shared on purpose so your code can reuse it; only the population behind the numbers differs.
  • occurredAt is the upload time of the event's first file, not the instant of the incident. It trails the incident by the device's upload delay — usually under a minute, but up to tens of minutes when the vehicle is out of coverage (measured 1–36 min). It is also the sort key of GET /v1/events and the field its from/to filters apply to.
Geofence and manual events

Geofence alerts (geofence_enter / exit / pass / speed) and manual recordings (manual) are produced by the cloud or by the driver's button, not by the dashcam's DMS counters, so they exist only as uploaded recordings and in GET /v1/geofence-alerts — never in the driving reports.

Date parameters of the report endpoints

The two driving-report endpoints take their period differently, and the server enforces the difference instead of silently ignoring a parameter it does not read.

EndpointPeriod parametersAccepted values
GET /v1/reports/driving (fleet ranking)from, to — inclusive UTC calendar daysYYYY-MM-DD or an RFC 3339 date-time such as 2026-07-01T09:00:00+09:00. A date-time is reduced to the UTC calendar date of that instant; its time of day is not used. Default: to = today (UTC), from = to − 6 days. At most 92 days.
GET /v1/reports/driving/{psn} (one vehicle, one day)date onlyYYYY-MM-DD (UTC day). Default: yesterday (UTC). from / to are rejected with 400 invalid_parameter — for a range call GET /v1/reports/driving?from&to and read that vehicle's row (one row per device; page with limit/after), or call this endpoint once per date.
GET /v1/events · GET /v1/safety-events/statsfrom, toRFC 3339 date-times, applied to occurredAt — an upload time, see the note above.

One client helper that formats new Date().toISOString() therefore works for every from/to in the API. Mind the calendar-date reduction on the ranking report: the instant 2026-07-02T08:00:00+09:00 selects the UTC day 2026-07-01, because that moment is 23:00 UTC on the 1st.

Field-level descriptions live in the API Reference under events and insights.