Platforms
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 |
| Schedule | Yes. YouTube itself releases the video at the scheduled time. | Scheduling |
| Edit after publishing | Description, title, tags, thumbnail, visibility, playlist. Unpublish removes the video. | Edit published posts |
| Analytics | Views, likes, comments, shares; daily views, retention, demographics, channel totals | Platform insights |
| Comments | List, reply, delete, moderate (approve, reject, hold, ban author) | Comments |
| DMs | No. YouTube has no direct messages. | |
| Transcripts | Read the caption track of the channel's own videos | Transcripts |
| Ads | Through Google Ads | Google Ads |
#Connect
Start with GET /v1/connect/youtube 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 and connect it there.
If YouTube suspends a channel, every upload fails with 403. When you post to channels you do not control, check 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.
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
}'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);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.
#Playlists
List the channel's playlists with List YouTube playlists and create one with Create YouTube playlist (only title is required; privacy defaults to private). Then pass the id as playlistId when you publish.
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 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 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: 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.
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 returns views, likes and comments per video. For more depth:
| Endpoint | What you get |
|---|---|
| YouTube daily views | Per-day views, watch time, subscribers gained and lost, likes, comments and shares for one video. 2 to 3 days behind. |
| Channel insights | Channel views, watch time, average view duration, subscriber changes. |
| Demographics | Age and gender as percentages, country as view counts, for the channel or one video. Signed-in viewers only. |
| Video retention | 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 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 endpoints, delete comments, and work the moderation queue with Set comment moderation status: 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 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. |
| 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. |
| 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 |
List YouTube playlists |
POST /v1/accounts/{accountId}/youtube-playlists |
Create YouTube playlist |
PUT /v1/accounts/{accountId}/youtube-playlists |
Set default YouTube playlist |
GET /v1/accounts/{accountId}/youtube-captions |
Get a YouTube video transcript |
POST /v1/posts/{postId}/edit |
Edit published post |
POST /v1/posts/{postId}/update-metadata |
Update post metadata |
GET /v1/analytics/youtube/daily-views |
Get YouTube daily views |
GET /v1/analytics/youtube/channel-insights |
Get YouTube channel insights |
GET /v1/analytics/youtube/demographics |
Get YouTube demographics |
GET /v1/analytics/youtube/video-retention |
Get YouTube video retention curve |
POST /v1/inbox/comments/{postId}/{commentId}/moderation |
Set comment moderation status |
POST /v1/inbox/posts/{postId}/like |
Like post |
DELETE /v1/inbox/posts/{postId}/like |
Unlike post |
#Webhook events
| Event | When it fires |
|---|---|
post.published |
Post published. |
post.failed |
Post failed. |
post.platform.published |
Post platform published. |
post.platform.failed |
Post platform failed. |
post.platform.deleted |
Post platform deleted. |
account.disconnected |
Account disconnected. |
comment.received |
Comment received. |