Skip to main content

Quick reference

FieldValue
Title limit100 characters
Description limit5,000 characters
Tags limit500 chars total (sum of tags)
Videos per post1
Video formatsMP4, MOV, WebM, MKV, FLV, AVI
Max file size256 GB (or 12 hours, whichever is less)
Max duration12 hours (verified accounts), 15 minutes otherwise
Shorts durationup to 60 seconds
Shorts aspect ratio9:16
Long-form aspect ratio16:9 recommended
Post typesVideo upload (Short or long-form, derived from duration + aspect)
First comment✅ Posts as a Community comment after upload
COPPA disclosure✅ Required (madeForKids boolean)

Before you start

YouTube requires every upload to declare madeForKids: true | false. The default in Postbreeze is false, but the choice matters — videos flagged Made For Kids have comments, notifications, and personalized ads disabled by YouTube. If you flag it wrong, you have to delete and re-upload. Always set this field explicitly so the decision is recorded in your code.
The connected YouTube channel must:
  • Have completed the OAuth flow.
  • Have a verified phone number (YouTube enforces this for any video > 15 minutes).
  • Have youtube.force-ssl scope granted (Postbreeze requests this by default — covers upload + first-comment).
Each upload counts as 1,600 quota units against the channel’s 10,000-unit daily quota. The first-comment call costs an additional 50 quota units. Postbreeze surfaces quota errors as YT_QUOTA_EXCEEDED. See Platform settings for the full platformOptions reference and Media uploads for the two ways to attach the video file (mediaItems URL ingest vs. pre-uploaded mediaIds).

Quick start

Workspace is inferred from your API key — no workspaceId argument. The flat shape (content + platforms) is canonical; pass YouTube’s madeForKids flag in platformOptions on every upload.
import Postbreeze from "@postbreeze/node";

const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY });

await postbreeze.posts.create({
  content: "60-second product demo",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaItems: [
    { type: "video", url: "https://cdn.example.com/demo.mp4" },
  ],
  platforms: [
    {
      accountId: "soc_youtube_…",
      platformOptions: {
        platform: "YOUTUBE",
        visibility: "PUBLIC",
        madeForKids: false,
      },
    },
  ],
});

Content types

Long-form video

Any video over 3 minutes, or any video that is not 9:16. The post content becomes the YouTube description; youtubeTitle (if set) becomes the YouTube title. If you don’t pass youtubeTitle, the first line of content (≤ 100 chars) is used.
await postbreeze.posts.create({
  content: "Deep dive on our new dashboard.\n\nChapters, links, and resources below.",
  scheduledFor: "2026-06-10T15:00:00Z",
  mediaItems: [
    { type: "video", url: "https://cdn.example.com/dashboard-deep-dive.mp4" },
  ],
  platforms: [
    {
      accountId: "soc_youtube_…",
      platformOptions: {
        platform: "YOUTUBE",
        visibility: "PUBLIC",
        madeForKids: false,
        youtubeTitle: "Our new dashboard — deep dive (16 min)",
      },
    },
  ],
});

Short

A video ≤ 3 minutes in 9:16 aspect ratio. YouTube auto-classifies it as a Short on their end — you don’t pass a Shorts flag; just ensure the file matches Shorts requirements.
await postbreeze.posts.create({
  content: "How we ship on Fridays",
  scheduledFor: "2026-06-10T15:00:00Z",
  mediaItems: [
    { type: "video", url: "https://cdn.example.com/friday-ship.mp4" },
  ],
  platforms: [
    {
      accountId: "soc_youtube_…",
      platformOptions: {
        platform: "YOUTUBE",
        visibility: "PUBLIC",
        madeForKids: false,
      },
    },
  ],
});

Unlisted preview

Set visibility: "UNLISTED" to upload without making the video discoverable. Useful for sharing with reviewers before public launch.
await postbreeze.posts.create({
  content: "Reviewer preview — please don't share",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaItems: [
    { type: "video", url: "https://cdn.example.com/preview.mp4" },
  ],
  platforms: [
    {
      accountId: "soc_youtube_…",
      platformOptions: {
        platform: "YOUTUBE",
        visibility: "UNLISTED",
        madeForKids: false,
      },
    },
  ],
});

Media requirements

Videos

PropertyRequirement
Videos per post1
FormatsMP4, MOV, WebM, MKV, FLV, AVI
Max file size256 GB
Max duration12 hours (verified accounts), 15 minutes (non-verified)
Shorts durationup to 3 minutes
Shorts aspect ratio9:16
Long-form aspect ratio16:9 recommended
CodecsH.264, VP9, AV1
Frame rate24–60 fps
Audio codecsAAC, MP3, Vorbis, Opus
YouTube targets accept a single video. Images, GIFs, and documents are rejected at validation. See Media uploads for the URL ingest vs. pre-upload trade-off.

