I left Claude running while I went to lunch.
Came back to find it had built a complete cross-platform social media automation system. Posting, commenting, replying, threading — across X, Facebook, and LinkedIn. With branded images on every post.
Here's the full build log. The wins, the failures, the workarounds, and what I learned.
The Problem
We run an AI-powered agency. Claude Code is the operator — it handles client work, content creation, email marketing, website updates, everything. But social media was half-automated. We could post to X and Facebook, but couldn't comment, reply, or engage programmatically. Three platforms, three different auth systems, three different API quirks.
The goal: post to every channel, reply to comments, build threads, engage with mentions — all autonomously. No manual Canva. No copy-pasting between tabs. No "I'll do it later."
X/Twitter: OAuth 1.0a from Scratch
X's API uses OAuth 1.0a, which means HMAC-SHA1 signatures on every request. If you've never implemented this, here's what it involves:
- Collect all OAuth parameters (consumer key, nonce, timestamp, token, signature method, version)
- Collect all request parameters (query params + body params)
- Sort ALL of them alphabetically by key
- Percent-encode every key and value
- Join them with
& - Build the signature base string:
METHOD&encoded_url&encoded_params - HMAC-SHA1 that with
consumer_secret&token_secret - Base64 encode the result
- Add it to the Authorization header
One wrong encoding anywhere = 401 Unauthorized. No error message telling you what's wrong.
We built this from scratch in a Next.js API route. The v2 API handles tweets and replies. The v1.1 API handles media upload (images). The two APIs use different auth patterns but the same signature method.
What works now: Posting with images, replying to tweets, building threads on our own posts, searching for conversations, monitoring @mentions. All via production API endpoints.
Facebook: The Six-Hour War
This was the hard one.
Facebook's Graph API needs a Page Access Token with pages_manage_engagement scope to comment on posts. Sounds simple. It wasn't.
The Trap
Our Facebook app (created through Meta's developer portal) had "use cases" attached to it — Marketing API, Meta Ads Manager, Threads API. These use cases auto-inject their required scopes into every OAuth flow. One of those scopes, pages_read_user_content, is invalid for our app type.
When you request OAuth authorization, Facebook checks ALL scopes from ALL use cases, finds the invalid one, and shows "Invalid Scopes" error. This blocks pages_manage_engagement from ever being granted.
What We Tried (All Failed)
- Graph API Explorer: Same error. The Explorer's OAuth popup inherits the app's use cases.
- Server-side OAuth flow: "Can't load URL" — redirect URI wasn't registered. Fixed that via API.
- Remove use cases via API: No endpoint exists. Facebook doesn't expose use case management programmatically.
- Remove use cases via UI: The portal UI marks them as "Required for use case" with no delete option. Meta locked them.
- Enable API access to app settings: Found the toggle in Advanced Settings, enabled it, set
valid_oauth_redirect_urisandapp_domains. OAuth still failed. - Switch to a different app in the Explorer: Other apps don't have
pages_manage_engagementconfigured. - Revoke all user permissions and re-auth: Clean slate, same error.
auth_type=rerequest: Same error.- Create a new app: Developer portal's create page renders blank in headless Chrome.
15+ approaches. Six hours. All failed at the same wall: pages_read_user_content.
The Solution: Chrome DevTools Protocol
If the API won't cooperate, use the browser.
Chrome's DevTools Protocol (CDP) lets you control Chrome programmatically via WebSocket. Navigate to pages, execute JavaScript, dispatch mouse and keyboard events.
Here's the flow:
- Copy the logged-in Chrome profile to a new data directory
- Launch headless Chrome with
--remote-debugging-port=9444 - Navigate to the Facebook post
- Find the
[contenteditable="true"]comment field viaRuntime.evaluate - Click it with
Input.dispatchMouseEvent - Type the comment with
Input.insertText - Press Enter with
Input.dispatchKeyEvent - Verify the comment appeared in the DOM
No OAuth token. No pages_manage_engagement. No Graph API. Just browser automation using the logged-in session's cookies.
This is the same pattern we use for LinkedIn commenting via LinkedIn's internal Voyager API.
LinkedIn: CDP + Voyager API
LinkedIn's official API for commenting requires their "Community Management API" product, which needs approval. We applied. Still waiting.
In the meantime, the Voyager API (LinkedIn's internal browser API) works perfectly from within a CDP session. The flow:
- Navigate to linkedin.com in the CDP browser (logged in via cookies)
- Extract the JSESSIONID CSRF token from cookies
- Call the Voyager comments endpoint with the CSRF token
- Parse the response for comment data
- Post replies with the comment mutation endpoint
It's not the official API, but it's the same API LinkedIn's own web app uses.
The Image Pipeline
Every post needs a branded image. Not the default OG card. Not a stock photo. A custom, scroll-stopping graphic generated specifically for that post's content.
We built 12 templates using Satori (the SVG-to-image library from Vercel):
- quote_card — Bold text with optional headshot
- stat_card — Big number with trend indicator
- list_card — Numbered items with accent highlights
- bar_chart — Horizontal bars with labels
- insight_card — Centered text, clean and bold
- comparison_card — Side-by-side before/after
- step_card — Numbered how-to steps
- data_table — Comparison table with headers
- progress_card — Metric with segmented bar
- workshop_promo — Event card with CTA
- carousel_cover — Bold title card
- testimonial_card — Quote with stars
Every template includes:
- Dark background (#080A0F) with subtle gradient
- Green accent (#00FFB2)
- Space Grotesk headings, Inter body text
- VT logo + "Powered by Claude" watermark
- Corner accents and dot grid pattern
Generated in under 2 seconds via API. 1080x1080 for social, 1200x675 for X/LinkedIn landscape.
Observations
1. CDP is the universal automation fallback. When APIs fail — and they do, constantly — the browser is always there. Every platform has a web UI. Every web UI can be automated. The skill gap in automation isn't "knowing the API." It's knowing what to do when the API doesn't work.
2. Facebook's developer portal is hostile to automation. React apps that don't render in headless browsers. Settings pages that redirect to dashboards. Use cases that can't be removed. API access toggles that don't persist. It felt intentionally designed to require human interaction at every step.
3. Image generation is a first-class feature, not an afterthought. The difference between a post that gets scrolled past and one that stops the thumb is the image. Auto-generated, branded images on every post. No exceptions. No "I'll add one later."
4. LaunchD > Cron for macOS services.
LaunchD agents with KeepAlive restart automatically if they crash. The Chrome CDP instance runs 24/7. If Chrome dies, launchd brings it back.
5. OAuth is the hardest part of any integration. Not the API calls. Not the data modeling. The auth flow. Every platform does it differently. Every platform has edge cases. Every platform has undocumented requirements. Building the OAuth 1.0a signature from scratch took longer than building the entire posting system.
What's Running Now
- X: Post with images, thread, reply, search mentions — all via production API
- Facebook: Post with images (Graph API), comment (CDP)
- LinkedIn: Post via Zapier, comment (CDP)
- Images: 12 templates, auto-generated, branded
- Chrome CDP: Running 24/7 via LaunchD on port 9444
- LinkedIn monitor: LaunchD agent checking comments every 3 hours
This is what an AI-run agency looks like in practice. Not a demo. Not a prototype. Production infrastructure, running autonomously, handling real engagement across real platforms.
The content creation is the easy part. The plumbing is where the engineering lives.
