Every Foil session produces a verdict (human, bot, or inconclusive) and a risk score (integer 0 – 100). Your backend uses these to decide what to do.
Verdicts
| Verdict | Score range | Meaning | Suggested action |
|---|
human | 0 – 39 | Real user behavior detected | Allow |
inconclusive | 40 – 69 | Not enough evidence for a confident call | Challenge or gather more context |
bot | 70 – 100 | Automated or non-human behavior detected | Block or rate-limit |
Risk score
The risk score is an integer from 0 (definitely human) to 100 (definitely bot). It’s normalized via a sigmoid function, so scores cluster near the extremes — most sessions score below 10 or above 90.
Use the score for granular policy when the verdict alone isn’t enough:
if risk_score < 10 → fast-path allow (no friction)
if risk_score < 40 → allow (human verdict)
if risk_score < 70 → challenge (inconclusive)
if risk_score >= 70 → block (bot verdict)
Evaluation phases
Foil evaluates sessions in two phases:
| Phase | When | What it uses | Confidence |
|---|
snapshot | Immediately on session creation | Environment probes, fingerprint, anti-tamper | Good for deterministic signals |
behavioral | After user interaction | Mouse, keyboard, touch, timing patterns | Higher confidence for ambiguous cases |
If you call getSession() before the user interacts with the page, you’ll get a snapshot-phase result. For highest confidence, wait for at least a few seconds of user interaction.
Preliminary vs final
| Status | Meaning |
|---|
preliminary | Early result, may be updated as more data arrives |
final | Complete evaluation, won’t change |
Snapshot-phase results are usually preliminary. Behavioral-phase results are final.
Automation attribution
When Foil identifies the specific automation tool, the session includes attribution details:
| Category | Examples |
|---|
automation | Playwright, Puppeteer, Selenium |
ai-agent | browser-use, OpenAI Operator |
crawler | Googlebot, Bingbot |
verified-bot | Legitimate crawlers with Web Bot Auth |
fabricated | Anti-detect browsers, spoofed profiles |
Attribution includes the framework name, variant, organization (if known), and a confidence score.
Using verdicts in your API
The sealed token returns the Decision shape directly:
{
"verdict": "bot",
"risk_score": 94,
"phase": "behavioral",
"is_provisional": false
}
The session detail endpoint (GET /v1/sessions/:id) renames these fields into a more descriptive, action-oriented vocabulary. (The list endpoint, GET /v1/sessions, keeps the sealed-token names — verdict, phase, is_provisional — in its latest_decision summary.)
| Sealed token field | Session API field | Values |
|---|
verdict | decision.automation_status | human → human, bot → automated, inconclusive → uncertain |
risk_score | decision.risk_score | 0 – 100 integer (same scale) |
phase | decision.evaluation_phase | snapshot or behavioral (may be null on sessions that haven’t evaluated yet) |
is_provisional | decision.decision_status | true → preliminary, false → final |
The underlying data is identical — only the field names and the automation_status / decision_status value labels differ.
Policy recommendations
- Start with report-only — log verdicts without blocking for the first week
- Treat
inconclusive as an opportunity — challenge with CAPTCHA or email verification, don’t block
- Wait for behavioral phase on high-value actions when possible
- Use the score for edge cases — a
bot verdict at 71 is weaker than one at 98
- Keep your Foil decision in your audit trail — log the sessionId alongside the business action
What’s next