> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ilyama.golain.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Your first alarm, end to end

> Build a high-temperature threshold alarm on a timeseries dataspace, get emailed when it fires, acknowledge it, and watch it resolve.

This walkthrough builds one complete alarm. You create a **threshold** rule called **High temperature** (key `high_temp`) that reads a timeseries dataspace of temperature readings. It fires when a device's latest reading stays above `80` for 60 seconds, and resolves when the reading drops below `75`. Then you subscribe an email address, trigger the alarm, acknowledge it on the **Board**, and watch it resolve.

Every step shows the Console path and the equivalent API call.

## Prerequisites

| You need | Details |
| - | - |
| A Project | All alarm routes are project-scoped. |
| A timeseries dataspace receiving data | Each row has a numeric `temperature` column. Rows carry `source_id` (the producer, such as the device) and `event_ts`. See [Timeseries dataspaces](/dataspaces/timeseries). |
| Permission to manage definitions | Creating, editing, and activating definitions requires the project permission `can_manage_assets`. |
| Permission to manage notifications (optional) | Creating a notification subscription requires the project permission `can_manage_integrations`. |

For the API tabs, set these variables:

```bash theme={null}
export API=https://api.ilyama.golain.io/core/api/v1
export TOKEN=…          # OIDC access token (same as Console)
export ORG_ID=…         # Organization UUID
export PROJECT_ID=…
export DATASPACE_ID=…   # The temperature dataspace
```

Every request sends `Authorization: Bearer $TOKEN` and `ORG-ID: $ORG_ID`. Successful responses use the envelope `{"ok": 1, "data": …}`. A single object comes back as `data.item` and a list as `data.items`.

## The rule you will build

| Setting | Value | Meaning |
| - | - | - |
| Kind | `threshold` | Runs a SQL query against the dataspace on a schedule. |
| Query | Latest `temperature` per `source_id` over the last 5 minutes | One result row per device. |
| Fires when | `event.temperature > 80` | CEL over each result row. Each column is `event.<column>`. |
| Resolves when | `event.temperature < 75` | Readings between 75 and 80 keep the alarm firing (a deadband). |
| One alarm per | `source_id` | Each device gets its own alarm instance. |
| Must hold for | 60 s | The condition must stay true this long before the alarm fires. |
| Evaluate every | 60 s | How often the query runs. Rules run at most once a minute. |
| Priority | `2` (High) | Sets the sort order on the Board. |

## Walkthrough

**1. Find the dataspace read view**

Threshold SQL reads from the dataspace's **read view**, not from the dataspace name. The read view's name looks like `ds_read_<hex>`.

**Console**

You don't need to look it up separately. In the alarm editor, pick the dataspace in **Dataspace**. The hint below the field then shows `Read view ds_read_…`, and the SQL editor label shows `SQL · ds_read_…`. Copy that name into your query.

If the hint says the dataspace has no read view yet, it has not received data. Send some readings first.

**API**

Read `data.item.read_view` from the dataspace:

```bash theme={null}
curl -sS \
  -H "Authorization: Bearer $TOKEN" \
  -H "ORG-ID: $ORG_ID" \
  "$API/projects/$PROJECT_ID/dataspaces/$DATASPACE_ID"
```

```json theme={null}
{
  "ok": 1,
  "data": {
    "item": {
      "id": "…",
      "name": "boiler_temperature",
      "modality": "timeseries",
      "read_view": "ds_read_3f9a…"
    }
  }
}
```

```bash theme={null}
export READ_VIEW=ds_read_3f9a…
```

**2. Create the definition**

**Console**

1. Go to **Alarms** > **Definitions** and click **New definition**.
2. In the **New alarm** dialog, choose **A value crosses a limit** and click **Continue**. The editor opens with the `threshold` kind selected.
3. Under **What to call it**, enter the **Name** `High temperature`. Change the generated **Key** to `high_temp`.
4. Set **Priority** to **High**.
5. Under **What to watch**, pick your dataspace in **Dataspace**.
6. In **Query**, enter the SQL below. Replace the read view with yours.
7. Set **Fires when** to `event.temperature > 80` and **Resolves when** to `event.temperature < 75`.
8. In **One alarm per**, replace `device_id` with `source_id`.
9. Under **Timing**, set **Evaluate every** to 60 s and **Must hold for** to 60 s. Set **Keep firing for** to 0 so the alarm resolves as soon as a reading drops below 75.
10. Click **Save and start watching**.

