Navigation

API Reference

Send a push notification to any Beeping device with a single HTTP request. You'll need two things: an API key from your own account (identifies and bills you, the sender) and a pairing code from the device you want to page (identifies the receiver).

Docs

Overview

Every Beeping device can generate one or more pairing codes from Settings and share them with whoever should be able to page it. Separately, every sender generates their own API key, also from Settings. Sends made with your key are billed against your own account — your free monthly quota or your subscription — never against the receiving device's. This is what lets you hand out your pairing code freely without worrying that someone else's scripted sends will eat into your own allowance.

Codes never expire on their own. They remain valid until manually revoked from the Settings tab on the receiving device, at which point any request using that code starts returning 404.

Authentication

All routes are served from a single base path: https://beeping.macspot.net/api

Sending a page requires an X-API-Key header. This key is tied to your account and is what determines who gets billed for the send — not the pairing code, which only identifies the receiver. Treat your API key like a password: it's shown once at creation time and can't be retrieved again, only revoked and replaced.

Avoid committing API keys or pairing codes to public repositories or logs. If a key leaks, revoke it from Settings → API Keys and generate a new one — anything still using the old key will start failing immediately with 401.

Quickstart

Get an API key and a pairing code, then send your first page in under a minute.

1. Create an API key

Open Beeping → Settings → API Keys → Create API Key. Copy the key immediately — it's only ever shown once.

2. Get a pairing code for the device you want to page

On the receiving device: Settings → Pairing Codes → Add Pairing Code. Tap the copy icon next to a code to copy a ready-to-run curl example (minus your key).

3. Send a page

curl -X POST https://beeping.macspot.net/api/sendByPairingCode \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pairingCode": "AB3D9F",
    "title": "Deploy failed",
    "body": "staging build #4821 failed at the lint step",
    "severity": "high"
  }'

That's it — the push arrives on the device that generated the pairing code, immediately. The send counts against your own account's quota, not theirs.

API keys

API keys are 8 characters long, generated and managed from Settings → API Keys in the app. There's currently no HTTP endpoint for key management — creating, listing, and revoking keys is done from the app itself, since it's tied to your signed-in account.

ActionWhere
Create a key Settings → API Keys → Create API Key. The plaintext key is shown once — save it immediately.
List your keys Settings → API Keys shows all your keys by a short, non-reversible identifier plus creation/last-used time. The plaintext value is never shown again after creation.
Revoke a key Tap the trash icon next to a key in Settings → API Keys. Revocation is immediate — any request using that key afterward gets 401.

Send a page

POST https://beeping.macspot.net/api/sendByPairingCode

Sends a push notification to whichever device is currently paired to pairingCode. Billed against the account that owns the X-API-Key used, not the receiving device's account.

Headers

HeaderDescription
X-API-Keyrequired Your API key, from Settings → API Keys. Identifies you as the sender for billing/quota purposes.

Request body

FieldTypeDescription
pairingCoderequired string The code generated from the receiving device's Settings tab. Case-insensitive.
titlerequired string Notification title shown on the receiving device.
bodyrequired string Notification body text.
severityoptional string One of normal, high, or critical. Defaults to normal. See Severity levels.
webhookUrloptional string A URL under your control that accepts a POST request. If set, it's delivered to the receiving device along with the page. If the recipient acknowledges the page from their app, their device POSTs an acknowledgment payload to this URL — see Acknowledge webhook payload. This is your predefined contract, not something the receiving device configures. There's no signature or shared secret on the request the device sends — if you need to verify it's authentic, embed your own token directly in the URL (e.g. as a query parameter) when you set it here.

Example request

curl -X POST https://beeping.macspot.net/api/sendByPairingCode \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pairingCode": "AB3D9F",
    "title": "Production DB unreachable",
    "body": "prod-db-1 failed 3 health checks in a row",
    "severity": "critical",
    "webhookUrl": "https://your-system.example.com/paging-ack"
  }'

Example response

{
  "ok": true,
  "alertId": "3f1b6c2a-...",
  "sent": 1,
  "failed": 0,
  "quotaRemaining": 287
}

alertId uniquely identifies the page that was just sent. It matches the alertId in the acknowledgment webhook payload, so you can correlate a send with its later acknowledgment.

