The one scoping rule
The proxy (
brain.streamR2File) always takes a workspace-relative key. When you get a key off a feed post or item, it is agent-scoped; resolve it to the workspace-relative form the same way the platform wrapper does (the project’s storage prefix plus the key) before streaming, or route through your own resolution step.
Only allowlisted prefixes are reachable (projects/, workflows/, config/, assets/, meta/, wrapper/, …). Everything else returns 403.
Stream large objects
getR2Bytes; that convenience is for small files like design-tokens.json.
Small text/JSON files
Feed media fields
AFeedPost carries everything you need to render it:
poster— still frame key (always present for video)playable— mp4/webm key, when a transcode existsisVideo— quick branch flagpages— ordered media for carousels and story setswidth/height— for layout before the bytes arrivekind—still|reel|carousel|story|deck(prefer this over legacymediaType)
Serving media to browsers
The proxy is authenticated with the workspace key, so browsers cannot hit it directly. Standard patterns:- Proxy through your backend, forwarding the key server-side.
- Fetch on the server and re-serve from your own storage/CDN.
- Keep the workspace key out of any client bundle. It grants full read of the workspace’s published data.