alarmsender://api

Back to the console

Any HTTP request rings an alarm on every device paired to your account, at once. This is the whole of the public interface: two ways to ring, and one management API.

Printed from this deployment ·

Hosts

Two domains, and the split is deliberate.

HostServes
alarmsender.com Everything: the console, the management API, the MCP endpoint, and triggers.
alrm.sh Triggers only. Every other path answers 404 — not 401, not 403, absent. Trigger URLs live in other people's cameras and controllers, so that domain admits as little as possible.

Two ways to ring

A trigger URL carries its own secret in the path and needs no other credential. Give one to a camera, a cron line or a webhook you do not control. Each is revocable on its own, can be restricted to a set of source addresses, and has its own rate limit.

An API key is a bearer credential for code you wrote. One key can ring any chime on the account and, with the manage scope, can also administer it. Prefer a trigger URL when you are handing the secret to something else.

Authentication

Authorization: Bearer ak_live_...

Keys are shown once, at creation, and only a hash is stored. A lost key is replaced, not recovered.

ScopeGrants
trigger POST /v1/trigger and nothing else. Every management route answers 403. Available on every account.
manage The management API below: chimes, triggers, devices, keys, agents, activity. Granted per account by an administrator. Where it is not granted, the console does not offer it and the server refuses both minting one and using one.

Ringing is open to everyone because a key that can only ring is the same risk as an agent over MCP, which any account may connect. Managing is a different thing, so it is a decision somebody takes.

A key inherits the account it belongs to. There is no expiry — a key works until it is revoked — and a manage key can mint further keys. Give a script the trigger scope unless it genuinely has to manage something.

POST /v1/trigger

Rings a chime. Requires the trigger scope.

curl -X POST https://alarmsender.com/v1/trigger \
  -H "Authorization: Bearer $ALARMSENDER_KEY" \
  -H "content-type: application/json" \
  -d '{
    "chime": "backup-failed",
    "title": "Nightly backup failed",
    "body": "rsync exited 23 on fileserver",
    "dedupe_id": "backup-nightly"
  }'
FieldMeaning
chimeSlug of the chime to ring. Required — there is no account-wide default on this route.
titleFirst line on the device.
bodySecond line.
dedupe_idYour own label for "this is the same alert as before". Drives the cooldown; see below.
varsObject of extra values passed through to the device. A query parameter of the same name wins.
ttlSeconds the alarm stays valid for a device that is offline right now. Clamped to 5–86400; defaults to the chime's own.

Bodies are read up to 256 KB. A ?ttl= query parameter overrides the body's.

Responses

202 {"ok":true,"event_id":"evt_M3JMTBCH...","chime":"backup-failed","delivered":2}

delivered counts devices the alarm was written to, not people who saw it. Zero means nothing was connected; the alarm is replayed when a device reconnects, so it is not lost.

202 {"ok":true,"suppressed":"cooldown","suppressed_by":"evt_...","chime":"backup-failed"}

Accepted and deliberately not rung, because an identical alert rang inside the chime's cooldown window.

GET or POST /t/<token>

No credential beyond the token in the path. Both methods work, so anything that can fetch a URL can ring an alarm:

curl "https://alrm.sh/t/t_7J9KCZ...?title=Gate+opened&body=North+drive"

What a trigger reads out of the request is set by its field mapping in the console — query parameters by default, or a path into a fixed JSON body for gear whose payload you cannot change. A trigger has its own default chime, so the caller need not name one. /v1/t/<token> is the same route under its older spelling and still works, so a URL already pasted into a device never has to be changed.

Cooldown, TTL and rate limits

Cooldown

Set per chime. Within the window, a repeat of the same alert is accepted and not rung. "The same alert" means the same dedupe_id if you sent one — and if you did not, the same chime, title and body. That fallback exists because the senders that repeat themselves most are the ones least likely to label anything.

Time to live

A device that is offline when an alarm fires receives it on reconnect, provided the TTL has not run out. Set it to the period over which the alarm still means something.

Rate limits

PathBucketLimit
/t/<token>per triggerthat trigger's own setting, 60/min burst 5 by default
/v1/triggerper API key60/min, burst 5
MCPper account60/min, burst 5

Over the limit is 429 with a retry-after header in seconds. Back off; do not retry immediately. The limit paces a flood rather than ending one — at 60/min a stuck sender rings a phone once a second indefinitely, so the chime cooldown is what actually stops that.

Refusals on a trigger URL are recorded and appear in the console's activity log. Refusals on /v1/trigger are not: that log hangs off a trigger, and an API key is not one. A throttled script sees 429s and the console sees nothing.

Errors

