Skip to content

Overview

Send-only alarm ingest API for DIY smart homes.

The OpenAlarm core API is the send-only endpoint your smart home calls to arm, disarm, trigger, and fire panic buttons. OpenAlarm is a messenger, not a monitor - these calls send texts, calls, and email to the contacts you have chosen; they never reach professional monitoring.

Authentication

Every request carries an account-level API key, sent either as X-API-Key: <key> (canonical) or Authorization: Bearer <key> (equivalent - the two are OR-ed). The key authorizes the alarm actions below plus a small read-only integration surface - it cannot change configuration or access your account. It opens the gate; it does not own data. See the Authentication guide.

GET, Bodyless

Every endpoint is a plain GET - the URL is the whole call and there is no request body to assemble, so a Home Assistant rest_command, a UniFi Protect webhook, or a one-line curl just hits a path. Because the API key travels in a header - never in the URL - a scanned or prefetched URL carries no key and cannot fire an alarm.

POST is reserved for the future - for endpoints that accept a request body - and is not used by the current ingest endpoints.

Modes and Defaults

Each alarm has five modes - Home, Away, Night, Vacation, and Disarmed.

Disarmed has its own endpoint. You do not arm to it - there is no arm/disarmed - you call disarm. It binds no escalation policy, so there is no trigger/disarmed either. It does carry its own settings, such as whether disarming clears an incident that is already open.

Arming and triggering come as matched pairs, and two bare shortcuts keep the common paths trivial:

  • arm with no mode re-arms the currently armed mode when there is one; from Disarmed (or if the armed mode was since deleted) it arms Away, the most conservative posture.
  • trigger with no mode fires the currently armed mode’s policy, falling back to Away when the alarm is Disarmed or the armed mode no longer exists. Naming a mode explicitly never falls back - if you asked for a specific policy, firing a different one is worse than firing none.

Disarming returns the alarm to Disarmed and clears the armed mode with it, so a later bare arm returns to Away rather than resurrecting the mode you last used.

Arming Decides Whether an Alarm Alerts

An alarm moves on two independent axes, and only one of them is arming.

  • Which mode it is in - Home, Away, Night, Vacation, or Disarmed. Only arm and disarm change it.
  • Triggered or not - an alarm is triggered for exactly as long as it has an open incident. trigger opens one; clear, a configured disarm, or the 24-hour auto-close ends it.

The two never interact. A trigger does not disarm your alarm, and clearing an incident does not either - an alarm armed Away when it fired is still armed Away while the incident runs and after it is cleared.

A trigger always fires, armed or not. Arming does not gate it - OpenAlarm sits downstream of whatever you already run, and that system has already decided this is worth alerting about. Arming answers a different question: which policy a bare trigger runs.

So a source that cannot arm needs no special setup. A UniFi Protect camera fires one fixed webhook and can never call arm, so its alarm sits in Disarmed forever; its bare trigger simply runs the Away policy.

If you would rather OpenAlarm did check, each alarm has an optional Only trigger when armed setting, off by default. See the Arming guide.

Panic buttons are never armed, so none of this applies to them. See the Arming guide.

Responses

Every endpoint returns 202 Accepted. There is no 200 anywhere on this API - arming, disarming, triggering, and clearing all answer with the same code, so a caller never branches on which 2xx came back. The call has been accepted and will be applied; for a trigger, alerts go out after the false-alarm cancellation window, so nothing has reached your contacts at the moment the response arrives.

Every response uses the same four-field envelope:

{
  "error": false,
  "message": null,
  "traceId": "8f14e45f-ea0d-4a1b-9f2c-6b3d70c5e881",
  "data": {
    "environment": "live",
    "location": { "id": "2c9wvhrq52k5refhtvmmr0f8yjz5j9e9", "name": "Home" }
  }
}

error is the outcome boolean. message is human-readable and null on success. traceId identifies that one call - worth logging, and worth quoting if you report a problem. data carries the payload: environment, and the location the alarm or panic button belongs to.

There is no status field: the HTTP status is already on the response, and echoing it into the body would create two sources of truth that can disagree.

  • 401 - the API key is missing or invalid. The one response that does not use the envelope - it returns only a message and no traceId.
  • 404 - the alarm or panic button is not available to fire. Four cases, deliberately indistinguishable - see the 404 reference on any operation page.

Test Mode

Every alarm and panic button is either live or test. You choose which when you create it, and you can change it at any time in the console - per alarm, or for a whole location at once.

A test is fully silent. Nobody is contacted - not your contacts, not you. The escalation policy runs on its real timeline: levels fire at their true delays, repeats repeat, and the incident ends the same three ways a live one does. Every contact the chain reaches is recorded with the delivery marked suppressed - who, on which channel, at the level and the time they would have been contacted - so a test reads as the real thing that did not happen, not as a failure. A contact who could not have been alerted anyway is recorded as skipped, with the reason. See the Test Mode guide for the full picture, including how test triggers look to connected systems like Home Assistant.

Live or test belongs to the alarm, never to the call. There is no query parameter and no per-call override; configuration lives in the console, not in the request. A flag deciding whether real people get woken up has no business being optional URL syntax, and a UniFi Protect webhook could not pass one anyway.

Every 2xx reports which one actually ran, as data.environment - so an automation that believes it is live can confirm it rather than assume it.

Information

  • OpenAPI version: 3.1.0

The account-level API key - oa_ followed by 64 characters. The canonical form.

Security scheme type: apiKey

Header parameter name: X-API-Key

The same account-level API key, sent as a bearer token. Equivalent to X-API-Key.

Security scheme type: http