# YouTube

Upload videos and Shorts to a YouTube channel with titles, thumbnails, playlists and visibility, edit them after publishing, and read channel analytics and transcripts.

Upload long-form videos and Shorts to a YouTube channel, schedule them to go public at an exact time, and change their details afterwards. The same account gives you per-video and channel analytics, comment moderation and the transcript of any of the channel's own videos. The platform id is `youtube`.

## What you can do

| Capability | Supported | Where |
| --- | --- | --- |
| Publish | Videos and Shorts, with title, description, tags, thumbnail, category, playlist | [Create a post](https://pozy.ai/docs/publishing) |
| Schedule | Yes. YouTube itself releases the video at the scheduled time. | [Scheduling](https://pozy.ai/docs/publishing/scheduling) |
| Edit after publishing | Description, title, tags, thumbnail, visibility, playlist. [Unpublish](https://pozy.ai/docs/api/unpublishPost) removes the video. | [Edit published posts](https://pozy.ai/docs/publishing/editing) |
| Analytics | Views, likes, comments, shares; daily views, retention, demographics, channel totals | [Platform insights](https://pozy.ai/docs/analytics/platform-insights) |
| Comments | List, reply, delete, moderate (approve, reject, hold, ban author) | [Comments](https://pozy.ai/docs/inbox/comments) |
| DMs | No. YouTube has no direct messages. | |
| Transcripts | Read the caption track of the channel's own videos | [Transcripts](#transcripts) |
| Ads | Through Google Ads | [Google Ads](https://pozy.ai/docs/ads/google-ads) |

## Connect

Start with [`GET /v1/connect/youtube`](https://pozy.ai/docs/api/getConnectUrl) and a `profileId`. Google shows its account chooser every time, and the channel that connects is the one owned by the identity picked there. There is no separate channel picker.

- **Personal channel:** pick your own Google identity.
- **Brand Account channel:** pick the Brand Account entry in the chooser. The person connecting needs owner or manager rights on the Brand Account in Google, not only on the channel.
- **YouTube Studio access is not enough.** Someone added as an editor or manager inside YouTube Studio cannot connect the channel through the API. A Brand Account owner or manager has to do it.

If the chosen identity has no channel, the connection fails with "We couldn't find a YouTube channel for the Google account you authorized". Start again and pick the right entry.

A profile holds one YouTube channel. To connect a second channel, create another [profile](https://pozy.ai/docs/build/profiles) and connect it there.

> **Suspended channels:**
> 
> If YouTube suspends a channel, every upload fails with `403`. When you post to channels you do not control, check [account health](https://pozy.ai/docs/connect/account-health) before scheduling.

## Post types

Every YouTube post is exactly one video. The type is decided by YouTube, not by a flag:

| Video | YouTube treats it as |
| --- | --- |
| 3 minutes or shorter, and vertical (9:16) or square (1:1) | Short |
| Longer than 3 minutes, or horizontal | Regular video |

The description is the whole of `content`. The title is `platformSpecificData.title`, or the first line of `content` when you leave it out. Tags go in the top-level `tags` array of the request.

```bash cURL
curl -X POST https://api.pozy.ai/v1/posts \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "We rebuilt our checkout in a weekend. Here is every step.\n\n00:00 Intro\n02:10 The plan",
    "tags": ["checkout", "case study", "ecommerce"],
    "mediaItems": [{
      "type": "video",
      "url": "https://cdn.example.com/checkout-rebuild.mp4",
      "thumbnail": "https://cdn.example.com/checkout-thumb.jpg"
    }],
    "platforms": [{
      "platform": "youtube",
      "accountId": "6720c1a94f3b2e0018d4a7f3",
      "platformSpecificData": {
        "title": "Rebuilding checkout in one weekend",
        "visibility": "public",
        "categoryId": "28",
        "madeForKids": false
      }
    }],
    "publishNow": true
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/posts", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    content: "We rebuilt our checkout in a weekend. Here is every step.\n\n00:00 Intro\n02:10 The plan",
    tags: ["checkout", "case study", "ecommerce"],
    mediaItems: [{
      type: "video",
      url: "https://cdn.example.com/checkout-rebuild.mp4",
      thumbnail: "https://cdn.example.com/checkout-thumb.jpg",
    }],
    platforms: [{
      platform: "youtube",
      accountId: "6720c1a94f3b2e0018d4a7f3",
      platformSpecificData: {
        title: "Rebuilding checkout in one weekend",
        visibility: "public",
        categoryId: "28",
        madeForKids: false,
      },
    }],
    publishNow: true,
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);

console.log(data.post.platforms[0].platformPostUrl);
```
```python Python
import os, requests

resp = requests.post(
    "https://api.pozy.ai/v1/posts",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={
        "content": "We rebuilt our checkout in a weekend. Here is every step.\n\n00:00 Intro\n02:10 The plan",
        "tags": ["checkout", "case study", "ecommerce"],
        "mediaItems": [{
            "type": "video",
            "url": "https://cdn.example.com/checkout-rebuild.mp4",
            "thumbnail": "https://cdn.example.com/checkout-thumb.jpg",
        }],
        "platforms": [{
            "platform": "youtube",
            "accountId": "6720c1a94f3b2e0018d4a7f3",
            "platformSpecificData": {
                "title": "Rebuilding checkout in one weekend",
                "visibility": "public",
                "categoryId": "28",
                "madeForKids": False,
            },
        }],
        "publishNow": True,
    },
)
print(resp.json()["post"]["platforms"][0]["platformPostUrl"])
```

Before upload, Pozy cleans the tag list: it strips leading `#`, splits comma-joined entries, drops duplicates and skips any tag over 100 characters. It keeps tags in order until the combined length would pass 500 characters.

Timestamps in the description (`00:00 Intro`) become chapters on YouTube as usual. End screens, cards and Premieres are not available through the API.

### Platform fields

All of these go in `platformSpecificData` on the YouTube entry (schema: `YouTubePlatformData`).

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `title` | string, up to 100 characters | First line of `content` | Video title. |
| `visibility` | `public`, `private`, `unlisted` | `public` | |
| `madeForKids` | boolean | `false` | Marks the video as made for children. This permanently turns off comments, the notification bell, personalised ads, end screens and cards on it. |
| `containsSyntheticMedia` | boolean | `false` | Discloses realistic altered or synthetic content. YouTube may label the video. |
| `categoryId` | string | `"22"` (People & Blogs) | Common values: `1` Film, `10` Music, `20` Gaming, `22` People & Blogs, `27` Education, `28` Science & Technology. |
| `playlistId` | string | | Adds the video to this playlist after upload. |
| `firstComment` | string, up to 10,000 characters | | Posted and pinned as the first comment. |
| `notifySubscribers` | boolean | `true` | Whether subscribers get notified about the upload. |

### Scheduling

When `scheduledFor` is in the future, Pozy uploads the video ahead of time so YouTube can finish processing it. A video meant to be public is uploaded as private with YouTube's own publish time set, and YouTube releases it at that second. Private and unlisted videos are uploaded with their final visibility. The video URL exists once the upload finishes, but viewers cannot open it before release. `firstComment` is posted at the scheduled time, not at upload.

Set `timezone` on the request so the time is read in the right zone. See [Scheduling and timezones](https://pozy.ai/docs/publishing/scheduling).

### Playlists

List the channel's playlists with [List YouTube playlists](https://pozy.ai/docs/api/getYoutubePlaylists) and create one with [Create YouTube playlist](https://pozy.ai/docs/api/createYoutubePlaylist) (only `title` is required; privacy defaults to private). Then pass the id as `playlistId` when you publish.

> **Playlist creation is not idempotent:**
> 
> Repeating a create request, including after a timeout, can create a second playlist. List the playlists before you retry an unclear failure.

[Set default YouTube playlist](https://pozy.ai/docs/api/updateYoutubeDefaultPlaylist) stores a default on the account for your own UI to prefill. It is not applied to posts that leave out `playlistId`.

### Edit a published video

[Edit published post](https://pozy.ai/docs/api/editPost) with `platform: "youtube"` replaces the description. Pozy strips `<` and `>` and truncates past 5,000 characters, and the call still succeeds when it does. There is no time limit or edit count, and the video keeps its URL.

For everything else, use [Update post metadata](https://pozy.ai/docs/api/updatePostMetadata): `title`, `description`, `tags`, `categoryId`, `privacyStatus`, `thumbnailUrl`, `madeForKids`, `containsSyntheticMedia` and `playlistId`. It also works on videos uploaded outside Pozy: use `_` as the post id and pass `videoId` and `accountId`.

```bash
curl -X POST https://api.pozy.ai/v1/posts/6721a3b84f3b2e0018d4b902/update-metadata \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "platform": "youtube", "title": "Checkout rebuild: the full story", "privacyStatus": "unlisted" }'
```

When the post went to several channels, pass `accountId` to choose which copy to change. Otherwise the first YouTube entry is used.

## Media requirements

Very large files (1 GB and up) can take YouTube 30 to 60 minutes or more to process. The video shows as processing meanwhile, so do not upload it again.

| | Short | Regular video |
| --- | --- | --- |
| Formats | MP4, MOV, AVI, WMV, FLV, 3GP, WebM | Same |
| Max size | 256 GB | 256 GB |
| Duration | Up to 3 minutes | Up to 15 minutes on unverified channels, 12 hours on verified channels |
| Aspect ratio | 9:16 or 1:1 | 16:9 |
| Recommended | 1080 x 1920, 30 fps, H.264, AAC 128 kbps | 1920 x 1080 or 4K, 24 to 60 fps, H.264 or H.265, AAC 384 kbps |

Shorts under 15 seconds loop automatically.

**Thumbnails** are set with `thumbnail` on the video item and only work on regular videos, not Shorts.

| Property | Requirement |
| --- | --- |
| Formats | JPEG, PNG, GIF |
| Max size | 2 MB (Pozy compresses larger images and rejects them if still too big) |
| Recommended | 1280 x 720 (16:9), at least 640 px wide |

YouTube only accepts custom thumbnails on phone-verified channels. On an unverified channel the video still publishes and only the thumbnail is skipped. Pozy then stops trying thumbnails on that channel for 7 days, so allow up to a week for them to work after the channel is verified.

## Limits

| Limit | Value |
| --- | --- |
| Title | 100 characters |
| Description | 5,000 characters |
| Tags | 100 characters each, 500 combined |
| Videos per post | 1 |
| First comment | 10,000 characters |

YouTube gives every app a daily quota and sets upload limits per channel. Liking a video costs 50 units of that quota, and reading a transcript for the first time costs 200, which is why Pozy caches transcripts. The API cannot post Community posts, go live, change monetization settings, dislike videos, upload captions or like comments.

## Analytics

[`GET /v1/analytics?platform=youtube`](https://pozy.ai/docs/api/getAnalytics) returns `views`, `likes` and `comments` per video. For more depth:

| Endpoint | What you get |
| --- | --- |
| [YouTube daily views](https://pozy.ai/docs/api/getYouTubeDailyViews) | Per-day views, watch time, subscribers gained and lost, likes, comments and shares for one video. 2 to 3 days behind. |
| [Channel insights](https://pozy.ai/docs/api/getYouTubeChannelInsights) | Channel views, watch time, average view duration, subscriber changes. |
| [Demographics](https://pozy.ai/docs/api/getYouTubeDemographics) | Age and gender as percentages, country as view counts, for the channel or one video. Signed-in viewers only. |
| [Video retention](https://pozy.ai/docs/api/getYouTubeVideoRetention) | The audience retention curve of one video, up to 100 points. |

YouTube's API does not expose impressions or thumbnail click-through rate.

## Transcripts

[Get a YouTube video transcript](https://pozy.ai/docs/api/getYoutubeCaptions) reads the caption track of one of the channel's own videos, including auto-generated tracks. An uploaded track wins over an auto-generated one in the same language. You get `text` plus timed `cues`, or SubRip with `format=srt`, and `availableTracks` lists the other languages.

The first read is fetched from YouTube; later reads come from Pozy's copy (`source: "cache"`). Pass `refresh=true` only when the captions changed. A video the channel does not own, or one with no track in that language, returns `404` (`captions_not_found`). Auto-captions can take a few hours after upload to appear, so treat that `404` as "not yet".

## Inbox

YouTube has comments only. List and reply with the [Comments](https://pozy.ai/docs/inbox/comments) endpoints, delete comments, and work the moderation queue with [Set comment moderation status](https://pozy.ai/docs/api/setCommentModeration): `published` approves, `rejected` removes, `heldForReview` sends it back. Add `banAuthor: true` with `rejected` to auto-reject that author from then on. You must own the channel or the video.

[Like post](https://pozy.ai/docs/api/likePost) likes a video as any connected channel, and `DELETE` on the same path clears the rating. A video whose owner turned off ratings returns `403`. Comments cannot be liked through the API.

New comments are picked up by polling, so a comment automation reply can lag the comment by a few minutes.

## Errors you may see

| Message | Cause | Fix |
| --- | --- | --- |
| The YouTube account of the authenticated user is suspended (`403`) | YouTube suspended the channel | Check the channel on YouTube and [account health](https://pozy.ai/docs/connect/account-health). |
| YouTube upload initialization failed: 403 | Suspension, exhausted upload quota, or a missing permission | Check each of these, then reconnect if permissions are missing. |
| YouTube permission error | The user did not grant every permission | Reconnect and accept every permission. |
| Failed to fetch video from URL: 404 | The video URL stopped working | Use a stable, public URL or [upload the file](https://pozy.ai/docs/publishing/media). |
| We couldn't find a YouTube channel | The chosen Google identity owns no channel | Connect again and pick the identity that owns the channel. |

A rejected `publishNow: true` post returns `207` with `post.status: "failed"` and the reason in `platforms[].errorMessage`.

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/accounts/{accountId}/youtube-playlists`](https://pozy.ai/docs/api/getYoutubePlaylists) | List YouTube playlists |
| [`POST /v1/accounts/{accountId}/youtube-playlists`](https://pozy.ai/docs/api/createYoutubePlaylist) | Create YouTube playlist |
| [`PUT /v1/accounts/{accountId}/youtube-playlists`](https://pozy.ai/docs/api/updateYoutubeDefaultPlaylist) | Set default YouTube playlist |
| [`GET /v1/accounts/{accountId}/youtube-captions`](https://pozy.ai/docs/api/getYoutubeCaptions) | Get a YouTube video transcript |
| [`POST /v1/posts/{postId}/edit`](https://pozy.ai/docs/api/editPost) | Edit published post |
| [`POST /v1/posts/{postId}/update-metadata`](https://pozy.ai/docs/api/updatePostMetadata) | Update post metadata |
| [`GET /v1/analytics/youtube/daily-views`](https://pozy.ai/docs/api/getYouTubeDailyViews) | Get YouTube daily views |
| [`GET /v1/analytics/youtube/channel-insights`](https://pozy.ai/docs/api/getYouTubeChannelInsights) | Get YouTube channel insights |
| [`GET /v1/analytics/youtube/demographics`](https://pozy.ai/docs/api/getYouTubeDemographics) | Get YouTube demographics |
| [`GET /v1/analytics/youtube/video-retention`](https://pozy.ai/docs/api/getYouTubeVideoRetention) | Get YouTube video retention curve |
| [`POST /v1/inbox/comments/{postId}/{commentId}/moderation`](https://pozy.ai/docs/api/setCommentModeration) | Set comment moderation status |
| [`POST /v1/inbox/posts/{postId}/like`](https://pozy.ai/docs/api/likePost) | Like post |
| [`DELETE /v1/inbox/posts/{postId}/like`](https://pozy.ai/docs/api/unlikePost) | Unlike post |

## Webhook events

| Event | When it fires |
| --- | --- |
| [`post.published`](https://pozy.ai/docs/webhooks/events#post-published) | Post published. |
| [`post.failed`](https://pozy.ai/docs/webhooks/events#post-failed) | Post failed. |
| [`post.platform.published`](https://pozy.ai/docs/webhooks/events#post-platform-published) | Post platform published. |
| [`post.platform.failed`](https://pozy.ai/docs/webhooks/events#post-platform-failed) | Post platform failed. |
| [`post.platform.deleted`](https://pozy.ai/docs/webhooks/events#post-platform-deleted) | Post platform deleted. |
| [`account.disconnected`](https://pozy.ai/docs/webhooks/events#account-disconnected) | Account disconnected. |
| [`comment.received`](https://pozy.ai/docs/webhooks/events#comment-received) | Comment received. |
