Crit Alarm API (v1)
The /v1/ routes provide management capabilities ntfy does not have: topic configuration, secret token provisioning, incident lifecycle tracking, two-stage acknowledgment, and server health diagnostics.
Authentication and Scoping
Section titled “Authentication and Scoping”All /v1/ endpoints require a management bearer token in the Authorization header:
Authorization: Bearer <management-token>The required token format depends on the server operational mode:
| Mode | Credential Format | Scope |
|---|---|---|
selfhosted |
Admin token (ad_...) |
Grants full access to the server. Single operator, single tenant. |
relay, hosted |
Device token (dv_...) |
Strictly scoped to the account that owns the registering device. |
Security Rules
Section titled “Security Rules”- Strict Mode Enforcement: In
selfhostedmode, device tokens (dv_...) are rejected. Inrelayandhostedmodes, admin tokens (ad_...) are rejected so that no single shared credential exposes multi-tenant data. - Enumeration Prevention: In multi-tenant modes, requests targeting resources belonging to another account return
404 Not Foundrather than403 Forbidden, preventing malicious actors from probing whether topic names exist. - Admin Token Lifecycle: On a self-hosted instance, the admin token is generated on initial startup, logged once, and stored in the database. You can display it via
critalarm token showor rotate it withcritalarm token rotate.
1. Topics API
Section titled “1. Topics API”Topics isolate alert feeds. Each topic maintains its own incident state and secret publish tokens.
List Topics
Section titled “List Topics”GET /v1/topicsResponse (200 OK):
[ { "name": "prod", "critical": true, "repeat_interval_s": 30, "max_ring_s": 1800, "desk_timer_s": 600, "relay_content": "none", "created_at": 1757462400 }]Create Topic
Section titled “Create Topic”POST /v1/topicsContent-Type: application/json
{ "name": "prod"}Response (201 Created):
{ "name": "prod", "critical": false, "repeat_interval_s": 30, "max_ring_s": 1800, "desk_timer_s": 600, "relay_content": "none", "created_at": 1757462400, "token": "tk_9q8w7e6r5t4y3u2i1o"}Important: The publish
tokenis returned exactly once upon topic creation. Store it securely in your monitoring tools.
criticalalways defaults tofalseon creation. Do not change this default.relay_contentis server-wide configuration and is read-only in this API.
Update Topic
Section titled “Update Topic”PATCH /v1/topics/prodContent-Type: application/json
{ "critical": true, "repeat_interval_s": 45, "max_ring_s": 3600, "desk_timer_s": 900}Response (200 OK): Returns updated topic object.
Delete Topic
Section titled “Delete Topic”DELETE /v1/topics/prodResponse (204 No Content). Deletes the topic and all associated tokens, incidents, and messages.
Create Additional Topic Token
Section titled “Create Additional Topic Token”POST /v1/topics/prod/tokensResponse (201 Created):
{ "token": "tk_a1b2c3d4e5f6g7h8i9"}Revoke Topic Token
Section titled “Revoke Topic Token”DELETE /v1/topics/prod/tokens/{token_id}Response (204 No Content).
2. Incidents API
Section titled “2. Incidents API”Incidents represent active or historical alerting events. An incident is opened whenever a priority 5 message is published to a topic with critical: true.
List Incidents
Section titled “List Incidents”GET /v1/incidents?limit=20&state=open&topic=prod- Query Parameters:
limit: Number of incidents to return (default: 20, max: 100).state: Filter by state (open,acked,closed,expired).topic: Filter by topic name.
Response (200 OK):
[ { "id": "inc_9a8b7c", "topic": "prod", "state": "open", "opened_at": 1757462400, "acked_at": null, "closed_at": null, "last_message_at": 1757462400, "messages": [ { "id": "m_7f3k2p9q", "time": 1757462400, "title": "Uptime Kuma", "message": "db01 is down", "priority": 5, "tags": ["warning"] } ] }]Get Single Incident
Section titled “Get Single Incident”GET /v1/incidents/{id}Response (200 OK): Full incident object. In relay-content: none mode, this endpoint is called by the iOS Notification Service Extension (NSE) to retrieve the alert title and body before displaying the critical notification.
Acknowledge Incident (Stage 1)
Section titled “Acknowledge Incident (Stage 1)”Acknowledging stops the active siren loop.
POST /v1/incidents/{id}/ack- Response (
200 OK):{"id": "inc_9a8b7c","state": "acked","acked_at": 1757462430,"desk_timer_fires_at": 1757463030} - Error (
409 Conflict): Returned if the incident is not in theopenstate.
Close Incident (Stage 2)
Section titled “Close Incident (Stage 2)”Closing marks the incident resolved once you are at your workstation.
POST /v1/incidents/{id}/close- Response (
200 OK):{"id": "inc_9a8b7c","state": "closed","closed_at": 1757462700} - Error (
409 Conflict): Returned if the incident is not in theackedstate.
3. Incident State Machine
Section titled “3. Incident State Machine”open ----(ack)----> acked ----(close)----> closed | | | +---- desk_timer expired (reopen) | +---- max_ring expired ----> expiredTransition Rules
Section titled “Transition Rules”- Flapping Guard: Only one active incident exists per topic at a time. If new priority 5 messages arrive while an incident is
openoracked, they are appended to the existing incident and updatelast_message_atwithout resetting the acknowledgment or creating duplicate alerts. - Repeat Loop: While in the
openstate, the server re-sends push notifications at the configuredrepeat_interval_s(default: 30s) using the incident ID as the push collapse key. - Desk Timer Expiry: When an incident is acknowledged (
acked), the repeat loop halts and a desk timer begins (default: 10 minutes). IfPOST /v1/incidents/{id}/closeis not called before the desk timer expires, the incident automatically returns toopenand restarts the alarm loop. - Max Ring Duration: If an incident remains
openwithout acknowledgment formax_ring_s(default: 30 minutes), the server transitions the incident toexpiredand ceases repeated pushes.
4. Test Alarm
Section titled “4. Test Alarm”Send an immediate test alert to verify mobile push delivery and critical sound bypass:
POST /v1/test?topic=prod- Response (
200 OK):{"incident_id": "inc_test_12345"} - Requirements: The specified topic must have
critical: true. If the topic is non-critical, the server answers409 Conflict:{"error": "topic is not critical"}
5. Server Information
Section titled “5. Server Information”Unauthenticated diagnostic endpoint called by mobile apps and health monitors to inspect server configuration:
GET /v1/infoResponse (200 OK):
{ "name": "critalarm", "version": "0.1.0", "base_url": "https://alerts.example.com", "relay_url": "https://relay.critalarm.app", "relay_content": "none", "mode": "selfhosted"}The mobile app queries this endpoint when you add a server URL to verify network connectivity, validate semver version compatibility, and read base_url for cryptographic topic hash derivation.