---
title: "Media uploads"
description: "Import images and videos into a Pallyy media library from a URL via the API, poll the upload's status, and use the result in posts."
category: "endpoints"
emoji: "⬆️"
order: 4
datePublished: "2026-08-20"
dateUpdated: "2026-08-20"
---

Media uploads import a file into a social set's [media library](/docs/api/media) from a public URL. Processing happens in the background: you create an upload, poll it until it is ready, then use the resulting `mediaLibraryId` in [post sets](/docs/api/post-sets).

An upload moves through these statuses:

| Status | Meaning |
| --- | --- |
| `CREATED` | Accepted, waiting to be processed |
| `PROCESSING` | Downloading and processing the file |
| `READY` | Done, `mediaLibraryId` points at the new library item |
| `FAILED` | Something went wrong, `failureReason` says what |

## Create a media upload

```
POST /v1/media-upload
```

Requires the `media:write` scope. Returns the upload with a `201` and status `CREATED`.

| Field | Required | Meaning |
| --- | --- | --- |
| `socialSetId` | Yes | The social set whose library receives the file |
| `sourceUrl` | Yes | A publicly reachable `http` or `https` URL, up to 2048 characters |

```bash
curl https://app.pallyy.com/api/v1/media-upload \
  -X POST \
  -H "Authorization: Bearer pallyy_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "socialSetId": "665f1c2ab7e2a4d1f0a1b2c3",
    "sourceUrl": "https://example.com/latte.jpg"
  }'
```

A few limits apply at creation time:

- At most **5 pending uploads** per social set. More return a `400` with `media-upload:pending_exceeded`, wait for them to finish first.
- A pending upload with the same `socialSetId` and `sourceUrl` already existing returns a `409` whose `data.conflictId` is the existing upload's id.
- The file must fit within your plan's remaining monthly upload allowance and storage, otherwise the request fails with `media-upload:unavailable_upload_bytes` or `media-upload:unavailable_storage`.

Processed bytes count against your plan's monthly upload allowance even when an import fails.

## Get a media upload

```
GET /v1/media-upload/{id}
```

Poll this endpoint until the upload leaves `PROCESSING`. Uploads usually finish within seconds, so polling every few seconds is plenty, and stays well within the [rate limits](/docs/api/rate-limits).

```json
{
  "id": "665f1c2ab7e2a4d1f0a1b2c6",
  "socialSetId": "665f1c2ab7e2a4d1f0a1b2c3",
  "sourceUrl": "https://example.com/latte.jpg",
  "status": "READY",
  "mediaLibraryId": "665f1c2ab7e2a4d1f0a1b2c5",
  "createdAt": "2026-08-20T10:00:00.000Z",
  "updatedAt": "2026-08-20T10:00:04.000Z"
}
```

| Field | Type | Meaning |
| --- | --- | --- |
| `id` | string | The upload id |
| `socialSetId` | string | The social set whose library receives the file |
| `sourceUrl` | string | The URL being imported |
| `status` | string | `CREATED`, `PROCESSING`, `READY`, or `FAILED` |
| `mediaLibraryId` | string | The created library item, present once `READY` |
| `failureReason` | string | Why the import failed, present when `FAILED` |
| `createdAt`, `updatedAt` | string | ISO 8601 timestamps |

## Typical flow

1. `POST /v1/media-upload` with the file's URL.
2. Poll `GET /v1/media-upload/{id}` until `status` is `READY`.
3. Create a post set whose media references the returned `mediaLibraryId`.