Platform-specific fields

FieldTypeRequiredDescription
platform"YOUTUBE"YesDiscriminator.
visibility"PUBLIC" | "UNLISTED" | "PRIVATE"No (default PUBLIC)Honored by YouTube exactly as set.
madeForKidsbooleanNo (default false)COPPA disclosure. Disables comments + personalized ads when true. Set explicitly on every upload.
youtubeTitlestringNoOverride for the title (≤ 100 chars). Falls back to first line of content.
Full schema in Platform settings → YouTube.

First comment

YouTube exposes a commentThreads.insert endpoint that lets the channel owner post a top-level comment on a video they own. Postbreeze calls it after the upload completes — it costs 50 quota units, comes back with a commentId, and posts as the channel itself (not as a personal Google account).
await postbreeze.posts.create({
  content: "Our new dashboard in 60 seconds",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaItems: [
    { type: "video", url: "https://cdn.example.com/demo.mp4" },
  ],
  platforms: [
    {
      accountId: "soc_youtube_…",
      platformOptions: {
        platform: "YOUTUBE",
        visibility: "PUBLIC",
        madeForKids: false,
      },
      firstComment: "Timestamps in the description ⬇️",
    },
  ],
});
If the video has comments disabled (e.g. madeForKids: true, or you’ve set channel-wide comments off), the first-comment call fails with YT_COMMENTS_DISABLED and the warning banner appears on the post. The main upload still succeeds.
Comment limit: 10,000 characters.

Analytics

MetricAvailable
Views
Watch time (minutes)
Avg view duration
Likes
Dislikes❌ (YouTube removed this from the public API)
Comments
Subscribers gained from video
Shares
Click-through rate (impressions → views)
Audience demographics✅ (channel-level)
Refresh cadence: every 14 days. Per YouTube’s retention rule, raw MetricSnapshot rows are purged on disconnect or on platform 404 (video deleted, channel suspended).

Common errors

ErrorMeaningFix
YT_QUOTA_EXCEEDEDChannel hit the 10,000-unit daily quotaWait for the daily reset. The first-comment call is skipped if the upload alone burns most of the budget.
YT_COMMENTS_DISABLEDFirst-comment failed because the video doesn’t allow commentsDisable madeForKids for the video, or accept that first-comment is impossible on this content.
YT_INVALID_VIDEO_FORMATContainer or codec YouTube doesn’t acceptRe-encode to H.264 + AAC.
YT_MADE_FOR_KIDS_REQUIREDThe COPPA disclosure was missingPass madeForKids explicitly.
YT_UPLOAD_QUOTA_EXCEEDEDMore than the daily upload count for unverified accountsVerify the channel’s phone number.
YT_TITLE_TOO_LONGTitle > 100 charsTrim.
YT_DESCRIPTION_TOO_LONGDescription > 5,000 charsTrim.
YT_TOKEN_EXPIREDOAuth refresh failedReconnect.

What you can’t do

  • ❌ Edit video metadata after upload (planned for v2)
  • ❌ Schedule using YouTube Studio’s native scheduler (we run our own queue)
  • ❌ Upload to a Brand Account other than the connected one
  • ❌ Set a custom thumbnail via API (works only for long-form videos on verified channels and isn’t exposed in v1)
  • ❌ Pass tags, override the category, or flag synthetic media — Postbreeze applies server-side defaults (category 22 — People & Blogs)
  • ❌ Add to playlists at upload time
  • ❌ Premieres or Live streams
  • ❌ Community posts (text/poll/image posts) — only video uploads + first-comments
  • ❌ Set end screens or cards

Full control: nested shape

The nested shape (caption + targets, socialAccountId per target) is the lower-level surface the flat shape compiles down to. Use it when you want a single payload that targets multiple platforms with distinct per-platform captions or media. The YouTube platformOptions are identical.
import Postbreeze from "@postbreeze/node";

const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY });

await postbreeze.posts.create({
  caption: "60-second product demo",
  scheduledAt: new Date(Date.now() + 30_000).toISOString(),
  mediaIds: ["med_video_…"],
  targets: [
    {
      socialAccountId: "soc_youtube_…",
      platformOptions: {
        platform: "YOUTUBE",
        visibility: "PUBLIC",
        madeForKids: false,
        youtubeTitle: "Our new dashboard in 60s",
      },
      firstComment: "Timestamps in the description ⬇️",
    },
  ],
});