POST /workspaces/{workspace}/media_contents: give it a public URL, or send the file itself. The server decides how to finish the upload and tells you with the item’s status. The media library endpoints are available on the Pro and Business plans.
Which way to upload
Both request bodies answer with
data, an array of media items, and a message.
Upload from a URL
The file type is read from the file’s bytes, never from the URL or the
Content-Type the other server sends. JPEG, PNG, GIF, WebP, MP4, and QuickTime (MOV) are accepted. The stored name is the name (or the URL’s file name) as a slug, plus six random characters and the extension, for example hero-k3j9xq.jpg.
Images come back ready
An image of up to 50 MB is downloaded during the request. The response is201 with the item active, ready to attach to a post:
Videos come back processing
A video, or any file the other server announces as larger than 50 MB, is downloaded in the background. The response is202 with the item processing:
GET /media_contents/{mediaContent} with that id until status changes:
active: the file is in the library. Itslinkis final now. While the item wasprocessing,name,full_name, andlinkcould still change.failed: the import did not work.error_messagesays why, for example that the file was not an image or video, was larger than 2 GB, or could not be downloaded. Fix the cause and import the URL again.
active. See Using new media in a post.
Upload files in the request
Send the files asmultipart/form-data, each one in files[]:
Content-Type yourself: curl’s -F sets multipart/form-data with the boundary.
files[]: required, 1 to 10 files per request, up to 200 MB each. Each file’s type is read from its bytes; JPEG, PNG, GIF, WebP, MP4, and QuickTime (MOV) are accepted.labelandfolder_id: optional, and applied to every file in the request.
201 with one active item per file, in the order you sent them, and the message Media uploaded successfully!. Each stored name is the uploaded file name as a slug plus six random characters and the extension.
If one file is not an image or video, nothing is stored. The response is 422 with an error keyed by the file’s position, for example files.1: Unsupported file type: the file is application/pdf, not an image or video. Supported: JPEG, PNG, GIF, WebP, MP4 and QuickTime.
size, width, height, and time are detected in the background and may come back null right after an upload. Fetch the item again a moment later (GET /media_contents/{mediaContent}) to read them.Upload errors
A URL that announces no size and turns out larger than 2 GB while downloading ends as
failed, with the reason in error_message.
Using new media in a post
Attach an item by itsid in the post’s media_ids once its status is active. Creating or updating a post with an item that isn’t ready returns 422:
processing:The file for media <id> has not finished importing. Poll GET /media_contents/<id> until its status is active before using this media.failed:The import of media <id> failed (<reason>). Import or upload it again before using this media.temporary(presigned, not uploaded or confirmed):The file for media <id> has not finished uploading. Upload it and confirm the upload before using this media.
Large files: presign, upload, confirm
For a file over the 200 MB limit of the request body, up to 2 GB, upload the bytes straight to storage in three steps. They never pass through the API:1
Presign
Tell the API which files you want to upload (name, MIME type, size). It pre-creates a
temporary media item for each and returns a short-lived, pre-signed upload URL.2
Upload
PUT each file’s bytes straight to the returned upload_url, replaying the signed headers. This goes to storage, not to the PublishBuddy API.3
Confirm
Tell the API which uploads finished. It verifies each object landed in storage, moves it into the workspace’s library, and marks it
active.1. Presign
name and size (bytes); mime is optional but recommended. You can presign up to 10 files per request.
Response: one entry per file:
id is the temporary media item’s UUID. You’ll pass it back in the confirm step. The upload_url is valid for expires_in seconds (30 minutes). Until you confirm, the item has status temporary and the media list leaves it out unless you ask for status=temporary.
2. Upload the bytes
PUT the file straight to upload_url with the headers the URL was signed with. upload_headers maps each of those header names to a list of values. Send every header except Host, with its list of values joined by commas. Your HTTP client sets Host from upload_url on its own:
api.publishbuddy.com. A 200/204 means the bytes are in place.
3. Confirm
active and ready to attach to posts:
size, width, height, and time may come back null right after confirm. Fetch the item again a moment later to read them.
Confirming is all or nothing. If any ID in media_ids has no file in storage, nothing is confirmed and the API returns 422 with an error for each missing file, keyed by its position (media_ids.0, media_ids.1, and so on), for example The file for media 9b1f2c3d-5a6e-7f8a-9b0c-1d2e3f4a5b6c was not found in storage. Upload it to the upload_url from presign, then confirm again. Upload the missing files and send the same request again: an ID that is already confirmed comes back unchanged. A file larger than 2 GB is deleted at this step and reported the same way.
Presign and confirm both accept up to 10 items per request: presign every file, PUT each one, then confirm them all together with their IDs in media_ids.
Bulk imports
For hundreds or thousands of files, send them in batches of 10 (the limit per request) and keep each file under 200 MB:id of every 202 answer, and poll those items until each one is active or failed. Send files over 200 MB through presign, upload, confirm.
The media_contents endpoint group is on the high-volume tier (1,000 requests per minute per user). Each upload request counts once whatever the number of files, and so do polling requests. With presign, the PUT uploads go to storage and don’t count.
Supported formats and limits
Per-network and per-plan limits may be lower and are enforced at publish time. For very large files (long-form video), upload them once and reuse the same media
id across multiple posts.
Organising with folders
Folders are workspace-internal. They help your team find assets and don’t change anything that gets sent to the social network.GET /workspaces/{workspace}/media_library/folders returns the top level, and the same call with parent_id=<folder-uuid> returns that folder’s children. Deleting a folder (DELETE /media_library/folders/{folder}) also deletes its subfolders and the files inside them, so move files out first if you want to keep them.
To upload straight into a folder, pass its folder_id with the upload. To move a file you already have, update its folder_id:
Filtering existing media
folder_id=root for files that are not in any folder. Combine folder_id with page and per_page (see Pagination) to walk large libraries.
The list shows active items only. Pass status=processing for URL imports still downloading, status=failed for imports that failed, or status=temporary for presigned uploads not confirmed yet:
Replacing a file
The binary attached to a media object is immutable. To “replace” an asset:- Upload the new file (any of the ways above gives you a new media
id). - Update any scheduled (
status: waiting) posts that reference the old ID: set theirmedia_idsto include the new ID instead. - Delete the old media object (optional, since already-published posts hold their own reference).
PUT is intentionally not supported; this keeps the audit trail clean (“the post that went out on Dec 25 used exactly this asset”).