Skip to content

The event object

The same shape is returned by POST /v1/events, GET /v1/events, GET /v1/events/{id}, the snapshot frame and every stream delta. There is one event shape, everywhere.

{
  "event_id": "3d5174f7ca2a5e60b7b32e4fa7713174",
  "schema_version": 2,
  "type": "stopped_vehicle",
  "subtype": null,
  "lat": 45.764,
  "lon": 4.8357,
  "geometry": null,
  "heading": 180,
  "road_ref": "A7",
  "severity": "medium",
  "description": "Broken-down van on the hard shoulder",
  "occurred_at": "2026-08-03T13:15:51.594177Z",
  "created_at": "2026-08-03T13:15:51.594177Z",
  "expires_at": "2026-08-03T14:15:51.594177Z",
  "source_kind": "network",
  "feed_id": null,
  "license": null,
  "upstream_updated_at": null,
  "fetched_at": null,
  "environment": "live",
  "status": "active",
  "verification": "unverified",
  "confidence": 0.3,
  "confirm_count": 0,
  "deny_count": 0,
  "updated_at": "2026-08-03T13:15:51.594177Z",
  "merged_into": null
}

Every timestamp on an event is RFC 3339 in UTC, with microsecond precision and a Z suffix — the stream-ticket response included. Parse with a real RFC 3339 parser rather than a fixed format string, and do not assume a fixed number of fractional digits.

Identity

FieldTypeNotes
event_idstringOpaque. Never parse it. 32 hex characters today; that is not a promise. Use it as a key and nothing else.
schema_versionintWhich envelope rules produced this row. Currently 2. See Compatibility.

client_event_id is deliberately not published. Your internal identifiers are not public data. Correlate a publish with its deltas using the event_id the POST returns.

The observation

Set by the publisher and immutable after write, except on public-feed events, which the upstream feed may revise.

FieldTypeNotes
typestringOne of /v1/meta/event-types. Treat an unknown value as opaque.
subtypestring | nullOptional fine detail under type, from /v1/meta/taxonomy. null means the publisher did not distinguish — always allowed, since type alone must stay sufficient to act on. Refine on it, never filter on it: unlike type it grows without notice.
lat, lonfloatWGS84 reference point, 5 decimals (~1 m). Always present. For a segment event this is the start of the segment.
geometryobject | nullGeoJSON LineString, or null for a point event. A structured object, not a string — never JSON.parse this field. Coordinates are [lon, lat].
headingint | null0–359, 0 = north. Direction of the road users at risk — not of whatever caused the event. null means both directions. Relay does not filter on it; direction filtering is the consumer's.
road_refstring | nullFree text as the publisher knows it (A7, N118). Display and filtering only; never parsed for routing.
severitystring | nulllow, medium, high, or null when unknown. Publisher's own assessment. Vocabulary may grow.
descriptionstring | nullShort free text, ≤ 280 chars. Never required to understand the event.

geometry may be null on an event that was published with one, if what was stored cannot be read back. That is deliberate: losing an event's extent is a degraded answer, losing the event is an outage. lat/lon are always there to fall back to.

Time

FieldTypeNotes
occurred_attimestampWhen the publisher observed it. Defaults to created_at. Never use it for ordering.
created_attimestampServer-trusted publication time. Immutable, and the ordering authority.
expires_attimestampWhen Relay stops serving this event. It moves — confirmations push it back. Drop the event yourself when it passes.
updated_attimestamp | nullStamped on every write, creation included. This is the row's version. Reconcile deltas on it. null only on rows written before the rule existed.

Provenance

FieldTypeNotes
source_kindstringnetwork (a participant) or public (an ingested feed). May grow.
feed_idstring | nullWhich public feed, when source_kind is public. e.g. datex2.fr.rrn. null on network events.
licensestring | nullA key of the upstream's licence, for a public row — null on network events. The field you most need: it says whether you may redistribute what we just handed you.
upstream_updated_attimestamp | nullWhen the SOURCE last revised this, for a public row — null on network events. Not the same as updated_at: this is the upstream's own freshness, not ours.
fetched_attimestamp | nullWhen our sweep last saw this row in the upstream feed, for a public row — null on network events. fetched_at minus upstream_updated_at is the source's lag; now minus fetched_at is ours — published so you can verify our latency claim rather than take it.
environmentstringlive or sandbox — which network the event belongs to. Taken from the key that published it; you cannot set it.

environment is why your sandbox key is useful. A live credential is served live events and nothing else. A sandbox credential is served both, so you can develop against real traffic with your own test events mixed into it — and this field is how you tell them apart. The asymmetry is deliberate and it runs one way only: nothing you publish with a sandbox key can ever reach a live consumer. You can read a live event with a sandbox key; you cannot signal one (403). Our own home page publishes through a sandbox key for exactly this reason.

No field identifies the publisher — not org_id, and since schema_version 2 not a pseudonym either. Provenance tells you what KIND of source an event came from, never which one. If you need to recognise your own events in a stream you also consume, use the event_id the POST response returned: it is a deterministic function of your (org_id, client_event_id), so you hold it before the delta arrives. client_event_id itself never appears on a published event. → Privacy

What the network says

The only layer that mutates after write. Full semantics in Trust.

FieldTypeNotes
statusstringactive, expired, invalidated, merged. May grow.
verificationstringunverified or verified. unverified on write for network events.
confidencefloat0…1, 3 decimals. The network's score.
confirm_countintDistinct REPORTERS — vehicles — whose latest signal is a confirmation. The publishing vehicle is excluded.
deny_countintDistinct reporters whose latest signal is a denial.
merged_intostring | nullThe event_id this one was deduplicated into, when status is merged. Reserved for cross-report merging and null on every event — never build on it being populated.

confirm_count and deny_count counted ORGANISATIONS before schema_version 2 and count vehicles now, so the same road reality yields a much larger number. They are participation, not a verdict: threshold on verification or confidence.Trust

Fields that are not there, and will not be

  • org_id — removed, and so was publisher_id, the pseudonym that briefly replaced it. See Privacy.
  • org_count, confirm_org_count and deny_org_count — removed with the organisation-granularity quorum they belonged to. They had stopped being maintained, and a number that is frozen without saying so is worse than one that is absent. Threshold on verification or confidence.
  • client_event_id — your identifier, not public data.
  • Geo index cells — an implementation detail of our storage. Publishing them would make our index part of a contract we promised never to break.

Reading rules

Three obligations, stated here because this is the page a client author has open:

  1. Ignore fields you do not know. New ones will appear. Never fail on an unexpected key.
  2. Tolerate values you do not know in type, status, severity, source_kind and verification. Skip the event; never crash, never coerce it onto a value you do know.
  3. Never parse event_id or feed_id. They are opaque strings whose shape is not part of the contract.

The reasoning behind all three: Compatibility.