API docs
Send a push notification to your phone with one web request.
Send a ping
POST https://ayeping.com/@username with your token in the Authorization header. /ping/@username works too, for tools that don't like an @ at the start of a path. Create tokens in the app, on the Tokens page.
curl https://ayeping.com/@you \
-H "Authorization: Bearer $AYEPING_TOKEN" \
-d '{"title": "Backup finished",
"message": "Saved **3 GB** to the NAS.",
"label": {"text": "nas", "color": "green"}}'A plain text body is the title, so the shortest ping is one line:
curl https://ayeping.com/@you \
-H "Authorization: Bearer $AYEPING_TOKEN" \
-d "Deploy finished"Body
JSON, up to 32 KB. A body that starts with { is read as JSON, so curl -d works without a Content-Type.
| Field | Required | Rules |
|---|---|---|
title | Yes | 1–100 characters, one line. |
message | No | Up to 2,000 characters. Markdown, see below. |
label.text | No | 1–20 characters, shown as a chip. |
label.color | No | gray, red, orange, yellow, green, teal, blue, purple, pink. Defaults to gray. |
Markdown
Messages support bold, italic, strikethrough, inline code, code blocks, lists, quotes, headings and links. A new line is a line break. Images aren't loaded (their alt text shows instead), so a message can't tell the sender when you read it. Links ask before they open.
More examples
await fetch("https://ayeping.com/@you", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AYEPING_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "Backup finished",
message: "Saved **3 GB** to the NAS.",
}),
});import json, os, urllib.request
request = urllib.request.Request(
"https://ayeping.com/@you",
data=json.dumps({"title": "Backup finished"}).encode(),
headers={
"Authorization": f"Bearer {os.environ['AYEPING_TOKEN']}",
"Content-Type": "application/json",
},
)
urllib.request.urlopen(request)Responses
Errors are JSON with an error code: { "error": "rate_limited" }.
| Status | error | Meaning |
|---|---|---|
| 202 | — | Accepted. The body is the ping's id: { "id": "…" }. |
| 400 | invalid_body | The body broke a rule below. message says which. |
| 401 | unauthorized | Missing or wrong token, or the token isn't for this username. |
| 402 | quota_exhausted | No pings left. You also get one push about it a day. |
| 403 | token_paused | The token is paused. Resume it on the Tokens page. |
| 413 | body_too_large | The body is over 32 KB. |
| 429 | rate_limited | Over the token's hourly limit, or too many requests from your IP. Wait Retry-After seconds. |
| 429 | too_many_failed_attempts | Too many wrong tokens from your IP. Wait a minute. |
Retries
Send an Idempotency-Key header (up to 255 characters) to retry safely. Another ping with the same key and token returns the first ping's id, and isn't delivered or counted again.
curl https://ayeping.com/@you \
-H "Authorization: Bearer $AYEPING_TOKEN" \
-H "Idempotency-Key: nightly-backup-2026-10-21" \
-d '{"title": "Backup finished"}'Limits
- Each token has an hourly limit, 1–3,600 (default 60). Accepted pings carry
RateLimit-Limit,RateLimit-RemainingandRateLimit-Resetheaders. - Every ping uses one from your quota. Free accounts get 5 to try; Hobby and Pro get a monthly or yearly allowance (see pricing).
- Pings are kept 90 days on Free and 365 days on Hobby and Pro.
Keeping tokens safe
A token can send pings to you, nothing else. Keep it in an environment variable or your CI's secrets, not in code. If one leaks, pause or delete it on the Tokens page; the others keep working.