# YouTube

Publish videos and Shorts to YouTube with the Zernio API, with custom thumbnails, visibility, playlists, COPPA and AI disclosure flags, and description edits after publishing.

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';

Publish videos and Shorts to YouTube with `POST /v1/posts` and `platform: "youtube"`. The same account serves analytics and comments.

## Quick reference

| Property | Value |
|----------|-------|
| Title limit | 100 characters |
| Description limit | 5,000 characters |
| Tags | Top-level `tags` array on the create request; 100 characters per tag, 500 combined |
| Videos per post | 1 |
| Video formats | MP4, MOV, AVI, WMV, FLV, 3GP, WebM |
| Video max size | 256 GB |
| Video max duration | 15 minutes (unverified channel), 12 hours (verified) |
| Thumbnail formats | JPEG, PNG, GIF |
| Thumbnail max size | 2 MB |
| Post types | Video, Shorts |
| Scheduling | Yes (a public video is uploaded early as private and released by YouTube at the scheduled time) |
| Editing published posts | Yes (description only; title and tags through a separate endpoint) |
| Inbox (comments) | Yes |
| Comment automations | Public reply only, read by the comment poll ([comment automations](/comment-automations/create-comment-automation)) |
| Inbox (DMs) | No (YouTube has no DM system) |
| Analytics | Yes |

## Before you start

YouTube requires a channel owned by the Google identity you authorize, on a personal Google account or a Brand Account. See [Brand Accounts and multiple channels](#brand-accounts-and-multiple-channels).

Every post is exactly one video, so there are no image-only or text-only posts.

Unverified channels are limited to 15-minute videos. Verify the channel by phone at [youtube.com/verify](https://www.youtube.com/verify) for longer uploads.

Daily upload quotas vary by channel. Shorts are detected from duration and aspect ratio, not chosen with a flag.

<Callout type="warn">
If a channel is suspended, every upload fails with a `403`. Call [Account health](/accounts/get-all-accounts-health) before scheduling posts to a channel you do not control.
</Callout>

## Connect

Call `GET /v1/connect/youtube` with `profileId` on [Get OAuth connect URL](/connect/get-connect-url).

The [connecting accounts guide](/guides/connecting-accounts) covers the OAuth flow and [scopes](/guides/connecting-accounts#scopes) in general. [Account health](/accounts/get-all-accounts-health) reports what a connected account can do with the scopes the user granted.

### Brand Accounts and multiple channels

There is no channel picker. YouTube connects straight after Google's OAuth screen.

The channel Zernio connects is the one owned by the Google identity you pick in Google's account chooser. Zernio forces that chooser on every connect (`prompt=select_account`), so the choice is always yours to make:

- A channel on your personal Google account: pick your personal identity.
- A channel that lives on a Brand Account: pick the Brand Account entry in the chooser, not your personal identity. You need owner or manager access to the Brand Account at [account.google.com/brandaccounts](https://account.google.com/brandaccounts).
- YouTube Studio "Manage access" permissions do not grant API access. Someone added as an editor or manager only inside YouTube Studio cannot connect that channel; a Brand Account owner or manager has to do it.

**No channel found.** The identity you picked may own no channel: a personal identity chosen by mistake, Studio-only access, or no channel created yet. The connect then fails with:

```
We couldn't find a YouTube channel for the Google account you authorized...
```

Restart the flow and pick the right entry in the chooser.

**Several channels.** A profile holds one YouTube channel. To connect another one:

1. Create a second [profile](/guides/profiles) and select it.
2. Start the YouTube connect again.
3. Pick the other channel's identity in the chooser.

Each channel then has its own `accountId`.

## Publish a post

This request uploads a public video to YouTube right away. `platformSpecificData` sets the title, visibility and category.

<Tabs items={['Node.js', 'Python', 'curl']}>
<Tab value="Node.js">
```typescript
import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: published } = await zernio.posts.createPost({
  body: {
    content: 'In this tutorial, I walk through building a REST API from scratch.\n\n#programming #tutorial',
    tags: ['rest api', 'node.js', 'backend tutorial'],
    mediaItems: [{
      type: 'video',
      url: 'https://cdn.example.com/long-form-video.mp4',
      thumbnail: 'https://cdn.example.com/thumbnail.jpg'
    }],
    platforms: [{
      platform: 'youtube',
      accountId: '66b2e19d8c3f5a7e9d0b1c2d',
      platformSpecificData: {
        title: 'Build a REST API from scratch',
        visibility: 'public',
        categoryId: '27',
        madeForKids: false
      }
    }],
    publishNow: true
  }
});

console.log(published.post.platforms[0].platformPostUrl);
```
</Tab>
<Tab value="Python">
```python
from zernio import Zernio

client = Zernio()

published = client.posts.create_post(
    content="In this tutorial, I walk through building a REST API from scratch.\n\n#programming #tutorial",
    tags=["rest api", "node.js", "backend tutorial"],
    media_items=[{
        "type": "video",
        "url": "https://cdn.example.com/long-form-video.mp4",
        "thumbnail": "https://cdn.example.com/thumbnail.jpg"
    }],
    platforms=[{
        "platform": "youtube",
        "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
        "platformSpecificData": {
            "title": "Build a REST API from scratch",
            "visibility": "public",
            "categoryId": "27",
            "madeForKids": False
        }
    }],
    publish_now=True
)

print(published["post"]["platforms"][0]["platformPostUrl"])
```
</Tab>
<Tab value="curl">
```bash
curl -X POST https://zernio.com/api/v1/posts \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "In this tutorial, I walk through building a REST API from scratch.\n\n#programming #tutorial",
    "tags": ["rest api", "node.js", "backend tutorial"],
    "mediaItems": [{
      "type": "video",
      "url": "https://cdn.example.com/long-form-video.mp4",
      "thumbnail": "https://cdn.example.com/thumbnail.jpg"
    }],
    "platforms": [{
      "platform": "youtube",
      "accountId": "66b2e19d8c3f5a7e9d0b1c2d",
      "platformSpecificData": {
        "title": "Build a REST API from scratch",
        "visibility": "public",
        "categoryId": "27",
        "madeForKids": false
      }
    }],
    "publishNow": true
  }'
```
</Tab>
</Tabs>

Every option is on [Videos & Shorts](/platforms/youtube/posts).

## In this section

<PlatformPages platform="youtube" />

## Related

- [Connecting accounts](/guides/connecting-accounts): the OAuth flow.
- [Create post](/posts/create-post): every field of the request.
- [Edit post](/posts/edit-post) and [Update post metadata](/posts/update-post-metadata): change a published video.
- [Media uploads](/guides/media-uploads): upload videos instead of hosting them.
- [Analytics](/analytics/get-analytics): post performance metrics.
- [Comments](/comments/list-inbox-comments): read and reply to comments.
- [Get a YouTube video transcript](/connect/get-youtube-captions): every parameter of the captions call.
- [Pricing](/pricing): what analytics, the inbox and outbound messages cost, and which replies count.