YouTube publishAt in the past publishes now: a guard for batches
The Videos resource says a past publishAt publishes the video immediately, and it works only on private videos. A Python guard for scheduled Shorts batches.

If you set status.publishAt to a time that has already passed, YouTube publishes the video immediately instead of scheduling it. The same field can be set only when the video's privacy status is private. When you upload a batch of Shorts that Sume produced overnight, a slow render or a timezone slip can turn a week of scheduled releases into fifteen instant posts, so check the value before you send it.
Both rules come from the Videos resource and the videos.insert reference, read on 2026-10-02.
What do the two rules say, exactly?
For status.publishAt, the Videos resource page says it is the date and time the video is scheduled to publish, that it can be set only if the privacy status is private, and that a past date triggers immediate publication. The insert reference lists status.privacyStatus and status.publishAt among the properties you can set on upload.
That gives a pairing rule for your body: a scheduled upload is privacyStatus: private plus a future publishAt. A past time publishes the video at once, and a non-private video cannot carry the field.
| What you send | Documented result |
|---|---|
private with a future publishAt | Scheduled |
private with a past publishAt | Publishes immediately |
public or unlisted with publishAt | Not allowed: the field can be set only on private videos |
No publishAt | Uses privacyStatus as sent |
Why do Sume batches make this more likely?
A Format bulk run queues up to 100 runs behind a concurrency window, and each child finishes when it finishes. If you compute the publish time when a render lands rather than when you plan the calendar, a delay moves the slot into the past. Compute the schedule once, at planning time, and store it next to the job id.
The jobs guide says a non-terminal job should be polled, not resubmitted. That also protects the calendar: a resubmitted render is a second video with its own slot.
A guard you can run
This function refuses a past time and a missing privacy pairing, and returns the status body to send. Times are UTC ISO strings, the common form for this field.
from datetime import datetime, timezone, timedelta
def scheduled_status(publish_at_iso, now=None, min_lead_minutes=15):
now = now or datetime.now(timezone.utc)
when = datetime.fromisoformat(publish_at_iso.replace("Z", "+00:00"))
if when < now + timedelta(minutes=min_lead_minutes):
raise ValueError(f"{publish_at_iso} is too close or past: YouTube would publish now")
return {"privacyStatus": "private", "publishAt": publish_at_iso}
now = datetime(2026, 10, 2, 12, 0, tzinfo=timezone.utc)
print(scheduled_status("2026-10-05T16:00:00Z", now))
try:
scheduled_status("2026-10-01T16:00:00Z", now)
except ValueError as e:
print("refused:", e)How far ahead should a slot be?
The guard above uses a 15-minute minimum lead. That number is ours, not YouTube's: the page says only that a past date publishes immediately. We chose a margin because your own clock, a slow upload, or a retry can eat a few minutes between computing the time and the API receiving it.
For a weekly series, keep the schedule in one list in your own system, with the Sume job id and the planned slot per row, and generate the status body from that list at upload time. Never derive the slot from the time a render finished.
What should I still verify by hand?
Upload one test Short with a schedule an hour out and read the video back to confirm it shows as scheduled. If your uploads from an unverified API project all come back private, that is a separate rule; see our post on the unverified project lock. Sume does not schedule or publish to YouTube; the upload call is yours.
The pages we read do not describe an undo for an early publish, and anyone who watched in the meantime has seen the video. That is why the guard raises an error instead of silently moving the slot: a refused upload is easy to retry, and an early public post is not.
Sources
Related posts
More in Developers
- YouTube search.list: 100 calls a day, and what Sume searches
search.list now has its own 100-calls-per-day bucket. What that means for finding reference Shorts, and why Sume trending search covers TikTok only.
- YouTube selfDeclaredMadeForKids vs madeForKids: set it on upload
On videos.insert you set status.selfDeclaredMadeForKids; the API returns madeForKids. How the two differ, and one template for a batch of AI-made Shorts.
- YouTube upload failureReason: codec, tooSmall, emptyFile
A failed YouTube upload reports one of six failureReason values. What each means and how to pre-check a clip with Sume video inspect before you upload.
- YouTube videos.batchGetStats: 1 quota unit to track your Shorts
videos.batchGetStats costs 1 quota unit and returns views, likes and comments for many videos. How to tie the numbers back to the Sume jobs that made them.
Written by Sume