YouTube API - Upload Video via REST API
Upload videos to YouTube programmatically using the Publora REST API. A simpler alternative to the official YouTube Data API, YouTube API client libraries, or Google APIs.
YouTube API Overview
Publora provides a unified REST API for uploading and publishing videos to YouTube with configurable privacy (public, unlisted, private), metadata (title, description, tags, category, made-for-kids), YouTube playlists, and a tracked custom thumbnail. No need to manage Google OAuth flows, handle resumable uploads, or navigate YouTube API quotas.
Why Use Publora Instead of YouTube Data API?
| Feature | Publora API | YouTube Data API |
|---|---|---|
| Authentication | Single API key | Complex Google OAuth 2.0 |
| Video upload | Simple REST endpoint | Resumable upload protocol |
| Quota management | Handled automatically | Manual quota tracking |
| Multi-platform | Post to 10 platforms | YouTube only |
| Setup time | 5 minutes | Hours (Google Cloud setup) |
| Privacy controls | Full support | Full support |
Keywords: YouTube API, YouTube upload video API, YouTube Data API, upload to YouTube programmatically, YouTube video upload API, YouTube posting API, YouTube REST API, YouTube developer API, YouTube automation API, publish video YouTube API, YouTube content API
Platform ID Format
youtube-{channelId}Where {channelId} is your YouTube channel ID assigned during account connection via Google OAuth.
Requirements
- A YouTube channel connected via Google OAuth through the Publora dashboard
- API key from Publora
- Video content is required (YouTube is a video platform)
Refresh token lifespan: When the OAuth provider does not specify an expiry, Publora defaults the refresh token lifespan to approximately 182 days (~6 months), calculated as
365/2days in the code. After that period, the user will need to re-authenticate via the Publora dashboard to reconnect their YouTube channel.
Supported Content
| Type | Supported | Limits |
|---|---|---|
| Text only | No | YouTube requires a video |
| Images | No | Not supported as standalone posts |
| Videos | Yes | MP4, MOV, AVI, WebM formats, single video only |
Important: YouTube posts through Publora support only a single video per post. If you attempt to attach multiple videos, the API will reject the request. To publish multiple videos, create separate posts for each.
Platform-Specific Settings
YouTube supports a platformSettings object for video metadata, a YouTube playlist, and a tracked custom thumbnail:
{
"platformSettings": {
"youtube": {
"privacy": "public",
"title": "My video title",
"tags": ["api", "tutorial"],
"categoryId": "22",
"madeForKids": false,
"playlist": {
"id": "PLxxxxxxxx",
"platformId": "youtube-UCxxxxxxxx"
},
"thumbnail": {
"mediaId": "665f...",
"url": "https://media.publora.com/..."
}
}
}
}| Setting | Values | Default | Description |
|---|---|---|---|
privacy |
"private", "public", "unlisted" |
"public" |
Video visibility on YouTube |
title |
string | "" (empty string) |
Title displayed on the video page. If empty, the publisher derives a title from the first 70 characters of the post content at publish time. |
tags |
string[] (or comma-separated string) | — | Video tags (snippet.tags). YouTube caps the combined length at 500 characters (tags containing whitespace are quote-wrapped, costing +2 chars each); the API trims the list to fit, so an oversized list can't fail the upload. Omitted entirely when empty. |
categoryId |
string | — | YouTube video category ID (e.g. "22" People & Blogs, "28" Science & Technology). When omitted, YouTube applies the channel's default. An invalid id will fail the upload — see Category IDs. |
madeForKids |
boolean | false |
Sets status.selfDeclaredMadeForKids — YouTube's required "made for kids" (COPPA) audience declaration. Always sent on every upload; defaults to false (not made for kids). |
playlist |
object { id, platformId } |
— | Add the published video to a YouTube playlist. playlist.id is the YouTube playlist id; playlist.platformId is the compound channel id (e.g. youtube-UCxxxxxxxx) and must match the single selected YouTube channel. Settable on create-post; changeable or clearable via update-post. See Playlist. |
thumbnail |
object { mediaId, url } |
— | Custom video thumbnail referencing a Publora-tracked media asset (uploaded via the dedicated thumbnail endpoint, which returns mediaId and url). Set via update-post (the upload requires a postGroupId). See Custom Thumbnail. |
Note: The full
contentis used as the video description. PassplatformSettingsdirectly in thecreate-postrequest body — the API merges your values with the per-platform defaults.
Tags
Pass tags as an array of strings (["api", "tutorial"]) or a comma-separated string ("api, tutorial"). The API trims blanks and enforces YouTube's 500-character combined cap by keeping tags until the budget is exhausted, so an over-long list is truncated rather than rejected.
Made for kids
madeForKids maps to YouTube's required selfDeclaredMadeForKids audience declaration and is sent on every upload (defaulting to false). Set it to true if your content is directed at children, per YouTube/COPPA rules.
Category IDs
categoryId accepts a YouTube category ID string. Common values: "22" (People & Blogs), "28" (Science & Technology), "24" (Entertainment), "27" (Education), "10" (Music). An invalid or region-disallowed id causes YouTube to reject the whole upload, so leave it unset to fall back to the channel default if unsure.
Playlist
Add the published video to one of the channel's YouTube playlists.
{
"platformSettings": {
"youtube": {
"playlist": {
"id": "PLxxxxxxxx",
"platformId": "youtube-UCxxxxxxxx"
}
}
}
}playlist.id— the YouTube playlist id.playlist.platformId— the compound YouTube platform id fromplatforms(e.g.youtube-UCxxxxxxxx). It must match the single selected YouTube channel.- A playlist can only be set when exactly one YouTube channel is selected.
- Set the playlist on
create-post, and change or clear it viaupdate-post. - The video is added to the playlist after the YouTube upload finishes processing.
- Best-effort: if adding the video to the playlist fails, the video stays published — only the playlist association is skipped.
- You cannot change the playlist while the post is already
processing(returns409).
Clear the playlist by sending empty strings:
{
"platformSettings": {
"youtube": {
"playlist": {
"id": "",
"platformId": ""
}
}
}
}Playlist errors:
| Error | Cause |
|---|---|
platformSettings.youtube.playlistId is not supported; use platformSettings.youtube.playlist. |
Legacy playlistId field was sent — use the playlist object |
platformSettings.youtube.playlist must be an object. |
playlist is not an object |
platformSettings.youtube.playlist.id must be a string. |
playlist.id is not a string |
platformSettings.youtube.playlist.platformId must be a string. |
playlist.platformId is not a string |
YouTube playlist requires both playlist.id and playlist.platformId. |
One of the two fields is missing |
YouTube playlist can only be set when exactly one YouTube channel is selected. |
More than one (or no) YouTube channel is selected |
YouTube playlist platformId must match the selected YouTube channel. |
playlist.platformId does not match the chosen channel |
Post group is currently being published; cannot change YouTube playlist. Retry once publishing completes. |
The post is publishing/processing |
Custom Thumbnail
A custom thumbnail is a Publora-tracked media asset, referenced by the thumbnail object:
{
"platformSettings": {
"youtube": {
"thumbnail": {
"mediaId": "665f...",
"url": "https://media.publora.com/..."
}
}
}
}Flow:
- Create a draft post (
create-post). - Upload the thumbnail image through the dedicated YouTube thumbnail upload endpoint — it requires the
postGroupId. - Use the returned
mediaIdandurl. - Update the post with
platformSettings.youtube.thumbnail(update-post). - Schedule the post.
Note: A thumbnail cannot be passed on
create-post— the thumbnail upload requires apostGroupId, so the post must exist first.
Constraints:
- Format: JPEG or PNG.
- Max size: 2 MB.
- Min dimensions: 640×360. Recommended: 1280×720 (16:9).
- Must be Publora-uploaded / tracked media — pass the
mediaIdandurlreturned by the thumbnail upload endpoint. Arbitrary URLs (including rawmedia.publora.comlinks) are not accepted; uploading via the generic media-upload flow is not sufficient. - The custom thumbnail is applied after the video finishes processing, via the YouTube
thumbnails.setAPI. - Best-effort: if YouTube rejects the thumbnail (unverified channel, oversized/invalid image), the video still publishes — only the thumbnail is skipped.
- Requires a verified YouTube channel.
- You cannot change the thumbnail while the post is already
processing(returns409).
Clear the thumbnail by sending an empty url:
{
"platformSettings": {
"youtube": {
"thumbnail": {
"url": ""
}
}
}
}Privacy Settings
| Value | Description |
|---|---|
private |
Only you can view the video |
public |
Anyone can search for and view the video |
unlisted |
Anyone with the link can view, but it will not appear in search results |
Title Behavior
If you do not specify a title in the platform settings, the API defaults the title to an empty string "" at post creation. The publisher service may derive a title from the first 70 characters of the post content at publish time. The full content is used as the video description. Pass platformSettings directly in the create-post request body. The API merges user-provided settings with defaults per platform.
Examples
Upload a Public Video
JavaScript (fetch)
const response = await fetch('https://api.publora.com/api/v1/create-post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-publora-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
content: 'How to Build a REST API in 10 Minutes - A complete tutorial covering Express.js setup, routing, middleware, error handling, and deployment to production. Perfect for beginners who want to get started with backend development.',
platforms: ['youtube-UCxxxxxxxx'],
platformSettings: {
youtube: {
privacy: 'public',
title: 'How to Build a REST API in 10 Minutes'
}
}
})
});
const data = await response.json();
console.log(data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }Python (requests)
import requests
response = requests.post(
'https://api.publora.com/api/v1/create-post',
headers={
'Content-Type': 'application/json',
'x-publora-key': 'YOUR_API_KEY'
},
json={
'content': 'How to Build a REST API in 10 Minutes - A complete tutorial covering Express.js setup, routing, middleware, error handling, and deployment to production. Perfect for beginners who want to get started with backend development.',
'platforms': ['youtube-UCxxxxxxxx'],
'platformSettings': {
'youtube': {
'privacy': 'public',
'title': 'How to Build a REST API in 10 Minutes'
}
}
}
)
data = response.json()
print(data)
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }cURL
curl -X POST https://api.publora.com/api/v1/create-post \
-H "Content-Type: application/json" \
-H "x-publora-key: YOUR_API_KEY" \
-d '{
"content": "How to Build a REST API in 10 Minutes - A complete tutorial covering Express.js setup, routing, middleware, error handling, and deployment to production. Perfect for beginners who want to get started with backend development.",
"platforms": ["youtube-UCxxxxxxxx"],
"platformSettings": {
"youtube": {
"privacy": "public",
"title": "How to Build a REST API in 10 Minutes"
}
}
}'
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }Node.js (axios)
const axios = require('axios');
const response = await axios.post('https://api.publora.com/api/v1/create-post', {
content: 'How to Build a REST API in 10 Minutes - A complete tutorial covering Express.js setup, routing, middleware, error handling, and deployment to production. Perfect for beginners who want to get started with backend development.',
platforms: ['youtube-UCxxxxxxxx'],
platformSettings: {
youtube: {
privacy: 'public',
title: 'How to Build a REST API in 10 Minutes'
}
}
}, {
headers: {
'Content-Type': 'application/json',
'x-publora-key': 'YOUR_API_KEY'
}
});
console.log(response.data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }Note: YouTube requires a video. First create the post, then upload the video using the media upload workflow with the returned
postGroupId.
Upload an Unlisted Video
JavaScript (fetch)
const response = await fetch('https://api.publora.com/api/v1/create-post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-publora-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
content: 'Internal demo recording for the team. This video covers the new dashboard features and upcoming roadmap items. Please do not share this link externally.',
platforms: ['youtube-UCxxxxxxxx'],
platformSettings: {
youtube: {
privacy: 'unlisted',
title: 'Internal Demo - Q1 Dashboard Features'
}
}
})
});
const data = await response.json();
console.log(data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }Python (requests)
import requests
response = requests.post(
'https://api.publora.com/api/v1/create-post',
headers={
'Content-Type': 'application/json',
'x-publora-key': 'YOUR_API_KEY'
},
json={
'content': 'Internal demo recording for the team. This video covers the new dashboard features and upcoming roadmap items. Please do not share this link externally.',
'platforms': ['youtube-UCxxxxxxxx'],
'platformSettings': {
'youtube': {
'privacy': 'unlisted',
'title': 'Internal Demo - Q1 Dashboard Features'
}
}
}
)
data = response.json()
print(data)
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }cURL
curl -X POST https://api.publora.com/api/v1/create-post \
-H "Content-Type: application/json" \
-H "x-publora-key: YOUR_API_KEY" \
-d '{
"content": "Internal demo recording for the team. This video covers the new dashboard features and upcoming roadmap items. Please do not share this link externally.",
"platforms": ["youtube-UCxxxxxxxx"],
"platformSettings": {
"youtube": {
"privacy": "unlisted",
"title": "Internal Demo - Q1 Dashboard Features"
}
}
}'
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }Node.js (axios)
const axios = require('axios');
const response = await axios.post('https://api.publora.com/api/v1/create-post', {
content: 'Internal demo recording for the team. This video covers the new dashboard features and upcoming roadmap items. Please do not share this link externally.',
platforms: ['youtube-UCxxxxxxxx'],
platformSettings: {
youtube: {
privacy: 'unlisted',
title: 'Internal Demo - Q1 Dashboard Features'
}
}
}, {
headers: {
'Content-Type': 'application/json',
'x-publora-key': 'YOUR_API_KEY'
}
});
console.log(response.data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }Upload with Auto-Generated Title
If you omit the title setting, the API defaults the title to an empty string "" at post creation. The publisher service may derive a title from the first 70 characters of the post content at publish time. To set a custom title, include it in platformSettings when calling create-post.
JavaScript (fetch)
const response = await fetch('https://api.publora.com/api/v1/create-post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-publora-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
content: 'Weekly Vlog: What I learned shipping 3 features in 5 days. This week was intense but incredibly productive. We managed to ship the new analytics dashboard, the team collaboration feature, and a complete redesign of the onboarding flow.',
platforms: ['youtube-UCxxxxxxxx']
})
});
const data = await response.json();
// Note: title defaults to an empty string at post creation — the publisher service may derive one from content at publish time. Include platformSettings.youtube.title to set a custom one
// Description will be the full content
console.log(data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }Python (requests)
import requests
response = requests.post(
'https://api.publora.com/api/v1/create-post',
headers={
'Content-Type': 'application/json',
'x-publora-key': 'YOUR_API_KEY'
},
json={
'content': 'Weekly Vlog: What I learned shipping 3 features in 5 days. This week was intense but incredibly productive. We managed to ship the new analytics dashboard, the team collaboration feature, and a complete redesign of the onboarding flow.',
'platforms': ['youtube-UCxxxxxxxx']
}
)
data = response.json()
# Note: title defaults to an empty string at post creation — the publisher service may derive one from content at publish time. Include platformSettings.youtube.title to set a custom one
print(data)
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }cURL
curl -X POST https://api.publora.com/api/v1/create-post \
-H "Content-Type: application/json" \
-H "x-publora-key: YOUR_API_KEY" \
-d '{
"content": "Weekly Vlog: What I learned shipping 3 features in 5 days. This week was intense but incredibly productive. We managed to ship the new analytics dashboard, the team collaboration feature, and a complete redesign of the onboarding flow.",
"platforms": ["youtube-UCxxxxxxxx"]
}'
# Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }Node.js (axios)
const axios = require('axios');
const response = await axios.post('https://api.publora.com/api/v1/create-post', {
content: 'Weekly Vlog: What I learned shipping 3 features in 5 days. This week was intense but incredibly productive. We managed to ship the new analytics dashboard, the team collaboration feature, and a complete redesign of the onboarding flow.',
platforms: ['youtube-UCxxxxxxxx']
}, {
headers: {
'Content-Type': 'application/json',
'x-publora-key': 'YOUR_API_KEY'
}
});
// Note: title defaults to an empty string at post creation — the publisher service may derive one from content at publish time. Include platformSettings.youtube.title to set a custom one
console.log(response.data);
// Response: { "success": true, "postGroupId": "abc123...", "scheduledTime": null }Platform Quirks
- Video only: YouTube does not support text-only or image-only posts through the API. A video file must always be included.
- MP4, MOV, AVI, WebM supported: Publora validates video format server-side using the platform limits configuration (
postValidationService.jscallsisVideoFormatSupported(platform, mimeType)for all platforms including YouTube). YouTube accepts MP4, MOV, AVI, and WebM formats. If you upload an unsupported format, Publora will reject it before it reaches the YouTube API. - Single video only: Publora supports uploading one video per post. If you attempt to attach multiple videos, the API will reject the request with an error.
- Default title is empty string: When no
titleis specified in platform settings, the API defaults the title to an empty string""at post creation. The publisher service may derive a title from the first 70 characters of the post content at publish time. It is recommended to explicitly set the title viaplatformSettingsin thecreate-postrequest for best results. - Content becomes description: The full
contenttext is used as the YouTube video description, while thetitle(or auto-generated title) is used as the video title. - Processing time: After upload, YouTube processes the video before it becomes available. This can take from a few seconds to several minutes depending on video length and resolution.
- Daily upload quota: YouTube enforces daily upload quotas. If you hit the quota, Publora will return the error from the YouTube API.
- Privacy changes: You can upload as
privateorunlistedfirst, then manually change the privacy topubliclater through YouTube Studio.
API Limits
Text Limits
| Element | Limit |
|---|---|
| Video title | 100 characters |
| Video description | 1,000 characters (frontend validation only; YouTube allows up to 5,000) |
| Default title | Empty string "" at post creation (the publisher service may derive a title from the first 70 characters of post content at publish time) |
| Visible description | First 150 characters (shown before "Show more") |
Media Limits
| Media Type | Max Size | Max Duration | Supported Formats |
|---|---|---|---|
| Videos | 256 GB through the presigned API upload flow | 12 hours | MP4, MOV, AVI, WebM |
| Images | Not supported | - | - |
Additional Notes
- YouTube is a video-only platform; images cannot be posted as standalone content
- The first 150 characters of the description are visible without clicking "Show more"
- Video processing time varies based on length and resolution
- Presigned API uploads support YouTube's 256 GB / 12-hour limit. The separate dashboard multipart video-processing route has a 512 MB multer cap; that cap does not apply to presigned API uploads.
- Publora enforces a 1,000-character limit on video descriptions via frontend validation only. The API itself does not enforce this limit, so API users can send up to YouTube's native 5,000-character limit.
Publora is built by Creative Content Crafts, Inc. Need AI-powered content creation for LinkedIn, Threads, and X? Try Co.Actor — the best AI service for authentic thought leadership at scale.