Morffeus Docs
ENSR morffeus.com

Media library

The media library holds your app's images, videos and documents, each at a public URL. Products, variants, store locations and pages point at those URLs. Use these endpoints to find files, keep them organised, swap a file for a new version without breaking the places that use it, and clean up.

How the library works#

The file object#

What the list endpoints return for each file.

FieldDescription
idintegerOur id of the file.
filenamestringThe name shown in the library.
keystringThe file's path in your storage. Delete calls take it.
urlstringThe public URL. After a replace it ends in ?v= and the version number.
folderstring · nullableThe folder's key, or null for the top level.
sizeintegerSize in bytes.
mime, width, heightnullableType and pixel size, once processed.
alt, captionstring · nullableAlternative text and caption for the storefront.
tagsarray of stringsYour tags.
descriptionstring · nullableFree text given at upload.
versioninteger1, plus one for every replace.
renditionsarray of objectsThe smaller copies: label, width, height, size, key, url.
usage_countintegerHow many places use the file.
hidden, hidden_by, hidden_at, hidden_noteWhether the file is hidden, and who hid it, when and why.
sourcestring · nullableWhere the file came from, such as admin or app.
last_modified, inserted_atdatetime · ISO 8601, UTCWhen the file was last written and first added. Sent without an offset.

Browse files#

GET /api/v2/cdn/files

The files of your library, a page at a time, with filters for the clean-ups you do most: unused files, images without alternative text, large files.

Query parameters

ParameterDescription
folderstringOnly the files directly in this folder, by its key. / is the top level. Leave it out for all files.
searchstringMatches the name, alternative text, caption and tags.
kindstringimage, vector, video, audio or doc.
unusedbooleanOnly files that nothing uses and that are older than the app's unused threshold.
noaltbooleanOnly images without alternative text.
min_sizeintegerOnly files of at least this many bytes.
since_daysintegerOnly files written in the last so many days.
hidden, hidden_onlybooleanInclude hidden files, or list only them.
orderBystring · default name ascname, size, inserted_at or last_modified, followed by asc or desc.
pageNointeger · default 1The page, starting at 1.
pageSizeinteger · default 30Files per page.

Returns

data.files: the page of file objects. data.total_count: how many files match. data.hidden_count: how many of them are hidden. Unlike most lists, this one tells you the total.

Two related reads: GET /cdn/folders returns the folder tree, each folder with id, name, key, parent_cdn_folder_path, file_count, total_file_count (with subfolders) and its folders; pass folder to start below one. GET /cdn/files/summary returns file counts per kind and storage used, in bytes.

GET/cdn/files
curl -G "https://api.morffeus.com/api/v2/cdn/files" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  -d "folder=products" -d "kind=image" \
  -d "noalt=true" -d "pageSize=50"
Response200 OK
{
  "data": {
    "files": [
      {
        "id": 7731,
        "filename": "dog-food.jpg",
        "key": "12-34/products/dog-food.jpg",
        "url": "https://cdn.example.com/12-34/products/dog-food.jpg",
        "folder": "products",
        "size": 84210,
        "mime": "image/webp",
        "width": 1600,
        "height": 1600,
        "alt": null,
        "tags": [],
        "version": 1,
        "usage_count": 2,
        "hidden": false,
        "renditions": [
          {
            "label": "w800",
            "width": 800,
            "height": 800,
            "url": "https://cdn.example.com/12-34/products/[email protected]",
            …
          }
        ],
        …
      }
    ],
    "total_count": 138,
    "hidden_count": 0
  }
}

Edit, move and file#

PATCH /api/v2/cdn/file

Changes a file's text and visibility. None of these calls changes a file's key or URL.

CallDescription
PATCH /cdn/fileBody: id (required) and any of alt, caption, tags (a list, or a comma-separated string), name (the name shown, which must be free in its folder), hidden and hidden_note. hidden: false keeps the file visible from then on. Returns the file, without url.
PATCH /cdn/files/moveBody: ids, a list of file ids, and folder, the target folder's key ("" or / for the top level). Missing folders are created. Returns one result per id, with success and message.
POST /cdn_foldersCreates a folder from {"cdn_folder": {…}} with name and key (both required), parent_cdn_folder_path, description. Returns 201.
PATCH /cdn/folderRenames or moves a folder. Body: id (required), name, parent_folder (the new parent's key, "" for the top level), description. The files inside move with it.

Errors

StatusWhen
404No such file or folder in your app: API.CdnFiles.FileNotFound, API.CdnFolders.FolderNotFound.
422The name or key is taken: API.CdnFiles.KeyAlreadyExists, API.CdnFolders.KeyAlreadyExists. The new parent does not exist or is inside the folder: API.CdnFolders.ParentNotFound, API.CdnFolders.InvalidParent.
PATCH/cdn/file
curl -X PATCH "https://api.morffeus.com/api/v2/cdn/file" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": 7731,
    "alt": "Dog food, 2 kg bag",
    "tags": ["dog", "food"]
  }'
Error404 Not Found
{
  "errors": {
    "CdnFiles": ["API.CdnFiles.FileNotFound"]
  }
}

Replace a file#

POST /api/v2/cdn/files/{id}/replace

Puts new content under the same key, so every product and page that uses the file shows the new version. The previous content is kept as a version you can go back to. The file's alternative text, caption, tags and visibility stay as they are, and its renditions are rebuilt.

Body parameters, as multipart form data

