Skip to content

Asset Library — Backend Spec

Frontend is done (mock data). This spec describes what the backend needs to provide so we can swap mock data for real API calls.

What the frontend does NOT need from backend

Folder tree. The folder hierarchy (Enterprise → Brand → Campaign → Creative Group) is already built in the frontend from existing Vuex data (brands, campaigns, creative groups). No folder endpoints needed.

Data model

assets table

ColumnTypeNotes
idbigint / uuidPrimary key
namestringOriginal filename or user-defined name
typeenumimage, font, video, url
urltext, nullableCDN URL. Null for url-type assets (those store just an alias)
aliasstring, nullableDisplay name for url-type assets (e.g. "Google Sheets - Leads 2026")
thumbnailtext, nullableThumbnail URL for images/videos
workspace_idbigintFK to workspaces — all queries scoped to workspace
scope_levelenumenterprise, brand, campaign, creativeGroup
scope_idstringID of the entity at that scope level
metadatajson, nullableSee below
created_attimestamp
updated_attimestamp

metadata JSON structure

json
// Image
{ "width": 1280, "height": 720, "fileSize": 85000 }

// Font
{ "fontFamily": "Kristiania Display", "fileSize": 34000 }

// Video
{ "width": 1920, "height": 1080, "duration": 45, "fileSize": 8500000 }

// URL
{ "fileSize": null }

Endpoints

1. List assets

GET /api/v1/workspaces/:workspaceId/assets

Query params (all optional):

  • type — filter by asset type (image, font, video, url)
  • scope_level — filter by scope level
  • scope_id — filter by specific scope entity

Response:

json
{
  "data": [
    {
      "id": "asset-123",
      "name": "campus-photo.jpg",
      "type": "image",
      "url": "https://delivery-6.cavai.com/assets/...",
      "alias": null,
      "thumbnail": "https://delivery-6.cavai.com/assets/.../thumb.jpg",
      "scopeLevel": "brand",
      "scopeId": "42",
      "metadata": { "width": 1280, "height": 720, "fileSize": 85000 },
      "createdAt": "2026-03-01T12:00:00Z",
      "updatedAt": "2026-03-01T12:00:00Z"
    }
  ]
}

Frontend maps scopeLevel+scopeId to full scope info (name, level) using its own Vuex data.

2. Upload asset

POST /api/v1/workspaces/:workspaceId/assets
Content-Type: multipart/form-data

Body:

  • file — the file (image, font, video)
  • typeimage | font | video
  • scope_levelenterprise | brand | campaign | creativeGroup
  • scope_id — ID of entity at that scope level

Response: single asset object (same shape as list items)

Notes:

  • Existing CreativeAssetsController.store() already handles image/font validation and Drive upload — this can be extended
  • Thumbnail generation for images: resize to ~200px width at upload time
  • Video uploads: can use existing Bunny integration (getBunnyVideoUploadToken)

3. Create URL-type asset

POST /api/v1/workspaces/:workspaceId/assets/url
Content-Type: application/json

Body:

json
{
  "alias": "Leads spreadsheet 2026",
  "url": "https://docs.google.com/spreadsheets/d/...",
  "scope_level": "campaign",
  "scope_id": "15"
}

Response: single asset object

4. Update asset (rename / move)

PATCH /api/v1/assets/:assetId
Content-Type: application/json

Body (all optional):

json
{
  "name": "new-name.jpg",
  "scope_level": "campaign",
  "scope_id": "15"
}

Response: updated asset object

5. Delete asset

DELETE /api/v1/assets/:assetId

Response: 204 No Content

Should also delete the file from storage (Drive / Bunny).

What already exists that can be reused

ExistingReuse for
CreativeAssetsController.store()File validation (types, max size), Drive upload logic
Drive config (local + S3 disks)Same storage backend
DELIVERY_URLSame base URL for serving assets
Bunny.getUploadToken()Video asset uploads
@adonisjs/attachment-liteOptional: thumbnail handling
Auth middlewareSame auth on all new routes
workspace_id scopingSame pattern as brands, campaigns, etc.

Frontend integration

Only 3 Vuex actions need to change (in store/modules/assetLibrary.ts):

  1. fetchAssetLibrary — swap mock data for GET /workspaces/:id/assets
  2. uploadAssetToLibrary — swap URL.createObjectURL() for POST /workspaces/:id/assets
  3. deleteAssetFromLibrary — add DELETE /assets/:id call

Plus two new actions for rename (PATCH) and move (PATCH).

The folder tree, filtering, breadcrumb nav, grid/list views, and picker mode are all done and don't depend on backend.

Storage considerations

Assets should go to Bunny CDN (aligning with the ongoing migration). The url field in the response should be the final CDN URL that the creative engine can use directly — same pattern as existing image uploads.

Not needed for v1

  • Asset usage tracking (which creatives use an asset)
  • Bulk operations (multi-select, bulk move/delete)
  • Tagging / search by metadata
  • Asset versioning

These can be added later without schema changes (usage tracking would need a join table).

Internal documentation