Share Links & Design Publishing
How session share links (/s/) and design publish links (/d/) work — TTL, expiry, automatic re-share, and the Cloudflare worker endpoints behind them.
Fabric Agents publishes two kinds of public URLs from inside the desktop app:
| Surface | Route | Tool / UI | Lifecycle |
|---|---|---|---|
| Session shares | https://agents.fabric.pro/s/<id> | "Share session" UI | Default 90-day TTL, refreshable |
| Design previews | https://agents.fabric.pro/d/<id>/ | publish_design tool (design skill) | Default 7-day TTL, max 30 |
Both run on the same Cloudflare worker, in the same account, with the same credentials. R2 buckets hold the content; a daily cron purges expired entries.
Session shares (/s/)
The "Share session" button uploads the current session JSON via POST /s/api. The response now includes expiresAt so callers can show a countdown.
Pre-existing share links keep working forever — they were created before TTL and have no expiry metadata. New shares default to a 90-day TTL, configurable per-share via the X-Fabric-Ttl-Days header (1 to 365, or 0/never to opt out).
What happens at expiry
| When | Recipient sees | Sender's "Update share" button does |
|---|---|---|
| Before TTL | Session loads normally | PUT refreshes the same TTL window |
| After TTL, before purge | Worker returns 410 Gone, viewer SPA shows "this link has expired — ask the sender to re-share" | PUT 410 → SessionManager falls back to a fresh shareToViewer POST, returns recreated: true |
| After cron purge | 404 Not Found, viewer shows "session not found" | Same auto-recreate as above |
The "self-healing" flow: clicking "Update share" on an expired session always ends with a working URL. The session's stored sharedId and sharedUrl are silently replaced. The recreated: true flag flows through ShareResult so the renderer can toast "Previous link expired — created a new one".
Caveats
- Old URLs are permanently dead. Re-sharing creates a new id; we don't reuse the previous one (otherwise revocation would be meaningless).
- Recipients must be re-told. Anyone with the expired URL needs the new one.
- Session content is preserved locally.
loadStoredSessionreads from disk, so re-sharing is always possible even after the cloud bundle was purged.
Manual TTL override (advanced)
You can post directly to the worker if you need a custom TTL outside the desktop app:
# 1-day TTL
curl -X POST -H 'content-type: application/json' \
-H 'x-fabric-ttl-days: 1' \
--data '{"kind":"manual"}' \
https://agents.fabric.pro/s/api
# Never expires
curl -X POST -H 'content-type: application/json' \
-H 'x-fabric-ttl-days: never' \
--data '{"kind":"manual"}' \
https://agents.fabric.pro/s/apiThe response includes expiresAt (epoch ms or null for never).
Design publishing (/d/)
publish_design from inside a design-mode chat uploads a bundle directory (HTML + assets) via multipart POST /d/api. The response gives you a public URL the recipient can open in any browser.
publish_design({
bundleDir: "/abs/path/.../design-bundles/landing-v1",
entry: "index.html", // optional
ttlDays: 7 // optional, default 7, max 30
})Limits:
| Max bundle size | 25 MiB total |
| Max TTL | 30 days |
| Required path | <dataPath>/design-bundles/... (publish gate) |
The viewer /d/<id>/<path> serves files directly from R2 with a strict CSP:
default-src 'self'- inline scripts/styles allowed (designs need them), no third-party scripts
frame-ancestors 'self' https://*.fabric.pro— only embeddable on Fabric domainsconnect-src https:— designs can fetch from any HTTPS API but not file://, ws://, etc.
Worker auth (operator)
Both write surfaces support an optional shared secret. Set the worker secret:
npx wrangler secret put PUBLISH_TOKEN \
--config cloudflare/agents-worker/wrangler.toml…and the same value as FABRIC_PUBLISH_TOKEN in the desktop app's environment. When set:
POST /d/apiandDELETE /d/api/<id>requireX-Fabric-Publish-Token: <secret>.GETon viewers stays anonymous so shared URLs work without credentials.
When unset, the write endpoints are open to the public Internet — fine for early development, not for production.
Backstops
| Layer | Mechanism |
|---|---|
| Worker cron | Daily sweep at 05:17 UTC purges any bundle past its expiresAt |
| R2 lifecycle | 60-day auto-delete on the designs/ prefix in fabric-agents-designs bucket |
| Opportunistic delete | GET /s/api/<id> on an expired session returns 410 and immediately deletes the R2 key, so the cron has less to do |
Operational endpoints
| Method | Route | Auth? | Purpose |
|---|---|---|---|
POST | /s/api | none | Create a share. Optional X-Fabric-Ttl-Days header. |
GET | /s/api/<id> | none | Fetch share JSON. Returns X-Fabric-Expires-At if TTL set. |
PUT | /s/api/<id> | none | Update share content. Refreshes existing TTL window. |
DELETE | /s/api/<id> | none | Revoke a share. |
POST | /d/api | secret (when configured) | Publish a design bundle (multipart). |
GET | /d/api/<id> | none | Fetch bundle metadata JSON. |
DELETE | /d/api/<id> | secret | Revoke a published design + its comments. |
GET | /d/api/<id>/comments | none | List all comments left by reviewers (JSON { comments: [...] }). |
POST | /d/api/<id>/comments | none | Append a comment. Anonymous; rate-limited by per-comment + per-bundle byte caps. |
GET | /d/<id>/[path] | none | Public viewer (entry HTML or specific asset). Injects the comment-collection bridge script. |
Collaborative comments
The /d/api/<id>/comments endpoints power the Design Collaboration loop. Storage:
- One JSONL object per bundle at R2 key
designs/<id>/_comments.jsonl. - Each line is a
DesignCommentRecordwithid,createdAt,selector,text,tag,rect,point,note, optionalauthor. - Limits: 4 KB per comment, 256 KB total per bundle (roughly 64 typical comments). Beyond the cap, POST returns 413.
- Control characters are stripped from every text field server-side to keep JSONL safe and prevent injection.
- Comments survive only as long as the bundle.
DELETE /d/api/<id>deletes the bundle, all its assets, and all comments together.
The published HTML viewer auto-injects a small bridge script that:
- Renders pins for any existing comments on page load.
- Adds a floating 💬 Comment button bottom-right.
- Click → click element → modal asks for note + optional name → POSTs to the comments endpoint.
- Polls every 30 seconds for new comments left by other reviewers.
The desktop site-preview block does the same poll on its end so the designer sees reviewer pins (in amber) within ~30s.
Smoke tests
# Session share + TTL
bash scripts/cloudflare-verify-share-links.sh https://agents.fabric.pro
# Design publish
bash scripts/cloudflare-verify-design-links.sh https://agents.fabric.proBoth scripts cover the happy path plus a few negative cases (invalid TTL, never-expires, expired-410).