Prufy notes
The app ยท 7 min

Setup links

The person claiming a domain often isn't the one who can change its DNS. A setup link hands that one job to a teammate, an agency or an AI agent, and nothing else.

A link as a capability

A session answers "who are you?", and then the server decides what you may do. A skips the first question. Holding the link is the permission, so the link has to be safe in anyone's hands. That's done by making it open as little as possible.

What each credential opensSwitch who's holding it
See the record to add
GET /api/setup/<token>
Can
See the status and the last check
GET /api/setup/<token>
Can
Ask for a check
POST /api/setup/<token>/checks
Can
See the activity log
Not in the response
Can't
Get a new code
No such route
Can't
Stop the claim
No such route
Can't
Make another link
No such route
Can't
See the owner's other domains
An empty list, like any stranger
Can't
Change where alerts go
401
Can't
A setup response is { view, zoneApex, lastCheck }. The owner's adds nameservers, domainConnect and events. Both are in apps/web/lib/contract.ts.
Predict it first
A helper thinks the code looks wrong and wants a fresh one. What can the link do about it?

From the owner's click to the helper's check

Handing over one jobStep through it
Owner
Prufy
Postgres
Helper
Step 1 of 11
The owner asks for a link
The body is optional: an email to send it to, and a note. withAccount and loadOwnedClaim run first, so only the claim's owner can make one. The claim has to be pending or at risk, the two states where there's a record to add.
apps/web/lib/server/share-service.ts

What keeps it safe to pass around

PropertyWhere
Random, and stored only as a hashSame as a session: 32 bytes, SHA-256 in claim_shares.token_hash. A test checks the token never appears in the table.
It expires30 days, from SHARE_TTL_MS. Long enough to forward, short enough to stop mattering.
It stays out of search and referrersThe /setup/<token> page sets robots to noindex and referrer to no-referrer, because the URL itself is the credential.
The note is textCapped at 280 characters by the contract and rendered as text in the email, so it can't carry markup.
The page says what it isn'tIt opens by explaining that someone asked for help, that the website and email keep working, and that Prufy never asks for a password.

The same link, for an AI agent

The fourth way to add the record is "Let an AI agent do it". It makes the same kind of link and wraps it in a message to paste into an assistant, written by agentMessage in components/claim/methods.tsx. The message gives the setup page, the same details as JSON at /api/setup/<token>, and four instructions: show what you'll add and wait for an OK, don't touch other records, never ask for passwords, and stop once the domain is verified.

GET  /api/setup/<token>          // the record, the steps, the status
POST /api/setup/<token>/checks   // ask for a check
// This link can't do anything else.

What's tested

apps/web/lib/server/alerts-and-shares.test.ts covers it through the real handlers: a link works with no session, the helper's check verifies the owner's claim, the two share one cooldown, the link stops after 30 days, only the hash is stored, the first open is logged once, another account can't make a link for a claim that isn't theirs, and a verified domain needs no link. The decision is in docs/decisions/0009-alerts-and-setup-links.md.