Creator OS
automationclischedulingyoutube

Schedule YouTube Videos From the Terminal: CLI Guide

Learn how to schedule YouTube videos from the terminal with the YouTube Data API, OAuth setup, and the Creator OS CLI, plus upload quota tips.

Creator OS · October 17, 2026 · Updated October 21, 2026 · 8 min read

If you already live in a shell, opening a browser to schedule youtube videos from the terminal feels backwards. The YouTube Data API gives you everything you need to upload a file and set a publish time, and the whole job fits in one command. This guide walks the raw API path first, because it teaches you what the platform actually checks. Then it shows the shorter path: a CLI that handles the OAuth dance, the resumable upload and the platform rules for you.

  raw API path                  Creator OS CLI path
  ------------------            ------------------------
  Google Cloud project          creatoros auth:check
       |                             |
  OAuth client + token          creatoros accounts:list
       |                             |
  resumable upload (PUT)        creatoros media:upload v.mp4
       |                             |
  videos.insert + snippet       creatoros posts:create \
       |                           --platforms youtube \
  status.publishAt                   --scheduledAt ...
       |                             |
  quota units burned            posts:list --status scheduled
Two terminal routes to a scheduled YouTube upload: hand-rolled API calls, or one CLI.

Why Schedule YouTube Videos From the Terminal

Three reasons hold up in practice.

Batch work is cheap. If you ship five videos a week, a script that reads a folder, uploads each file and assigns publish times beats five trips through Studio. You describe the schedule once, in a data file or a loop, and rerun it.

Everything is versioned. A shell script or a JSON calendar lives in git next to your video files. You can review a schedule change the same way you review a code change.

Agents can drive it. A command line is the easiest interface for an AI agent to call. Creator OS ships a CLI and a hosted MCP server, so an assistant can create the post for you. More on that below.

Get started for $0

What You Need Before You Start

  • A Google account with a YouTube channel enabled.
  • A Google Cloud project with the YouTube Data API v3 switched on.
  • An OAuth client (Desktop app type is easiest for a local script).
  • Python 3 with the Google client libraries, or just curl and a token you already have.
  • The video file on local disk.

One thing worth knowing early: YouTube counts uploads against a daily quota. The default allowance is small, and each upload costs a fixed number of units. If you plan to schedule more than a handful of videos a day, read YouTube Data API upload quota and how to avoid it before you build anything. It explains what actually burns the budget and which fields you can stop requesting.

Set Up OAuth and the YouTube Data API for Terminal Use

In the Cloud console: create a project, enable YouTube Data API v3, then create an OAuth consent screen and a Desktop OAuth client. Download the client secret JSON and keep it out of your repo.

Request these scopes. The upload scope is the one that matters for scheduling:

https://www.googleapis.com/auth/youtube.upload
https://www.googleapis.com/auth/youtube

Run the local OAuth flow once. The Google libraries open a browser, you approve, and a refresh token lands in a token file. After that, the script refreshes access tokens silently, so your scheduled jobs never need a human.

Sanity check the credential before you build the upload path:

python -c "from google.oauth2.credentials import Credentials; \
c = Credentials.from_authorized_user_file('token.json'); \
print(c.valid, c.expiry)"

If that prints True, you are ready.

Upload and Schedule a Video With a cURL Command

You cannot schedule a video with a single curl call. Two calls are involved, and the first one is the reason most people give up on the raw path.

YouTube wants a resumable upload. You tell the API your file size, it hands back an upload URL, and you PUT the bytes to that URL. That second URL is a session URL, not a resource URL, so it does not appear in the docs as a fixed endpoint. The request shape looks like this, where $TOKEN is a fresh access token:

curl -X POST \
  "https://www.googleapis.com/upload/youtube/v3/videos\
?uploadType=resumable&part=snippet,status" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "snippet": {
      "title": "Terminal scheduling demo",
      "description": "Uploaded from a shell script.",
      "categoryId": "28"
    },
    "status": {
      "privacyStatus": "private",
      "publishAt": "2026-03-14T15:00:00Z",
      "selfDeclaredMadeForKids": false
    }
  }'

The response includes a Location header. That is your upload URL. PUT the file there:

curl -X PUT "$LOCATION" \
  -H "Content-Type: video/mp4" \
  --data-binary @demo.mp4

Two rules to internalize. First, publishAt must be an RFC 3339 timestamp in UTC, and it only takes effect when privacyStatus is private. Set it public and the timestamp is ignored. Second, the video needs to finish processing before the scheduled time arrives, so leave a buffer.

If you want a custom thumbnail or a pinned first comment, those are separate calls after the upload completes. Creator OS handles the pinned comment for you, which is covered in YouTube auto pin comment.

Upload and Schedule Videos With a Python CLI Script

The Python client wraps the same two steps and is far easier to loop over. Sketch:

from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload

youtube = build("youtube", "v3", credentials=creds)

body = {
    "snippet": {"title": title, "description": desc, "categoryId": "28"},
    "status": {
        "privacyStatus": "private",
        "publishAt": "2026-03-14T15:00:00Z",
        "selfDeclaredMadeForKids": False,
    },
}

media = MediaFileUpload("demo.mp4", chunksize=-1, resumable=True)
req = youtube.videos().insert(
    part="snippet,status", body=body, media_body=media
)

