Posts
Upload videos to YouTube through the shared posts API, with visibility settings, Shorts, and scheduling.
Publishing to a YouTube channel means uploading exactly one video through the shared posts surface with mediaKind: "video". Any other kind is rejected.
Quick Start
Upload a video in under a minute:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "My first upload",
"mediaUrls": [
"https://cdn.onepostly.com/media/demo.mp4"
],
"mediaKind": "video",
"destinations": [
{
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "My first upload",
"mediaUrls": [
"https://cdn.onepostly.com/media/demo.mp4"
],
"mediaKind": "video",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "youtube",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.youtube.com/watch?v=17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}The response is 202 Accepted with status: "queued". When published, the destination carries the YouTube video id (externalPostId) and a https://www.youtube.com/watch?v=VIDEO_ID link. Poll GET /v1/posts/:id until the destination is published or failed, or receive webhooks instead of polling.
Content Types
Regular Video
Long-form, horizontal (16:9) content. Upload exactly one video and YouTube handles the rest:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.createPost({
createPostBody: {
"text": "Build a REST API from Scratch",
"mediaUrls": [
"https://cdn.onepostly.com/media/rest-api-tutorial.mp4"
],
"mediaKind": "video",
"destinations": [
{
"accountId": "YOUR_YOUTUBE_ACCOUNT_ID",
"privacyStatus": "public",
"description": "In this tutorial, I walk through building a REST API from scratch.\n\nTimestamps and links in the description."
}
]
},
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "Build a REST API from Scratch",
"mediaUrls": [
"https://cdn.onepostly.com/media/rest-api-tutorial.mp4"
],
"mediaKind": "video",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "YOUR_YOUTUBE_ACCOUNT_ID",
"platform": "youtube",
"status": "draft",
"externalPostId": "17895695668004550",
"externalUrl": "https://www.youtube.com/watch?v=17895695668004550",
"errorCode": null,
"errorMessage": null,
"publishedAt": "2026-08-25T10:00:00.000Z",
"metrics": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Shorts
There is no separate Shorts post type. YouTube classifies a video as a Short when it is vertical (9:16) and 3 minutes or shorter. Anything else is a regular video. No flag is needed.
- The upload flow is identical to regular videos
- Shorts do not support custom thumbnails
- Videos under 15 seconds loop automatically on YouTube
Destination Parameters
Every YouTube destination accepts the following parameters.
Prop
Type
text maps to the YouTube title, truncated at 100 characters. A title override wins when both are present. All fields above are YouTube-only. Sending them on a non-YouTube destination returns VALIDATION_ERROR.
Title And Description Formatting
Both are plain text. Markdown or HTML tags appear as literal characters on YouTube. Newlines work: use "Line one\nLine two" in JSON.
Media Requirements
| Property | Requirement |
|---|---|
| Videos per post | Exactly 1 |
| Onepostly upload cap | 256 MB |
| YouTube native limit | 256 GB |
| Formats | MP4, MOV, AVI, WMV, FLV, 3GP, WebM |
| Recommended | MP4 (H.264 video, AAC audio) |
| Regular videos | 16:9 horizontal, 1080p or higher |
| Shorts | 9:16 vertical, 1080 × 1920, 3 minutes or shorter |
Media URL Requirements
These do not work as media URLs:
- Google Drive returns an HTML download page, not the file
- Dropbox returns an HTML preview page
- OneDrive / SharePoint returns HTML
- iCloud returns HTML
Test your URL in an incognito browser window. If you see a webpage instead of the raw video, the URL will not work.
Any public HTTPS URL that returns video bytes works. Host media on a fast, reliable CDN.
Processing takes time
Large videos can take 30 to 60 minutes to process on YouTube's side after the upload completes. The destination is already published at that point. Do not re-upload while YouTube is still processing.
Schedule
Add scheduledFor (local wall time) and timezone to the same call. The request shape is otherwise identical to an immediate upload.
timezonedefaults toUTC. Use an IANA name likeEurope/Istanbul- Do not append
Zor an offset toscheduledFor. It is local wall time - Scheduled posts return
201 Createdand upload automatically at the scheduled moment - Max 1 year ahead
- Cancel with
posts.cancelPost({ id })/posts.cancel_post(id=…), orDELETE /v1/posts/:id, any time before then
Limits
| Limit | Value |
|---|---|
| Onepostly upload cap | 256 MB |
| Google quota per upload | 1,600 units (~6 uploads/day on default quota) |
| Title length | 100 characters (longer input is truncated) |
| Description length | 5,000 characters (over-length returns VALIDATION_ERROR) |
If an upload fails right after you published several videos, you have likely hit the daily quota. Retry the next day.
Remote Delete
Removes the video from the channel and emits the post.platform.deleted webhook:
import { Configuration, PostsApi } from "@onepostly/sdk";
const postsApi = new PostsApi(
new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);
const response = await postsApi.deletePostDestination({
destinationId: "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
});
console.log(response);{
"post": {
"id": "b3e1a7c2-8f4d-4e6a-9c1b-2d3e4f5a6b7c",
"text": "My first upload",
"mediaUrls": [
"https://cdn.onepostly.com/media/demo.mp4"
],
"mediaKind": "video",
"status": "queued",
"scheduledFor": null,
"timezone": null,
"destinations": [
{
"id": "d4e5f6a7-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
"platform": "youtube",
"status": "queued",
"externalPostId": "dQw4w9WgXcQ",
"externalUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"errorCode": null,
"errorMessage": null,
"publishedAt": null
}
],
"createdAt": "2026-08-25T10:00:00.000Z",
"updatedAt": "2026-08-25T10:00:00.000Z"
}
}Publish Errors
| Code | Cause | Fix |
|---|---|---|
UNSUPPORTED_MEDIA_KIND | mediaKind other than video | YouTube only accepts video |
INVALID_MEDIA | More than one media URL, or a non-video file | Pass exactly one video URL |
MEDIA_UNFETCHABLE | The video URL could not be downloaded | Use a public HTTPS URL |
MEDIA_UPLOAD_FAILED | Upload rejected by YouTube | Check size (256 MB cap), format (MP4), and retry |
UPLOAD_LIMIT_EXCEEDED | The channel has used YouTube's daily video upload allowance | Post the remaining videos after the allowance resets |
QUOTA_EXCEEDED | YouTube's own API quota for the day is spent | Post again once the quota resets |
RATE_LIMITED | YouTube throttled the request | Wait out the window, then post again |
PERMISSION_DENIED | 403 from YouTube. Suspended channel or missing scopes | Check the error message, then reconnect the account |
UNSUPPORTED_FEATURE | quoteTweetId or reply fields sent to YouTube | These are not YouTube concepts. Remove them |
TOKEN_INVALID | Connection token expired or revoked | Reconnect the account |
VALIDATION_ERROR | Over-length description, or a YouTube-only field on another platform | Trim the description |