Concepts
Alerts
Alert types, severity and states; the difference between the control room taking charge and the worker seeing the alert; and how to acknowledge or close it.
On this page
An alert is something that has happened in the plant and that someone has to deal with: an SOS, a fall, someone entering where they shouldn’t, a watch with a flat battery or an earthquake warning. It’s the central object of the API.
The object
The alert arrives complete in every webhook (data.alert) and is what GET /v1/alerts/{id} returns. The most commonly used fields:
| Field | What it is |
|---|---|
type, severity, status | What has happened, how serious it is and where it stands in the response. |
title | The title the control room reads. |
site, zone | Where. zone can be null. |
worker, device | Who it happened to and with which watch. null in plant-wide alerts. |
location, location_withheld | Where the person is, if the plant’s privacy level allows it to be seen. |
details | Data specific to each type. |
acknowledged, resolved, assigned_to | Who has taken charge, who has closed it and who it is assigned to. |
date_created, date_last_modified | When it was created and when it last changed. |
simulated | true if it comes from a drill or from simulated watches. |
panel_url | Link to its page in the SafeOnuba dashboard. |
The full list, field by field, is in the schema Alert.
Types
| type | What it means |
|---|---|
sos | The person has asked for help from the watch, or the control room has raised it on their behalf. |
fall_detected | The watch has detected a fall. |
restricted_zone_entry | Entry into a restricted zone. |
forbidden_zone_entry | Entry into an off-limits zone. |
permit_expired_inside | The permit to work has expired with the person still inside the zone. |
permit_closed_inside | The permit to work has been closed with the person still inside the zone. |
heat_stress | Heat stress: the watch has told them to stop and rest. |
evacuation_help | During an evacuation, the person says from the watch that they can’t get out on their own or that they are helping someone else. |
worker_call_request | The person alerts the control room from the watch. Without coverage, the watch records a voice message that can be heard in the dashboard. |
device_offline | The watch hasn’t communicated for a while. |
low_battery | The watch is low on battery. |
earthquake | Earthquake. It is plant-wide: it carries no worker or device. |
tsunami_risk | Tsunami risk. It is plant-wide: it carries no worker or device. |
A nearby vehicle is not an alert
Nearby-vehicle warnings don’t generate alerts of their own. They go inside the alert for an incident, in two lists:
nearby: who was within 100 metres when a critical alert (SOS or fall) was raised, people or vehicles. It gives the distance, never the position, and doesn’t count anyone who was in a privacy zone.vehicle_warnings: the nearby-vehicle warnings that person received in the incident window, with the distance and whether they saw them on the watch.
Details for each type
details changes according to type. For example, SosDetails says how the SOS was raised (trigger), ZoneDetails carries the permit to work that applied (permit_reference) and HazardDetails the magnitude, the distance and whether the warning is official. The variants are in AlertDetails.
Severity
| severity | What it means |
|---|---|
low | Information. |
medium | Warning. |
high | Serious. |
critical | Critical: SOS, fall, “I can’t evacuate”. |
The severity filter of GET /v1/alerts and the min_severity of webhooks are minimums: high returns high and critical.
States
| status | What it means |
|---|---|
unacknowledged | Nobody has taken charge yet. |
acknowledged | An operator has taken charge. |
resolved | Closed. |
- Acknowledging assigns the alert to whoever acknowledges it, if nobody was handling it. Acknowledging is not resolving: an acknowledged alert keeps marking the person on the map until it is resolved.
- Resolving records who, when and with what note, in
resolved. - A resolved alert can be reopened. The
alert.reopenedevent announces it.
Acknowledging and resolving from your control room
PUT /v1/alerts/{id} with the alerts:write scope. The body:
| Field | Required | What it is |
|---|---|---|
status | Yes | acknowledged or resolved. |
actor | Yes | Your control room’s operator: name and, if you want, external_id. |
resolution_reason | No | When resolving, why. Up to 200 characters. |
note | No | A note. Up to 500 characters. |
The operator is required because “acknowledged” measures a person’s response. In the alert’s timeline it appears as “Customer · Operator (via integration)”.
curl -X PUT https://sandbox.api.safeonuba.com/v1/alerts/0f6c2b7e-3a1d-4c55-9b0e-7d2f4a8c1e90 \
-H "Authorization: Bearer $SAFEONUBA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "resolved",
"resolution_reason": "False alarm",
"note": "Confirmed by radio with the supervisor.",
"actor": { "name": "E. Example", "external_id": "OP-117" }
}'const res = await fetch(`https://sandbox.api.safeonuba.com/v1/alerts/${alertId}`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${await safeOnubaToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: 'resolved',
resolution_reason: 'False alarm',
note: 'Confirmed by radio with the supervisor.',
actor: { name: currentOperator.name, external_id: currentOperator.id },
}),
});
if (res.status === 409) {
// It was already resolved, or the transition isn't possible: nothing to do.
} else if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`);
}res = requests.put(
f"https://sandbox.api.safeonuba.com/v1/alerts/{alert_id}",
headers={"Authorization": f"Bearer {safeonuba_token()}"},
json={
"status": "resolved",
"resolution_reason": "False alarm",
"note": "Confirmed by radio with the supervisor.",
"actor": {"name": current_operator.name, "external_id": current_operator.id},
},
timeout=10,
)
if res.status_code != 409: # 409: it was already resolved
res.raise_for_status()var body = JsonContent.Create(new
{
status = "resolved",
resolution_reason = "False alarm",
note = "Confirmed by radio with the supervisor.",
actor = new { name = currentOperator.Name, external_id = currentOperator.Id },
});
var res = await http.PutAsync($"https://sandbox.api.safeonuba.com/v1/alerts/{alertId}", body);
if (res.StatusCode != System.Net.HttpStatusCode.Conflict) res.EnsureSuccessStatusCode();The response is the updated alert. If the transition isn’t possible —for example, resolving or acknowledging an alert that is already resolved— the API responds 409.
Querying alerts
GET /v1/alerts returns alerts from newest to oldest, paginated. Filters:
| Parameter | What it filters |
|---|---|
site_id | A site. |
type | A type. |
status | A state. |
severity | Minimum severity. |
since, until | Date range, in ISO 8601. |
limit, cursor | Pagination (see Pagination). |
To follow alerts in real time, don’t poll in a loop: use webhooks. The list is for loading the state at start-up and for searching.
Worker names
Names are optional by contract. If your contract doesn’t include them, worker.name doesn’t appear and title is built from the type, the zone and the company identifier (external_id), without a name. See Workers and watches.