# Schemas · Reference

> The objects the API accepts and returns, field by field, exactly as the OpenAPI contract defines them.

Page: https://safeonuba.com/en/developers/reference/schemas · SafeOnuba API v1 (`api_version: "2026-09-24"`), in preview: the contract may change.

## Event

Type: `object`

- `id` (string (uuid), required): For deduplication: delivery is “at least once”.
- `type` (EventType, required) Values: `alert.created`, `alert.acknowledged`, `alert.resolved`, `alert.reopened`, `alert.worker_acknowledged`, `muster.started`, `muster.kind_changed`, `muster.phase_changed`, `muster.targets_changed`, `muster.updated`, `muster.worker_missing`, `muster.ended`.
- `api_version` (string, required) Values: `2026-09-24`.
- `created_at` (string (date-time), required)
- `organization_id` (string (uuid) | null, required): The site’s company. `null` in test events.
- `site_id` (string (uuid), required)
- `data` ({ alert } | { muster }, required): The complete object exactly as the `GET` would return it at that moment.
  - Variant 1:
    - `alert` (Alert, required)
  - Variant 2:
    - `muster` (Muster, required)

## EventType

Type: `string`

`muster.updated`: the headcount has changed (checked every 5 s, only emitted if it changes). `muster.targets_changed`: the control room has ruled out or restored a muster point.

Values: `alert.created`, `alert.acknowledged`, `alert.resolved`, `alert.reopened`, `alert.worker_acknowledged`, `muster.started`, `muster.kind_changed`, `muster.phase_changed`, `muster.targets_changed`, `muster.updated`, `muster.worker_missing`, `muster.ended`.

## Alert

Type: `object`

- `id` (string (uuid), required)
- `type` (AlertType, required) Values: `sos`, `fall_detected`, `restricted_zone_entry`, `forbidden_zone_entry`, `permit_expired_inside`, `permit_closed_inside`, `heat_stress`, `evacuation_help`, `worker_call_request`, `device_offline`, `low_battery`, `earthquake`, `tsunami_risk`.
- `severity` (Severity, required) Values: `low`, `medium`, `high`, `critical`.
- `status` (AlertStatus, required) Values: `unacknowledged`, `acknowledged`, `resolved`.
- `title` (string, required): With `include_worker_names`, the title the control room reads (it carries the person’s name). Without it, one composed of the type, the zone and the `external_id`, with no name.
- `date_created` (string (date-time), required)
- `date_last_modified` (string (date-time), required): For ordering: webhooks don’t guarantee order, and anything older than what you already have is discarded.
- `site` (SiteRef, required)
- `zone` (ZoneRef, required)
- `worker` (WorkerRef, required)
- `device` (DeviceRef, required)
- `location` (Location, required)
- `location_withheld` (boolean, required): The position exists but is not delivered: in “emergencies only”, an alert below the severity that lifts the veil doesn’t show it, and neither does the dashboard.
- `details` (AlertDetails, required)
- `nearby` (array<Nearby>, required)
- `vehicle_warnings` (array<VehicleWarning>, required)
- `worker_acknowledged_at` (string (date-time) | null, required): The worker pressed “Got it” on their watch (watch time).
- `acknowledged` (ActionBy, required)
- `resolved` (object | null, required): Resolved: by whom, when and with what note.
  - `at` (string (date-time), required)
  - `by` (string, required): Who: a person from the dashboard, or “Client · Operator (via integration)”.
  - `note` (string | null, required)
- `assigned_to` (string | null, required)
- `simulated` (boolean, required): Raised in a drill or with simulated watches.
- `panel_url` (string (uri), required)

## AlertType

Type: `string`

What has happened. `earthquake` and `tsunami_risk` concern the whole plant: they carry no `worker` or `device`. A nearby vehicle is **not** an alert: it appears inside an incident’s alert (`nearby`, `vehicle_warnings`).

Values: `sos`, `fall_detected`, `restricted_zone_entry`, `forbidden_zone_entry`, `permit_expired_inside`, `permit_closed_inside`, `heat_stress`, `evacuation_help`, `worker_call_request`, `device_offline`, `low_battery`, `earthquake`, `tsunami_risk`.

## Severity

Type: `string`

Severity: `low` (information), `medium` (warning), `high` (serious), `critical` (critical: SOS, fall, “I can’t evacuate”).

