Media
Upload, serve, and manage assets via cms.api.media.
Media methods live under cms.api.media. For the model behind them, see the Media concept.
Uploads
createSignedUpload
Returns presigned PUT URLs; the browser uploads each file straight to the bucket. POST, input via body.
| Body field | Type | Description |
|---|---|---|
files | { name, size, type, variantOf? }[] | Files to upload (at least one). |
folderId | string | Target folder (optional). |
Returns { assets: { id, slug, objectKey, url, signedUrl, headers }[], expiresAt }. New assets are created private. url is the direct object URL the asset will have once uploaded — for internal/admin display (e.g. a media library); serve assets in content through the public delivery gate. signedUrl is the presigned PUT — PUT each file to it with the returned headers (Content-Type, x-amz-acl: public-read).
uploadAssets
Uploads file bytes through the server. POST, input via body. Same shape as createSignedUpload, but each file carries a buffer: Blob | ArrayBuffer. Returns { assets: { id, slug, objectKey, url }[] }.
Assets
listAssets
GET. Query: folderId?, status? ('private' | 'public'), search?, limit? (default 20), offset?, sortBy? ('createdAt' | 'slug' | 'size'), sortOrder?. Returns { assets, total, hasMore }. Each asset includes a direct url (${publicUrl}/${objectKey}) for internal/admin display such as a media library — no presign or publicUrl lookup needed; serve assets in content through the public delivery gate.
updateAssetStatus
POST, body: { assetIds: string[], status: 'private' | 'public' }. Returns { updated }.
archiveAsset
Soft-deletes assets (and their variants). POST, body: { assetIds: string[] }. Returns { archived, archivedIds, skipped }. Assets used by live content are skipped, not failed.
moveAssets
POST, body: { assetIds: string[], folderId: string | null } (null moves to the root). Returns { moved, movedIds, skipped }. Moves assets between folders; non-existent, out-of-scope, archived, and variant ids are skipped (surfaced in skipped), and a moved asset's variants follow it into the same folder (variants are never moved on their own). Throws FOLDER_NOT_FOUND (unknown/out-of-scope target) or ASSET_NOT_FOUND (no live ids).
replaceAsset
POST, body: { assetId: string, file: { name, size, type, buffer } }. Returns { asset: { id, slug, objectKey, url, mimeType, size } }. Swaps the bytes behind an asset while keeping its id (and folderId / status), so every content reference picks up the new image with no content change — the id-addressed gate re-resolves it. A new slug / object key is minted for a clean cache-bust and the asset's old variants are archived (regenerate them afterward). Server-side and atomic. Throws CANNOT_REPLACE_VARIANT (target is itself a variant), ASSET_NOT_FOUND, FILE_TOO_LARGE / INVALID_FILE_TYPE, or UPLOAD_FAILED (asset left unchanged).
getAssetUsages
GET, query: { assetId }. Returns { pageCount, pages: { rootId, collection, slug, occurrences }[] }.
Folders
listFolders
GET, query: { parentId? }. Returns { folders: { id, name, parentId, createdBy, createdAt }[] } — the direct child folders of parentId, or the root-level folders when parentId is omitted, sorted by name. Navigate the tree level by level (pass a folder's id to get its children).
The mutations below manage the tree:
| Method | Input | Returns |
|---|---|---|
createFolder | body: { name, parentFolderId? } | { folder } |
moveFolder | body: { folderId, newParentFolderId? } | { folder } |
deleteFolder | body: { folderId } | { folderId } (fails with FOLDER_HAS_CONTENT) |
Public delivery
GET /media/asset/{id} is a public redirect to the asset's object URL, addressed by the asset id (what content stores) — the way to serve assets in content and on public pages. It honors ?format=webp|jpeg|png, ?w=<width> (resolving a pre-uploaded variant, falling back to the original), and ?download. Private assets return ASSET_ACCESS_DENIED. The redirect is short-cached (max-age=300, not immutable), so swapping the object behind the same id propagates automatically.