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.

Updated

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-Type you 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

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_found for an id that is not yours.
  • 409 media_in_use while 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.

  • altText can be at most 2000 characters, the longest any platform allows. Longer is 400 invalid_request. Each platform's own, shorter limit still applies when a post is checked, and shows in checks.
  • 404 not_found for 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.