Values: `low`, `medium`, `high`, `critical`.

## AlertStatus

Type: `string`

`acknowledged`: an operator has taken charge. It is not the same as `worker_acknowledged_at`, which means the worker has seen the alert on their watch.

Values: `unacknowledged`, `acknowledged`, `resolved`.

## SiteRef

Type: `object`

- `id` (string (uuid), required)
- `name` (string, required)

## ZoneRef

Type: `object | null`

- `id` (string (uuid), required)
- `name` (string, required)

## WorkerRef

Type: `object | null`

- `id` (string (uuid), required)
- `external_id` (string | null, required): The worker’s code at the company (the one in its personnel system). `null` if not given.
- `name` (string): Full name. **Only** if the contract includes it; otherwise the property doesn’t appear.
- `contractor` (string | null, required)

## DeviceRef

Type: `object | null`

- `id` (string (uuid), required)
- `serial` (string, required)
- `battery` (integer | null, required) (from 0 to 100)
- `date_last_seen` (string (date-time) | null, required)

## Location

Type: `object | null`

- `latitude` (number, required) (from -90 to 90)
- `longitude` (number, required) (from -180 to 180)
- `uncertainty_m` (number | null, required): Uncertainty radius in metres. `null` if unknown: not knowing it is not the same as it being perfect. (minimum 0)
- `source` (string, required): `zone`: the person was in a privacy zone and the point is the zone’s, not theirs. Values: `gps`, `network`, `zone`.
- `masked` (boolean, required): The point is the representative one for a privacy zone.
- `position_date_utc` (string (date-time), required)
- `height` (object | null, required): Height above ground by barometer, when it can be stated; `null` if not.
  - `height_m` (number, required)
  - `uncertainty_m` (number, required)
  - `building` (string | null, required)
  - `floor` (string | null, required)

## AlertDetails

Type: `SosDetails | FallDetails | ZoneDetails | HeatStressDetails | EvacuationHelpDetails | WorkerCallDetails | DeviceOfflineDetails | LowBatteryDetails | HazardDetails`

Depends on `type`.

## SosDetails

Type: `object`

- `trigger` (string, required): How it was requested: on the watch screen, with the physical button, after an unanswered fall, or from the control room. Values: `watch_button`, `watch_hardware_button`, `after_fall`, `control_room`.

## FallDetails

Type: `object`

- `escalated_to_sos` (boolean, required): Did not answer the watch’s “Are you OK?” and it escalated to SOS.

## ZoneDetails

Type: `object`

- `zone_type` (string, required) Values: `restricted`, `forbidden`.
- `permit_reference` (string | null, required): The permit to work (job safety analysis, JSA) in force, if there was one.

## HeatStressDetails

Type: `object`

Nothing derived from heart rate: vital signs don’t leave through the API, not even aggregated.

- `action` (string, required): What the watch has told them: stop, drink and find shade. There are no levels. Values: `stop_and_rest`.
- `heat_index_c` (number | null, required): The plant’s heat index (°C).
- `sun_exposure_minutes` (integer | null, required)

## EvacuationHelpDetails

Type: `object`

- `reason` (string | null, required): What they said on the watch: trapped, injured or helping someone else. Values: `trapped`, `injured`, `helping`.
- `muster_id` (string (uuid), required)

## WorkerCallDetails

Type: `object`

- `voice_note` (boolean, required): With no coverage, the watch recorded a voice message (it can be heard in the dashboard).

## DeviceOfflineDetails

Type: `object`

- `minutes_silent` (integer, required)

## LowBatteryDetails

Type: `object`

- `battery_pct` (integer, required) (from 0 to 100)

## HazardDetails

Type: `object`

- `magnitude` (number | null, required)
- `distance_km` (number | null, required)
- `official` (boolean, required): `false`: a SafeOnuba estimate with no official warning. `true`: an official bulletin confirmed by a person.
- `felt_at_site` (boolean, required)
- `eta` (string (date-time) | null, required): Estimated arrival of the wave (tsunami).

## Nearby

Type: `object`

Who was within 100 m when a critical alert was raised (SOS, fall). Distance, never position; anyone in a privacy zone doesn’t count.

