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. Beyond creating a folder, operators can now rename or move a folder, delete one, and download a whole folder as a zip — from the admin Assets tab or the API:
# Create a folder
POST /admin/clients/:clientId/assets/folders
{
"name": "Blog Images",
"parentId": "OPTIONAL_PARENT_FOLDER_ID"
}
# Rename or move a folder
PATCH /admin/clients/:clientId/assets/folders
{
"folderId": "FOLDER_ID",
"name": "NEW_NAME", // optional: rename
"parentId": "NEW_PARENT_ID" // optional: move
}
# Delete a folder
DELETE /admin/clients/:clientId/assets/folders
{
"folderId": "FOLDER_ID"
}
# Download a folder as a zip
GET /admin/clients/:clientId/assets/folders/download?folderId=FOLDER_ID
# List assets and folders
GET /admin/clients/:clientId/assets?folderId=FOLDER_ID
The client portal has the same rename, move, delete, and zip-download controls on its own Assets page, so clients can manage their own folders without asking the agency.
The Social folder and its Social/Posted subfolder are protected — they can't be renamed or moved, from either the admin or the portal side. The Social agent owns their structure to keep posted-content tracking consistent.
Editing Asset Name and Description
Open an asset to view its details, then click the pencil icon next to the title to edit the name and description in place. Save writes through the same API the upload flow uses, and the change appears immediately — no need to close and reopen the dialog. This is available in both the admin Assets tab and the client/user portal, so clients can keep their own asset details up to date without asking the agency.
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 |
