🪟 Windows TippsBitLocker stuck on Decrypting or Encrypting in Windows 11(17.09.2026 um 00:29 Uhr)
🕵️ SicherheitslückenCVE-2026-69110 | Microck opencode-studio up to 2.4.3 missing authentication(17.09.2026 um 03:21 Uhr)
🪟 Windows TippsBitLocker stuck on Decrypting or Encrypting in Windows 11(17.09.2026 um 00:29 Uhr)
🕵️ SicherheitslückenCVE-2026-69110 | Microck opencode-studio up to 2.4.3 missing authentication(17.09.2026 um 03:21 Uhr)
🔧 Programmierung 🕛 vor 2 Monaten 5 Min Lesezeit
0

API-first or browser automation? Lessons from shipping content autoposting

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

We built a pipeline that generates content and ships it to several platforms. Generation turned out to be the easy half. Publishing is where the real engineering was — and where I burned the most hours. Here is what I'd tell my past self.






The rule I landed on: API when it exists, browser only when it doesn't



Not every platform exposes a publishing API. The tempting shortcut is to drive a headless browser everywhere. Don't. Two reasons:





  1. Some platforms explicitly forbid it. X's automation rules are blunt: "Non-API-based forms of automation, such as scripting the X website, may result in permanent suspension." If an API exists, scripting the site is not a clever workaround — it's a ban waiting to happen.


  2. Browser automation is inherently fragile. It breaks the moment someone renames a CSS class.



So the rule: API adapter by default. Browser automation only where no API exists, and only for actions the account owner is allowed to perform.






What platforms actually allow



I checked the primary docs (not blog posts) for each. Official publishing APIs where automation of your own account is permitted:











































Platform Auth Content
Telegram (Bot API) static bot token short / channel posts
Bluesky (AT Protocol) app password → session JWT short
Mastodon OAuth token short
DEV.to / Forem
api-key header
long-form Markdown
Ghost Admin API key → JWT long-form
LinkedIn OAuth (w_member_social) short/medium


The universal enforcement rule is remarkably consistent across all of them:




Posting your content to your account: fine.

Identical content across multiple accounts, bulk/aggressive actions, unsolicited notification spam: banned.




Vary content per platform, respect rate limits, don't automate interactions. That's the whole game.






Browser automation gotchas (the expensive ones)






1. "Am I logged in?" — don't assert on text anonymous users also see



My first check looked for words like "My feed" and "Write". Both are visible to logged-out visitors. The script cheerfully reported success while completely unauthenticated.



Assert on the absence of the thing that shouldn't be there:




CODE
async function isLoggedIn(page: Page): Promise<boolean> {
const loginBtn = page.getByRole("button", { name: "Sign in", exact: true }).first();
return !(await loginBtn.isVisible().catch(() => false));
}









2. Native confirm() dialogs are not in the DOM



The publish button fired a native confirm(). I spent an embarrassing amount of time hunting for a second DOM button that never existed. Playwright auto-dismisses native dialogs unless you handle them:




CODE
page.on("dialog", (d) => d.accept());









3. Rich editors: the body block may not exist yet



The empty editor had only a title field. The body block is created when you press Enter from the title. My naive "click the editor and type" appended the first paragraph into the title:




CODE
Title: My Article HeadlineThe first paragraph of my article...






The fix is to type like a human:




CODE
await titleField.click();
await page.keyboard.type(title);
await page.keyboard.press("Enter"); // <- this creates the body block
for (const [i, p] of paragraphs.entries()) {
await page.keyboard.type(p);
if (i < paragraphs.length - 1) await page.keyboard.press("Enter");
}






And then verify by reading it back. Don't trust a screenshot that "looked fine":




CODE
const titleText = await titleField.innerText();
const firstBody = await page.locator(".editor-text-tool").first().innerText();






That single assertion caught the bug that three screenshots missed.






4. Sessions expire, quietly



A saved storageState worked beautifully — for about two to three weeks. Then a run came back auth_required. Build re-authentication in as a normal automated step, not a manual emergency.






API gotchas






Versioned APIs expire



LinkedIn requires a LinkedIn-Version: YYYYMM header and keeps roughly the last 12 months active. A hardcoded default that was fine at write-time returns this at run-time:




CODE
HTTP 426 — Requested version 20250501 is not active






Anything with a date baked into it belongs in config, not in a constant. Same for token lifetime (60 days for LinkedIn) — treat rotation as a scheduled task, not a surprise.






The architecture that held up





  • One adapter contract per platform: validate → prepare → publish → verify → collectResult. Adding a platform means writing one file, not touching the orchestrator.


  • Secrets in env, never in the database. Outward (UI / API / logs) we expose only masked booleans: configured, publishEnabled. A token has never once been logged.


  • Idempotency by request hash: sha256(draftId + destinationId + contentHash + mode). A prior success → skip, return the existing URL. A prior unknown (the network died mid-request, we genuinely don't know if it landed) → never blind-retry; flag needs_manual_review. Duplicate posts are worse than a missing one.


  • Two flags to publish for real: dryRun=false and publishConfirm=true. Defaults are safe, so an accidental run can only ever produce a preview.


  • Stop, don't bypass. Captcha, 2FA, an emailed verification code → status waiting_human. We never solve a challenge. That line is what separates "a robot operating the owner's account" from "a bot pretending to be a human".






Takeaways





  1. API-first. Browser automation is a fallback, not a default — and on some platforms it's a ban.


  2. Read the automation policy before writing the adapter. It changes what you build, not just whether you're allowed.


  3. Assert on absence, not on presence of a happy-path string.


  4. Anything dated (API versions, tokens) goes in config. It will expire while you sleep.


  5. Make "publish for real" require two deliberate flags. Your future self will hit Enter on the wrong terminal eventually.



The unglamorous truth: the model that writes the post is the commodity. The reliability layer around it — idempotency, gating, honest failure states — is the actual product.

Vollständiger Original-Artikel
Den kompletten Beitrag mit allen Details direkt auf dev.to lesen.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Hybrid Delivery Isn't a Compromise. It's the Strategy You Were Pretending Wasn't Happening.
1 Quelle
Google Warns DMA Search Changes Could Reshape Visibility for European Businesses
1 Quelle
A stored-XSS report we couldn't quite reproduce, and hardened anyway
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten API-first or browser automation? Lessons from shipping content autoposting

Thematisch verwandte Begriffe: APIfirst, browser, automation, Lessons · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...