Posting your own blog to social every day takes longer than you'd think. Write the caption in two languages, find an image, put the link in the right place, click through each platform: half an hour a day that should go into real work.
We wanted an AI assistant to do it for us. But one moment stuck with us. It once reported the post was up when it wasn't, and trusting the word "done" without checking, we only found out later that it had vanished.
The real problem is that public posting is hard to undo. People have already seen it. So for work like this, "automate" doesn't just mean writing a script that can fire. It means setting up guardrails too. This post covers both: the real setup step by step that you can follow, and the three gates that let you hand the posting to a system without holding your breath. All of it done for real, including the parts where we got stuck.
Part 1Two platforms, different rules
Don't assume Facebook and LinkedIn are the same thing with a different endpoint. Three real differences, each easy to trip on.
One, Facebook only lets you post via API to a Page, not a personal profile. LinkedIn is the reverse: you can post to your personal profile through a scope called w_member_social.
Two, the article link goes in different places. Facebook suppresses the reach of posts with a link in the body, so the link should go in the first comment. On LinkedIn a link in the post body also gets suppressed; the less painful route is to put it in your Featured section or bio. Each platform owns its own link rules.
Three, a Facebook Page has two identifying numbers: the one you see in the Page URL, and the one the API actually uses. They're different numbers; grab the wrong one and nothing fires.
Part 2Set up Facebook, step by step
- Create an app at developers.facebook.com and pick the Business type. As long as you're the admin of your own Page, Development mode lets you post to that Page right away, no App Review needed.
- Open the Graph API Explorer, request the scopes
pages_show_listpages_manage_postspages_read_engagement, then click Generate Access Token. You get a user token, short-lived, about one to two hours. The thing that cost us time was hunting for a "User Token" in the dropdown. Don't. Just click Generate Access Token directly; that dropdown is for switching token types after you already have one, not before. - Trade it for a permanent token. Exchange the short token for a long-lived one (sixty days) first, then call
/me/accountswith that long token. It returns a Page token that doesn't expire. Keep that one for posting. - Easy to get wrong: the Page number that
/me/accountsreturns is the one the API actually uses. Don't grab the number from the Page URL; it's a different one. Let the system pull the right number itself rather than eyeballing it. - Post. Send
POST /{page-id}/photoswith the text and an image url (posts with an image get pushed harder than text alone). You get a post id back, then sendPOST /{post-id}/commentsto drop the article link as the first comment. - Verify. Read the post back from Facebook and confirm the text, image, and comment are really there.
Part 3Set up LinkedIn, step by step
LinkedIn is one step messier because it uses full OAuth.
- Create an app at linkedin.com/developers. The system forces you to link it to a Company Page; you can create an empty Page just to satisfy this. The actual posting goes to your profile, not this Page.
- Verify the app in the Settings tab. This one matters and people skip it often. Without verification the products won't work.
- In the Products tab, add two:
Share on LinkedInunlocks the posting scopew_member_social, andSign In with LinkedIn using OpenID Connectunlocks pulling your identity. - In the Auth tab, keep the Client ID and Client Secret, and add a redirect URL that matches exactly what you'll use.
- Run OAuth. Open the authorization link in a browser, approve, and you get a code back. Exchange the code for a token on the server side, then call
/v2/userinfoto get your identity (the person URN) so the post knows who's posting. - Post. Send
POST /v2/ugcPosts, and don't forget the headerX-Restli-Protocol-Version: 2.0.0.
The thing that stuck us longest all session was step 5. Opening the authorization link kept landing on unauthorized_scope_error or a "Bummer, something went wrong" page, over and over. What we learned, in order:
- This error page is a catch-all. It shows up both when a scope is missing and when the redirect URL doesn't match. One symptom, several causes, so don't jump to a conclusion.
- LinkedIn rejects the whole set if even one requested scope is missing. The way to pinpoint the missing one is to open the authorization link one scope at a time; whichever breaks is the product that isn't enabled. That isolates the variable instead of guessing.
- Scopes only appear in the Auth tab once the product is added. If you go looking for the scope before adding the product, it's empty and you're left wondering where it went.
- The most-skipped step is verifying the app in Settings. But in our case the funny part was that both products showed as added and it still broke. The real culprit was a redirect URL that wasn't registered to match exactly. Once it matched, it passed immediately. The lesson: check every layer, don't stop at the first hypothesis.
Another limit we hit in practice: having AI comment the link on your own post via the API does not work unless your app is a LinkedIn partner (Marketing Developer Platform). The w_member_social scope can post a share but cannot comment (you get a 403). The simplest path for a solo setup is to put the link in the caption itself, unlike Facebook where you can comment on your own Page for free.
Part 4Make it automatic, and the 3 guardrails
The last step is to make it fire on its own on a schedule. Use a machine that's on all day and set a cron job. But this is exactly where the guardrails matter more than the script.
Guardrail 1: automate the timing, not the decision
The most tempting trap is to have AI write the caption itself and post it in one shot. That hands an irreversible decision to a machine. The line we draw splits two things: deciding whether this text should go public is the human's, while firing it at 10am is the machine's. The way to do it is a queue of approved items: a caption that has passed your eyes and entered the queue counts as approved, and cron just picks up whatever is due and fires it. The human stays in the loop on content; the machine only takes the timing. One more detail: the two platforms fire at different times because the audiences are in different zones. Tie the schedule to the reader's time zone rather than picking a central time and computing offsets yourself, because some countries shift their clocks seasonally; a fixed time drifts twice a year, silently. Let the scheduler know about time zones itself.
Guardrail 2: trust the real thing, not your own answer
Back to the AI that said "done" when it wasn't. When you fire the request, the platform replies that it accepted it, but the reply at fire-time and what's actually live on the page are two different things. This guardrail enforces a single step: after posting, read it back from the platform again. Only count it done once you see the real text, the real image, the real comment. Nothing is true because the system says it's true. It's true when it can point to evidence you can see.
Guardrail 3: when a key leaks, rotate it, don't delete history
The token you post with is a secret. While setting things up, it got written to a file in a folder that auto-syncs to a code repo, and within minutes it was committed and pushed up before we even noticed. Two halves to the lesson. The first half is prevention: before you write a secret anywhere that auto-syncs, tell that place to ignore secret files first, confirm the rule actually takes effect, and only then write. The order can't be swapped. The second half is once it's already leaked: a secret that has hit a code repo stays in the history even if you delete the file later. The real fix is to rotate the key, revoke the old token so it no longer works and issue a new one, and prove the old one is dead by calling with it and getting rejected. Once the old one is useless, the value sitting in the history is junk, not a risk anymore.
What to take away
Wiring it up to fire is only half. The other half is the three guardrails.
- Automate the timing, not the decision. For irreversible work, the human owns the approval point and the machine owns the cadence.
- Trust the real thing. Read the result back from the destination; don't trust the reply at fire-time.
- When a key leaks, rotate it. Prevent with the right order before writing a secret; if you slip, rotate the token rather than deleting history.
If you're about to let AI or a script do work with public consequences for you, start by asking what the irreversible point is, and put a human there, at that one point. Let the machine run full speed on the rest. The system gets faster while you stay in control.
- Every step and gate in this post comes from doing it for real on productize.life (June 2026), including getting stuck on LinkedIn's
unauthorized_scope_errorand the time a token leaked to a code repo by accident. - Posting to a Facebook Page via API follows the official Meta docs, "Pages API" and "Page Photos" developers.facebook.com/docs/pages-api/posts
- Posting to a LinkedIn profile via
w_member_socialand/v2/ugcPostsfollows Microsoft Learn, "Share on LinkedIn" learn.microsoft.com/.../share-on-linkedin