Automation Pipeline4 min read

I automated daily posting to YouTube and Instagram — the gotchas nobody warns you about

Building an unattended pipeline that posts to YouTube Shorts (via the Data API) and Instagram (via the Graph API) every day with launchd — the OAuth traps, the media-container polling bug, and how I kept it from becoming spam.

#automation#youtube-api#instagram-graph-api#launchd#social-media
Concept diagram: daily auto-publish pipeline for YouTube and Instagram
A concept diagram of the two-track pipeline.

I build a lot of small web apps, and the bottleneck was never building — it was reach. So I built a pipeline that posts to YouTube and Instagram every day, unattended. Here's the honest version, including the traps that ate whole evenings.

The idea

One person can't hand-post to two platforms every day forever. I wanted a launchd job that wakes up, makes something worth posting, and publishes it — while I sleep. The hard constraint: it must not turn into spam, because that gets you banned and, on YouTube, demonetized under the inauthentic-content policy.

That constraint shaped everything. The rule I settled on: the source has to be finite and real. The pipeline draws from my actual blog posts and app data — not an infinite prompt — so when the source is exhausted, it stops. That's the line between "automation" and "mass-produced slop."

YouTube track: the gotchas

The Shorts side converts a real post into a script, renders it, and uploads via the Data API. What actually cost me time:

  • OAuth grabbed the wrong channel. The first auth silently bound to my personal channel instead of the brand channel. Every "test upload" went to the wrong place. You have to pick the brand channel explicitly in the consent screen, and keep a separate token per channel.
  • 403 until I was a test user. OAuth threw access_denied until I added myself as a test user on the consent screen. You do not need full app verification to upload to your own channel — that trap wastes days.
  • Analytics lags ~1 day. The channel's total view count and retention don't show up in the API for about a day. Same-day, you only get Studio's realtime view. So my "which format works" logic reads per-video view counts (near-realtime) and switches to retention once it lands.
  • Headless auth actually works. I run the script generation through a headless CLI call inside launchd, authenticated via the home directory. That's what makes it truly unattended.

The publish job ranks the queue by how each hook type actually performed, posts one per channel per day, dedupes, and no-ops when the queue is empty.

Instagram track: the Graph API traps

Browser automation on a new Instagram account is a fast path to a ban, so I went with the official Instagram Graph API. The traps there are different:

  • App review is 2–4 weeks — or zero. Publishing needs the instagram_business_content_publish permission, which normally means weeks of app review. In development mode, with your own account added as a tester, you can publish to yourself immediately and skip the wait.
  • JPEG only, public URL only. The API won't take a PNG, and it won't take a local file — the image must be a JPEG at a public URL, sized 1:1 or 4:5. My OG images (PNG, 1.91:1) were useless here; I render Instagram-specific cards (headless Chrome → JPEG) and deploy them to a public path first.
  • The publish-too-fast bug. Publishing is two steps: create a media container, then publish it. Call publish immediately and you get Media ID is not available. The container isn't ready yet — you have to poll its status until it reads FINISHED. This one looks like a permissions error and isn't.
  • App secret vs. access token. Easy to paste the wrong one — the app secret is a 32-char hex string, the access token is a long IGAA… string. Refresh needs only the token; the initial exchange needs the secret.

Once those are handled, a launchd job renders a card, deploys it to a public URL, and runs the two-step publish every morning.

The honest part

Two things I'd underline. First: auto-posting is not reach. A brand-new account gets near-zero distribution no matter how clean your pipeline is — the automation lowers the cost of trying, but the content and the early algorithm still decide. Second: the safety of the whole thing lives in that finite-source rule. A pipeline that generates from an endless prompt is a demonetization risk; a pipeline that re-formats your real, finite work and then stops is just a scheduler doing what you'd do by hand.

Automate the posting. Don't automate the pretending.

Related