The community layer
Your users see things no road operator will ever publish
Relay carries the French national road feed, so a navigation app that only reads is already useful on day one. This page is about the other direction: the specific, nameable part of the vocabulary that can only ever arrive from a windscreen — and what it costs you to put it there.
10 reports no operator feed produces
Not a judgement about operators — a consequence of what an operator is. A road authority publishes what it has dispatched a crew to, and a pothole before that dispatch exists in nobody’s system. The first rows are whole types with no public source anywhere: nobody publishes their own police control, an unattended camera car, or a dead signal before somebody telephones it in. Every row below is a type or a value that no mapping in any national feed we ingest can produce.
- Police check
police_checkNo authority publishes its own controls, and none ever will. It is the most-tapped button on every community network there has been — and the one a small app cannot get from anywhere else at any price.
- Speed camera · Mobile camera
speed_camera · mobile_cameraA camera car parks for two hours and appears in no dataset. The fixed ones are open data; this one exists only in the windscreens that just passed it.
- Infrastructure fault
infrastructure_faultA dark traffic signal is reported to a municipality by telephone, on office hours, and reaches no feed at all. Every driver at that junction knows within seconds.
- Roadworks · Emergency works
roadworks · emergency_worksPlanned works are published weeks ahead and we deliberately do not carry them. The dig that opened at six this morning is in nobody's plan.
- Road damage · Pothole
road_damage · potholeNobody has dispatched a crew yet, so it is in no operator's system. A suspension sensor reports it with no driver involved at all.
- Road damage · Sinkhole
road_damage · sinkholeThe one road-damage value that is an emergency, and it is seen from a car first.
- Object on road · Debris
object_on_road · debrisThe feed carries lost cargo and fallen trees. Ordinary debris is what a driver swerves around and nobody declares.
- People on road · Road worker
people_on_road · road_workerA crew on the carriageway outside a declared worksite — the case a planned-works record does not cover.
- People on road · Traffic officer
people_on_road · traffic_officerAn officer directing traffic at a junction. Real, immediate, and in no feed.
- People on road · Cyclist
people_on_road · cyclistA cyclist on a road that does not expect one. A hazard to them before it is a hazard to anyone else.
And on everything the feed does carry, your users are earlier
This is the bigger half and it is not a vocabulary question. An operator publishes when the operator knows; a report from inside the queue is the same event, sooner, and on stretches nobody monitors it is the only report there will be.
Accident
The feed publishes when the operator knows. Somebody in the third car back knows first.
Congestion
A queue is declared when it is detected on the managed network. Your users are inside it.
Stopped vehicle
On the shoulder of an unmonitored stretch, a windscreen is the only sensor there is.
Weather hazard
Black ice is a hundred metres long. Detection at that resolution is a car, not a station.
Road closure
An operator declares the closure it planned. The one an accident caused four minutes ago is announced by the drivers already stopped at it.
A report button and a confirm button. That is the whole mechanic
You already have the reporting sheet — a grid of icons your users tap. What you do not have is somewhere for those reports to go and somebody to confirm them. Both are one HTTP call, and publishing is free forever.
1 · A driver reports
Map your sheet onto the vocabulary once. Filter on type, refine on subtype — the coarse list grows rarely, so this table is written once and does not rot. The time to live comes from the type: you never expire a marker yourself.
const API = "https://api.cudatus.com";
// Your report sheet, mapped onto the vocabulary once.
const SHEET = {
police: { type: "police_check" },
queue: { type: "congestion", subtype: "queue" },
accident: { type: "accident" },
roadClosed: { type: "road_closure", subtype: "full_closure" },
speedCamera: { type: "speed_camera" },
pothole: { type: "road_damage", subtype: "pothole" },
trafficLightOut: { type: "infrastructure_fault", subtype: "traffic_signal" },
roadworks: { type: "roadworks" },
debris: { type: "object_on_road", subtype: "debris" },
brokenDown: { type: "stopped_vehicle", subtype: "broken_down" },
blackIce: { type: "weather_hazard", subtype: "black_ice" },
personOnRoad: { type: "people_on_road", subtype: "pedestrian" },
};
// A driver taps a button. This is the whole publish path.
export async function report(kind, { lat, lon }, severity = "medium") {
const res = await fetch(`${API}/v1/events`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RELAY_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ ...SHEET[kind], lat, lon, severity }),
});
const event = await res.json();
return event.event_id; // it is on the stream before this line runs
}2 · The next driver answers
“Still there” extends the event’s life; “gone” is what clears it. Enough denials end it early — for everyone on the network, not just for your users — and a denial clears rather than condemns, so nobody is accused of a bad report by a road that simply got swept.
// The marker is on screen. The driver says "still there" or "gone".
export async function answer(eventId, stillThere, device) {
const res = await fetch(`${API}/v1/events/${eventId}/signals`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RELAY_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
action: stillThere ? "confirm" : "deny",
// What the quorum counts: one stable token per DEVICE.
reporter_token: device.id,
// Optional, and worth sending — a confirmation from the
// scene is recorded as coming from the scene.
lat: device.lat,
lon: device.lon,
}),
});
// 409: a quorum cleared it before this driver answered.
if (res.status === 409) return "already over";
return (await res.json()).verification; // unverified | verified
}reporter_token is the field to get right
The quorum counts distinct vehicles, not organisations. Send a stable per-device token and a hundred of your users confirming a pothole is a hundred voices; omit it and your whole app counts as one, which still works and promotes nothing. It never reaches the wire: it is salted with your organisation as it arrives and the raw value is never stored, so it cannot be correlated back to a person by us or by anyone reading the network.
Three behaviours you would otherwise have to build
The reason a community layer is expensive is never the report button. It is everything that has to happen to a report afterwards, and all of it is on this side of the wire.
Markers that disappear on their own
Every event carries a time to live from its type — minutes for a queue, up to weeks for road damage. A client drops it when its own deadline passes, and nothing has to be sent to say so. No stale-hazard sweep to write.
Confirmation without a moderation team
Distinct vehicles reporting the same thing promote it; enough denying ones clear it. You are not deciding whether a report is true, and neither are we — the road is.
Reports from users who are not yours
This is the part a single app cannot buy at any price. A pothole confirmed by a fleet's trucks reaches your users too, because everyone on the network reads the same wire.
Police checks are in the vocabulary, and suspension is why that took a while
It is the type a community app is asked for first. It ships, and the capability it rides on is the single clearest case on this site for plugging into a shared trunk.
Rebroadcasting police-control reports can be required to stop over a given zone, for a given duration, on demand. That suspension belongs at the trunk rather than in each app: a small number of active zones, applied at the three places the network decides what a subscriber may see.
Every navigation app faces that requirement and almost none can afford to meet it alone. Solving it once for everyone on the wire is what a shared trunk is for — and it is a capability your own roadmap would never reach.
Publishing is free, so the sandbox is where this begins
Issue a sandbox key, wire the two calls above, and watch your own reports arrive on the stream you are already reading. A sandbox key reads live national traffic too, so your map is never empty while you test against it.