Asset Management
Client asset library with Cloudflare R2 storage, upload workflows, and surface-level access controls
Overview
The client asset library stores images and videos in Cloudflare R2, organized per client with folder support and surface-level access controls. Assets are used across the platform — SEO blog posts, social media, GBP updates, and website execution jobs.
Setup
Cloudflare R2 must be configured before assets can be uploaded, using the environment variables below.
Required environment variables:
CF_R2_ACCOUNT_ID=your_cloudflare_account_id
CF_R2_ACCESS_KEY_ID=your_r2_access_key_id
CF_R2_SECRET_ACCESS_KEY=your_r2_secret_access_key
CF_R2_BUCKET=affinity-client-assets
A single R2 bucket serves all clients. The backend namespaces objects by client ID automatically.
Upload Workflow
Browser Upload
Assets upload directly from the browser to R2 via presigned URLs:
- The browser requests an upload session from the API
- The API generates a presigned PUT URL targeting the client's R2 prefix
- The browser uploads the file directly to R2
- The browser finalizes the upload with the API
- The API creates the asset record with metadata
Via API
# 1. Create upload session
POST /admin/clients/:clientId/assets/upload-session
{
"originalFilename": "hero-image.png",
"mimeType": "image/png",
"expectedSizeBytes": 245678
}
# 2. PUT the file to the returned uploadUrl (presigned R2 URL)
# 3. Finalize the upload
POST /admin/clients/:clientId/assets/upload-session/finalize
{
"uploadToken": "TOKEN_FROM_SESSION",
"title": "Homepage Hero",
"altText": "Company headquarters exterior",
"tags": ["homepage", "hero"],
"allowedSurfaces": ["website", "social"]
}
Supported File Types
| Type | MIME |
|---|---|
| JPEG | image/jpeg |
| PNG | image/png |
| WebP | image/webp |
| HEIC | image/heic |
| MP4 | video/mp4 |
Other file types are rejected at finalization.
Asset Surfaces
Surfaces control where an asset can be used:
| Surface | Usage |
|---|---|
website | Website execution jobs, Elementor edits |
social | Social media posts, carousel frames |
gbp | Google Business Profile updates |
An empty allowedSurfaces list means the asset is universal — available everywhere.
Folder Organization
Assets can be organized into folders within each client's library:
# Create a folder
POST /admin/clients/:clientId/assets/folders
{
"name": "Blog Images",
"parentId": "OPTIONAL_PARENT_FOLDER_ID"
}
# List assets and folders
GET /admin/clients/:clientId/assets?folderId=FOLDER_ID
Referencing Assets in Agent Workflows
Agents reference assets by assetRef instead of storing duplicate copies:
- SEO blog posts — pass
featuredAssetReforassetRefsfor featured images - Social media — use asset references for post images
- Website execution — reference assets in content change requests
The platform resolves assetRef to the actual R2 object and uploads it to the target system (e.g., WordPress media library) when needed.
Asset Previews
Asset previews are served through authenticated API routes — not public R2 URLs. This keeps client assets private while still allowing the admin and portal UIs to display them.
# Get asset preview
GET /admin/clients/:clientId/assets/:assetId/preview
Troubleshooting
| Issue | Resolution |
|---|---|
| Upload session creation fails | Verify R2 env vars are set and the API server has been restarted |
| Browser PUT returns 403 | Check R2 API credentials and bucket name |
| CORS error on browser upload | Add the frontend origin to the R2 bucket CORS policy |
| Finalize fails with unsupported type | Only JPEG, PNG, WebP, HEIC, and MP4 are accepted |
| Preview returns 404 | Asset may not have been finalized — check the upload session status |