quotaRemaining shows how many sends your account (the API key's owner) has left this calendar month under your plan's monthly send allowance.

When nothing gets sent

Some conditions return 200 with sent: 0 rather than an error, since they're expected outcomes, not failures — for example, the receiving device has Do Not Disturb enabled (and you sent normal or high severity — see Severity levels, critical always gets through), or has disabled critical alerts specifically and you sent a critical severity page.

{
  "ok": true,
  "sent": 0,
  "message": "Target device has Do Not Disturb enabled"
}

Severity levels

Severity controls how the notification interrupts the receiving device.

ValueBehavior
normal Standard notification. Respects Focus modes, the silent switch, and Do Not Disturb normally.
high Time-sensitive interruption level — can break through some Focus modes. Still respects Do Not Disturb.
critical Highest priority. Bypasses Do Not Disturb — Do Not Disturb is meant to pause routine notifications, not actual incidents. Skipped entirely only if the receiving device has disabled critical alerts specifically in Settings (independent of Do Not Disturb).

Acknowledge webhook payload

If you set webhookUrl on a page, it travels with that specific page to the receiving device, along with the pairingCode it was sent to. If the recipient taps Acknowledge on the page's detail screen, their device POSTs the following JSON body to that URL:

{
  "event": "page.acknowledged",
  "alertId": "3f1b6c2a-...",
  "pairingCode": "AB3D9F",
  "severity": "critical",
  "receivedAt": "2026-07-18T09:14:02Z",
  "acknowledgedAt": "2026-07-18T09:16:41Z"
}

alertId uniquely identifies this specific message. pairingCode identifies which of your recipients acknowledged it — useful if you send through several different pairing codes and need to tell them apart.

This is a best-effort notification: the acknowledgment is always recorded locally on the receiving device regardless of whether your webhook endpoint is reachable or returns an error. Your endpoint should respond with a 2xx status; the device doesn't retry on failure. webhookUrl is entirely optional — omit it and acknowledgment stays local to the receiving device.

The receiving device sends this request directly to your URL, with no signature or shared secret attached by us. If you need to verify a request actually came from a real acknowledgment, embed your own verification token as part of the URL itself (e.g. a query parameter) when you set webhookUrl — for example https://your-system.example.com/paging-ack?token=YOUR_SECRET. Check for that token on your end when the request arrives.

Rate limits & quota

Per-code rate limit

Requests against the same pairing code are limited to roughly one every 3 seconds. A request that arrives sooner receives a 429. This exists to blunt naive retry loops and brute-force attempts against a single code, not to constrain normal usage.

Monthly send quota

Each plan has a monthly send allowance, tracked per sender (the account that owns the API key used) and reset automatically at the start of each month: Free gets 300 sends, Pro gets 3,000, and Premium gets 30,000. Quota is consumed only on requests that actually result in a send attempt — an unknown pairing code or a device that's off-call doesn't count against it.

Errors

400 Bad Request 401 Unauthorized 402 Payment Required 404 Not Found 405 Method Not Allowed 429 Too Many Requests 500 Internal Server Error
StatusMeaning
400 pairingCode, title, or body missing from the request.
401 Missing or invalid X-API-Key header. Check that the key hasn't been revoked from Settings → API Keys.
402 Your (the sender's) free monthly send quota is exhausted. Response body includes {"error": "quota_exceeded"}.
404 The pairing code doesn't exist (never issued, revoked, or replaced), the receiving device is no longer registered, or it has no push token yet.
429 Too many requests against this pairing code in a short window. Response body includes {"error": "rate_limited"}. Wait a few seconds and retry.
500 Unexpected server error. Safe to retry with backoff.

curl

curl -X POST https://beeping.macspot.net/api/sendByPairingCode \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "pairingCode": "AB3D9F",
    "title": "Backup finished",
    "body": "412 GB archived to cold storage",
    "severity": "normal"
  }'

Node.js

async function sendPage({ title, body, severity = "normal" }) {
  const response = await fetch(
    "https://beeping.macspot.net/api/sendByPairingCode",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-API-Key": process.env.PAGING_API_KEY,
      },
      body: JSON.stringify({
        pairingCode: process.env.PAGING_CODE,
        title,
        body,
        severity,
      }),
    }
  );

  if (!response.ok) {
    throw new Error(`Page failed: ${response.status}`);
  }

  return response.json();
}

await sendPage({
  title: "Deploy failed",
  body: "staging build #4821 failed at the lint step",
  severity: "high",
});

Python

import os
import requests

def send_page(title: str, body: str, severity: str = "normal") -> dict:
    response = requests.post(
        "https://beeping.macspot.net/api/sendByPairingCode",
        headers={"X-API-Key": os.environ["PAGING_API_KEY"]},
        json={
            "pairingCode": os.environ["PAGING_CODE"],
            "title": title,
            "body": body,
            "severity": severity,
        },
        timeout=10,
    )
    response.raise_for_status()
    return response.json()

send_page(
    title="Production DB unreachable",
    body="prod-db-1 failed 3 health checks in a row",
    severity="critical",
)

Shell script monitoring example

A common pattern: wrap a health check in a cron job, and page yourself only when it fails.

#!/usr/bin/env bash
set -euo pipefail

PAGING_API_KEY="YOUR_API_KEY"
PAGING_CODE="AB3D9F"
ENDPOINT="https://beeping.macspot.net/api/sendByPairingCode"

if ! curl -sf https://example.com/health > /dev/null; then
  curl -X POST "$ENDPOINT" \
    -H "X-API-Key: $PAGING_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"pairingCode\":\"$PAGING_CODE\",\"title\":\"Health check failed\",\"body\":\"example.com/health did not return 200\",\"severity\":\"critical\"}"
fi

Schedule it with cron, launchd, or your CI system's scheduled-job feature. Store PAGING_API_KEY as a secret, not a plaintext script variable, in real deployments.