ParameterDescription
filerequiredfileThe new content.
cache_strategyrequiredstringHow browsers and the CDN get the new version. version: the URL we return ends in ?v= and the version; use it where you show the file. purge: we clear the CDN's copies of the file and its renditions, and cache.purged says whether it worked; some setups do not support it. ttl: nothing is cleared, and caches pick up the change when their copy expires.

Returns

data: the file, with its new version. cache: the strategy, the url to use, and purged for the purge strategy.

Related callDescription
POST /cdn/files/{id}/revertGoes back to a kept version. Body: cache_strategy (required) and version (default: the latest kept one). Kept versions expire on the Trash schedule.
POST /cdn/files/{id}/renditions/regenerateRebuilds the file's renditions, optionally with new widths in renditions, for example [1600, 800, 400]. Returns 202: the work runs in the background.

Errors

StatusWhen
400No file (API.CdnFiles.FileRequired), an unknown strategy (API.CdnFiles.UnknownCacheStrategy), purge where it is not available (API.CdnFiles.PurgeNotConfigured), or a file in Trash (API.CdnFiles.FileInTrash).
404No such file in your app, or no version to revert to (API.CdnFiles.NoVersionToRevert).
POST/cdn/files/{id}/replace
curl -X POST "https://api.morffeus.com/api/v2/cdn/files/7731/replace" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  -F "file=@dog-food_new.jpg" \
  -F "cache_strategy=version"
Response200 OK
{
  "data": {
    "id": 7731,
    "key": "12-34/products/dog-food.jpg",
    "version": 2,
    …
  },
  "cache": {
    "strategy": "version",
    "url": "https://cdn.example.com/12-34/products/dog-food.jpg?v=2"
  }
}

Where a file is used#

GET /api/v2/cdn/files/{id}/usages

Every place that stores the file's URL. Check it before you replace or delete a file.

Query parameters

ParameterDescription
offset, limitinteger · default 0, 30Paging.

Returns

data: one entry per use, with entity_type and entity_id (for example product and its id), field (such as images or image_url), label (a readable name), url as stored, and total_count, the number of uses. A file that is not in your app returns an empty list.

GET /cdn/usages/broken lists the opposite: stored URLs that point at a file the library no longer has, with the same fields and a search parameter. Files in Trash do not count as broken.

Response200 OK
{
  "data": [
    {
      "entity_type": "product",
      "entity_id": 48377,
      "field": "images",
      "label": "Dog food",
      "url": "https://cdn.example.com/12-34/products/dog-food.jpg",
      "total_count": 2
    },
    {
      "entity_type": "product_variant",
      "entity_id": 91120,
      "field": "image_url",
      …
    }
  ]
}

Delete, restore and purge#

DELETE /api/v2/cdn

Moves a file to Trash. Its URL keeps working until the file is purged, so nothing breaks at once. Delete calls take the file's key; restore and purge take its id. Send the inputs of DELETE calls in the query string: some HTTP clients drop the body of a DELETE.

CallDescription
DELETE /cdn?file=One file to Trash, by its key. A file already in Trash also returns 200, and its Trash time is not reset.
DELETE /cdn/listSeveral files: files, a list of {"key": …}. Returns one result per file, with success and message.
DELETE /cdn/folder?folder=A folder by its key: every file in it and its subfolders goes to Trash, and the folders are removed. Returns deleted_files and deleted_folders.
GET /cdn/trashThe files in Trash, each with deleted_at, deleted_by and purge_at, and a total_count. Parameters: search, page_no, page_size (default 30), order_by (default deleted_at desc). Note the underscores, unlike GET /cdn/files.
POST /cdn/files/{id}/restoreTakes one file out of Trash, back into its folder. POST /cdn/files/restore with ids does several.
DELETE /cdn/files/{id}/purgeRemoves a file in Trash for good, with its renditions and kept versions. DELETE /cdn/files/purge with ids does several. Pages that still use the URL then show up in GET /cdn/usages/broken.

Your app can be set to refuse deleting files that are in use; the delete then fails with API.CdnFiles.FileReferenced.

Errors

StatusWhen
400A purge of a file that is not in Trash: API.CdnFiles.FileNotInTrash. A delete of a file in use, where your app refuses it: API.CdnFiles.FileReferenced.
404No such file in your app: API.CdnFiles.FileNotFound.
422DELETE /cdn/folder without a folder: API.CdnFolders.FolderRequired.
DELETE/cdn
curl -X DELETE -G "https://api.morffeus.com/api/v2/cdn" \
  -H "Authorization: Bearer $MORFFEUS_TOKEN" \
  --data-urlencode "file=12-34/products/dog_food_old.jpg"
Response200 OK
{
  "data": { "message": "File deleted", "success": true }
}
Error400 Bad Request
{
  "errors": {
    "CdnFiles": ["API.CdnFiles.FileNotInTrash"]
  }
}

Images on products and locations#

Products, variants and store locations store image URLs. To use a library file, send its url as image_url in the product's or variant's images with POST /int/products. We store the URL as you send it and do not download it, so a URL from outside the library works too, but it is not tracked. A library URL counts as a usage of the file straight away.

Two more reads help here. GET /cdn_upload_presets lists your app's upload presets, with the output format, maximum width, quality and rendition widths each applies; the default one has is_default: true. GET /cdn/files/pdf-renders?name= finds PDFs whose pages were rendered as images, with the URL of each page.

Last updated 1 October 2026 · API v2 Something wrong on this page?