```sql theme={null}
SELECT source_id, argMax(temperature, event_ts) AS temperature
FROM ds_read_3f9a…
WHERE event_ts >= now() - INTERVAL 5 MINUTE
GROUP BY source_id
```

**API**

```bash theme={null}
curl -sS -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "ORG-ID: $ORG_ID" \
  -H "Content-Type: application/json" \
  "$API/projects/$PROJECT_ID/alarm-definitions" \
  -d @- <<EOF
{
  "key": "high_temp",
  "name": "High temperature",
  "priority": 2,
  "kind": "threshold",
  "source_dataspace_id": "$DATASPACE_ID",
  "query_spec": {
    "kind": "sql",
    "sql": "SELECT source_id, argMax(temperature, event_ts) AS temperature FROM $READ_VIEW WHERE event_ts >= now() - INTERVAL 5 MINUTE GROUP BY source_id"
  },
  "condition_cel": "event.temperature > 80",
  "resolve_cel": "event.temperature < 75",
  "labels_by": ["source_id"],
  "for_seconds": 60,
  "keep_firing_for_seconds": 0,
  "eval_interval_seconds": 60,
  "on_no_data": "keep_firing"
}
EOF
```

The response returns the stored definition as `data.item`. Save its `id`:

```bash theme={null}
export ALARM_DEF_ID=…   # data.item.id
```

The request body is strict. An unknown field returns `400`.

The query must follow these rules, or the save is rejected:

| Rule | Why |
| - | - |
| Bound `event_ts` with a relative window, such as `event_ts >= now() - INTERVAL 5 MINUTE` | An unbounded scan is rejected. |
| Keep the window at 24 hours or less | Longer windows are rejected. |
| Don't use absolute dates or timestamps | Bounds like `'2026-01-01'` are rejected. Use a window relative to `now()`. |
| Return one row per labelset | Each row is checked against **Fires when** on its own. |

> [!TIP]
>
> `argMax(temperature, event_ts)` picks each device's latest reading in the window. Using `max(temperature)` instead would keep the alarm firing until the hot reading leaves the 5-minute window.

**3. Confirm it is active**

New definitions are active by default and start evaluating as soon as you save them. There is no separate activation step.

**Console**

In **Alarms** > **Definitions**, the **Status** column shows **Evaluating**. If it shows **Deactivated**, open the definition and click **Activate**.

**API**

Check that `data.item.is_active` is `true`. If it is `false`, activate the definition:

```bash theme={null}
curl -sS -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "ORG-ID: $ORG_ID" \
  "$API/projects/$PROJECT_ID/alarm-definitions/$ALARM_DEF_ID/activate"
```

**4. Subscribe an email address**

Definitions decide when an alarm fires. Notification subscriptions decide who hears about it. Subscribe to two alarm events:

| Event key | Console label | Sent when |
| - | - | - |
| `alarm.alarm_instance.activated.v1` | **Alarm opens** | The alarm starts firing |
| `alarm.alarm_instance.cleared.v1` | **Alarm resolves** | The alarm resolves |

**Console**

1. After you save, the Console opens the definition. In the **Notifications** card, click **Open Notifications**.
2. In **Send to**, choose **Email · recipients you enter** and enter the recipient address.
3. Under **When**, keep **Alarm opens** and **Alarm resolves** selected. **Someone acknowledges** is optional.
4. Under **Which alarms**, check that **High temperature** is selected.
5. Click **Create subscription**.

**API**

```bash theme={null}
curl -sS -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "ORG-ID: $ORG_ID" \
  -H "Content-Type: application/json" \
  "$API/projects/$PROJECT_ID/notification-subscriptions" \
  -d @- <<EOF
{
  "subscribed_events": [
    "alarm.alarm_instance.activated.v1",
    "alarm.alarm_instance.cleared.v1"
  ],
  "channel_ref": { "kind": "email", "to": ["oncall@example.com"] },
  "alarm_def_ids": ["$ALARM_DEF_ID"]
}
EOF
```

`alarm_def_ids` limits the subscription to this definition. Leave it empty to hear about every alarm in the Project.

> [!NOTE]
>
> Only project-scoped subscriptions can target individual definitions. Org- and fleet-scoped subscriptions receive every alarm.

**5. Trigger the alarm**

Publish readings with `temperature` above `80` from one device, and keep them coming for more than a minute.

Here is what happens:

