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. 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 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