Documentation
Getting started · Ringing · Ringtones · Deadlines · Senders · Keeping ring URLs private · Limits and bans · API reference
Getting started
- Download and run the Mac app. It creates a listener key, registers a doorbell, and shows your ring URL.
- Use the app's Open test page to ring yourself from the browser, or Test to ring from the app.
- Give the ring URL to your agents, ideally in the
DOORBELL_URLenvironment variable.
A ring URL looks like https://doorbell.test01.tomorrowtoday.com/r/db_7h3kq9x2mvb8wz4tn6rc5py1fd. Anyone with it can ring you. Only your Mac can read the rings.
Ringing
Ring with a GET on the ring URL, or a POST of JSON to it. Both take the same fields.
curl "$DOORBELL_URL?source=ci&ringtone=done&message=Build%20passed"
curl -X POST "$DOORBELL_URL" \
-H 'Content-Type: application/json' \
-d '{"source":"ci","ringtone":"done","message":"Build passed","link":"https://ci.example/runs/8812"}'
| Field | Required | Limit | Meaning |
|---|---|---|---|
source | Yes | 40 characters | The sending app or agent. Use the same name every time. |
ringtone | No; defaults to default | Must be in the doorbell's list | Which ringtone plays |
message | No | 80 characters; longer is cut | Text shown with the ring |
link | No | 2,000 characters; http or https | Opened when the ring is clicked |
| Result | Status | Body |
|---|---|---|
| Accepted | 202 | ring_id, received_at, and truncated: true if the message was cut |
| Same ring within the last 60 seconds | 200 | duplicate_of: the earlier ring ID |
No source | 400 | Plain text explaining how to ring |
| Unknown ringtone | 422 | error and valid_ringtones |
| Other malformed request | 400 | error |
| Too many rings, or banned | 429 | Retry-After header |
| Unknown address | 404 | The same plain response as any unknown path |
Ring once per event, and do not retry a ring that returned 2xx. A duplicate counts as delivered.
Ringtones and naming
Ringtone names are lowercase kebab-case: letters a to z, digits, and single hyphens, starting with a letter, at most 32 characters. Most doorbells have default, done, needs-input, error, missed, and emergency. The Mac app owns the list and the sounds; the server knows only names and descriptions. Until the app publishes its list, only default is accepted.
curl -s "$DOORBELL_URL/info"
returns the doorbell's ringtones with descriptions, so an agent can choose one. It never returns rings.
Deadlines
Before long or fragile work, an agent can promise a ring. If no matching ring arrives in time, the server rings missed, so you know to check on the agent.
curl -s "$DOORBELL_URL/deadline?source=etl-agent&ringtone=done&like=Refresh*&minutes=20"
| Field | Required | Meaning |
|---|---|---|
source | Yes | The source the expected ring will come from |
ringtone | No; default | The ringtone the expected ring will use |
like | No; * | Pattern for the whole message: * is any run of characters, ? is one, case-insensitive |
minutes | Yes | 1 to 1,440 |
missed_ringtone | No; missed | The ringtone played if the deadline is missed |
The response is 202 with deadline_id and due_at. Any matching ring clears the deadline, including a duplicate. Setting the same source, ringtone, and pattern again restarts the clock. Cancel with DELETE $DOORBELL_URL/deadlines/{deadline_id}. A doorbell may have 20 active deadlines. Deadlines are checked when your Mac polls, so a missed one rings within one poll interval, or when the Mac comes back online.
Senders
- curl or any HTTP client: the examples above.
- Agent skill: doorbell-skill.zip holds a
SKILL.mdthat teaches skill-capable agents to ring and set deadlines. It contains no secret; give agents the ring URL separately. - CLI, MCP server, and Claude Code hooks ship with the Mac app. The app's Connect agents window has ready-made snippets.
Keeping ring URLs private
A ring URL is a secret, like a webhook URL. Keep it in DOORBELL_URL or a config file rather than in code. Never commit it to a public repository, and do not paste it into chat: link previews can fetch it and ring you. If it leaks, use Replace address in the app and update your agents. The worst a leaked URL allows is unwanted rings, which the limits below contain.
Messages are readable by the server and held for an hour. Treat a message like a text on a lock screen.
Limits and bans
- A doorbell accepts 30 rings a minute. Further rings get
429until the minute passes. - Ringing the same doorbell with the same message more than 5 times in a minute bans your IP address for 2 hours.
- The desktop app may call each of its endpoints once every 15 seconds per doorbell. The app spaces its calls for you.
- A banned client gets
429withRetry-Afteron every endpoint until the ban ends. - Rings are held for one hour. A doorbell is removed 30 days after its app last polled; the app restores it quietly with the same address.
API reference
| Operation | Method and path | Needs |
|---|---|---|
| Ring | GET /r/{address}, POST /r/{address} | Ring URL |
| Doorbell info | GET /r/{address}/info | Ring URL |
| Set a deadline | GET /r/{address}/deadline, POST /r/{address}/deadlines | Ring URL |
| Cancel a deadline | DELETE /r/{address}/deadlines/{deadline_id} | Ring URL and deadline ID |
| Register | POST /v1/doorbells | Listener key |
| Start a session | POST /v1/doorbells/{address}/sessions | Listener key |
| Poll for rings | GET /v1/doorbells/{address}/rings | Session token |
| Publish ringtone list | PUT /v1/doorbells/{address}/ringtones | Session token |
| Replace address | POST /v1/doorbells/{address}/replace | Session token |
| Test page | GET /test/{address}?exp=…&sig=… | A link signed by the app |
| Health | GET /health, GET /health.json | Nothing |
| Agent skill | GET /downloads/doorbell-skill.zip | Nothing |
Desktop endpoints are used by the Mac app. Their signing rules are part of the project's scope notes rather than this page.