Docs › API reference › Media
Media API: upload images and video, check limits
Upload images and video, check them against each platform before you post, and edit an image description.
Upload a file once, then attach it to posts by its id in mediaIds. OmniPosts reads the file, records its size and
dimensions, and tells you which platforms would object to it before you send anything.
POST /v1/media
Upload a file
POST /v1/media takes multipart/form-data with these parts:
| Part | Meaning |
|---|---|
file |
Required. The image or video |
altText |
Optional. A description of the image for people who cannot see it |
The answer is 201:
(The platform ids in checks depend on which platforms are open for posting, and change over time.)
curl https://omniposts.dev/v1/media \
-H "Authorization: Bearer omniposts_..." \
-F "file=@bowls.jpg" \
-F "altText=A stack of six hand-thrown bowls on a wooden shelf"
{
"id": "0192a3b4-0000-7000-8000-0000000000b1",
"mime": "image/jpeg",
"bytes": 482113,
"width": 1600,
"height": 1200,
"altText": "A stack of six hand-thrown bowls on a wooden shelf",
"checks": {
"bluesky": [],
"mastodon": []
}
}
Limits
- Types: JPEG, PNG, GIF, WebP images, and MP4 or QuickTime video. OmniPosts reads the file's own bytes to decide the
type. The
Content-Typeyou send is ignored. - Size: up to 50 MB (50,000,000 bytes). An image can be at most 50 megapixels.
- A platform's own limits are stricter and differ. They show up in
checks, and in Providers. - Uploads share a limit of 10 a minute for your organization. See Rate limits.
Errors
| Status and code | Meaning |
|---|---|
400 invalid_request |
The request has no file part |
422 media_rejected |
The file is empty, over 50 MB, over 50 megapixels, not a supported type, or an image that cannot be read. The message says which |
415 unsupported_media_type |
The request was not multipart/form-data |
Upload from a link
If the file is already on the web, send application/json to the same endpoint and OmniPosts downloads it:
The answer is the same 201 media object. The link must be a public https address (port 443 or 8443); private,
internal and loopback addresses are refused, and so is a redirect to one. Up to three redirects are followed. The
50 MB limit and the type rules above apply to what is downloaded, not to what the link claims to be.
| Status and code | Meaning |
|---|---|
400 invalid_request |
The body has no url |
422 media_rejected |
The link is not a public https address, could not be downloaded, answered an error, redirected too often, is over 50 MB, or is not a supported type. The message says which |
curl https://omniposts.dev/v1/media \
-H "Authorization: Bearer omniposts_..." \
-H "Content-Type: application/json" \
-d '{ "url": "https://cdn.example.com/bowls.jpg", "altText": "Six bowls on a shelf" }'
GET /v1/media
List your media
GET /v1/media returns your files, newest first, as { "items": [...], "nextCursor": ... }. Each item is the
media object above. Pass limit (default 25, at most 100) and the previous page's nextCursor as cursor. See
Pagination.
DELETE /v1/media/{id}
Delete a file
DELETE /v1/media/{id} removes the file. The answer is 204.
404 not_foundfor an id that is not yours.409 media_in_usewhile a draft, or a post that has not finished (anything but published or cancelled), still names the file. Remove it from the draft, or cancel the post or wait for it to finish, then delete again. A published post is not affected by a delete: the platform has its own copy.
Preflight verdicts: checks
checks is a map from provider id to a list of objections that platform has to this file. It has an entry for
each platform that is open for posting. An empty list means the platform is fine with the file. A non-empty
list means a post with this file to that platform would be refused for that channel.
Each objection is {code, message}:
Codes you can see include media_format (wrong type), media_too_large
(file over that platform's size limit), too_many_images, too_many_videos and alt_text_too_long. Read
message to show a person; branch on code.
The upload itself succeeds either way. checks is advice: use it to warn a person, or to pick the channels the
file can go to.
{ "code": "media_too_large", "message": "A file is 2.1 MB; Bluesky accepts up to 2.0 MB." }
PATCH /v1/media/{id}
Edit alt text
PATCH /v1/media/{id} changes the description. The body is { "altText": ... }. The field is required, and
null or a blank string clears the description.
The answer is 200 with the media object, and checks run again against the new description.
altTextcan be at most 2000 characters, the longest any platform allows. Longer is400 invalid_request. Each platform's own, shorter limit still applies when a post is checked, and shows inchecks.404 not_foundfor an id that is not yours.409 already_sent: a post that uses this file has already started sending, so the description is settled. Upload the file again to use a new description.
curl -X PATCH https://omniposts.dev/v1/media/0192a3b4-0000-7000-8000-0000000000b1 \
-H "Authorization: Bearer omniposts_..." \
-H "Content-Type: application/json" \
-d '{ "altText": "Six bowls on a shelf, glazed in blue" }'
GET /v1/media/{id}/content
Download a file
GET /v1/media/{id}/content returns the file's bytes with its type. Only your own organization can read it.
Use media in a post
Put the ids in mediaIds on the post to use them on every channel, or on one target to use them on that
channel only. A target's mediaIds replaces the post's, so list the files again. See
Posts. A post cannot mix images and video.