# Media & Uploads

Attach images, videos and PDFs by public URL or from your project's media library.


Posts can carry up to 10 media items in the `media` array. Every target in the post gets the same media. Each item points to a file in one of two ways:

- A **public URL** (`https://...`) that the platform can download.
- A **media library reference** (`media://<id>`) to a file you've uploaded in the dashboard.

```json
{
  "content": "Behind the scenes at the shoot",
  "targets": [{ "accountId": "8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b" }],
  "media": [
    { "url": "https://cdn.example.com/bts-1.jpg", "type": "image", "altText": "Crew setting up lights" },
    { "url": "media://3f2e1d0c-4b5a-4968-8776-5a4b3c2d1e0f", "type": "image", "altText": "Camera on a dolly" }
  ],
  "publishNow": true
}
```

## Media item fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | Yes | `http(s)` URL or `media://<id>`, up to 2,048 characters. |
| `type` | string | Yes | `image`, `video` or `document`. |
| `altText` | string | No | Up to 1,000 characters. Passed to platforms that support alt text. |
| `title` | string | No | Up to 400 characters. Title shown on a document card (documents only). |

- **GIFs** use `type: "image"`. Platforms that treat GIFs specially detect them from the `.gif` file extension.
- **Documents** are PDFs and are only supported on LinkedIn. Sending a document to any other platform fails validation with `400`.

## Public URLs

When you use a URL, the platform (or Relaycast on its behalf) downloads the file **at publish time**, not when you create the post. So:

- The URL must be publicly reachable without cookies or authentication. Signed URLs work if they're still valid when the post publishes.
- For scheduled posts, keep the file available until the scheduled time.
- Use a URL path that ends in the real file extension (`.jpg`, `.png`, `.mp4`, `.gif`). Some platform checks use it, for example Instagram only accepts JPEG images and X detects GIFs from it.
- If the download fails, the target fails with `errorCode: "media_error"`. A `4xx` response from your server is treated as permanent, while a `5xx` or network error is retried.

> [!WARNING]
> URLs pointing at `localhost`, private networks or pages that need a login will fail at publish time. If you're testing locally, upload files to the media library instead.

## Media library

The media library stores files privately in your project. Use it when you don't have your own public hosting, or when you don't want files to be publicly reachable.

### Upload

Uploads happen in the dashboard. In the [Playground](/docs/playground), open the media panel and use the **Upload** tab, or pick an earlier upload from the **Library** tab. Accepted files:

| Kind | Types |
| --- | --- |
| Image | JPEG, PNG, WebP |
| GIF | GIF |
| Video | MP4, MOV (QuickTime) |
| Document | PDF |

Files can be up to **150 MB**. Individual platforms have their own, often smaller, limits. See the [platform guides](/docs/platforms).

> [!NOTE]
> The public API has no media upload endpoint. Upload in the dashboard and reference files by ID, or host files yourself and pass public URLs.

### Reference library files

Each upload has a reference of the form `media://<id>`. After adding a library file in the Playground, the **Live request** panel shows the exact `media` item to copy into your own requests.

When you create a post, Relaycast checks that every `media://` reference belongs to your project. Unknown references are rejected before anything is created:

```json
{
  "error": {
    "code": "unknown_media",
    "message": "Some media library items were not found",
    "details": { "missing": ["media://3f2e1d0c-4b5a-4968-8776-5a4b3c2d1e0f"] }
  }
}
```

At publish time, each reference is turned into a short-lived signed URL that only the platform uses to download the file. The file itself never becomes public.

`media://` references also work inside [platform options](/docs/platform-options) that take an image, such as a custom video thumbnail.

### Deleting library files

If you delete a library file while a scheduled post still uses it, that post's targets fail at publish time with a message saying the item was deleted. Posts that already published aren't affected, because the platform keeps its own copy.

## Per-platform rules

Validation runs per target when you create the post, so a mismatch shows up as a `400` before anything is charged. Typical rules:

- Bluesky: up to 4 images or one video, not both.
- X (Twitter): up to 4 images, or one video, or one GIF.
- YouTube: exactly one video.
- TikTok: one video or a set of photos, not both.
- Instagram: at least one image or video, JPEG images only.

These are examples, not a complete list. Each [platform guide](/docs/platforms) lists its media rules, and the [Playground](/docs/playground) shows them live as you compose.
