FabricFabric
Reference

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:

SurfaceRouteTool / UILifecycle
Session shareshttps://agents.fabric.pro/s/<id>"Share session" UIDefault 90-day TTL, refreshable
Design previewshttps://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

WhenRecipient seesSender's "Update share" button does
Before TTLSession loads normallyPUT refreshes the same TTL window
After TTL, before purgeWorker 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 purge404 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. loadStoredSession reads 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/api

The 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 size25 MiB total
Max TTL30 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 domains
  • connect-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/api and DELETE /d/api/<id> require X-Fabric-Publish-Token: <secret>.
  • GET on 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

LayerMechanism
Worker cronDaily sweep at 05:17 UTC purges any bundle past its expiresAt
R2 lifecycle60-day auto-delete on the designs/ prefix in fabric-agents-designs bucket
Opportunistic deleteGET /s/api/<id> on an expired session returns 410 and immediately deletes the R2 key, so the cron has less to do

Operational endpoints

MethodRouteAuth?Purpose
POST/s/apinoneCreate a share. Optional X-Fabric-Ttl-Days header.
GET/s/api/<id>noneFetch share JSON. Returns X-Fabric-Expires-At if TTL set.
PUT/s/api/<id>noneUpdate share content. Refreshes existing TTL window.
DELETE/s/api/<id>noneRevoke a share.
POST/d/apisecret (when configured)Publish a design bundle (multipart).
GET/d/api/<id>noneFetch bundle metadata JSON.
DELETE/d/api/<id>secretRevoke a published design + its comments.
GET/d/api/<id>/commentsnoneList all comments left by reviewers (JSON { comments: [...] }).
POST/d/api/<id>/commentsnoneAppend a comment. Anonymous; rate-limited by per-comment + per-bundle byte caps.
GET/d/<id>/[path]nonePublic 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 DesignCommentRecord with id, createdAt, selector, text, tag, rect, point, note, optional author.
  • 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:

  1. Renders pins for any existing comments on page load.
  2. Adds a floating 💬 Comment button bottom-right.
  3. Click → click element → modal asks for note + optional name → POSTs to the comments endpoint.
  4. 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.pro

Both scripts cover the happy path plus a few negative cases (invalid TTL, never-expires, expired-410).

On this page