1. On the first evaluation that sees a reading above 80, Golain opens an instance for that `source_id` with `condition_state` `pending`.
2. When the condition has held for 60 seconds, the instance moves to `firing`.
3. The **Alarm opens** email is sent.

If the reading drops back to 80 or below before the 60 seconds pass, the pending instance closes. No notification is sent.

**6. See it on the Board**

**Console**

Go to **Alarms** > **Board**. The **High temperature** alarm appears with a **P2** chip. Unacknowledged alarms sort above acknowledged ones at the same priority. Click the alarm name to open its detail page, which shows its labels and the **Fires when** condition.

**API**

List active instances, filtered to this definition:

```bash theme={null}
curl -sS \
  -H "Authorization: Bearer $TOKEN" \
  -H "ORG-ID: $ORG_ID" \
  "$API/projects/$PROJECT_ID/alarm-instances/active?alarm_def_id=$ALARM_DEF_ID"
```

```json theme={null}
{
  "ok": 1,
  "data": {
    "items": [
      {
        "id": "9b2e…",
        "alarm_def_id": "…",
        "process_state": "active",
        "ack_state": "unacknowledged",
        "condition_state": "firing",
        "labels": { "source_id": "4c1d…" },
        "opened_at": "2026-10-05T09:14:02Z",
        "active_since": "2026-10-05T09:14:02Z",
        "acknowledged_at": null,
        "cleared_at": null,
        "closed_at": null
      }
    ]
  }
}
```

```bash theme={null}
export INSTANCE_ID=9b2e…
```

| Field | Values in this walkthrough |
| - | - |
| `process_state` | `active` while pending or firing, `normal` after it resolves |
| `condition_state` | `pending` → `firing` → `resolved` |
| `ack_state` | `unacknowledged` → `acknowledged` |

> [!TIP]
>
> The instance's `trigger_value` is read from a result column named `value`. To fill it, also select the reading as `value`, for example `argMax(temperature, event_ts) AS value`.

**7. Acknowledge it**

Acknowledging records that someone has seen the alarm. It does not resolve it.

**Console**

Click **Acknowledge** on the alarm's row on the **Board**, or on its detail page. The alarm stays on the Board while it is still firing. It now shows under the **Acked** filter.

**API**

```bash theme={null}
curl -sS -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "ORG-ID: $ORG_ID" \
  "$API/projects/$PROJECT_ID/alarm-instances/$INSTANCE_ID/acknowledge"
```

The response returns the instance as `data.item`, with `ack_state` set to `acknowledged` and `acknowledged_at` / `acknowledged_by` filled in.

**8. Watch it resolve**

Send readings below `75`. On the next evaluation:

1. The instance moves to `resolved` and `process_state` becomes `normal`.
2. It leaves the **Board** and the `/alarm-instances/active` list.
3. The **Alarm resolves** email is sent.
4. Because you already acknowledged it, the instance closes on the following evaluation and `closed_at` is set.

Readings between 75 and 80 do not resolve the alarm. This deadband stops it from flapping around the limit.

**Console**

On the **Board**, click the **History** filter to see the resolved alarm.

**API**

```bash theme={null}
curl -sS \
  -H "Authorization: Bearer $TOKEN" \
  -H "ORG-ID: $ORG_ID" \
  "$API/projects/$PROJECT_ID/alarm-instances/$INSTANCE_ID"
```

> [!NOTE]
>
> If nobody acknowledges a resolved alarm, it stays open in `resolved` until someone does. That way, an alarm that cleared on its own still gets reviewed.

## What you built

| Piece | What it does |
| - | - |
| Definition `high_temp` | Every minute, reads each device's latest temperature from the last 5 minutes. |
| Fire and resolve conditions | Fires after the reading stays above 80 for 60 s. Resolves below 75. |
| One instance per `source_id` | Each device alarms on its own. |
| Email subscription | Sends an email when the alarm opens and when it resolves. |
| Acknowledgement | Records who saw the alarm. An acknowledged alarm closes once it resolves. |

## Next steps

> **[Kinds and conditions](/alarms/kinds-and-conditions)**
>
> Event, threshold, and staleness rules. SQL, CEL, timing, and no-data handling.

> **[Notifications](/alarms/notifications)**
>
> Other channels, re-notify, escalation, and work orders.

> **[Silences and shelving](/alarms/silences-and-shelving)**
>
> Hide nuisance alarms for a set time without losing them.

> **[API reference](/alarms/api-reference)**
>
> Every alarm endpoint, with request and response shapes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.