# Publish to YouTube

> Upload a YouTube video or Short from a URL or base64.

Canonical: <https://www.tryadeli.com/docs/api/posts/publish-youtube>

`POST https://app.tryadeli.com/api/v1/posts`

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

YouTube takes one video per post. There is no separate Shorts endpoint: a
vertical or square video up to three minutes long becomes a Short. Send
`application/json` with a public HTTPS `video.url` (Adeli downloads it, up to
1 GB) or base64, or upload the file itself with
[multipart](https://www.tryadeli.com/docs/api/posts/upload-youtube-video-file).

> **Warning: Uploads are private until Adeli passes YouTube's audit**
>
> YouTube locks every video uploaded through an app to private until that app
> passes the YouTube API Services audit. Until Adeli does, ask for any
> visibility you like: the response reports what YouTube actually applied in
> `appliedPrivacy`, and `privacyRestricted` is `true` when that is not what you
> asked for. Change the visibility in YouTube Studio afterwards.

**Body**

| Name                       | Type                                  | Required | Description                                                                                                                              |
| -------------------------- | ------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `platform`                 | `"youtube"`                           | Yes      |                                                                                                                                          |
| `accountId`                | `uuid`                                | Yes      | A connected YouTube channel in your organization.                                                                                        |
| `profileId`                | `uuid`                                |          | Optional, because accountId names its profile. If you pass both, they must agree.                                                        |
| `title`                    | `string`                              | Yes      | 1–100 characters, with no `<` or `>`.                                                                                                    |
| `privacy_status`           | `"public" \| "unlisted" \| "private"` | Yes      | Who can see the video. There is no default.                                                                                              |
| `made_for_kids`            | `boolean`                             | Yes      | Whether the video is made for kids, as YouTube requires under COPPA. This is the channel owner's own declaration: ask them, never guess. |
| `video`                    | `object`                              | Yes      | `{ url }`, a public HTTPS URL of up to 1 GB, or `{ content_type, base64 }` with `video/mp4`, `video/quicktime`, or `video/webm`.         |
| `description`              | `string`                              |          | Up to 5,000 bytes of UTF-8, with no `<` or `>`.                                                                                          |
| `tags`                     | `string[]`                            |          | Up to 500 characters in total, counting the commas YouTube puts between them.                                                            |
| `category_id`              | `string`                              |          | A numeric YouTube category id. Defaults to `22`, People & Blogs.                                                                         |
| `contains_synthetic_media` | `boolean`                             |          | Discloses realistic altered or synthetic content.                                                                                        |
| `publish_at`               | `string`                              |          | An ISO 8601 time in the future. Requires `privacy_status: "private"`: YouTube makes the video public at that time.                       |
| `notify_subscribers`       | `boolean`                             |          | Defaults to true.                                                                                                                        |

Returns `202` once YouTube has the file. Processing continues after; poll
[YouTube upload status](https://www.tryadeli.com/docs/api/posts/get-youtube-upload-status) with the
`publishId`.

**Fields**

| Name             | Type                                         | Required | Description                                                                                                        |
| ---------------- | -------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `publishId`      | `string`                                     |          | The YouTube video id, the same as `videoId`. Pass it to the status endpoint.                                       |
| `appliedPrivacy` | `string`                                     |          | The visibility YouTube applied. `privacyRestricted` is true when it is `private` but you asked for something else. |
| `thumbnail`      | `"set" \| "not_allowed" \| "failed" \| null` |          | `null` when no thumbnail was sent; `not_allowed` when the channel cannot use custom thumbnails.                    |
| `warnings`       | `object[]`                                   |          | Present only when non-empty: `privacy_restricted`, `thumbnail_not_allowed`, or `thumbnail_failed`.                 |

## Request examples

**cURL**

```bash
curl --fail-with-body -X POST "https://app.tryadeli.com/api/v1/posts" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "platform": "youtube",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "title": "Heron fishing at dawn",
  "privacy_status": "public",
  "made_for_kids": false,
  "video": {
    "url": "https://cdn.example/heron.mp4"
  }
}'
```

**JavaScript**

```javascript
const response = await fetch("https://app.tryadeli.com/api/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ADELI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    platform: "youtube",
    accountId: "00000000-0000-0000-0000-000000000000",
    title: "Heron fishing at dawn",
    privacy_status: "public",
    made_for_kids: false,
    video: {
      url: "https://cdn.example/heron.mp4",
    },
  }),
});
console.log(await response.json());
```

**Python**

```python
import os

import requests

response = requests.post(
    "https://app.tryadeli.com/api/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['ADELI_API_KEY']}"},
    json={
        "platform": "youtube",
        "accountId": "00000000-0000-0000-0000-000000000000",
        "title": "Heron fishing at dawn",
        "privacy_status": "public",
        "made_for_kids": False,
        "video": {
            "url": "https://cdn.example/heron.mp4",
        },
    },
)
print(response.json())
```

## Responses

### 202

```json
{
  "id": "youtube_post_00000000-0000-0000-0000-000000000000_dQw4w9WgXcQ",
  "platform": "youtube",
  "accountId": "00000000-0000-0000-0000-000000000000",
  "profileId": "00000000-0000-4000-8000-000000000001",
  "publishId": "dQw4w9WgXcQ",
  "videoId": "dQw4w9WgXcQ",
  "permalink": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "status": "ACCEPTED",
  "requestedPrivacy": "public",
  "appliedPrivacy": "private",
  "privacyRestricted": true,
  "publishAt": null,
  "thumbnail": null,
  "statusUrl": "/api/v1/posts/youtube/status?accountId=...&profileId=...&videoId=dQw4w9WgXcQ",
  "warnings": [
    { "code": "privacy_restricted", "message": "YouTube locked this upload to private: the app has not yet passed the YouTube API Services audit" }
  ]
}
```

### 422

```json
{
  "error": {
    "code": "upload_limit_exceeded",
    "message": "This channel has reached YouTube's upload limit; try again later"
  }
}
```
