Modulify

Move or rename stored files

move_storage_files

Moves files and folders to another folder in a site's storage bucket, or renames a single file.

POST /v1/move_storage_filesScopedata:writeMakes changes

To rename a single file, pass exactly one file key and a destination ending in the new filename rather than a slash. That destination is the file's whole new key, so keys of ["notes/a.txt"] with a destination of archive renames the file to archive at the top level instead of moving it into a folder, which is why a folder destination should always end in a slash. A destination ending in a slash always means a folder to move into, and a folder is never renamed, only moved.

A move never changes visibility: a private file stays private and a public one stays public wherever it lands, so use set_storage_visibility for that. Each key is copied to the destination and the original then removed, so any published page or CDN link pointing at the old key stops working immediately.

The response reports how many objects were moved, skipped, failed and missing, and in tooLarge how many folders the per call limit left where they were. It also carries destinationVisibility, the default of the folder the files landed in, with publicItems and privateItems counting the moved keys on each side, and when publicItems is above zero and destinationVisibility is private, the tool tells the client to tell you those files are still public.

One call moves at most 5,000 objects. The files it names count toward that first, wherever they sit in the list, so a folder that would take the call past it is left where it is and counted in tooLarge, before any of its files move. Move that folder in a separate call, or what is inside it in smaller groups.

Request

Call it with a POST to https://api.modulify.ai/v1/move_storage_files, sending the inputs below as a JSON object. The token needs the data:write scope.

It makes changes, so send an Idempotency-Key header whenever you might retry it. A retry with the same key gets the first answer back instead of running again.

curl -X POST https://api.modulify.ai/v1/move_storage_files \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"PROJECT_ID","keys":[]}'

Over MCP, the same method is the move_storage_files tool.

Skipped and refused moves

A key is skipped when something already sits at the destination, when it is already there, when another key in the same call already claimed that destination, when a folder would be moved inside itself, or when the destination name is the reserved .modulify-keep. A failed key means the copy errored and the original was left alone, and a missing key no longer exists, so there was nothing to move.

A destination longer than 1016 bytes, or one whose folder path is longer than 1001 bytes, is refused outright, before anything moves. So is any call that would leave a file named put, get, list, delete, create-folder, delete-folder, move, set-visibility or signed-url, or a file whose key ends in the two segments email/send, email/quota, email/email, email/emails, analytics/status, analytics/realtime, analytics/stats, analytics/timeseries, analytics/breakdown, analytics/overview or analytics/export, in any case, because the CDN routes those paths to the storage, email and analytics APIs. That applies to a file moved into a folder as much as to a rename, so moving a file named send into email/ is refused too.

Folder keys are exempt from those names, since a folder keeps its own name and so every key inside it keeps its last two segments. A folder is skipped, with nothing inside it moved, when its new path or the path of any folder inside it would be longer than 1001 bytes, or any key inside it would end up longer than 1016 bytes.

A key inside the CMS folder at the top level, or a move into that folder, is refused with a 403 and The CMS folder holds the files of your CMS items, so it cannot be changed from storage. Add files from a CMS field, and they are deleted with their item! The CMS manages that folder, and a CMS file is deleted with the item that uses it. See The CMS folder.

While a storage backup is being restored, the call is refused with a 409 and Your storage is being restored from a backup. Try again when the restore finishes! See While a restore runs.

Inputs

Input Type Required Description
projectId string Yes The site id.
keys array of strings Yes The full keys to move, from list_storage_files, at most 200. A key ending in a slash moves the whole folder.
destination string No The folder to move them into, ending in a slash, such as archive/, or an empty string for the top level of the bucket. With exactly one file key, a destination that does not end in a slash is read as that file's whole new key, so always end a folder destination with a slash.

Response

Every call answers with the JSON envelope of success, message, data, code and version. data holds the result described above, and on a method that returns a total, count carries it. The response headers carry the call's X-Request-Id and what is left of your per-minute budget in X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Errors explains every status code a call can answer with.