- `kind` (string, required) Values: `person`, `vehicle`.
- `worker` (object, required)
  - `id` (string (uuid) | null, required): `null` in alerts from before 24 September 2026, when it was not yet being copied.
  - `external_id` (string | null, required): The worker’s code at the company (the one in its personnel system). `null` if not given.
  - `name` (string): Full name. **Only** if the contract includes it; otherwise the property doesn’t appear.
  - `contractor` (string | null, required)
- `vehicle_type` (string | null, required)
- `distance_m` (integer, required)
- `reported_at` (string (date-time), required)

## VehicleWarning

Type: `object`

Nearby-vehicle warnings that person received during the incident window. They are warnings, not alerts.

- `at` (string (date-time), required)
- `vehicle_type` (string | null, required)
- `distance_m` (integer, required)
- `worker_acknowledged_at` (string (date-time) | null, required)

## ActionBy

Type: `object | null`

- `at` (string (date-time), required)
- `by` (string, required): Who: a person from the dashboard, or “Client · Operator (via integration)”.

## Muster

Type: `object`

- `id` (string (uuid), required)
- `site` (SiteRef, required)
- `status` (string, required) Values: `active`, `completed`, `cancelled`.
- `phase` (string, required): `inspecting`: headcount complete and the control room is checking the plant; watches say “do not go back until further notice”. Values: `evacuating`, `inspecting`.
- `kind` (MusterKind, required) Values: `general`, `tsunami`, `earthquake`.
- `reason` (string | null, required)
- `started_at` (string (date-time), required)
- `ended_at` (string (date-time) | null, required)
- `deadline` (object | null, required)
  - `at` (string (date-time), required)
  - `source` (string, required): Tsunami: the time of an official warning, or the ceiling in the zone’s plan. Never a physical calculation. Values: `official`, `plan_estimate`.
- `origin` (object | null, required): Where the emergency comes from. `null` in an evacuation with no origin (a general drill).
  - `latitude` (number, required)
  - `longitude` (number, required)
  - `radius_m` (integer, required)
  - `label` (string | null, required)
  - `zone` (ZoneRef, required)
- `targets_revision` (integer, required): Goes up every time the targets change (ruling out, restoring, change of type).
- `totals` (object, required): The same figures as the dashboard banner and the closing summary: the server counts them. In an ended evacuation, what remains is what was recorded on ending —arrived and total— and the rest comes out as `null`.
  - `expected` (integer, required)
  - `safe` (integer, required)
  - `help` (integer | null, required)
  - `wrong_point` (integer | null, required)
  - `pending` (integer | null, required)
  - `no_signal` (integer | null, required)
  - `not_worn` (integer | null, required)
- `assembly_points` (array<AssemblyPoint>, required): The targets for the type, ruled-out ones included.

## MusterKind

Type: `string`

Values: `general`, `tsunami`, `earthquake`.

## AssemblyPoint

Type: `object`

- `id` (string (uuid), required)
- `name` (string, required)
- `status` (string, required) Values: `available`, `excluded`.
- `count` (integer | null, required): People safe at this point. `null` in an ended evacuation: on ending, only the total is kept.
- `excluded` (object | null, required)
  - `reason` (string, required)
  - `recommended` (boolean, required): The system had recommended it (radius or wind). Whoever rules it out is always a person.
  - `at` (string (date-time), required)
  - `by` (string, required)

## TokenResponse

Type: `object`

- `access_token` (string, required)
- `token_type` (string, required) Values: `Bearer`.
- `expires_in` (number, required) Values: `3600`.
- `scope` (string, required)

## OAuthError

Type: `object`

Token error, in the RFC 6749 §5.2 format.

- `error` (string, required) Values: `invalid_request`, `invalid_client`, `unauthorized_client`, `unsupported_grant_type`, `invalid_scope`.
- `error_description` (string)

## TokenRequest

Type: `object`

- `grant_type` (string, required) Values: `client_credentials`.
- `client_id` (string, required)
- `client_secret` (string, required)
- `scope` (string): Space-separated; a subset of the client’s. Without it, all of the client’s.

## SiteList

Type: `object`

- `data` (array<Site>, required)

## Site

Type: `object`

- `id` (string (uuid), required)
- `name` (string, required)
- `location` (string | null, required)
- `privacy_mode` (string, required): `on_demand` (“emergencies only”): a person’s position is only visible with an open alert from `unveil_min_severity`, during an evacuation, or with an approved request. The API obeys the same rule as the dashboard. Values: `continuous`, `on_demand`.
- `unveil_min_severity` (Severity, required) Values: `low`, `medium`, `high`, `critical`.

