Skip to content

Publish API (ntfy-compatible)

Crit Alarm exposes an ntfy-compatible publish and poll interface at the server root. Any existing tool, library, or script designed for ntfy can publish to Crit Alarm without modification.

Every example here uses the base URL https://alerts.example.com.

POST /{topic}
PUT /{topic}
  • Body: Plain text message body.
  • Empty body: Publishes the message text triggered, matching ntfy behavior.
  • Topic names: Must match regex pattern [-_A-Za-z0-9]{1,64}. Any other character or length returns 400 Bad Request.
Terminal window
curl -X POST https://alerts.example.com/prod \
-H "Authorization: Bearer tk_xxxxxxxxxxxx" \
-H "X-Title: Database Alert" \
-H "X-Priority: 5" \
-H "X-Tags: warning,database" \
-d "db01 replica lag exceeded 300 seconds"

Authentication is required on every publish request. Unlike ntfy, Crit Alarm has no public topics.

You can authenticate using any one of the following methods:

  1. Bearer token (recommended):
    Authorization: Bearer tk_xxxxxxxxxxxx
  2. Basic auth:
    Authorization: Basic base64(anything:tk_xxxxxxxxxxxx)
    The username portion is ignored; the password must be the topic token.
  3. Query parameter:
    POST /prod?auth=Bearer%20dGtf...
    Pass the URL-encoded string or base64 representation of Bearer tk_xxxxxxxxxxxx. Use this for tools and webhook senders that cannot set HTTP headers.

The provided token must belong to {topic}. If the token is missing, invalid, or belongs to another topic, the server answers:

{
"code": 40101,
"http": 401,
"error": "unauthorized"
}

Header names are case-insensitive. Every header supports standard ntfy aliases and short query parameter equivalents.

Header Aliases Type Default Used For
X-Title Title, ti, t string topic name Notification title
X-Priority Priority, prio, p 1 to 5 or name 3 Delivery class; priority 5 opens an incident on critical topics
X-Tags Tags, tag, ta comma-separated list none Stored and shown in app; emoji shortcodes rendered like ntfy
X-Click Click URL none URL opened when notification is tapped
X-Markdown Markdown, md true, 1, yes off Message body rendered as Markdown in mobile app

Priority names map to numeric levels as follows:

Priority Name Numeric Value Description
min 1 Minimum priority
low 2 Low priority
default 3 Default priority
high 4 High priority (Time-Sensitive push)
urgent 5 Urgent priority (Triggers full incident alarm if topic is critical)
max 5 Equivalent to urgent

Publish parameters are also accepted via URL query parameters using the same short names:

  • ?t= Notification title
  • ?p= Priority (1 to 5 or name)
  • ?ta= Comma-separated tags
  • ?m= Message body text

To prevent breaking existing ntfy webhook integrations, Crit Alarm accepts and ignores the following headers in v1:

  • X-Actions
  • X-Attach
  • X-Filename
  • X-Icon
  • X-Email
  • X-Call
  • X-Template
  • X-Cache
  • X-Firebase
  • X-UnifiedPush

X-Delay and its aliases (At, In, or JSON field delay) are explicitly rejected with:

{
"code": 40000,
"http": 400,
"error": "scheduled delivery not supported"
}

Crit Alarm never drops a delay and never delivers the message early. Returning success for a schedule it cannot keep is dangerous.

Crit Alarm supports publishing messages as JSON payloads to the root endpoint /:

POST /
Content-Type: application/json
Authorization: Bearer tk_xxxxxxxxxxxx
{
"topic": "prod",
"message": "db01 is down",
"title": "Uptime Kuma",
"priority": 5,
"tags": ["warning"],
"click": "https://dashboard.example.com",
"markdown": false
}
  • topic (string, required): Destination topic name.
  • message (string, optional): Message content. Defaults to "triggered".
  • title (string, optional): Notification title. Defaults to the topic name.
  • priority (integer, optional): Priority level from 1 to 5. Defaults to 3.
  • tags (array of strings, optional): List of tags or emoji shortcodes.
  • click (string URL, optional): URL opened on tap.
  • markdown (boolean, optional): Whether to parse message content as Markdown.

Unknown fields are ignored. Legacy ntfy fields (actions, attach, email, call) are accepted and ignored, except delay, which returns 400 Bad Request.

A successful publish returns 200 OK with a JSON payload:

{
"id": "m_7f3k2p9q",
"time": 1757462400,
"expires": 1757505600,
"event": "message",
"topic": "prod",
"title": "Uptime Kuma",
"message": "db01 is down",
"priority": 5,
"tags": ["warning"],
"incident_id": "inc_9a8b7c"
}

The response fields and order match ntfy’s message object so ntfy client libraries parse responses without modification.

The incident_id field is an additive Crit Alarm extension. It is present only when a new incident was opened or an existing open incident was joined.

Priority Topic Critical Toggle Result
5 ON Incident opened, or joined if one is open. Database repeat loop begins. incident_id returned in response. Phone sounds emergency alarm.
5 OFF Message stored in database. Forwarded to push relay as Time-Sensitive / high priority. No incident is opened and no repeat loop runs.
4 ON or OFF Message stored. Forwarded to push relay as Time-Sensitive / high priority notification.
1 to 3 ON or OFF Message stored in local database. App polls for history. Message is not forwarded to push relay.

Error responses follow ntfy JSON conventions:

HTTP Status Error Code Error Message Reason
400 40001 invalid topic name Topic name contains invalid characters or exceeds 64 characters.
400 40000 scheduled delivery not supported X-Delay header or delay field was specified.
401 40101 unauthorized Missing, invalid, or mismatched topic token.
413 41301 message too large Request body exceeds 4096 bytes.
429 42901 rate limited Ingress publish rate limit exceeded.

To retrieve message history and sync offline state, clients query the polling endpoint:

GET /{topic}/json?poll=1&since=12h
Authorization: Bearer tk_xxxxxxxxxxxx
  • Query Parameters:
    • poll=1 (required): Enables one-shot polling mode.
    • since (optional): Filter messages since a message ID, a Unix timestamp (seconds), or a relative duration like 10m, 1h, or all. Defaults to the last 12 hours.
  • Output: Newline-delimited JSON (NDJSON), returning one message object per line (matching the format in section 6), ordered from oldest to newest.

Streaming subscriptions (/json without poll=1, /sse, /ws, and /raw) are not supported in v1 and return 501 Not Implemented.