# Prepare media uploads

> Create short-lived direct S3 PUTs for post media.

Canonical: <https://www.tryadeli.com/docs/api/posts/create-media-uploads>

`POST https://app.tryadeli.com/api/v1/media/uploads`

Authorization: `Bearer <API key>` — see [Authentication](https://www.tryadeli.com/docs/authentication)

Create short-lived S3 PUT descriptors before publishing any local file. This is
Adeli's recommended media transport for Instagram, Facebook, TikTok, YouTube,
X, and Bluesky. File bytes go directly to private S3 rather than through a
Vercel Function.

`clientId` is your correlation value and must be unique inside the request.
Use `post_media` for normal media and `youtube_thumbnail` only for a YouTube
JPEG or PNG thumbnail. Counts, MIME types, and sizes are checked against the
selected platform before any capability is issued.

The `201` response has one entry per declaration:

PUT the raw bytes to `url` with **every returned header unchanged**. Do not send
your Adeli API key to S3 and do not add or alter metadata. URLs expire after ten
minutes and grant access to one random object only. Uploads may run concurrently;
use `clientId` to restore the original order.

Then publish a small JSON request with staged references:

```json
{
  "platform": "youtube",
  "profileId": "00000000-0000-4000-8000-000000000001",
  "accountId": "00000000-0000-4000-8000-000000000002",
  "title": "Launch",
  "privacy_status": "unlisted",
  "made_for_kids": false,
  "video": { "upload_id": "youtube/staging/opaque.mp4" },
  "thumbnail": { "upload_id": "youtube/staging/opaque.jpg" }
}
```

Before contacting a provider, Adeli verifies the object's organization, profile,
platform, purpose, MIME declaration, and exact S3 byte length. Missing, expired,
or mismatched media returns `422 invalid_media`. Retry initiation if a URL
expires. Retrying a single PUT restarts that file; resumable S3 multipart upload
is not part of this version.

API keys belong on your server. Do not expose one in browser code or request
broad customer-origin bucket CORS.

## Request examples

**cURL**

```bash
curl -X POST "https://app.tryadeli.com/api/v1/media/uploads" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "profileId": "00000000-0000-4000-8000-000000000001",
  "platform": "youtube",
  "files": [
    {
      "clientId": "video",
      "purpose": "post_media",
      "contentType": "video/mp4",
      "size": 75497472,
      "filename": "launch.mp4"
    },
    {
      "clientId": "thumb",
      "purpose": "youtube_thumbnail",
      "contentType": "image/jpeg",
      "size": 240000,
      "filename": "launch.jpg"
    }
  ]
}'
```

**JavaScript**

```javascript
const response = await fetch("https://app.tryadeli.com/api/v1/media/uploads", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ADELI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    profileId: "00000000-0000-4000-8000-000000000001",
    platform: "youtube",
    files: [
      {
        clientId: "video",
        purpose: "post_media",
        contentType: "video/mp4",
        size: 75497472,
        filename: "launch.mp4",
      },
      {
        clientId: "thumb",
        purpose: "youtube_thumbnail",
        contentType: "image/jpeg",
        size: 240000,
        filename: "launch.jpg",
      },
    ],
  }),
});
console.log(await response.json());
```

**Python**

```python
import os

import requests

response = requests.post(
    "https://app.tryadeli.com/api/v1/media/uploads",
    headers={"Authorization": f"Bearer {os.environ['ADELI_API_KEY']}"},
    json={
        "profileId": "00000000-0000-4000-8000-000000000001",
        "platform": "youtube",
        "files": [
            {
                "clientId": "video",
                "purpose": "post_media",
                "contentType": "video/mp4",
                "size": 75497472,
                "filename": "launch.mp4",
            },
            {
                "clientId": "thumb",
                "purpose": "youtube_thumbnail",
                "contentType": "image/jpeg",
                "size": 240000,
                "filename": "launch.jpg",
            },
        ],
    },
)
print(response.json())
```

## Responses

### 201

```json
{
  "uploads": [{
    "clientId": "video",
    "uploadId": "youtube/staging/opaque.mp4",
    "method": "PUT",
    "url": "https://signed-s3-url.example/...",
    "headers": {
      "content-type": "video/mp4",
      "cache-control": "public, max-age=300",
      "x-amz-server-side-encryption": "AES256",
      "x-amz-meta-organization": "..."
    },
    "expiresAt": "2026-10-26T12:10:00.000Z"
  }]
}
```

### 422

```json
{
  "error": {
    "code": "invalid_media",
    "message": "The media declaration exceeds the platform limit"
  }
}
```
