Upload images, video and PDFs via API
Upload and manage media files for use in posts. Every media file is stored in your workspace's media library and can be referenced by multiple posts.
Upload a file
Use POST /media/upload with a multipart form. Maximum file size is 100 MB for a direct upload. To ingest larger files (up to 1 GB), use Upload from a URL, which downloads asynchronously.
Terminal
Response:
JSON
The media record is nested under data. Note that size is a human-readable string (e.g. "0.80 MB"), not a byte count. width and height are the image's pixel size (null for videos and for files uploaded before July 2026); they also come back on GET /media and GET /media/:id. The compatibility block is a preflight check of the file against your connected platforms. Save data.id; you reference media on posts through the media_ids field.
status is ready for normal uploads. Large asynchronous URL ingests start as processing; poll GET /media/:id until status is ready before using the file in a post. A source that can't be fetched or validated becomes failed.
Upload from a URL
Skip the download-and-reupload dance by pointing OmniSocials at a public URL. The server fetches the file (up to 1 GB) and stores it in your media library.
Terminal
The response shape matches POST /media/upload. Files above ~100 MB are ingested asynchronously and come back with status: "processing".
Upload a PDF as a carousel
Upload a PDF and OmniSocials rasterizes it into one image slide per page (up to 20 pages), keeps the original file, and returns a media ID for every slide. Pass all of the returned IDs to media_ids on a post to publish the deck as a carousel: on LinkedIn it posts as a native swipeable document made from your original PDF (text stays sharp, in-document links keep working, viewers download the real file, every page is included); on Instagram, TikTok, Threads and Pinterest as an image carousel. See LinkedIn: Original PDF for the exact rule and the document_source opt-out.
PDFs are accepted on every upload path: POST /media/upload, upload-from-url, upload-from-base64, the presigned upload-by-token flow, and media_urls on a post (where the PDF expands in place into one slide per page). The upload response carries the whole set:
JSON
data mirrors the first slide for backward compatibility; use media_ids to attach the full carousel. Every slide's Media object carries pdf (the kept file's id, name, url, and this slide's page, total_pages, rendered_pages), so a slide in GET /media can always be traced to its file and page. Deleting the last slide of a PDF removes the kept file too.
Keep the PDF as one item
Pass pdf_mode: "document" on any upload endpoint (a form field on the multipart and presigned paths) and the PDF stays one media item instead of one item per page. This is what the OmniSocials web library does. The item has type: "document", its page images in pdf.pages (in order), page 1 as thumbnail_url, and the kept PDF as url. Its single id in media_ids expands into every page at post time, so one id attaches the whole deck:
JSON
Uploading the same PDF again returns the existing document item. GET /media lists document items with type: "document". Deleting a document item removes its page images and, when no split slide still points at it, the kept PDF.
POST /media/{id}/split turns a document item into one image item per page (copies of the page images, each carrying pdf.page), for the case where a single page is needed as a normal image. The document item stays. A full, ordered set of those slides still publishes the original PDF on LinkedIn.
The default stays pdf_mode: "slides" so existing integrations that read one media id per page keep working.
Inline URLs on a post
If you only need the media for a single post, skip the media library entirely. Pass URLs directly in media_urls on the post body. Note the size split: media_urls on a post caps at 100 MB per file, while upload-from-url handles up to 1 GB; for big videos, upload first and pass the returned media id.
JSON
The API downloads, validates, and attaches the file automatically. The maximum number of items is platform-dependent (Instagram, Threads, Telegram, Discord and Bluesky up to 10, TikTok up to 35, X and Mastodon up to 4, Pinterest up to 5, Google Business 1). Use the media library (via upload or upload-from-url) when the same file appears on multiple posts, to save on download bandwidth.
Any entry, in media_urls or media_ids, can be an object instead of a bare string to carry an accessibility description (alt text, max 1500 characters): { "url": "https://example.com/bike.jpg", "alt": "A red bicycle leaning against a brick wall" } (or { "id": "123", "alt": "..." } for library media). See Attaching media for which platforms receive it.
Presigned uploads
When the uploading party cannot send an Authorization header (a browser form, an AI sandbox, a third-party service), mint a one-time upload URL instead of sharing your API key:
Terminal
The response contains an upload_url of the form https://api.omnisocials.com/v1/media/upload-by-token?upload_token=.... POST a multipart form with a file field to that URL, with no auth header; the token in the URL is the auth. Tokens are single-use and expire after 10 minutes. The upload response matches POST /media/upload (including the PDF media_ids expansion).
Folders
Organize the library into folders. Reading uses media:read, writing uses media:write.
| Endpoint | Description |
|---|---|
GET /folders | List folders |
POST /folders | Create a folder |
PATCH /folders/:id | Rename a folder |
DELETE /folders/:id | Delete a folder |
Move a file into a folder with PATCH /media/:id and a folder_id; filter the library by folder with GET /media?folder_id=.
Searching the library
GET /media takes four optional query parameters:
| Parameter | Description |
|---|---|
limit | Items per page, 1 to 100 (default 20) |
offset | Skip this many items |
search | Case-insensitive match on the media name and filename |
folder_id | Only items in this folder |
Use search to find an existing asset by name instead of re-uploading it.
Supported formats
| Media type | Formats |
|---|---|
| Images | JPEG, PNG, GIF, WebP, HEIC/HEIF (converted to JPEG on upload) |
| Videos | MP4, MOV, AVI |
| Documents | PDF (rasterized into page images; one item per page, or one document item with pdf_mode: "document"; see above) |
Platform-specific size and duration requirements are enforced when a post is scheduled or published, not at upload. A video can upload successfully and still fail to publish to Instagram if it exceeds Instagram's length limit. Aspect ratio is handled at post time too, in two steps: a feed image outside what a channel shows uncropped is cropped or kept per channel and reported in the post response (media_fit, see Image fit per channel), and the hard limits that remain (Instagram feed images outside 9:16 to 1.91:1, Pinterest carousel slides at mixed ratios) return 400 validation_error. See the Platforms section for per-platform media specs.
Check compatibility before posting
POST /media/check tells you, before you post, how a file does on your connected channels. Pass one of url (size and type are read with a HEAD request), media_id (a library file) or size_bytes plus mime. It returns connected_platforms, a compatible boolean and per-platform warnings (size and format, with reasons like max 300 MB). Every upload response carries the same compatibility block.
For a library image (media_id) the response also carries file.width / file.height and a fit array: one row per connected channel with an image fit rule, saying how that channel shows the image as a feed post.
JSON
fits: false means the ratio is outside the window the channel shows uncropped; default_action is what a post create does when media_fit is not set (crop on Instagram, keep on Pinterest, null when the image fits). Only images with a known size get rows, so videos, url checks and files uploaded before July 2026 answer an empty fit. The check is advisory: the file still posts to every channel that accepts it.
Managing media
| Endpoint | Description |
|---|---|
GET /media | List everything in the library |
GET /media/:id | Fetch a single media record |
PATCH /media/:id | Rename a media file or move it to a folder |
DELETE /media/:id | Delete from the library |
POST /media/check | Preflight-check a file's compatibility with your connected platforms, including how each channel shows an image as a feed post (fit, see above) |
POST /media/upload-from-base64 | Upload a base64-encoded file |
POST /media/:id/split | Turn a PDF document item into one image item per page |
POST /media/create-upload-url | Mint a one-time presigned upload URL (see above) |
Reading media (GET /media, GET /media/:id) requires the media:read scope; all writes require media:write.
Deleting a media file that is still attached to a scheduled or in-progress post returns 409 media_in_use. Deleting one that is only on already-published posts succeeds and does not remove it from those live posts.