## Problem

Type: `object`

Error in RFC 9457 format (`application/problem+json`), with the real HTTP status code.

- `type` (string, required): URI that identifies the problem type.
- `title` (string, required)
- `status` (integer, required)
- `detail` (string)
- `instance` (string)
- `code` (string, required): Stable error code, to program against. The text may change; this won’t.

## ZoneList

Type: `object`

- `data` (array<Zone>, required)

## Zone

Type: `object`

- `id` (string (uuid), required)
- `name` (string, required)
- `type` (string, required) Values: `work_area`, `restricted`, `forbidden`, `assembly_point`, `privacy`, `building`.
- `geometry` (object | null, required): GeoJSON polygon (WGS84). `null` for privacy zones: the name and type are given, not where they are (pending validation with the committee).
  - `type` (string, required) Values: `Polygon`.
  - `coordinates` (array<array<array>>, required)
- `assembly_point` (object | null, required): Only on muster points.
  - `serves` (array<string>, required) Values: `general`, `tsunami`, `earthquake`.
  - `elevation_m` (number | null, required)
  - `elevation_source` (string | null, required) Values: `dem`, `verified`.
  - `vertical` (boolean, required): Vertical refuge (a building).
  - `capacity` (integer | null, required)

## AlertPage

Type: `object`

- `data` (array<Alert>, required)
- `next_cursor` (string | null, required): Opaque cursor for the next page; `null` if there are no more.

## AlertUpdate

Type: `object`

Two states, `acknowledged` and `resolved`, plus the operator. Acknowledging self-assigns the alert if nobody was handling it, as in the dashboard.

- `status` (string, required) Values: `acknowledged`, `resolved`.
- `resolution_reason` (string): On resolving: why. Free text; it goes in the alert’s timeline next to the note. (up to 200 characters)
- `note` (string) (up to 500 characters)
- `actor` (Actor, required)

## Actor

Type: `object`

The operator in the customer’s control room who takes the action. Required: “acknowledged” measures a person’s response, and without a name the timeline would only show the client’s.

- `name` (string, required) (up to 120 characters)
- `external_id` (string) (up to 64 characters)

## MusterPage

Type: `object`

- `data` (array<Muster>, required)
- `next_cursor` (string | null, required): Opaque cursor for the next page; `null` if there are no more.

## MissingWorkerList

Type: `object`

- `data` (array<MissingWorker>, required)

## MissingWorker

Type: `object`

- `worker` (WorkerRef, required)
- `device` (DeviceRef, required)
- `status` (string, required) Values: `help`, `wrong_point`, `pending`, `no_signal`, `not_worn`.
- `needs_rescue` (boolean, required): Has an open SOS or fall and has not arrived: cannot leave on their own.
- `help_reason` (string | null, required) Values: `trapped`, `injured`, `helping`.
- `assembly_point` (object | null, required): The point where they are or said they were (with `wrong_point`, one that is unusable).
  - `id` (string (uuid), required)
  - `name` (string, required)
  - `valid` (boolean, required)
- `declared_at` (string (date-time) | null, required)
- `last_seen_at` (string (date-time) | null, required)
- `location` (Location, required)
- `location_withheld` (boolean, required)

## WorkerPage

Type: `object`

- `data` (array<Worker>, required)
- `next_cursor` (string | null, required): Opaque cursor for the next page; `null` if there are no more.

## Worker

Type: `object`

- `id` (string (uuid), required)
- `external_id` (string | null, required): The worker’s code at the company (the one in its personnel system). `null` if not given.
- `name` (string): Full name. **Only** if the contract includes it; otherwise the property doesn’t appear.
- `contractor` (string | null, required)
- `role` (string | null, required)
- `is_active` (boolean, required)
- `device` (DeviceRef, required)
- `date_last_seen` (string (date-time) | null, required): The most recent of the last position and the watch’s last contact.

## DevicePage

Type: `object`

- `data` (array<Device>, required)
- `next_cursor` (string | null, required): Opaque cursor for the next page; `null` if there are no more.

## Device

Type: `object`

