# 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](#upload-from-a-url), which downloads asynchronously.

```bash
curl -X POST https://api.omnisocials.com/v1/media/upload \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -F "file=@photo.jpg"
```

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",
    "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. 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.

```bash
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), then 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; on Instagram, TikTok, Threads and Pinterest as an image carousel.

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" },
  "slides": [ { "id": "201" }, { "id": "202" }, { "id": "203" } ],
  "media_ids": ["201", "202", "203"],
  "pdf": { "totalPages": 3, "renderedPages": 3, "truncated": false }
}
```

`data` mirrors the first slide for backward compatibility; use `media_ids` to attach the full carousel.

## 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 up to 10, TikTok up to 35, X/Bluesky/Mastodon up to 4). 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](/creating-posts#attaching-media) for which platforms receive 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 image slides — see above) |

Platform-specific size, aspect ratio, and duration requirements are enforced at publish time, not upload time. A video can upload successfully and still fail to publish to Instagram if it exceeds Instagram's length limit. See the [Platforms](/platforms) section for per-platform media specs.

## 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 |
| `POST /media/upload-from-base64` | Upload a base64-encoded file |

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.
