API reference
Everything you need to send notifications. Your URL is the only credential — keep it private and rotate it from the app if it leaks.
Quick start
The simplest possible request:
curl "https://flarebird.app/k7Qm2x9LpR4sTvWz8aBcDe/Build%20finished"Send a notification
POST JSON to the push endpoint. Only title or body is required. POST /api/v1/push/{key}
curl -X POST "https://flarebird.app/api/v1/push/k7Qm2x9LpR4sTvWz8aBcDe" \
-H "content-type: application/json" \
-d '{"title":"Build finished","body":"All 84 tests passed in 42s","url":"https://github.com","group":"ci","level":"timeSensitive"}'Fields
| Field | Type | Notes |
|---|---|---|
title | string | Title or body is required. Up to 250 characters. |
subtitle | string | Shown under the title. Up to 250 characters. |
body | string | The message. Alias: message. Shortened with … if it exceeds Apple's 4 KB limit. |
url | https URL | https:// only, up to 2048 characters. Opened when you tap the notification. |
image | https URL | https:// only. Downloaded on the device (max 10 MB, 5 s) and attached. |
group | string | Groups notifications and history. Up to 64 characters. |
sound | enum | default, aviso or none. |
level | enum | passive, active (default) or timeSensitive. critical is not supported. |
badge | integer | App icon badge number (0–99999). |
ttl | integer | Seconds Apple keeps retrying if the phone is offline (default 86400, 0 = now or never). |
copy | string | Text copied by the "Copy" action. Defaults to the body. |
collapse | string | Notifications with the same value replace each other. |
GET with query parameters
Same fields as query parameters. Perfect for browsers and Shortcuts.
curl "https://flarebird.app/api/v1/push/k7Qm2x9LpR4sTvWz8aBcDe?title=Build%20finished&body=All%2084%20tests%20passed%20in%2042s&group=ci"URL shortcuts
Even shorter. Path segments are URL-decoded.
GET /{key}/{body} | Body only |
GET /{key}/{title}/{body} | Title and body |
POST /{key} | POST text, JSON or a form |
curl "https://flarebird.app/k7Qm2x9LpR4sTvWz8aBcDe/All%2084%20tests%20passed%20in%2042s"
echo "All 84 tests passed in 42s" | curl -X POST --data-binary @- "https://flarebird.app/k7Qm2x9LpR4sTvWz8aBcDe"Responses
Successful requests return the APNs id. truncated is present when text had to be shortened.
{ "ok": true, "id": "8C6F1B7A-5B9E-4C1D-9F3A-2E7D6B0A1C44" }Errors
Error messages follow your Accept-Language header (English or Spanish).
{ "ok": false, "error": { "code": "rate_limited", "message": "…" } }| Code | HTTP | Meaning |
|---|---|---|
invalid_key | 404 | Unknown or rotated URL |
rate_limited | 429 | Too many requests — see Retry-After |
invalid_payload | 400 | Validation failed — see fields |
critical_not_supported | 400 | The critical level isn't available |
device_gone | 410 | The iPhone is no longer registered |
unavailable | 503 | Temporarily unavailable |
Limits
- 30 pings per minute and 1,000 per day per URL
- 120 requests per minute per IP address
- 4 KB per notification (longer text is shortened, never rejected)
Browsers & CORS
Push endpoints allow any origin, so you can call them from a web page.
Examples
curl "https://flarebird.app/k7Qm2x9LpR4sTvWz8aBcDe/Build%20finished/All%2084%20tests%20passed%20in%2042s"
# or with every option
curl -X POST "https://flarebird.app/k7Qm2x9LpR4sTvWz8aBcDe" \
-H "content-type: application/json" \
-d '{"title":"Build finished","body":"All 84 tests passed in 42s","url":"https://github.com","group":"ci"}'Use Flarebird from your own projects
Store your URL in an environment variable called FLAREBIRD_URL and call it when something happens. The helper below never raises, so a failed ping can't break your script.
import json, os, urllib.request
FLAREBIRD_URL = os.environ.get("FLAREBIRD_URL", "https://flarebird.app/k7Qm2x9LpR4sTvWz8aBcDe")
def ping(title: str, body: str = "", **extra) -> bool:
"""Send a push to your iPhone. Never raises."""
data = json.dumps({"title": title, "body": body, **extra}).encode()
req = urllib.request.Request(FLAREBIRD_URL, data=data,
headers={"content-type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=10) as res:
return res.status == 200
except Exception:
return False
ping("Build finished", "All 84 tests passed in 42s")