- `id` (string (uuid), required)
- `serial` (string, required)
- `battery` (integer | null, required) (from 0 to 100)
- `date_last_seen` (string (date-time) | null, required)
- `model` (string | null, required)
- `status` (string, required) Values: `active`, `inactive`, `maintenance`, `lost`.
- `worn` (boolean | null, required): `null`: not known (no wrist sensor).
- `worker` (WorkerRef, required)

## PositionList

Type: `object`

- `data` (array<Position>, required)

## Position

Type: `object`

Only the positions the privacy veil allows at that moment: in “emergencies only”, anyone with nothing open doesn’t appear.

- `device_id` (string (uuid), required)
- `worker` (WorkerRef, required)
- `location` (Location, required)
- `in_vehicle` (boolean, required)

## EventPage

Type: `object`

- `data` (array<Event>, required)
- `next_cursor` (string | null, required): Opaque cursor for the next page; `null` if there are no more.

## WebhookEndpointList

Type: `object`

- `data` (array<WebhookEndpoint>, required)

## WebhookEndpoint

Type: `object`

- `id` (string (uuid), required)
- `url` (string (uri), required)
- `event_types` (array<EventType | "alert.*" | "muster.*">, required)
- `filters` (object, required)
  - `site_ids` (array<string (uuid)>)
  - `min_severity` (Severity) Values: `low`, `medium`, `high`, `critical`.
  - `contractors` (array<string>)
- `is_active` (boolean, required)
- `failing_since` (string (date-time) | null, required): Has been failing since then. The destination is **not** disabled automatically: the next SOS would stop arriving without anyone deciding it.
- `created_at` (string (date-time), required)

## WebhookEndpointCreated

Type: `object`

- `id` (string (uuid), required)
- `url` (string (uri), required)
- `event_types` (array<EventType | "alert.*" | "muster.*">, required)
- `filters` (object, required)
  - `site_ids` (array<string (uuid)>)
  - `min_severity` (Severity) Values: `low`, `medium`, `high`, `critical`.
  - `contractors` (array<string>)
- `is_active` (boolean, required)
- `failing_since` (string (date-time) | null, required): Has been failing since then. The destination is **not** disabled automatically: the next SOS would stop arriving without anyone deciding it.
- `created_at` (string (date-time), required)
- `secret` (string, required): The signing secret. **Shown only this once.**

## WebhookEndpointCreate

Type: `object`

- `url` (string, required)
- `event_types` (array<EventType | "alert.*" | "muster.*">, required)
- `filters` (object)
  - `site_ids` (array<string (uuid)>)
  - `min_severity` (Severity) Values: `low`, `medium`, `high`, `critical`.
  - `contractors` (array<string>)

## WebhookSecretRotated

Type: `object`

- `secret` (string, required)
- `previous_valid_until` (string (date-time), required)

## SandboxScenarioRun

Type: `object`

- `run_id` (string (uuid), required)
- `scenario` (SandboxScenario, required) Values: `sos`, `fall`, `heat_stress`, `evacuation`.
- `site_id` (string (uuid), required)
- `resource` (object, required): What it has opened: check it with its `GET`, or wait for the webhook.
  - `type` (string, required) Values: `alert`, `muster`.
  - `id` (string (uuid), required)
- `ends_at` (string (date-time), required): When it closes by itself.

## SandboxScenario

Type: `string`

- `sos`, `fall`, `heat_stress`: a test person presses SOS, falls or suffers heat stress. The alert goes out (`alert.created`) and, if nobody resolves it first with `PUT /v1/alerts/{id}`, it resolves itself after 10 min (`alert.resolved`).
- `evacuation`: a general evacuation of 3 min. At 15 s four people arrive at point A; at 35 s someone arrives at B and the control room rules it out (`muster.targets_changed`); at 55 s a person asks for help (`alert.created`, `evacuation_help`); at 95 s whoever was at B moves on to C; at 3 min, all clear (`muster.ended`). In between, `muster.updated` every time the headcount changes.

Values: `sos`, `fall`, `heat_stress`, `evacuation`.

## SandboxScenarioRequest

Type: `object`

- `scenario` (SandboxScenario, required) Values: `sos`, `fall`, `heat_stress`, `evacuation`.
- `site_id` (string (uuid)): The sandbox site. Without it, the first one of yours that is a sandbox site.
