Posting Infrastructure — Auto-Posting Architecture
Purpose: Move BLO’s social presence from “manually posted via Meta Business Suite browser” to a queued, scheduled, multi-channel pipeline. Recommend ONE architecture. Author: Vega (with technical hand-offs where needed)
1. Instagram — Meta Graph API (the one that’s currently broken)
Current state (per memory, 12d old — verify)
- Meta App ID:
3852884318351764 - FB Page:
1022988797569167 - IG: @boldlittleoracle, connected to FB Page via Meta Business Suite
- Facebook Login for Business: configured
- Instagram Graph API permissions: NOT RESOLVING
- Current posting: manual via Meta Business Suite web UI
What Joshua needs to click (in order) to enable API posting
The issue per memory is “permissions not selectable in current app type.” Almost certainly: the app is configured as the wrong type, OR the Instagram Platform product is not added, OR Page Publishing Authorization (PPA) hasn’t been completed on the FB Page itself.
Path A — Preferred: “Instagram API with Facebook Login”
In order, at https://developers.facebook.com/apps/3852884318351764/ :
-
Dashboard → App Settings → Basic - Confirm App Type is
Business(not Consumer, not None). If not, change it. This is the gate that makes Instagram Graph permissions selectable. - App Domains must include:boldlittleoracle.unicorn.land- Privacy Policy URL:https://boldlittleoracle.unicorn.land/privacy- Terms of Service URL:https://boldlittleoracle.unicorn.land/support(or /terms) - Category:LifestyleorBusiness and Pages -
Left sidebar → “Add Product” - Find “Instagram” → Click “Set Up” - Choose: “Instagram API with Facebook Login” (NOT “Instagram API with Instagram Login” — that’s a different product flow)
-
Instagram → API Setup with Facebook Login - Add the Instagram Business account (@boldlittleoracle). - Confirm it’s linked to FB Page 1022988797569167.
-
Add Product → Facebook Login for Business - Configuration: create a new “Facebook Login for Business” configuration - Variation: “Instagram Business Login via Facebook” (this is the one memory flagged as missing) - Permissions to request (toggle on):
instagram_basicinstagram_content_publishpages_read_engagementpages_show_listbusiness_management(optional but recommended for multi-asset access)- Valid OAuth Redirect URIs:
https://boldlittleoracle.unicorn.land/api/meta/callback(add to_worker.jsroutes)
-
App Review → Permissions and Features - Request Advanced Access for:
instagram_content_publish,instagram_basic,pages_read_engagement- Standard Access may be enough for a single-account tool, but Advanced is safer. - Review requires: screencast of OAuth flow + use case description. Template: > “This app is used by Bold Little Oracle (a solo developer) to programmatically publish daily oracle card posts to our own @boldlittleoracle Instagram Business account connected to Facebook Page 1022988797569167. No third-party user content is posted. All posts are authored by the page owner.” -
Facebook Page → Page Publishing Authorization (PPA) - Go to FB Page 1022988797569167 settings - Security → Page Publishing Authorization - Complete ID verification if prompted. - Note from Meta docs: “An Instagram professional account connected to a Page that requires PPA cannot be published to until PPA has been completed.” This is often the silent blocker.
-
Generate a long-lived Page Access Token - Graph API Explorer → select app → “Get Page Access Token” → select FB Page → grant permissions listed above. - Exchange short-lived (1h) → long-lived (60d) via:
GET /oauth/access_token? grant_type=fb_exchange_token& client_id={app-id}& client_secret={app-secret}& fb_exchange_token={short-lived-token}- Store the long-lived token in a Cloudflare Worker secret (not in code, not in git). -
Test publish via cURL ```bash # Step 1: Create media container curl -X POST “https://graph.facebook.com/v21.0/{ig-user-id}/media” \ -d “image_url=https://boldlittleoracle.unicorn.land/posts/test.jpg” \ -d “caption=Test from API” \ -d “access_token={long-lived-token}”
# Step 2: Publish curl -X POST “https://graph.facebook.com/v21.0/{ig-user-id}/media_publish” \ -d “creation_id={container-id-from-step-1}” \ -d “access_token={long-lived-token}” ```
Rate limit: 100 API-published posts per 24h rolling window. Far more than needed.
Common failure modes (if step 7 fails)
- “Permission denied” → PPA not complete (step 6)
- “Invalid scope” → wrong app type (step 1) or Instagram not added as Product (step 2)
- “No page access token” → logged in as personal account, not admin of the FB Page
- Permissions grayed out during OAuth → App is in Development Mode; submit for App Review (step 5) OR add Joshua’s FB account as a Test User in Roles.
2. Bluesky — AT Protocol API
Auth model: App Password (simpler than OAuth). Bluesky handle + app-password → JWT.
Setup:
1. bsky.app → Settings → Privacy and Security → App Passwords → Create new
2. Label it “BLO-poster”. Save the password — shown once.
3. Store as BSKY_APP_PASSWORD secret.
Post flow (pseudo):
POST https://bsky.social/xrpc/com.atproto.server.createSession
{ identifier: "boldlittleoracle.bsky.social", password: APP_PASSWORD }
→ returns { accessJwt, refreshJwt, did }
POST https://bsky.social/xrpc/com.atproto.repo.createRecord
Authorization: Bearer {accessJwt}
{
repo: "{did}",
collection: "app.bsky.feed.post",
record: { text: "...", createdAt: ISO8601, langs: ["en"] }
}
For images: com.atproto.repo.uploadBlob first → include blob ref in post record embed.
Rate limits (2026): 5,000 points/hour, 35,000 points/day. Create = 3 points. Means 1,666 posts/hour worst case. A non-issue for BLO.
Library: @atproto/api npm package (TypeScript). Works in Cloudflare Workers without polyfills.
3. Reddit — Reddit API (OAuth)
Auth model: OAuth2 with a “script” app type (since u/obadiah_finch is a single user).
Setup:
1. reddit.com/prefs/apps → create app → type script
2. Record: client_id, client_secret, username, password.
3. Store all as Worker secrets.
Auth flow:
POST https://www.reddit.com/api/v1/access_token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=obadiah_finch&password={pw}
User-Agent: BoldLittleOracle/1.0 (by u/obadiah_finch)
→ { access_token, expires_in: 3600 }
POST https://oauth.reddit.com/api/submit
Authorization: Bearer {access_token}
User-Agent: BoldLittleOracle/1.0
sr={subreddit}&kind=self&title={title}&text={body}
Rate limits: 60 requests/min authenticated. Plenty.
WARNING: Reddit detests automated posting of promotional content. Use this ONLY for: - Scheduling already-human-drafted value posts (which are approved in PUSH-QUEUE.md) - Automation for Obadiah’s own account should NEVER run more than 1 post/day across all subs - Comments should remain 100% manual. Reddit’s anti-spam ML is aggressive.
Library: direct fetch is simpler than snoowrap in a Worker. 60 LOC total.
4. TikTok — Content Posting API
Auth model: OAuth2 with video.publish scope.
Setup:
1. developers.tiktok.com → register as developer
2. Create app, connect TikTok account (@boldlittleoracle — create if not exists)
3. Apply for video.publish scope via Audit process.
Approval blocker: Apps in unaudited status can only post in “private viewing mode” — i.e., the post exists but no one can see it. Useless.
Audit requirements (2026): - 5-10 business day review (per web research, Apr 2026) - Requires: working demo, clear use case doc, UX compliance (creator info retrieval before publish, consent screen) - Caps: ~15 posts per creator account per 24h once approved
Verdict: TikTok API is NOT worth building for BLO in week 1. The audit cycle + UX compliance for a single-account self-post tool is weeks of overhead for zero gain. Recommendation: post TikTok manually through the mobile app for the first 30-60 days. Reconsider API once posting frequency exceeds 2/day AND manual becomes the bottleneck.
5. Scheduler Architecture — RECOMMENDATION
Options evaluated
| Option | Pros | Cons |
|---|---|---|
| A. Cloudflare Worker + Cron Trigger | Infra already there (bold-little-oracle Pages project + _worker.js handles /api/subscribe). Free tier allows cron. No new machines. Secrets live in CF dashboard. |
No rich queue UI. Logs via wrangler tail. |
| B. Local cron on Joshua’s machine | Dead simple. crontab -e + a Node script. |
Requires machine on. Not resilient. Secrets on disk. |
| C. Cambium Rhythms | Joshua’s own agent framework — dogfooding. Native scheduling. | Adds a dependency on an in-development system. Overkill for 4 channels. |
| D. Buffer / Hootsuite / Postiz | GUI, done for you. | Paid, generic, can’t do Reddit or Bluesky well, defeats “built by Obadiah” narrative. |
Recommendation: Option A — Cloudflare Worker with Cron Trigger
Reasons:
1. Infrastructure already deployed (_worker.js on Pages project bold-little-oracle, account bc0c90d4bc8c0d8b4b247f42c57aa65b).
2. Secrets management already in use (Beehiiv key is already stored server-side per memory).
3. Cron triggers are free and run every N minutes.
4. Posts can be drafted as JSON files in a /posts directory in the repo — git-reviewable, deterministic, versioned.
5. No new infra for Joshua to own/pay for.
6. Cambium Rhythms (Option C) is a great future home but not worth blocking on.
Minimal architecture
bold-little-oracle/
├── posts/
│ ├── 2026-04-17-ig-day1.json # { platform, publishAt, body, imageUrl, hashtags }
│ ├── 2026-04-17-bsky-day1.json
│ └── ...
├── website/
│ └── _worker.js # existing Beehiiv proxy
└── workers/
└── scheduler/
├── wrangler.toml # cron = "*/15 * * * *" (every 15 min)
├── src/index.ts # fetches pending posts, publishes, marks done
└── src/providers/
├── instagram.ts # Graph API Page post
├── bluesky.ts # AT Proto
└── reddit.ts # OAuth script
Post JSON shape:
{
"id": "2026-04-17-ig-day1",
"platform": "instagram",
"publishAt": "2026-04-17T13:00:00Z",
"status": "queued",
"body": "I'm Obadiah. I built an oracle card app...",
"imageUrl": "https://boldlittleoracle.unicorn.land/posts/day1-hero.jpg",
"hashtags": ["#oraclecards", "#witchtok", "#dailyoracle", "#indieapp", "#oracledeck"]
}
State: Store status and publishedAt in Cloudflare KV or a R2 JSON file. Never mutate the post .json in git (keeps history clean).
Failure handling: If publish errors, mark status: "error", store error message, alert Joshua via Beehiiv webhook → email, or push to a Discord webhook.
Manual approval toggle: Add status: "pending-approval" state. Joshua hits a URL (/ops/approve?id=...) to flip to queued. Default new posts to pending-approval for first 2 weeks.
Build effort
- Scheduler Worker MVP: ~4 hours of dev
- IG provider: ~2 hours once Meta app config done (step 1 above)
- Bluesky provider: ~1 hour
- Reddit provider: ~2 hours (manual human-in-loop per post)
- Post JSON migration from PUSH-QUEUE.md: ~1 hour
Total: ~1 day of engineering once Meta config is unblocked.
Why NOT Cambium Rhythms
Cambium is early. BLO revenue pressure is immediate. Ship the thinnest thing. Port to Rhythms later — the post JSONs are portable, the providers are just fetch calls.
6. What to Build FIRST (sequence)
- Today (Joshua, 30 min): Complete Meta App config steps 1-6 above. This unblocks everything else.
- Tomorrow (Joshua or agent, 1h): Create u/obadiah_finch on Reddit. Create Bluesky handle (@boldlittleoracle.bsky.social). Create app passwords / OAuth creds. Store in CF Worker secrets.
- Day 3 (1 day of eng): Ship scheduler Worker MVP with IG + Bluesky providers only. Reddit stays manual. TikTok stays manual.
- Day 4: Convert PUSH-QUEUE.md entries to post .json files, test publish one to IG from the queue.
- Day 5+: Publish daily per CONTENT-CALENDAR.
7. Observability
- Simple Discord webhook (create
#blo-postschannel) that pings on publish success/failure - Weekly roll-up: Worker sends weekly email via Beehiiv to ops@unicorn.land with last 7 days of posts + engagement (manually updated from each platform’s insights)
- No pixel tracking. Privacy-first ethos extends to ops.
8. Risk register
| Risk | Likelihood | Mitigation |
|---|---|---|
| Meta config still fails after steps | Medium | Fall back: keep posting via Meta Business Suite manually until App Review clears |
| Bluesky app password revoked by bsky breach | Low | Rotate quarterly; auto-refresh is not needed at this scale |
| Reddit auto-flags u/obadiah_finch as spam | Medium | Manual posting for first 30 days; API only for scheduled VALUE posts (not promo) |
| Joshua forgets to approve queued posts | High | Build slack/discord ping with one-click approve URL |
| Cloudflare Worker cold-start misses a cron tick | Low | Cron triggers are reliable; backfill logic scans “should have published by now” every 15 min |