Platforms
YouTube
Connect a YouTube channel, upload and schedule videos and Shorts, manage comments and likes, and read video analytics.
Upload videos and Shorts, manage comments, like videos and read metrics.
Quick Reference
| Property | Value |
|---|---|
| Account types | The signed-in Google account's channel |
| Connection method | Google OAuth |
| Post types | video |
| Max media per post | 1 |
| Text limit | 5,000 characters, title 100 |
| Threads | No |
| Quotes | No |
| Remote delete | |
| Comments | |
| Likes | |
| Reposts | No |
| Bookmarks | No |
| Direct messages | No |
| Post analytics | |
| Account analytics | No |
Every post is exactly 1 video. Its text becomes the description. The title comes from the text unless you set youtube.title.
Connect
Sign in with the Google account that owns the channel. Without a channel, it fails. Connect in Dashboard → Connections, or with a connect session.
Permissions
| Scope | Used for |
|---|---|
youtube.upload | Uploading videos |
youtube.force-ssl | Comments, likes, deletes, uploads list and stats |
yt-analytics.readonly | Analytics, including shares |
Google lets the owner skip permissions. Missing ones fail with a *_PERMISSION code until you reconnect and grant them.
Staying Connected
Access renews itself, so canAutoRenew is true.
It ends if the owner removes access in Google. You then get TOKEN_INVALID. Reconnect with the same Google account. The id stays the same.
Publish
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Build a REST API from scratch",
"mediaKind": "video",
"media": [
{
"url": "https://cdn.onepostly.com/media/rest-api-tutorial.mp4"
}
],
"accounts": [
{
"accountId": "acc_YOUR_YOUTUBE_ACCOUNT_ID",
"youtube": {
"title": "Build a REST API from Scratch",
"description": "A step by step walkthrough.\n\n00:00 Intro\n02:15 Routes",
"privacyStatus": "public",
"tags": [
"api",
"tutorial"
],
"categoryId": "27"
}
}
]
},
});
console.log(response);{
"groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
"data": [
{
"id": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
"groupId": "grp_7d2e5b1a9c4f4a68b3e07f1c2d9a6e45",
"accountId": "acc_YOUR_YOUTUBE_ACCOUNT_ID",
"platform": "youtube",
"source": "onepostly",
"status": "publishing",
"text": "Build a REST API from scratch",
"mediaKind": "video",
"media": [
{
"url": "https://cdn.onepostly.com/media/rest-api-tutorial.mp4"
}
],
"thread": [],
"scheduledFor": null,
"timezone": null,
"options": {
"youtube": {
"title": "Build a REST API from Scratch",
"description": "A step by step walkthrough.\n\n00:00 Intro\n02:15 Routes",
"privacyStatus": "public",
"tags": [
"api",
"tutorial"
],
"categoryId": "27"
}
},
"platformPostId": null,
"url": null,
"error": null,
"publishedAt": null,
"metrics": null,
"createdAt": "2027-03-15T09:00:00.000Z",
"updatedAt": "2027-03-15T09:00:00.000Z"
}
]
}Once published, platformPostId is the video id and url its watch link.
Post Types
mediaKind | Publishes | Media |
|---|---|---|
video | A video or a Short | Exactly 1 video, up to 256 MB |
Any other mediaKind returns 400 VALIDATION_ERROR.
Shorts
A Short is a normal upload, with no flag. YouTube decides by its shape. It must be vertical or square and up to 3 minutes long.
Options
Options go under youtube. Another platform's options there return 400.
Prop
Type
categoryId | Category |
|---|---|
10 | Music |
20 | Gaming |
22 | People and Blogs |
27 | Education |
28 | Science and Technology |
An unknown category fails the post with VALIDATION_ERROR.
Title and Description
Without title, the title is the text on one line, cut at 100 with .... Empty text gives the title "Untitled video". Without description, it is the text. Both are plain text, so Markdown and HTML show as typed.
First Comment
If YouTube refuses firstComment, the video still publishes. A madeForKids video has comments off, so its first comment never posts.
Media Requirements
These are YouTube's limits, on top of the Media URL Requirements.
| Media | Formats | Max size |
|---|---|---|
| Videos | MP4, MOV, AVI and WebM | 256 MB |
A file outside these formats and sizes is refused. See Platform Limits.
| Property | Requirement |
|---|---|
| Recommended | MP4, H.264 and AAC |
| Regular video | 16:9, 1080p or higher |
| Short | 9:16 or 1:1, up to 3 minutes |
| Duration | Up to 15 minutes without phone verification |
A URL must send a Content-Length header. Uploaded files always do.
Large videos keep processing after published. Do not upload again.
Scheduling, Editing and Deleting
Scheduling and editing work as usual. The video uploads at the scheduled time. A published video returns NOT_EDITABLE. Delete it and upload it again.
Deleting removes the video, including one uploaded outside Onepostly. One already gone counts as deleted. A delete during upload returns POST_PUBLISHING.
Engagement
See Comments and Likes and Reposts for the requests.
| Action | Supported | Notes |
|---|---|---|
| List comments | Top-level, 50 per page, with some replies | |
| Comment or reply | As the channel, up to 10,000 characters | |
| Delete a comment | Ones the channel owns or moderates | |
| Like or unlike a video | Any video, as the channel | |
| Hide or like a comment | No | UNSUPPORTED_PLATFORM |
| Repost, quote, bookmark | No | UNSUPPORTED_PLATFORM |
Comments need the video's pst_ id. Outside videos get one after a sync. Likes also take any YouTube video id. Another account's pst_ id fails.
import { Configuration, EngagementApi } from "@onepostly/sdk";
const engagementApi = new EngagementApi(
new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);
const response = await engagementApi.likePost({
id: "acc_YOUR_YOUTUBE_ACCOUNT_ID",
postId: "dQw4w9WgXcQ",
});
console.log(response);{
"postId": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
"accountId": "acc_YOUR_YOUTUBE_ACCOUNT_ID",
"platform": "youtube",
"liked": true
}Commenting on a video with comments off returns COMMENTS_PERMISSION.
Inbox
YouTube has no inbox through Onepostly.
Analytics
Metrics
| Field | YouTube metric | Notes |
|---|---|---|
impressions | Views | Same as views |
reach | None | Always null |
views | Views | |
likes | Likes | |
comments | Comments | |
shares | Shares | Needs yt-analytics.readonly |
saves | None | Always null |
engagement | Likes + comments + shares |
Read Post Analytics
import { Configuration, AnalyticsApi } from "@onepostly/sdk";
const analyticsApi = new AnalyticsApi(
new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);
const response = await analyticsApi.getPostAnalytics({
id: "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
});
console.log(response);{
"postId": "pst_3c8a2d6e91b44f7a8e250d6b4c1f9a73",
"accountId": "acc_1699f4157fb643f49d2ac447491f32a8",
"platform": "youtube",
"status": "ready",
"metrics": {
"impressions": 456,
"reach": null,
"likes": 123,
"comments": 12,
"shares": 4,
"saves": null,
"views": 456,
"engagement": 139
},
"fetchedAt": "2027-03-15T10:00:00.000Z",
"error": null
}Freshness is the standard. Shares arrive a few days after the rest. A new video reads pending. A private one can stay pending until you reconnect. A removed video reads unavailable with VIDEO_NOT_FOUND, for good.
At capacity, it reads failed with QUOTA_EXCEEDED for a while.
List Account Media lists uploads, 20 per page. Its metrics and durationSec are null. It includes videos uploaded outside.
Limitations
Not available for YouTube through Onepostly.
- Text and image posts
- Threads and quotes
- Custom thumbnails
- Playlists, end screens and captions
- Community posts, Premieres and live streams
- Editing a published video
- Deleting outside videos
- Hiding or liking comments
- Direct messages
- Channel stats
Errors
Breaking a rule on this page returns 400 VALIDATION_ERROR and creates nothing. YouTube can also refuse the metadata at upload, failing the post the same way. Every code is in Errors, with its cause and fix.
Publish Errors
| Code | Cause | Fix |
|---|---|---|
INVALID_MEDIA | An empty file | Send a video that plays |
MEDIA_UNFETCHABLE | No download, or no Content-Length | Upload the file instead |
MEDIA_UPLOAD_FAILED | YouTube rejected the file | Re-encode as MP4 |
PUBLISH_FAILED | YouTube did not confirm the upload | Check YouTube Studio first |
UPLOAD_LIMIT_EXCEEDED | YouTube's daily upload limit | Post again the next day |
QUOTA_EXCEEDED | YouTube is at capacity | Try again in a few hours |
PERMISSION_DENIED | A suspended channel or missing permission | Check the channel, then reconnect |
NOT_EDITABLE | An edit after publish | Delete and upload again |
POST_PUBLISHING | A delete during upload | Delete once published |
Engagement Errors
| Code | Cause | Fix |
|---|---|---|
POST_ACCOUNT_MISMATCH | Another account's pst_ id | Use the YouTube video id |
COMMENTS_PERMISSION | Comments off, or not the channel's comment | Turn comments on, or skip it |
ENGAGEMENT_PERMISSION | The channel cannot rate videos | Reconnect with every permission |
Analytics Errors
| Code | Cause | Fix |
|---|---|---|
ANALYTICS_PENDING | No metrics yet | Read again later |
VIDEO_NOT_FOUND | The video is gone | Stop reading it |
QUOTA_EXCEEDED | YouTube is at capacity | Read again in a few hours |
LIST_FAILED | Uploads did not load | Try again later |