# SafeOnuba API

> Receive SafeOnuba alerts and evacuations in your control room, your SCADA, your PSIM or your intranet, and acknowledge or close alerts from there.

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

> **v1 · preview**
>
> The contract is awaiting sign-off from the first customer and may change. Any change will be noted in the [changelog](https://safeonuba.com/en/developers/changelog).

The SafeOnuba API takes your plant’s alerts and evacuations to the systems your control room already uses: a SCADA, a PSIM or the intranet. It is meant so that the operator in your control room sees the same as the SafeOnuba dashboard, on their own screen, and can respond from there.

## What you can do

- **Receive in real time** every alert and every change to an evacuation with [webhooks](https://safeonuba.com/en/developers/webhooks): a signed `POST` to your server for each event.
- **Query** alerts, evacuations and their headcount, who is missing, workers, watches, sites, zones and the positions the plant’s privacy level lets you see.
- **Acknowledge and close alerts** from your control room with `PUT /v1/alerts/{id}`. Always on behalf of an operator, who is recorded in the alert’s timeline.
- **Catch up on what you missed** if your receiver goes down: `GET /v1/events` returns the events from the last 30 days, byte for byte the same as the ones that were sent.
- **Integrate without watches** in the [sandbox](https://safeonuba.com/en/developers/sandbox), with simulated SOS, fall, heat stress and full evacuation scenarios.

## What it doesn’t do

- **In v1 it doesn’t start evacuations.** The system proposes and a person decides in the SafeOnuba dashboard. Your control room receives the evacuation as soon as it starts and follows it until it ends.
- **It doesn’t give vital signs**, neither individual nor aggregated. A heat stress alert says what the worker has been told to do, not their heart rate.
- **It sees no more than the dashboard.** The API sees the same as a dashboard user with those permissions would. [Privacy and security](https://safeonuba.com/en/developers/privacy-and-security) explains it.

## Environments

| Environment | Address | Status |
| --- | --- | --- |
| Production | `https://api.safeonuba.com` | Activated when the customer is registered. Not yet open to the public. |
| Test (sandbox) | `https://sandbox.api.safeonuba.com` | Simulated watches. This is where you integrate. |

Both have the same paths and the same contract. The only things that change when you move to production are the address and the credentials.

## Conventions

- All paths are under `/v1`, and every event carries `api_version: "2026-09-24"`. More in [Versioning](https://safeonuba.com/en/developers/versioning).
- Bodies are JSON, except the token request, which is `application/x-www-form-urlencoded`.
- Identifiers are UUIDs. Dates are ISO 8601 in UTC.
- Errors arrive as `application/problem+json` with the real HTTP status. See [Errors](https://safeonuba.com/en/developers/errors).
- Lists are paginated with a cursor. See [Pagination](https://safeonuba.com/en/developers/pagination).
- Two paths are public and need no token: `GET /v1/health` and `GET /v1/openapi.json`, the contract.

> **Call the API from your server**
>
> The API has no CORS: a browser can’t call it directly. Requests go out from your backend, which is also where the `client_secret` should live. That is why this documentation has no “try it” button: the examples are code to copy.

## How it fits into your control room

The usual route has three parts:

1. **A webhook receiver** on your network, with a public https URL. It verifies the signature, responds `2xx` straight away and puts the event on a queue.
2. **Your control room system** consumes that queue and draws the alert or the evacuation banner. Each alert carries `panel_url`, the link to its page in the SafeOnuba dashboard, in case the operator needs the detail.
3. **Occasional API calls** when they are needed: the list of who is missing in an evacuation, acknowledging an alert, or `GET /v1/events` to catch up after an outage.

From the moment an SOS goes off until it reaches your receiver, it normally takes 1 or 2 seconds. That is a typical measurement, not a service commitment.

## Using this documentation with AI

If you integrate with an AI assistant, give it the documentation in Markdown rather than the URL: it reads the whole thing, without menus or tabs, and with the links already complete.

- The **Copy** button, at the top right of each page, copies that page as Markdown.
- Every page has its text version by adding `.md` to the address: [/en/developers/webhooks.md](https://safeonuba.com/en/developers/webhooks.md).
- [/en/llms.txt](https://safeonuba.com/en/llms.txt) is the index for tools that read it on their own, and [/en/llms-full.txt](https://safeonuba.com/en/llms-full.txt) is all the documentation as a single text.
- To generate typed code, the [OpenAPI 3.1 contract](https://safeonuba.com/en/developers/openapi.json) is the source of truth.

## All operations

| Operation | What it does | Scope |
| --- | --- | --- |
| [`POST /v1/oauth/token`](https://safeonuba.com/en/developers/reference/authentication#createToken) | Request a token (client_credentials) | No token |
| [`GET /v1/sites`](https://safeonuba.com/en/developers/reference/sites#listSites) | The client’s sites | Any |
| [`GET /v1/sites/{id}/zones`](https://safeonuba.com/en/developers/reference/sites#listSiteZones) | A site’s zones and muster points | `alerts:read` |
| [`GET /v1/alerts`](https://safeonuba.com/en/developers/reference/alerts#listAlerts) | List alerts | `alerts:read` |
| [`GET /v1/alerts/{id}`](https://safeonuba.com/en/developers/reference/alerts#getAlert) | Alert detail | `alerts:read` |
| [`PUT /v1/alerts/{id}`](https://safeonuba.com/en/developers/reference/alerts#updateAlert) | Acknowledge or resolve an alert | `alerts:write` |
| [`GET /v1/musters`](https://safeonuba.com/en/developers/reference/evacuations#listMusters) | List evacuations | `musters:read` |
| [`GET /v1/musters/{id}`](https://safeonuba.com/en/developers/reference/evacuations#getMuster) | Status and headcount of an evacuation | `musters:read` |
| [`GET /v1/musters/{id}/missing`](https://safeonuba.com/en/developers/reference/evacuations#listMissingWorkers) | Who is missing | `musters:read` |
| [`GET /v1/workers`](https://safeonuba.com/en/developers/reference/workers#listWorkers) | List workers | `workers:read` |
| [`GET /v1/workers/{id}`](https://safeonuba.com/en/developers/reference/workers#getWorker) | Worker detail | `workers:read` |
| [`GET /v1/devices`](https://safeonuba.com/en/developers/reference/workers#listDevices) | List watches | `workers:read` |
| [`GET /v1/positions/latest`](https://safeonuba.com/en/developers/reference/positions#listLatestPositions) | Latest visible positions | `positions:read` |
| [`GET /v1/events`](https://safeonuba.com/en/developers/reference/events#listEvents) | Replay events | Any |
| [`GET /v1/webhooks`](https://safeonuba.com/en/developers/reference/webhooks#listWebhooks) | List destinations | `webhooks:manage` |
| [`POST /v1/webhooks`](https://safeonuba.com/en/developers/reference/webhooks#createWebhook) | Create a destination | `webhooks:manage` |
| [`DELETE /v1/webhooks/{id}`](https://safeonuba.com/en/developers/reference/webhooks#deleteWebhook) | Delete a destination | `webhooks:manage` |
| [`POST /v1/webhooks/{id}/rotate-secret`](https://safeonuba.com/en/developers/reference/webhooks#rotateWebhookSecret) | Rotate the signing secret | `webhooks:manage` |
| [`POST /v1/webhooks/{id}/test`](https://safeonuba.com/en/developers/reference/webhooks#testWebhook) | Send a test event | `webhooks:manage` |
| [`POST /v1/sandbox/scenarios`](https://safeonuba.com/en/developers/reference/sandbox#runSandboxScenario) | Launch a test scenario | Any |

## Next step

[Getting started](https://safeonuba.com/en/developers/getting-started): from access to your first webhook in five minutes.
