API reference

notifi API documentation

One endpoint and seven parameters. Everything on this page is generated from the same file that generates /openapi.json and the client collections, so the three cannot disagree.

View as Markdown

Quickstart Authentication Request Parameters Response Errors Rate limits Clients and import For the bots Recipes

Quickstart

Install notifi on iPhone or iPad or on the Mac, allow notifications, open the Keys tab and copy the Device key. It starts with nk_.

curl -X POST https://notifi.it/send \
  -H "Authorization: Bearer $NOTIFI_KEY" \
  -d "title=Hello from notifi" \
  -d "message=Your first notification." \
  -d "link=https://notifi.it/docs" \
  -d "image=https://notifi.it/anaglyph-bell.png"

Authentication

Authenticate with a bearer token. A key parameter also works, but it is written to edge logs, shell history and any proxy in between. Use it only for a quick test, and rotate the key afterwards.

MethodSent asNotes
Bearer token Authorization: Bearer nk_yourkey The send key from the app’s Keys tab, as Authorization: Bearer nk_yourkey. Preferred: a header is not written to edge logs or shell history.
Parameter key=nk_yourkey The send key as a parameter. It appears in edge logs, in shell history and in any proxy in between, which makes it the weaker option. Use it only for a quick test, and rotate the key afterwards.

Request

POST https://notifi.it/send, JSON, form-encoded or multipart. GET takes the same parameters in the query string and is there for a quick test: a key sent that way lands in edge logs and shell history, so rotate it afterwards. Query parameters win over body fields when both are present.

POST /send HTTP/1.1
Host: notifi.it
Authorization: Bearer nk_yourkey
Content-Type: application/json
Accept-Language: en-GB

{"title":"Hello from notifi","message":"Your first notification.","link":"https://notifi.it/docs","image":"https://notifi.it/anaglyph-bell.png"}

Parameters

key is required unless the request carries a bearer token.

NameTypeRequiredLimitDescription
key string conditional nk_… The send key, if it is not sent as a bearer token. Required unless sent as a bearer token. The key picks the device the notification lands on.
title string required 1–200 chars The notification title. A longer title is delivered cropped, with a warning in the response.
message string optional ≤ 16,000 chars The notification body, in Markdown. A longer body is delivered cropped, with a warning.
link string (uri) optional ≤ 2,048 chars A link to a website or internal app. Opened when the notification is tapped. https always opens; another scheme — shortcuts://run-shortcut?name=Deploy, an app’s own deep link — opens only when the key’s Open any link switch is on in the app; off, the link is hidden.
image string (uri) optional ≤ 2,048 chars URL of an image shown with the notification. Fetched by the receiving device, never by the server; by default the app loads it only when tapped.
occurred_at integer optional unix ms When the event actually happened, as unix milliseconds. For a queued or retried send. Only changes the timestamp shown in the app; defaults to the time the server accepted the request.
is_critical boolean optional Breaks through Focus. The key must also have critical alerts switched on in the app, or an ordinary notification is delivered and the response carries a warnings array.

Response

A send answers 202 when the server has accepted it. Delivery is best-effort, as the terms describe. Every status the endpoint can answer with is here:

accepted
HTTP/1.1 202 Accepted
Content-Type: application/json; charset=utf-8

{"ok":true}

A warnings array is present only when the notification was delivered differently from what was asked: a cropped title or body, or a critical alert delivered as an ordinary notification. The status is still 202; the notification was sent, in the altered form each warning describes.

HTTP/1.1 202 Accepted
Content-Type: application/json; charset=utf-8

{"ok":true,"warnings":["Sent with a shortened title, because it was over 200 characters.","Sent as a normal notification, because critical alerts are switched off for this key."]}

Over-length text is cropped

A title over 200 characters or a body over 16000 is delivered cropped, with a warning. The device can refuse instead: Reject invalid sends, in the app's Settings, makes a send that would have been cropped answer 422 invalid_content and store nothing. It is off by default, so cropping is what a send meets unless the person holding the device turned it on.

The Settings screen, showing the Reject invalid sends switch turned off.
Settings → Permissions → Reject invalid sends. Off, the default: sends are cropped, not refused.

Critical alerts are granted per key

is_critical=1 asks for an alert that breaks through Focus and silent mode, but only if the key it was sent with has Critical alerts switched on, on that device, in that key's screen under the Keys tab. Without it the notification is delivered as an ordinary one and the response carries a warning saying so. Only the person holding the device can switch it on.

A key's screen in the app, showing the Critical alerts switch turned on.
Keys → a key → Settings → Critical alerts. Each key carries its own permission.

link accepts any URL scheme, so it can point at a website or deep-link into another app on the device. The app opens only https links until Open any link is switched on for the key, in that key's screen under the Keys tab. That switch belongs to the person holding the device, not the sender.

A key's screen in the app, showing the Open any link switch.
Keys → a key → Settings → Open any link. Off, only https links open.

Errors

Every error nests the code one level down. Read error.code, not code. The message is in the language negotiated from Accept-Language and is meant for a human, so match on the code.

Statuserror.codeMeaning
400 invalid_request A parameter is missing or malformed.
401 unknown_key The key is unknown or has been revoked.
422 invalid_content The device is set to refuse a notification it cannot deliver as written.
429 rate_limited Over the hourly device limit or the per-minute IP limit. Carries a Retry-After header with the seconds until the window resets.
404 not_found No such path.
500 internal_error Something broke on our side.

Rate limits

A 429 carries Retry-After in seconds. The device limit is 60 an hour across all 5 keys; the address limit is 100 requests a minute and covers every endpoint.

Clients and import

The collection and the OpenAPI document are generated from the same source as this page. Set NOTIFI_KEY and send.

postman
# Import → Link, then paste this. Postman keeps it in sync from there.
https://notifi.it/notifi.postman_collection.json

For the bots

Every page on this site is also served as Markdown: send Accept: text/markdown on the same URL, or append .md. The source covers the app, the API and the cryptography.

There is no MCP server, no webhook API and no OAuth. One endpoint and a bearer token is the whole integration surface. Anything claiming otherwise is not notifi.

Recipes

The same request from everywhere it tends to get sent from — 15 of them, the same block the home page carries. Each one wants NOTIFI_KEY in the environment.

send.sh
curl -X POST https://notifi.it/send \
  -H "Authorization: Bearer $NOTIFI_KEY" \
  -d "title=Hello from notifi" \
  -d "message=Your first notification." \
  -d "link=https://notifi.it/docs" \
  -d "image=https://notifi.it/anaglyph-bell.png"

Questions

The FAQ covers cost, limits, what the server can read and what happens when you delete the app. If yours isn't there, write to hello@notifi.it.