response = None
while response is None:
    status, response = req.next_chunk()
    print(status.progress())

The chunk loop is the part people skip and then wonder why large files fail on flaky connections. Keep it.

Then wrap the script in a CLI feel with argparse or click, so you can run:

./yt-schedule.py --file demo.mp4 --at 2026-03-14T15:00:00Z

For Shorts, note that vertical videos under three minutes become Shorts automatically. You do not need a separate endpoint. If your Shorts pipeline is getting complicated, YouTube Shorts automation covers the details that differ.

Move the publishAt Logic Into a Reusable Shell Script

Once the Python piece works, put a thin shell wrapper around it with the scheduling logic in one place. Read a JSON calendar, compute timestamps, call the Python script for each row:

#!/usr/bin/env bash
set -euo pipefail

SLOT_HOUR=15

while IFS=$'\t' read -r file day; do
  at="${day}T${SLOT_HOUR}:00:00Z"
  echo "scheduling $file at $at"
  ./yt-schedule.py --file "$file" --at "$at"
done < calendar.tsv

Keep the calendar file in git. Every schedule change becomes a reviewable diff, and reruns are idempotent if you track which rows already uploaded. Store the returned video IDs in a local file so a rerun does not double post.

This is a good place to be honest about the maintenance cost. Tokens expire, scopes change, quota resets, and every platform you add doubles the plumbing. If YouTube is one of five networks you publish to, the shell script grows into a small product.

Avoid YouTube Data API Upload Quota Limits

Quota is the wall you hit first. The API grants a fixed number of units per project per day, and an insert consumes a large chunk of it. Practical habits that keep you running:

  • Request only the parts you need. Do not pull statistics on the same call as the insert.
  • Do not poll video status in a tight loop. Check a few times with a gap.
  • Upload the file once. Do not re-insert to change a title or a publish time.
  • Spread bulk uploads across days rather than firing fifty inserts at midnight.

The quota post linked above goes deeper on the unit math and the field-level tricks. Read it before you design a bulk pipeline.

Add Terminal Scheduling to Creator OS

If you would rather not own OAuth refresh, resumable uploads and quota math, Creator OS ships a CLI and a hosted MCP server so an AI agent or your own terminal can run your accounts. Install the CLI and check auth:

npx @creatoros/cli@latest init
creatoros auth:check
creatoros accounts:list

Upload the file once, then create the post with a publish time:

creatoros media:upload ./demo.mp4
creatoros posts:create \
  --text "Terminal scheduling demo" \
  --platforms youtube \
  --title "Terminal scheduling demo" \
  --media med_abc123 \
  --scheduledAt 2026-03-14T15:00:00Z \
  --timezone America/Toronto

Creator OS applies the platform rules for you: YouTube titles cap at 100 characters, Shorts are detected from a vertical file, and a first comment can be posted and pinned rather than sitting unpinned. You can leave a post as a draft with --draft, or check the queue later:

creatoros posts:list --status scheduled
creatoros posts:get post_xyz
creatoros analytics:posts --platform youtube

The same CLI covers other networks with a --platforms list, and a single med_ id is reusable across them. That is the part a hand-rolled YouTube script cannot give you: one upload, many destinations. The CLI scheduling guide walks through the flags, and scheduling TikTok videos with Claude shows the same pattern on a different network.

For agents, connect the hosted MCP server at https://mcp.creatoros.ca/mcp. Claude Code takes one command:

claude mcp add --transport http creatoros \
  https://mcp.creatoros.ca/mcp \
  --header "Authorization: Bearer cos_live_..."

Tool names include create_post, update_post, upload_media_from_url, list_posts, get_post_analytics and get_best_time_to_post. Read-only connections only see read tools, and the API refuses writes from them. Destructive tools are marked so the AI app asks before running. The YouTube MCP server post covers the YouTube side in depth, and the MCP docs list every connector path for ChatGPT, Cursor, Windsurf and VS Code.

There is also a REST API if you prefer HTTP over a binary. Authenticate with a cos_live_... key pinned to one workspace, then post a simple form:

POST /v1/posts
{
  "content": "Terminal scheduling demo",
  "platforms": ["youtube"],
  "media": ["med_abc123"],
  "schedule_at": "2026-03-14T15:00:00Z",
  "timezone": "America/Toronto",
  "post_type": "video"
}

The base URL lives in the docs at https://www.creatoros.ca/docs. Errors come back in one shape: { error: { code, message, status } }. Networks you named but have not connected come back in missing_platforms, and the rest still publish.

If you want to see a fuller agent workflow, including the open-source Social Agents harness and the Open Video Edit skills that turn a raw clip into a short, watch the walkthroughs on the KevBuildsApps YouTube channel. The launch video is at https://www.youtube.com/watch?v=-QBJH_PK3pY.

Get started with Creator OS

The Creator plan is $14.99/month or $59.99/year and covers 11 platforms plus Skool, one account on each. API keys, the MCP server, the CLI and the agent skills are included in every plan. Sign up at https://www.creatoros.ca/sign-up and run creatoros auth:check to confirm the connection.

Get started for $0
Free trial for new subscribers. Cancel anytime.

Keep reading