OmniSocialsOmniSocials
DocumentationAPI reference
Help centerSupport
  • Documentation
  • API reference
Developers
  • Quickstart
  • API reference
  • SDKs
  • MCP server
  • CLI
For AI agents
  • Docs for AI agents
  • llms.txt
  • OpenAPI spec
  • Postman collection
Product
  • OmniSocials
  • Pricing
  • Changelog
  • Help center
Company
  • Support
  • Privacy policy
  • Terms of service

© 2026 OmniSocials

OmniSocials on GitHubOmniSocials on XOmniSocials on LinkedIn
Overview
Get started
SDKs
CLI
MCPDocs for AI agentsWebhooks
Guides
    PostsStoriesReelsCross-postingMediaAnalyticsSocial inboxAutomations
Platforms
Integrations
Migrate
Plans and billing
GlossaryChangelogAPI reference
Guides

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.

TerminalTerminal
curl -X POST https://api.omnisocials.com/v1/media/upload \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \ -F "[email protected]"

Response:

JSON
{ "data": { "id": "123", "url": "https://cdn.omnisocials.com/workspace-42/photo.jpg", "thumbnail_url": null, "type": "image", "name": "photo.jpg", "filename": "photo-1a2b3c.jpg", "folder_id": null, "size": "0.80 MB", "width": 1080, "height": 1350, "status": "ready", "created_at": "2026-04-10T14:22:00Z" }, "compatibility": { "overall": "ok", "platforms": {} }, "message": "File uploaded successfully. Use the 'id' in your post request." }

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.

TerminalTerminal
curl -X POST https://api.omnisocials.com/v1/media/upload-from-url \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/photo.jpg" }'

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": { "id": "201", "type": "image", "status": "ready", "pdf": { "id": "9d2c…", "name": "deck.pdf", "page": 1, "total_pages": 3, "rendered_pages": 3 } }, "slides": [ { "id": "201" }, { "id": "202" }, { "id": "203" } ], "media_ids": ["201", "202", "203"], "pdf": { "total_pages": 3, "rendered_pages": 3, "truncated": false, "id": "9d2c…", "name": "deck.pdf", "url": "https://cdn.omnisocials.com/file/library-omnisocials/…/9d2c….pdf", "size_bytes": 184320 } }

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
{ "data": { "id": "301", "type": "document", "name": "deck.pdf", "pdf": { "id": "9d2c…", "total_pages": 3, "rendered_pages": 3, "pages": [ { "url": "…/p1.png" }, { "url": "…/p2.png" }, { "url": "…/p3.png" } ] } }, "media_ids": ["301"], "pdf": { "total_pages": 3, "rendered_pages": 3, "truncated": false, "id": "9d2c…", "name": "deck.pdf", "url": "…/9d2c….pdf" } }

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
{ "content": { "default": "Check this out" }, "accounts": ["your-account-id"], "media_urls": ["https://example.com/photo.jpg"] }

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:

TerminalTerminal
curl -X POST https://api.omnisocials.com/v1/media/create-upload-url \ -H "Authorization: Bearer $OMNISOCIALS_API_KEY"

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.

EndpointDescription
GET /foldersList folders
POST /foldersCreate a folder
PATCH /folders/:idRename a folder
DELETE /folders/:idDelete 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:

ParameterDescription
limitItems per page, 1 to 100 (default 20)
offsetSkip this many items
searchCase-insensitive match on the media name and filename
folder_idOnly items in this folder

Use search to find an existing asset by name instead of re-uploading it.

Supported formats

Media typeFormats
ImagesJPEG, PNG, GIF, WebP, HEIC/HEIF (converted to JPEG on upload)
VideosMP4, MOV, AVI
DocumentsPDF (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
{ "file": { "size_bytes": 812345, "mime": "image/jpeg", "size_known": true, "width": 1080, "height": 1920 }, "connected_platforms": ["instagram", "pinterest", "linkedin"], "compatible": true, "warnings": [], "summary": null, "fit": [ { "platform": "instagram", "display_name": "Instagram", "fits": false, "ratio": "9:16", "shown": "cut", "target": "4:5", "default_action": "crop", "message": "Instagram: This image is 9:16. Instagram shows feed images between 4:5 and 1.91:1, and Meta cannot boost a feed post outside that range. A post crops it to 4:5 unless media_fit says \"keep\"." }, { "platform": "pinterest", "display_name": "Pinterest", "fits": false, "ratio": "9:16", "shown": "cut", "target": "2:3", "default_action": "keep", "message": "Pinterest: This image is 9:16. Pinterest may cut a Pin taller than 2:3 in the home feed. The Pin page shows it whole. Pinterest cuts it to fit." } ] }

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

EndpointDescription
GET /mediaList everything in the library
GET /media/:idFetch a single media record
PATCH /media/:idRename a media file or move it to a folder
DELETE /media/:idDelete from the library
POST /media/checkPreflight-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-base64Upload a base64-encoded file
POST /media/:id/splitTurn a PDF document item into one image item per page
POST /media/create-upload-urlMint 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.

Last modified on October 22, 2026
Cross-postingAnalytics
On this page
  • Upload a file
  • Upload from a URL
  • Upload a PDF as a carousel
    • Keep the PDF as one item
  • Inline URLs on a post
  • Presigned uploads
  • Folders
  • Searching the library
  • Supported formats
  • Check compatibility before posting
  • Managing media
JSON
JSON
JSON
JSON
JSON