PublishQPublishQ

Media folders

Organize your media library into folders through the API, SDK, CLI or MCP server. List folders, upload into one, move files, rename and delete folders.

Folders organize the media library, for example one folder per channel, client or campaign. A folder is a path such as Channel A/Shorts, with folder names separated by /. The same folders show in the web app, so whatever an agent files away is where the user expects it.

Posts use media IDs, never folders, so moving a file or renaming its folder never changes a post.

Folder paths

  • Folder names are separated by /. Channel A/Shorts is the folder Shorts inside Channel A.
  • Each name is trimmed and is at most 100 characters. Folders nest at most 10 levels deep.
  • A path cannot start or end with / or contain an empty name, so Channel A//Shorts is refused with 400. Invisible and text-direction characters (such as a zero-width space) are refused too, so two folders never look the same.
  • Folder names ignore capitals: Shorts and shorts are the same name. Using a path that differs only in capitals from an existing folder is refused with 409 media_folder_exists, which names the existing folder, so use its exact spelling.
  • A file with folderPath: null sits at the top level of the library.
  • A folder exists while at least one file is in it or under it. You create a folder by uploading or moving a file into a new path.

List folders

curl https://publishq.com/api/v1/media/folders \
  -H "Authorization: Bearer pq_live_..."
{
  "folders": [
    { "path": "Channel A", "name": "Channel A", "fileCount": 5 },
    { "path": "Channel A/Shorts", "name": "Shorts", "fileCount": 3 },
    { "path": "Channel B", "name": "Channel B", "fileCount": 1 }
  ]
}

Every folder is listed, each parent before its subfolders. fileCount counts the files in the folder and in all of its subfolders.

List the files in a folder

Add folderPath to List media. Every media object also carries its folderPath.

QueryReturns
No folderPathThe whole library
?folderPath=Channel%20AFiles directly in Channel A
?folderPath=Channel%20A&includeSubfolders=trueFiles in Channel A and every folder under it
?folderPath=Files at the top level

Upload into a folder

  • One-request upload: add folderPath to the query string of Upload media, for example POST /media?folderPath=Channel%20A.
  • Storage upload: send { "folderPath": "Channel A" } as the body of Complete media upload. A file you never complete lands at the top level when a post first uses it, and you can move it afterwards.

Move files

curl -X POST https://publishq.com/api/v1/media/move \
  -H "Authorization: Bearer pq_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["f6370ca1-1f47-4f68-be72-c1206913b326"], "folderPath": "Channel A/Shorts" }'

Send up to 100 IDs per request. "folderPath": null moves the files to the top level. The response says how many files moved; IDs that are not yours are skipped.

Rename a folder

curl -X POST https://publishq.com/api/v1/media/folders/rename \
  -H "Authorization: Bearer pq_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "path": "Channel A/Shorts", "name": "Reels" }'

Only the last part of the path changes, so this folder becomes Channel A/Reels, and its files and subfolders follow. If no folder has that path, you get 404 media_folder_not_found. If a folder with the new name, in any capitals, already exists at that level, the rename is refused with 409 media_folder_exists instead of merging the two.

Delete a folder

Deleting a folder deletes every file in it and in its subfolders, from the library and from storage. It takes two calls, so nothing is deleted that nobody looked at.

  1. Preview media folder deletion returns the fileCount, the subfolderCount, and the keptCount of files that a draft or scheduled post still uses.
  2. Delete media folder takes the same path and that fileCount as expectedFileCount.
curl "https://publishq.com/api/v1/media/folders/delete-preview?path=Channel%20A" \
  -H "Authorization: Bearer pq_live_..."

curl -X POST https://publishq.com/api/v1/media/folders/delete \
  -H "Authorization: Bearer pq_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "path": "Channel A", "expectedFileCount": 12 }'

A path with no folder answers 404 media_folder_not_found on both calls. If the folder holds a different number of files by the time you delete, nothing is deleted and you get 409 media_folder_changed with the new counts. Files that unpublished posts use are kept, so those posts can still publish, and the folder stays with them in it.

Deleting cannot be undone

An agent should show the preview numbers to the user and wait for a yes before it deletes a folder. To keep some of the files, move them to another folder first.

From the SDK, CLI and MCP server

SDKCLIMCP tool
List foldersmedia.listFolders()pq media folderspublishq_list_media_folders
Files in a foldermedia.list({ folderPath })pq media list --folder <path>publishq_list_media
Upload into a foldermedia.upload({ file }, { folderPath })pq media upload --file <path> --folder <path>publishq_upload_media with folderPath
Move filesmedia.move({ ids, folderPath })pq media move <ids..> --folder <path>publishq_move_media
Renamemedia.renameFolder({ path, name })pq media folders rename <path> <name>publishq_rename_media_folder
Preview a deletionmedia.previewFolderDeletion(path)pq media folders preview-delete <path>publishq_preview_media_folder_deletion
Deletemedia.deleteFolder({ path, expectedFileCount })pq media folders delete <path> --expected-file-count <n>publishq_delete_media_folder

Reading folders needs the media:read scope. Uploading into, moving, renaming and deleting folders needs media:write.

On this page