How to Auto-Post YouTube Shorts via API

Auto-publish YouTube Shorts at scale with Postproxy. Title, privacy, tags, category, AI-disclosure, and how scheduling actually works.

What YouTube actually requires for a Short

A YouTube Short is a regular video that meets two conditions:

  1. Aspect ratio 9:16 (vertical)
  2. Duration ≤ 60 seconds

YouTube auto-detects Shorts from these properties — there is no separate Shorts endpoint. It’s videos.insert with a 9:16 ≤60s file. The hard parts are OAuth refresh, the resumable upload protocol, and getting Google to approve your project for production use (without that, refresh tokens silently expire after seven days).

With Postproxy

Terminal window
curl -X POST "https://api.postproxy.dev/api/posts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"post": {
"body": "60 seconds on the new release.\n\n#Shorts"
},
"profiles": ["youtube"],
"media": ["https://yourcdn.com/short-1080x1920.mp4"],
"platforms": {
"youtube": {
"title": "How we cut publishing latency in half",
"privacy_status": "public"
}
}
}'

Postproxy runs against a verified YouTube project and handles OAuth refresh, the resumable upload, and the Shorts metadata. You hand over an MP4 and a title; the rest is on us.

YouTube platform parameters

ParameterTypeRequiredDescription
titlestringNoVideo title (max 100). Defaults to first line of body if omitted
privacy_statusstringYespublic, unlisted, or private
cover_urlstringNoCustom thumbnail URL (requires verified YouTube account)
tagsarrayNoTags for discovery
category_idstringNoYouTube category ID (default: "22" / People & Blogs)
made_for_kidsbooleanNoRequired by YouTube as of 2020
contains_synthetic_mediabooleanNoDisclose AI-generated content

The post body becomes the YouTube description (max 5,000 chars).

Terminal window
curl -X POST "https://api.postproxy.dev/api/posts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"post": {
"body": "Why we cut our publishing latency in half.\n\nFull writeup: postproxy.dev/blog\n\n#Shorts #BuildInPublic"
},
"profiles": ["youtube"],
"media": ["https://yourcdn.com/latency-cut.mp4"],
"platforms": {
"youtube": {
"title": "How we cut publishing latency in half",
"privacy_status": "public",
"tags": ["shorts", "buildinpublic", "saas"],
"category_id": "28",
"made_for_kids": false,
"contains_synthetic_media": false
}
}
}'

Common category IDs: 22 (People & Blogs), 27 (Education), 28 (Science & Technology), 26 (How-to & Style), 24 (Entertainment), 10 (Music).

Scheduled publish

YouTube’s API doesn’t accept a future timestamp on a public upload — only on a private upload via status.publishAt. Pass scheduled_at to Postproxy and it holds the post (uploads at the scheduled time):

Terminal window
curl -X POST "https://api.postproxy.dev/api/posts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"post": {
"body": "Tutorial drop: AI agents that can publish.",
"scheduled_at": "2026-05-20T15:00:00Z"
},
"profiles": ["youtube"],
"media": ["https://yourcdn.com/agents-tutorial.mp4"],
"platforms": {
"youtube": {
"title": "AI agents that publish",
"privacy_status": "public"
}
}
}'

Custom thumbnail

{
"platforms": {
"youtube": {
"title": "How to ship faster",
"privacy_status": "public",
"cover_url": "https://yourcdn.com/thumbnail-1280x720.jpg"
}
}
}

cover_url requires a verified YouTube account (phone-verified). Without verification, YouTube auto-generates the thumbnail from a frame.

Bulk auto-posting from a content folder

A common pattern: an editorial team produces 3 Shorts a week. The team uploads finished MP4s to a shared folder; a worker picks them up and queues them.

pip install postproxy-sdk

import asyncio
import glob
import os
from postproxy import PostProxy, PlatformParams, YouTubeParams
QUEUE_ID = "q_yt_shorts"
async def main():
async with PostProxy(os.environ["POSTPROXY_API_KEY"], profile_group_id="pg-abc") as client:
for path in sorted(glob.glob("/content/shorts/*.mp4")):
cdn_url = upload_to_cdn(path)
title = os.path.basename(path).replace(".mp4", "").replace("-", " ").title()
await client.posts.create(
f"{title}\n\n#Shorts",
profiles=["youtube"],
media=[cdn_url],
queue_id=QUEUE_ID,
platforms=PlatformParams(
youtube=YouTubeParams(
title=title,
privacy_status="public",
),
),
)
os.rename(path, path + ".queued")
asyncio.run(main())

The queue assigns each Short to the next slot. See Schedule TikTok posts for the queue creation example, or the Queues API docs.

Common failure modes

SymptomCauseMitigation
quotaExceededYouTube project daily upload quota hitPostproxy runs on a verified project; transient quota errors retry automatically
invalid_grant on refreshRefresh token revokedRe-do OAuth (one-click reconnect in Postproxy)
Upload stalls at ~80%Resumable session timeoutPostproxy retries from last chunk
Video uploads but isn’t a ShortWrong aspect ratio (16:9) or >60sRe-encode to 1080×1920, ≤60s

DIY vs delegate

DIY makes sense if you publish 1-2 videos a week and have an in-house engineer who can babysit the pipeline. Delegate when you cross any of: 5+ videos/week, multi-channel, multi-brand, or “the marketer needs to be unblocked when an engineer is on vacation.” For the deeper guide, see YouTube upload API guide and YouTube API posting integration.

Ready to get started?

Start with our free plan and scale as your needs grow. No credit card required.