Affinity Design
Agency Guide

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:

  1. The browser requests an upload session from the API
  2. The API generates a presigned PUT URL targeting the client's R2 prefix
  3. The browser uploads the file directly to R2
  4. The browser finalizes the upload with the API
  5. 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

TypeMIME
JPEGimage/jpeg
PNGimage/png
WebPimage/webp
HEICimage/heic
MP4video/mp4

Other file types are rejected at finalization.

Asset Surfaces

Surfaces control where an asset can be used:

SurfaceUsage
websiteWebsite execution jobs, Elementor edits
socialSocial media posts, carousel frames
gbpGoogle 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 featuredAssetRef or assetRefs for 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

IssueResolution
Upload session creation failsVerify R2 env vars are set and the API server has been restarted
Browser PUT returns 403Check R2 API credentials and bucket name
CORS error on browser uploadAdd the frontend origin to the R2 bucket CORS policy
Finalize fails with unsupported typeOnly JPEG, PNG, WebP, HEIC, and MP4 are accepted
Preview returns 404Asset may not have been finalized — check the upload session status

On this page