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/Shortsis the folderShortsinsideChannel 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, soChannel A//Shortsis refused with400. 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:
Shortsandshortsare the same name. Using a path that differs only in capitals from an existing folder is refused with409 media_folder_exists, which names the existing folder, so use its exact spelling. - A file with
folderPath: nullsits 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.
| Query | Returns |
|---|---|
No folderPath | The whole library |
?folderPath=Channel%20A | Files directly in Channel A |
?folderPath=Channel%20A&includeSubfolders=true | Files in Channel A and every folder under it |
?folderPath= | Files at the top level |
Upload into a folder
- One-request upload: add
folderPathto the query string of Upload media, for examplePOST /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.
- Preview media folder deletion returns the
fileCount, thesubfolderCount, and thekeptCountof files that a draft or scheduled post still uses. - Delete media folder takes the same
pathand thatfileCountasexpectedFileCount.
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
| SDK | CLI | MCP tool | |
|---|---|---|---|
| List folders | media.listFolders() | pq media folders | publishq_list_media_folders |
| Files in a folder | media.list({ folderPath }) | pq media list --folder <path> | publishq_list_media |
| Upload into a folder | media.upload({ file }, { folderPath }) | pq media upload --file <path> --folder <path> | publishq_upload_media with folderPath |
| Move files | media.move({ ids, folderPath }) | pq media move <ids..> --folder <path> | publishq_move_media |
| Rename | media.renameFolder({ path, name }) | pq media folders rename <path> <name> | publishq_rename_media_folder |
| Preview a deletion | media.previewFolderDeletion(path) | pq media folders preview-delete <path> | publishq_preview_media_folder_deletion |
| Delete | media.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.
Media uploads
Upload images and videos to PublishQ and attach them to your posts. Send the file in one request, or straight to storage for files up to 4 GB.
MCP Server
Run the PublishQ MCP server so any MCP client can list your connected accounts, upload media, and schedule posts across your social platforms.