# Signals standalone

Canonical page: https://docs.ovok.com/signals/standalone

Use Signals' own API through Ovok. You keep your patients and readings in your own system, send them to Signals under your own ids, and read back the alerts Signals raises. You need no FHIR data and no Signals credentials: Ovok holds them and scopes every call to your project.

Every route is Signals' path under `/v1/signals/api`. For example, Signals' `GET /api/alert` is `GET /v1/signals/api/alert`. The [API reference](#api-reference) lists each route Ovok serves; any other path answers `404`.

## Before you start [#before-you-start]

| Requirement | Details |
| --- | --- |
| Your own Signals tenant | Your project needs a Signals tenant of its own. A project on the shared tenant gets `409` `SIGNALS_TENANT_NOT_OWN`. |
| Standalone only? | Switch [`SIGNALS_SEND_AUTO` and `SIGNALS_ANNOTATE_OBSERVATIONS`](/signals/with-ovok/settings) off. Both are on by default, and annotations would write a FHIR `DetectedIssue` for each of your alerts, pointing at a `Patient` that does not exist. |
| Base URL | `https://api.sandbox.ovok.com/v1/signals/api` in the sandbox. |
| Authentication | An Ovok access token as `Authorization: Bearer`, or a client application's id and secret as HTTP Basic. |
| Capabilities | The caller's access policy needs the `signals_*` rows each route names. See [Signals route policies](/access-policies/signals). |

## App types [#app-types]

Signals has three roles: a manager runs the tenant, a submitter posts readings, and a subscriber receives webhooks. In Ovok, create a **Manager** or **Submitter** application in the Console (Applications). Its access policy is made from the routes that role may call, so it holds exactly those capabilities.

| App type | Signals role | What it can call |
| --- | --- | --- |
| Manager | `MANAGER` | Every route except submitting readings: patients, configs, alerts, settings, users, webhooks and events. |
| Submitter | `SUBMITTER` | Submitting readings, and reading patients. |

A manager application answers `403` when it posts a reading: use a submitter application for that. Subscribers are not Ovok applications. You create them through the API, and they never sign in; see [Webhooks](/signals/standalone/webhooks).

Project admins pass every capability check. Client applications never get that bypass, even when marked as admins: they need the capabilities in their own policy.

## What Ovok adds to each call [#what-ovok-adds-to-each-call]

Bodies and answers pass through as Signals sends them, with these exceptions.

| Area | What Ovok does |
| --- | --- |
| Project scope | Alert reads and acknowledgements are pinned to your project: Ovok sets `project`, and naming another project answers `403`. |
| New patients | Ovok adds your project's submitter to each new patient and stamps `metadata.project` with your project, so your submitter can post their readings and their alerts show in your project. |
| Alerting changes | Writing a patient's `config`, or `alertsPaused`, `alertIfEmpty`, `alertIfNote` or `alertInactiveDays`, also needs `signals_config:update` and `signals_silence:update`. |
| Tenant writes | `PATCH /tenant` takes only `orgSettings`, `resolvedAt` and `settings.episodicAlerts`. Any other field answers `403`. |
| Users | Only subscribers are created, changed or deleted. Ovok generates their id and Signals login. |
| Billing | Patients you create count toward your plan's monitored-patient cap; past it, the create answers `402`. Each accepted reading counts its patient as monitored. |
| Bodies | Bodies must be JSON (`415` otherwise). On patient create, patient update and subscriber create, which Ovok rewrites, a number out of range answers `422`. |
| Deprecations | `GET` and `PUT /settings/org` carry `Deprecation` and `Sunset` headers. Use `orgSettings` on `PATCH /tenant` instead. |

### Errors [#errors]

Errors come in two shapes. Ovok's own refusals are [Problem Details](/errors) (`application/problem+json`): a missing token (`401`), a missing capability or another project (`403`), no tenant of your own (`409`), and the cap (`402`). Signals' own refusals, such as a validation failure or an unknown patient, keep Signals' JSON shape.

When Signals cannot be reached, Ovok answers `502`, `503` with `Retry-After`, or `504` on a timeout. Signals' own answers, `5xx` included, come back as Signals sent them, with one exception: Signals never refuses your call with `401`. If it rejects Ovok's own credential, you get `502`.

## Quickstart [#quickstart]

These calls give your tenant a heart-rate band, create a patient, post a reading above the band and read the alert it raises. Set `OVOK_MANAGER_ID`/`OVOK_MANAGER_SECRET` and `OVOK_SUBMITTER_ID`/`OVOK_SUBMITTER_SECRET` to your two applications.

### 1. Set a heart-rate band [#1-set-a-heart-rate-band]

Signals' default rules judge heart rate (LOINC `8867-4`) against a band, and a new tenant has none, so a reading raises nothing yet. Set one for the whole tenant:

```bash
curl --request PATCH 'https://api.sandbox.ovok.com/v1/signals/api/tenant' \
  --user "${OVOK_MANAGER_ID}:${OVOK_MANAGER_SECRET}" \
  --header 'Content-Type: application/json' \
  --data '{"orgSettings":{"bands":{"8867-4":{"low":40,"high":110}}}}'
```

`orgSettings` is merged by key. A patient's own band, set with [`PUT /patient/:id/settings`](/signals/standalone/api/settings/replace-a-patients-own-rule-settings), wins over the tenant's. If a breaching reading still raises nothing, check the tenant's triggers with [`GET /tenant`](/signals/standalone/api/tenants/get-the-callers-tenant).

### 2. Create a patient [#2-create-a-patient]

`refId` is your own id for the patient.

```bash
curl --request POST 'https://api.sandbox.ovok.com/v1/signals/api/patient' \
  --user "${OVOK_MANAGER_ID}:${OVOK_MANAGER_SECRET}" \
  --header 'Content-Type: application/json' \
  --data '{"patients":[{"refId":"resident-001","name":"Resident 001"}]}'
```

The answer lists the patients as stored, each with its Signals `id`. Creating the same `refId` again updates it rather than duplicating it.

### 3. Post a reading [#3-post-a-reading]

The body is a FHIR R4 Observation. Name the patient with `patientRefId`.

```bash
curl --request POST 'https://api.sandbox.ovok.com/v1/signals/api/observation' \
  --user "${OVOK_SUBMITTER_ID}:${OVOK_SUBMITTER_SECRET}" \
  --header 'Content-Type: application/json' \
  --data '{
    "patientRefId": "resident-001",
    "identifier": [{ "value": "reading-0001" }],
    "code": { "coding": [{ "system": "http://loinc.org", "code": "8867-4" }] },
    "valueQuantity": { "value": 132, "unit": "/min" },
    "effectiveDateTime": "2026-10-25T08:00:00Z"
  }'
```

Signals judges the reading before it answers. The answer lists the alerts it raised only if you send `Prefer: respond-sync`, and then no webhook is sent for them; otherwise read them with `GET /alert` or receive them by [webhook](/signals/standalone/webhooks). The first `identifier` is stored as the reading's `refId`; sending the same one again within an hour answers `409` `DUPLICATE_READING`.

### 4. Read the alerts [#4-read-the-alerts]

```bash
curl --get 'https://api.sandbox.ovok.com/v1/signals/api/alert' \
  --user "${OVOK_MANAGER_ID}:${OVOK_MANAGER_SECRET}" \
  --data-urlencode 'status=ACTIVE'
```

For episodes, one row per clinical event rather than one per breach, add `episodes=true` and use `status=OPEN` or `CLOSED`. Episodes need `episodicAlerts` on for your tenant; see [Update tenant settings](/signals/standalone/api/tenants/update-tenant-settings).

### 5. Acknowledge one [#5-acknowledge-one]

```bash
curl --request POST 'https://api.sandbox.ovok.com/v1/signals/api/alert/:id/ack' \
  --user "${OVOK_MANAGER_ID}:${OVOK_MANAGER_SECRET}" \
  --header 'Content-Type: application/json' \
  --data '{"actor":{"reference":"Practitioner/123","display":"Dr. Max Mustermann"}}'
```

Acknowledging closes the alert. The next breaching reading opens a new one.

## API reference [#api-reference]

The route pages are generated from the [API Explorer](/api-explorer) document, so they always match what Ovok serves. Each page lists the capabilities the route needs, what Ovok changes, and every answer.

## Related [#related]

- [Webhooks](/signals/standalone/webhooks)
- [Signals route policies](/access-policies/signals)
- [Signals with Ovok](/signals/connected-ovok)
