API reference menu
Getting started
OverviewAuthentication and rate limitExamplesSending
Send a notificationPriority and repeatLinks, images and buttonsSourcesScheduled sendsWebhook templatesReceipts and replies
Receipts and callbacksRepliesTeams
Teams, roles and endpointsChannels and escalationWebhooks and emailAlert email and timelineTeam formsPlans, audit log and transferCompany sign‑in and SCIMThe web consoleAccount
Identity and moving phonesDevices, keys and historyThe Upgrade PassCompatibility
ntfy's publish APIProwl's APIAppendix
Endpoint indexErrors and status codesLimitsPriorities and fieldsGlossarySend a notification
The whole product. Send it an API key and a notification, it reaches every device on the account.
POST
/v1/notify
Auth#
Either header works:
X-Nowlark-Key: nlk_…
Authorization: Bearer nlk_…
Body#
As JSON:
| Field | Type | Required | Limit | Meaning |
|---|---|---|---|---|
source |
string |
yes |
256 |
The sender's name, shown on every notification and listed in Sources. One per script or job, e.g. "Certificate renewal", "Nightly backup". See Choosing a source. |
title |
string |
one of title/body |
1024 |
The banner's headline. |
body |
string |
one of title/body |
10000 |
The detail below it. |
url |
string |
no |
512 |
The link the notification carries. http or https only. See Links. |
image |
string |
no |
512 |
An image shown on the notification and in the app. https only, because the device fetches it automatically, with no tap. Part of the Upgrade Pass; without one the send is refused 402 upgrade_required. |
image_id |
string |
no |
An image you uploaded with POST /v1/images, for a picture that has no web address of its own, such as a screenshot. Not with image. Part of the Upgrade Pass. |
|
actions |
string[] |
no |
3 |
Buttons on the notification, from open, copy, mute, done. Part of the Upgrade Pass. |
copy_text |
string |
with copy |
512 |
What the Copy button puts on the clipboard: a code, a URL, anything. Copying happens without opening the app. |
priority |
int |
no |
−2…2 |
See Priority. Defaults to 0. |
group_key |
string |
no |
128 |
Notifications sharing this stack together on the phone. |
dedupe_key |
string |
no |
128 |
Send the same value twice and only the first is delivered. |
replace_key |
string |
no |
128 |
A later notification from the same source with the same replace_key takes this one's place: the older one is removed from the feed and its banner is replaced on the Lock Screen. For a job reporting progress or state. Also ends any repeat the older one had. |
delay |
int |
no |
10 s to 7 days |
Deliver this many seconds from now. See Scheduled sends. Not with at. |
at |
number or string |
no |
10 s to 7 days ahead |
Deliver at this time: epoch seconds, or ISO 8601 with a zone ( 2026-10-07T09:00:00Z). Not with delay. |
callback_url |
string |
no |
512 |
An https URL Nowlark calls once, when this is first read or acknowledged. See Receipts and callbacks. |
replies |
string |
no |
A fixed reply set the recipient taps on the notification: approve_deny, yes_no, approve_deny_later or yes_no_later. See Replies. Not with actions, or on a channel send. |
|
callback_secret |
string |
with replies and callback_url |
16 to 128 |
Signs the reply callback. Never returned. |
reply_within |
int |
no |
10 s to 7 days |
Seconds the question stays open. After that a reply is refused and the receipt says reply_state: "expired". Default 7 days. |
ttl |
int |
no |
10 s to 7 days |
Seconds Apple may hold the push if the device is unreachable before dropping it ( apns-expiration, counted from when it was made). A repeat never outlives it. |
tags |
string[] |
no |
5, 32 chars each |
Short labels. No markup or control characters; duplicates dropped. Returned in the history and the push payload. |
devices |
string[] |
no |
Address only these platforms: ios, ipados, macos, watchos, visionos. Default all. A device whose platform cannot be told is never chosen by a filter. Repeats go to the same ones. |
|
every |
int |
no |
60 s to 7 days |
|
count |
int |
with every |
1 to 1000 |
How many times to send. |
until |
number or string |
with every |
at most a year |
Stop after this time (epoch seconds or ISO 8601 with a zone). |
Response#
200:
{
"id": "ntf_5f3c…",
"duplicate": false,
"muted": false,
"delivered": 2,
"devices": 2,
"remaining": 997,
"reset_at": 1789665600.0
}
delivered/devices: how many of the account's registered devices Apple accepted it for.delivered: 0withdevices: 0means no phone has registered yet, not a failure.duplicate: true: thededupe_keyhad already been used. The call succeeded and nothing was pushed. This is not an error; a retrying cron job should not treat it as one.muted: true: the user muted thissource. Stored in history, not pushed.remaining/reset_at: calls left in this key's hour, and when the window resets.scheduled_for: present only when the send was accepted for later (epoch seconds). Nothing has been pushed yet, sodeliveredanddevicesare0;idis the id the notification will have.