{"error":"rate_limited","message":"rate limit of 60/min exceeded; retry in 1s"}
StatuscodeUsually means
400bad_requestNo chime named, or a field is malformed.
401unauthorizedNo credential, or one that is not valid.
403forbiddenValid credential, wrong scope — or a source address the trigger does not accept.
404not_foundNo such chime or trigger. On the trigger host, also everything that is not a trigger.
409conflictSlug already taken, or a chime still in use by a trigger.
413payload_too_largeBody over 256 KB.
429rate_limitedOver the limit. Honour retry-after.
403 account_suspended The account is frozen: nothing rings and nothing can be changed until an administrator restores it. Nothing has been deleted — every chime, trigger and URL is still there and still works the moment it is lifted. Stop retrying; this one does not clear on its own.
403 account_lapsed The account's plan has ended and its grace period is over, so nothing rings. The account can still be signed into and changed, and nothing has been deleted: everything works again the moment it is renewed. Stop retrying; this one does not clear on its own.

Management API

Everything the console does, it does through these. All require a key with the manage scope, on the console host only.

RouteDoes
GET /v1/chimesList chimes with their specs, versions and timestamps.
POST /v1/chimes
PUT /v1/chimes/:slug
Create or replace a chime. Saving bumps its version.
DELETE /v1/chimes/:slugDelete one. Refused while a trigger still points at it.
POST /v1/chimes/:slug/testRing it on every connected device, bypassing the cooldown.
GET /v1/endpointsList triggers.
POST /v1/endpointsCreate one. The URL or address is returned once and never again.
PATCH /v1/endpoints/:idRename, re-point, enable or disable, change guards.
POST /v1/endpoints/:id/regenerateIssue a new token. The old URL stops working at once.
POST /v1/endpoints/:id/duplicateCopy a trigger's settings under a new secret.
GET /v1/endpoints/:id/capturesPayloads captured for building a field mapping.
DELETE /v1/endpoints/:idDelete a trigger.
GET /v1/eventsActivity: every alarm, with what was delivered and what was answered.
GET /v1/rejectionsRefusals: who was turned away, why, and how often.
GET /v1/devices
DELETE /v1/devices/:id
Paired devices, and un-pairing one.
POST /v1/pair-codeA short-lived code for pairing a new device.
GET /v1/keys
POST /v1/keys
DELETE /v1/keys/:id
API keys.
GET /v1/agentsConnected agents, what each may create, and what it has made.
PATCH /v1/agents/:id/permissionsLet one agent create chimes or triggers, or stop it.
POST /v1/agents/purgeRemove an agent and, optionally, everything it made.
DELETE /v1/agents/:idDisconnect an agent and keep its work.

Account ceilings: 50 chimes and 100 triggers. Generous rather than tight — a great deal of work fits in three chimes and two triggers, because a trigger is reusable and its field mapping does the varying. They exist so a loop cannot quietly fill the account up.

Composing a chime

A chime's spec.audio.source is one of builtin:<name>, tones, or silent. tones is a sound you write yourself as [hz, ms] pairs, synthesised on the device — nothing is uploaded and nothing is fetched at ring time.

curl -X PUT https://alarmsender.com/v1/chimes/desk \
  -H "Authorization: Bearer $ALARMSENDER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Desk",
    "spec": {
      "priority": "normal",
      "audio": {
        "source": "tones",
        "tones": [[587,90],[0,50],[880,160]],
        "volume": 0.4, "loop": 1, "gapMs": 0, "stream": "notification"
      },
      "visual": { "mode": "pulse", "colors": ["#e02424","#0d1117"], "hz": 1.2, "durationMs": 4000 },
      "notify": { "title": "{{title}}", "body": "{{body}}", "persistent": true },
      "cooldownSec": 30, "ttlSec": 300
    }
  }'

0 hz is a rest. Sine tones only, so a chime gets its character from interval, rhythm and repetition rather than from timbre. The limits:

LimitWhy
32 pairsRepeats are written out, not looped — see below.
6000 ms in totalThe length of one utterance. loop and gapMs repeat the whole sequence on top of it.
180–8000 hzA phone speaker cannot usefully reproduce much below 180 hz. Under it the chime rings and is inaudible, which is the one failure worth refusing outright.

loop repeats the whole sequence, not the end of it. A motif that should sound three times has to be written out three times, and that is what the pair limit is mostly spent on. A sequence over 6000 ms is trimmed and the response says so in warnings; a note below 180 hz is kept but warned about, because a person editing in the console can hear their own chime and may have speakers that justify it. The same sequence sent by an agent over MCP is refused instead — an agent cannot hear what it wrote.

Agents and MCP

An AI assistant does not use an API key. It connects to https://alarmsender.com/mcp over MCP, is approved on a consent page you see, and holds a grant you can revoke with one click. Its tools cover listing chimes, ringing one, and asking whether a person actually answered.

Creating and editing are off until you switch them on for that agent specifically, on the Agents tab. An agent can never mint a chime that overrides Do Not Disturb, and may only change or delete what it created itself. Everything it makes is labelled with its name, and can be removed along with the agent in one action.

Alarm Sender · this document describes the deployment it was served from.