Any endpoint that accepts user-authored public content - posts, status updates, comments, replies, reviews - is a target for LLM-powered content generation. The right integration runs at the content-submit handler, blocks
bot sessions, and uses the visitor fingerprint to cap posting velocity across account rotations.The threat
Public content surfaces have two automation shapes stacked on top of each other:- Scripted posting - headless browsers or direct API calls submitting content on a schedule. Headless browsers get the
automationattribution (Playwright, Puppeteer, Selenium). Direct API calls skip your page, so they carry no Foil token and your handler rejects them. - LLM-written content - the script is still automated, but now the content itself is generated by a language model in the same pipeline. The browser looks real because a real (headless) Chromium is driving it; the text looks real because it came from a capable LLM. Foil classifies the session by how the browser is driven, not by who wrote the text, so a script that posts LLM-written text is
automation. Theai-agentcategory applies to agents that operate a browser themselves.
- Spam - outbound links, crypto shilling, promotion of other accounts or products.
- Astroturfing - coordinated posting to manufacture consensus or suppress criticism.
- Engagement farming - AI-generated posts designed to get reactions, build account reputation, and later pivot to spam or resale.
The flow
1
Start Foil on the composer surface
Wherever the user actually drafts content - the compose modal, the reply box, the review form.
2
Call getSession() at the submit click
This captures keystroke timing, paste patterns, and the full fingerprint in the sealed handoff.
3
Verify and inspect the actor label
Check
decision.verdict and the actor label in attribution.bot.labels (the label whose kind is actor). You’ll treat automation and ai-agent differently from human, and you may want to allow verified agents and crawlers through read endpoints (see API abuse).4
Apply a visitor-fingerprint velocity cap
Even a “human” verdict shouldn’t let one fingerprint publish 200 posts per hour. Rate-limit by
visitor_fingerprint.id, not just account ID.5
Consider shadow mode
For surfaces where silent rejection is unacceptable, score content but don’t block - feed the verdict into trust-and-safety tooling instead.
Client integration
getSession() fails, the page sends the post without a handoff, so a blocked script doesn’t leave the composer unresponsive. The server rejects a post without a valid token, as the next section shows.
Keystroke and paste events are part of Foil’s behavioral signal set, and they fire strongest when the user is actually composing in your page. Mount the client on the composer, not just the top-level app shell.
Server verification
Decisioning policy by actor label
The top-level verdict tells you whether to block; the actor label inattribution.bot.labels tells you why and helps you build useful signal for trust and safety teams. A session can have no actor label, so treat a missing label as unknown.
Persist the actor label alongside the content row (the examples store it as
foilActor or foil_actor). A post that was created with a human verdict but whose decision.manipulation.verdict is high is a useful thing to surface to moderators without blocking the user outright. decision.manipulation is null when Foil has no manipulation assessment for the session, so check for that before you read verdict.
The server SDKs don’t check a token’s age. Reject a token whose decision.evaluated_at is more than a few minutes old, so that an attacker can’t replay a token captured from one session. See Verify the token on your server for the check in each language.
Velocity caps that survive account rotation
Attackers rotate accounts - making a hundred accounts and posting from each one is cheaper than making one account and posting a hundred times. A per-account rate limit catches the second pattern and misses the first. Per-fingerprint caps catch both: thevisitor_fingerprint.id persists across account creation on the same device, so a fingerprint that signed up three times in six hours is already suspicious by the time it tries to post.
Node.js
The server SDKs type
visitor_fingerprint as nullable, so the examples check for a missing ID. Foil resolves the visitor fingerprint before it issues a token, so a token from the browser SDK carries one. If your code does see a missing ID, fall back to IP-based limiting.Shadow mode
Not every surface can silently reject content. A comments section on a news site where users expect their comment to appear might reasonably want to ship the post even on a bot verdict - but route it to a moderation queue, not to the public feed. Or score against a shadow threshold for 30 days before flipping to enforcement. Two patterns worth keeping separate:- Shadow scoring - verify the token, persist the verdict alongside the post, publish the post anyway. Used to baseline verdict distribution before you turn enforcement on. See Going to production.
- Shadow ban - accept the post, publish it to the author only, suppress it from other feeds. Useful against low-grade spam where you want to waste the spammer’s time rather than tip them off that detection fired.
What’s next
API abuse & scraping
The read-side counterpart: allow crawlers, block LLM scrapers.
Signup protection
Stop the account factory before the posts start.
Verdicts & scoring
How
verdict, risk_score, and attribution fit together.Going to production
Report-only and shadow-mode rollout plans.