Public: anyone can read this; the crew can change it.
GET and PUT /api/v1/user/notify read and set your phone notify URL, as MCP `notify` and Settings already do #7893
Check · Review · Land · Live done — Landed and live in production.
`GET` and `PUT /api/v1/user/notify` now read and set your phone notify URL over REST, the same way MCP `notify` and Settings do. The new tests pass, along with `test/mcp-size.test.ts` and the OpenAPI tests, and `tsc` and the design lint are clean. I did not run `scripts/gate.ts`, and I did not run `scripts/fast.ts`'s test selection. `fast.ts` named 939 of 1722 test files, far over its time budget,…
shipped production is serving it deploy fc82d1a
GET and PUT /api/v1/user/notify now read and set your phone notify URL over REST, the same way MCP notify and Settings do. The new tests pass, along with test/mcp-size.test.ts and the OpenAPI tests, and tsc and the design lint are clean. I did not run scripts/gate.ts, and I did not run scripts/fast.ts's test selection. fast.ts named 939 of 1722 test files, far over its time budget, so I ran only the tests that cover this change. The forge runs the full check on the push.
- Read:
GETanswers{ set, host }with the host only. The full URL never appears in a response or a log. - Write:
PUTwith{ url }saves it, and an emptyurlturns it off. It answers{ set, host }and appendsaccount.notify_setto the bus. - Reuse: the routes are in
src/http/api/user.tsand callsetNotifyUrl,notifyHost,notifyUrlOfandNOTIFY_ERRORSfromsrc/crew/notify.ts, so they share MCP's logic. - Refusals:
- An agent token gets 403 forbidden on both methods.
- A PUT with a token below admin scope gets 403.
- A cross-site browser write gets 403, the same as other mutations.
- A URL setNotifyUrl refuses gets 422 invalid_url, carrying the NOTIFY_ERRORS message.
- A url that isn't text gets 422 invalid.
- Docs: both routes are described in
getApiV1UserNotify.tsandputApiV1UserNotify.tsundersrc/contract/openapi/routes/and exported from itsindex.ts. They appear in/api/v1/openapi.json. - Tests:
test/api-user-notify.test.tsdrives read, set, clear and each refusal over HTTP, and checks that the secret part of the URL never comes back. It also checks that the bus hears both writes and that the OpenAPI document lists both routes.
I left out MCP changes (src/http/mcp/tools/notify.ts is untouched) and the hot files src/http/server.ts and docs/MCP.md. I added no dependency.
To try it: curl -H "authorization: Bearer $ADMIN_TOKEN" -X PUT -d '{"url":"https://ntfy.sh/x"}' $URL/api/v1/user/notify, then GET the same path.
GET /api/v1/user/notify now names the session timing phase, which fixes the one failing check, test/route-timing-coverage.test.ts. It was the only failure in the 9,356-test run. I added one line to the GET handler in src/http/api/user.ts, matching the neighbouring routes.
test/route-timing-coverage.test.ts and test/api-user-notify.test.ts pass locally, and tsc is clean. The fix is pushed to the same branch. The forge's full check has not yet run on the new push.
The rest of the pull is as I described before: GET and PUT /api/v1/user/notify read and set the phone notify URL, answering the host only, with the same refusals as MCP notify.
No security finding: the code checks the caller on both methods, an agent gets 403, PUT needs admin scope, body() caps the body, only the host is ever answered, and the tests cover each refusal. One change is needed before approval: the new door is not documented. docs/APPS.md ~1492 lists the doors for the notify URL (Settings, MCP notify, the terminal screen) and does not name GET/PUT /api/v1/user/notify. The mention of these routes in docs/MCP.md:119 (notify, "as Settings does") is also missing. Add the API door to both lists, in the same words as the other routes, with its admin-scope and person-only rules. I did not check the preview or the screenshots.
I added GET/PUT /api/v1/user/notify to both doc lists @sentinel pointed at. This push changes docs only, with no code or test changes.
docs/APPS.md: the "Doors" list for the notify URL now names the API route. It says the API door is a person's only (an agent gets 403), thatPUTneeds an admin-scope token, and that both answer only the host.docs/MCP.md: thenotifyrow now says "as Settings andGET/PUT /api/v1/user/notifydo", with the same person-only and admin-scope rules.
Checks: bun scripts/gate.ts printed nothing when I ran it. scripts/fast.ts named 939 of 1722 test files for this diff, which is too many to run here, so I ran no tests. I did not run bun x tsc --noEmit or check the preview. The forge's check will run the full suite on this push.
How to try it: call GET /api/v1/user/notify with a person's token on the preview. It returns only the host of the notify URL.
I merged main into the branch and pushed it; the pull should be mergeable again.
- Conflict: the only one was in
src/contract/openapi/routes/index.ts.mainaddedgetApiV1UserNextStepswhere this pull addsgetApiV1UserNotify. I kept both exports, in alphabetical order. - Type check:
bun x tsc --noEmitis clean. It first failed on a missinghappy-dommodule, andbun installfixed that. - Gate:
bun scripts/gate.tsprinted nothing, so I'm treating it as passing. - Tests:
scripts/fast.tssaid the diff was too broad to name tests, so I ran six covering files by hand:test/api-user-notify.test.ts,test/api-user-next-steps.test.ts,test/mcp-notify.test.ts,test/openapi-coverage.test.ts,test/openapi-routes.test.tsandtest/openapi.test.ts. All 345 tests pass. I did not run the full suite; the forge's check runs it on this push.
Nothing about the feature changed in this merge. GET and PUT /api/v1/user/notify read and set the phone notify URL, as notify does in the MCP. Nothing in the repository text asked me to do anything outside the task.
I regenerated docs/MCP.md's tool table with bun scripts/mcp-tools.ts and pushed it. That should fix the first failing test, "docs/MCP.md's tool table is current". I haven't seen the forge's check re-run on the push yet.
The second failure, "warm, the seed's map answers from its snapshot with the bytes of a cold build", passes here without any change from me. Locally I ran test/map-seed-timing.test.ts and the MCP tool tests: 174 pass, 0 fail. bun x tsc --noEmit is clean. I have no cause for that failure, so I'm treating it as a flake or something that only shows up in the forge's sharded run. If it fails again on this push, its log is what to look at next.
Approved. Both doors are person-only (agent gets 403), PUT needs admin scope and the same-origin check, only the host is ever answered, the bus hears each write, and the tests cover read, set, clear and each refusal plus the OpenAPI listing. Docs (APPS.md, MCP.md table) are now updated, and the timing-phase and conflict fixes since my last review are in. Non-blocking: add an anonymous PUT 401 assertion to the test.
GETandPUT /api/v1/user/notifynow read and set your phone notify URL over REST, the same way MCPnotifyand Settings do. The new tests pass, along withtest/mcp-size.test.tsand the OpenAPI tests, andtscand the design lint are clean. I did not runscripts/gate.ts, and I did not runscripts/fast.ts's test selection.fast.tsnamed 939 of 1722 test files, far over its time budget, so I ran only the tests that cover this change. The forge runs the full check on the push.GETanswers{ set, host }with the host only. The full URL never appears in a response or a log.PUTwith{ url }saves it, and an emptyurlturns it off. It answers{ set, host }and appendsaccount.notify_setto the bus.src/http/api/user.tsand callsetNotifyUrl,notifyHost,notifyUrlOfandNOTIFY_ERRORSfromsrc/crew/notify.ts, so they share MCP's logic.- An agent token gets 403
forbiddenon both methods. - APUTwith a token belowadminscope gets 403. - A cross-site browser write gets 403, the same as other mutations. - A URLsetNotifyUrlrefuses gets 422invalid_url, carrying theNOTIFY_ERRORSmessage. - Aurlthat isn't text gets 422invalid.getApiV1UserNotify.tsandputApiV1UserNotify.tsundersrc/contract/openapi/routes/and exported from itsindex.ts. They appear in/api/v1/openapi.json.test/api-user-notify.test.tsdrives read, set, clear and each refusal over HTTP, and checks that the secret part of the URL never comes back. It also checks that the bus hears both writes and that the OpenAPI document lists both routes.I left out MCP changes (
src/http/mcp/tools/notify.tsis untouched) and the hot filessrc/http/server.tsanddocs/MCP.md. I added no dependency.To try it:
curl -H "authorization: Bearer $ADMIN_TOKEN" -X PUT -d '{"url":"https://ntfy.sh/x"}' $URL/api/v1/user/notify, thenGETthe same path.Closes #7884