Instagram and Threads fetch media from your URL at publish time, so anything behind auth, an expired signed URL, or a private bucket fails the target. For X and LinkedIn, Chirio downloads the bytes and re-uploads them — same requirement.
Not hosting media is deliberate: storage, CDN, scanning and retention policy are their own
product, and you almost certainly already have somewhere to put a file.
Limits
The request-level cap is 10 media items, whatever the platform allows. On top of that:
Platform
Images
Video
Notes
Instagram
1–10, **JPEG only**
1 (single video = Reel)
Media is required; 2+ items publish as a carousel
Threads
0–10
video items allowed
Text-only posts are fine
X
up to 4
1
One video **or** up to 4 images, never both
LinkedIn
up to 10
1
2+ images publish as a multi-image post; one video **or** images
Violating any of these rejects the whole request with 400 invalid_post before anything
publishes, naming the platform and the rule.
Describing the media
A media item carries more than a type and a URL. These describe the media itself, so they sit
on the item rather than the post — which is what lets each image in a carousel have its own
alt text.
A platform with no equivalent leaves these alone rather than refusing the post — that way a
field is not rejected today and accepted the week a platform's API gains it. The exceptions
are cases where the platform does support the idea by another route, and silence would
mislead: LinkedIn refuses thumbOffsetMs pointing at coverUrl, and Instagram refuses a
coverUrl inside a carousel pointing at thumbOffsetMs.Cover images are worth getting right rather than getting accepted. Instagram wants JPEG,
8 MB or less, sRGB, and 9:16 — an off-ratio cover is not rejected, it is cropped to the middle
rectangle, so anything near an edge is lost. See Instagram.
Video takes time
Video does not publish instantly anywhere. The platform processes the file first, and Chirio
waits for it — up to roughly four minutes per item. A video-heavy carousel can therefore take
several minutes in a single call.
Two consequences:
Set a generous client timeout, and always send an Idempotency-Key so a timeout on your
side is safe to retry. See Idempotency.
A slow item can time out and fail that target with media_processing_timeout. Retry just
that target.
Or schedule it: a publishAt post returns immediately and the dispatcher does the waiting.
Ordering
mediaItems order is preserved. For carousels it is the slide order the reader sees — see
Carousels.
Practical checklist
Serve media over HTTPS from a stable, public URL.
Keep the URL alive until the call returns — for video, that can be several minutes.
Use JPEG for Instagram images; PNG and WebP are refused.
Do not mix a video and images in one X or LinkedIn post.
Set a coverUrl on video rather than accepting whatever first frame the platform picks.
Give images an altText — it costs nothing and the people who need it cannot work around
its absence.
If media uploads fail on X while text posts work, the connection predates X media
permission — reconnect the account.