I build LazyRelay, a social scheduling tool. Something that became obvious once we started looking closely: a successful API response from a social platform does not mean your content is actually visible to anyone.
The gap
Every platform's "create post" endpoint can return 200 OK and a post ID, and the content still isn't live. A few real ways that happens:
- The platform accepts the write, then silently rejects it a few seconds later during async moderation.
- Media processing fails after the initial call succeeds, before the post actually renders.
- The account hits a rate limit or shadow restriction that doesn't surface as an error on the write itself.
- A token has a narrower scope than you think, so the write "succeeds" against a sandbox or test context you didn't intend.
If your scheduler treats "the POST call didn't throw" as "it worked," you'll eventually tell a customer their content is live when it isn't — and you won't find out until they do.
What we do instead
After every publish attempt, we make a second, independent read-back call — not trusting the write response, actually re-fetching the post from the platform's API and checking it resolves.
Here's the real shape of it, from our Mastodon adapter:
async verifyPublished(platformPostId: string, accessToken: string): Promise<VerifyResult> {
const res = await fetch(`${DEFAULT_INSTANCE}/api/v1/statuses/${platformPostId}`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
const json = await res.json();
if (!res.ok || json.id !== platformPostId) {
return {
verifiedLive: false,
platformPostUrl: null,
errorMessage: json.error ?? `Mastodon status verification failed (HTTP ${res.status})`,
};
}
return {
verifiedLive: true,
platformPostUrl: json.url ?? `${DEFAULT_INSTANCE}/web/statuses/${platformPostId}`,
errorMessage: null,
};
}
Every platform adapter implements the same verifyPublished() contract. The scheduler calls it right after the post attempt, before it ever marks anything as done:
const attempt = await adapter.post({ ...content });
if (!attempt.success || !attempt.platformPostId) {
// handle failure — never reached "verified" state
}
// The post API call succeeding is NOT the same as the content being
// live — this read-back check is the actual differentiator, not an
// optional extra step.
const verification = await adapter.verifyPublished(attempt.platformPostId, accessToken);
if (!verification.verifiedLive) {
// treated as a real failure, not a success with an asterisk
}
Only after that second call comes back clean does a post get marked verified_live: true and a customer-visible confirmation goes out. If it doesn't, it's a real failure — not a success with an asterisk quietly left off.
Why this is worth the extra call
It's one more HTTP round-trip per post, on top of the write itself. That's a real cost, and I get why most schedulers skip it — it only pays off when something's already gone wrong, which is exactly when it's easy to convince yourself "the API said it worked, so it's fine."
If you're building anything that writes to a third-party API and reports success back to a user, it's worth asking: does "success" mean the call didn't error, or does it mean you went and checked? Those are different guarantees, and only one of them is actually true when you say it.
If you're curious about the rest of it — LazyRelay also ships an MCP server so an AI agent can schedule posts, pull this same verified-live data, and read mentions directly, without a human in the loop for the read side.
Top comments (0)