2026-06-18
Automate YouTube with OpenClaw: Treat Publish as a Commit
A 60-case admission drill binds one reviewed render to one channel, resumable upload, video ID, thumbnail, and explicit public-release approval.
A video upload can finish at the exact moment an automation loses the response. That is the awkward case: the file may already exist on the channel, but the worker sees a timeout and is tempted to start again. A second upload does not repair the first one. It creates a second video ID, a second thumbnail target, and two objects that can later be published.
That failure changes the design of YouTube automation. Research, scripting, voice, and rendering are replaceable production steps. Uploading is a stateful transfer. Making the result public is a separate, reputational commit. An OpenClaw worker should cross those boundaries with receipts, not confidence.
A timeout is not evidence that the upload failed.
This teardown uses current YouTube Data API documentation and a deterministic 60-case admission fixture. It does not upload a video or authorize a Google account. Its useful result is narrower: a publish packet can be evaluated before any remote write, and an interrupted transfer can be reconciled before a retry creates new work.
The dangerous gap begins after the render
A generated MP4 is still a local artifact. It can be reviewed, hashed, replaced, or discarded without changing a channel. The risk jumps when an upload session is created. From that point on, the workflow owns remote state whose identity may outlive the process that initiated it.
The legacy five-stage story treated the handoffs as a smooth assembly line. Production systems are less polite. A thumbnail can be approved for one render and accidentally attached to another. OAuth consent can point at the wrong channel. A policy check can be complete while the audience declaration is absent. A transport timeout can hide a successful upload. A scheduled release can be configured against the wrong privacy state.
The fix is not a longer prompt. It is a packet that binds the intended channel, approved asset digest, metadata, rights review, audience and synthetic-media decisions, thumbnail, requested visibility, approval, resumable-session receipt, and eventual video ID. Every transition consumes that packet and emits a receipt for the next transition.
One reviewed render. One session. One video ID.
What YouTube accepts is not what the agent may publish
The official videos.insert reference accepts video media and writable metadata including title, description, category, privacy state, scheduled publication time, made-for-kids declaration, and synthetic-media disclosure. It also warns that uploads from unverified API projects created after July 2020 are restricted to private viewing until the project passes an audit.
Those are API capabilities, not a complete editorial policy. A valid request can still target the wrong channel, use an unreviewed render, omit a rights receipt, or publish copy that nobody approved. Treat the API as the final actuator behind a stricter local contract.
| Boundary | Receipt to retain | Failure that should stop the run |
|---|---|---|
| Render approval | Asset SHA-256 plus rights and policy review | The bytes no longer match the reviewed artifact |
| Channel authorization | Authorized channel identity plus upload scope | The intended and authorized channels differ |
| Upload session | Session URI bound to the asset digest | A retry cannot prove which bytes the session owns |
| Upload completion | Returned video ID | The response is ambiguous and session status was not queried |
| Thumbnail | Human choice plus media validation | The thumbnail belongs to another render or exceeds API limits |
| Public release | Exact video ID, desired visibility, and fresh approval | A worker infers permission from an earlier production step |
The smallest relevant OAuth grant is the documented youtube.upload scope. Even a narrow scope is powerful: it manages videos for the consenting account. The workflow therefore needs an explicit channel binding after consent rather than assuming that the operator selected the intended channel.
Testing / Sixty packets expose the real control surface
The local fixture evaluated 60 declared publish packets across 32 outcomes. All 60 matched their expected result. Fourteen were ready for a private upload, approved schedule, or public commit; ten were held for human or policy decisions; thirty-one were blocked; three reused an existing video or completed-session receipt; and two entered reconciliation.
The ready count is not a success rate. The cases were deliberately constructed to cover boundaries, not sampled from channel traffic. The value is in seeing which conditions must remain distinct:
Admission is a decision table, not a vibe.
- an asset hash is not interchangeable with a filename;
- OAuth scope and channel identity are separate checks;
- rights review, made-for-kids status, and realistic synthetic-media disclosure are separate decisions;
- a title may be syntactically valid while the publish action is still unauthorized;
- a resumable session is bound to one asset and cannot safely inherit a replacement render;
- an existing video ID is a terminal receipt, not a reason to upload again;
- thumbnail approval does not imply approval to make the video public.
The fixture made no YouTube request, performed no OAuth flow, uploaded no bytes, set no thumbnail, and published nothing. Node.js was available; a youtube-upload CLI was not. No package or credential was introduced to make the test look more realistic than it was.
A resumable upload is already an idempotency mechanism
YouTube's resumable-upload protocol begins with a session-creation request. The response supplies a unique session URI in the Location header. Subsequent bytes go to that URI. If the connection breaks, the client can send an empty status request and use the returned range to continue at the next byte.
That sequence matters more than a generic retry counter. After an ambiguous response, the correct next action is to probe the existing session. If it reports completion, retain the returned video identity. If it reports 308 Resume Incomplete, continue from the byte after the acknowledged range. If the session expired, create a replacement only after the old identity and asset binding are recorded as terminal.
Starting videos.insert again before reconciliation changes the operation from “resume this upload” to “create another video.” Exponential backoff helps with transient timing; it does not supply idempotency by itself.
A second insert is a second object.
if (receipt.videoId) return receipt
if (upload.responseWasAmbiguous) {
const status = await probe(upload.sessionUri)
if (status.completed) return recordVideoId(status.videoId)
if (status.incomplete) return resumeAt(status.nextByte)
if (!status.expired) return holdForReconciliation()
}
return startSession({
channelId,
assetSha256,
metadataSha256
})
Store the session URI like a secret because it authorizes continued transfer. Store the asset digest beside it because a session for yesterday's render must not silently receive today's replacement file.
Step 1 / Make the video ID the durable primary key
When videos.insert succeeds, the response contains the YouTube video resource and its ID. Everything after upload should be keyed to that ID: processing observation, thumbnail assignment, metadata review, schedule, release approval, and notifications. A watch URL is a presentation derived from the ID, not the identity itself.
The video ID is the handle.
The thumbnail endpoint illustrates why. It requires a videoId, accepts JPEG, PNG, or octet-stream media, and documents a 2 MB maximum. The admission fixture held an unselected thumbnail, blocked WebP and GIF inputs, blocked a file one byte over the ceiling, and accepted reviewed JPEG and PNG packets. That local check avoids spending a remote call on an obviously invalid or unapproved image.
A thumbnail is also a taste and representation decision. Automating three candidates is reasonable. Choosing one without a human rule or explicit approval is a product decision disguised as plumbing.
“Private” is a staging state; “public” is a commit
Upload privately first. That is not merely caution: it creates space to inspect the actual processed result, confirm the channel, attach the approved thumbnail, review metadata, and verify disclosures against the final asset. It also respects the private-only restriction that can apply to unverified API projects.
Public is a different verb.
Public release should consume a fresh approval bound to the exact video ID and metadata digest. A message such as “publish the video” is insufficient if two retries produced two video IDs. The approval packet needs one target. Scheduling deserves the same treatment: the fixture only admitted a future schedule from the private state and held it until explicit approval.
YouTube exposes status.selfDeclaredMadeForKids and status.containsSyntheticMedia as writable fields. The platform's altered and synthetic content guidance describes disclosure for realistic altered or generated scenes. The worker should collect those decisions before upload and preserve who or what supplied them. It should not infer legal or policy classifications from a script summary.
Checklist / The durable runbook is smaller than the content pipeline
- Freeze the artifact. Record the render hash, metadata hash, rights receipt, policy review, audience declaration, and synthetic-media decision.
- Bind the principal. Confirm that the OAuth-authorized channel matches the requested channel and that the requested scope includes upload capability.
- Create one private resumable session. Persist its URI, asset hash, content length, MIME type, and creation time before sending media bytes.
- Reconcile every ambiguous response. Query session status and resume from the acknowledged range. Never turn a timeout directly into another insert.
- Adopt the returned video ID. Make it the key for every subsequent operation and notification.
- Inspect the processed object. Confirm the channel, playback, metadata, disclosures, and selected thumbnail against the approved packet.
- Commit visibility separately. Require an approval naming the exact video ID, target visibility or schedule, and metadata digest.
- Retain receipts and support deletion. YouTube's developer-policy guidance emphasizes user control, privacy, and deletion obligations for stored user data.
OpenClaw is useful here because it can coordinate files, review messages, local checks, and remote API calls across time. Its job is not to erase the boundary between creative production and publication. Its job is to make that boundary explicit and recoverable.
What the fixture does not prove
The 60 cases do not demonstrate that a particular channel can upload, that a Google project has passed compliance review, that a video clears copyright checks, that processing will finish within a particular time, or that YouTube will accept a specific piece of content. No live API, media file, thumbnail, account, credential, browser benchmark, model, or customer channel was used.
The test proves only the local admission logic declared in the fixture. Production still needs live receipts, provider errors, policy review, and human judgment. That trade-off is intentional: a strict gate delays some uploads that a permissive agent would attempt immediately, but it leaves an operator with one reconstructable path from reviewed bytes to one public video.
Primary sources
- YouTube Data API:
videos.insert - YouTube video resource fields and limits
- YouTube resumable upload protocol
- YouTube Data API:
thumbnails.set - YouTube OAuth 2.0 for web-server applications
- YouTube altered or synthetic content disclosure guidance
- YouTube developer policy guidance
If the workflow currently jumps from a rendered file to a public upload, change one thing first: make the initial upload private and persist the resumable-session receipt. That single boundary prevents the most expensive retry from masquerading as recovery. For a companion treatment of approval scope, see OpenClaw approvals; for a similar interruption problem in real-time media, see voice AI interruption handling.