Skip to main content
There are two ways to attach media to a post. Pick by size.
Default to the streaming upload. Base64 inflates a payload by about a third and the whole thing has to be held in memory at once, so it stops working well before the documented size limits.

Streaming upload

Send the raw bytes as the request body with the file’s own Content-Type (image/* or video/*). Not multipart, not base64: the bytes themselves.
Response
Pass the whole handle object (both fields) in uploadedHandles when you create the post:
preview.r2Url is a real URL you can show in a composer before the post exists. It is a staging location, not the published permalink.

Inline base64

For small images, skip the upload round trip:
Send the base64 payload only, with no data:image/jpeg;base64, prefix.

Carousels

Order is the order you supply. Sources are concatenated as uploadedHandles then mediaItems, so don’t split one carousel across both unless you want that ordering.

Video covers

Pass coverImage to choose a video’s thumbnail. Same base64 shape as mediaItems:

Validation

The item count is checked for every post before it is accepted (for example, an Instagram carousel takes 2 to 10 items). Inline mediaItems are also checked against the target platform and mediaType (dimensions, duration, and file size). A rejection comes back as 400 naming the offending item:
Streamed files are not inspected when you create the post. If the platform rejects one, the post moves to failed and errorMessage says why.

Removing media from a post

While a post is still editable, drop one item without touching the rest:
mediaId comes from the media[] array on the post. This removes the attachment and deletes its stored file when the file belongs to that post. An attachment that references the asset library is removed, but the library file stays. For a clip pipeline that removes files after publication, see Media retention and cleanup.