Quick start
Get a decision on your first session in under 5 minutes. No credit card needed.
Get from zero to a scored session in four steps. There are two ways to run Botardo, and both start with the same tag:
- Automatic. Turn on form protection and we look after your forms for you: the token, the challenge, all of it. Skip there after step 2.
- Manual. Ask for a token before any request you want protected, and verify it on your server. If we can't tell yet whether it's a person, the visitor is challenged before the token exists.
Either way, your server only ever hears one of two answers: allow or block.
1. Create an account
Sign up at botardo.com/signup to get your site key. The free plan covers every page on every site. No credit card needed.
2. Add the snippet
Drop this tag into the <head> of every page. Scoring starts on load and keeps watching as the visitor moves around the site. A session covers one browser tab, across every page it opens on your site, for up to three hours. A new tab starts a new session.
<script
src="https://cdn.botardo.com/v1.js"
data-site="YOUR_SITE_KEY"
defer
></script>
**Note:** The snippet is **< 4 KB** gzipped, loads from the edge, and defers automatically. No measurable impact on Lighthouse scores.
3. Ask for a token, then verify it
Wherever you need a decision, ask the snippet for a token and send it with the request. A login, a checkout, a comment, an account creation, any API call your bots like to hit.
const token = await botardo.token();
await fetch("/api/login", {
method: "POST",
headers: { "x-bot-token": token },
body: JSON.stringify(credentials),
});
We score the session when you ask for a token, using everything we've received from it so far, earlier pages in the same tab included. botardo.token() doesn't wait for the last events to upload. They go out alongside the token request. Tokens are single use and expire after 2 minutes. Ask for a fresh one for each request you want scored.
The token itself doesn't carry the result, so nobody in the browser can read it. On your server, trade the token for a decision before you do the work:
import { sentinel } from "@botardo/server";
export async function POST(req: Request) {
const { decision } = await sentinel.verify(req.headers["x-bot-token"]);
if (decision === "block") return reject();
// handle the request
}
decision is always "allow" or "block", so that one line covers every case. We only say "block" when we find clear signs of automation, never on a guess. Anything in between was settled in the browser before the token existed.
A missing, expired or already used token comes back as "block" too. A bot that skips the snippet sends no token at all, so letting those requests through would let any bot in.
Verify also returns a status and a risk:
statusis"ok","token.invalid"(missing, unknown or expired) or"token.reused". It tells you why a request without a valid token was blocked. If you'd rather let visitors whose snippet never loaded through to a check of your own, this is where you'd tell them apart.riskruns from 0 (a person) to 100 (a bot). You don't need it to decide. It's there for logging and for extra care on sensitive actions, covered in how scoring works. It's left out when the token isn't valid.
That's all verify returns. We don't say which signals led to the decision, to you or to anyone else.
4. Check the Console
Log into the Console to see live session data. Within a few minutes of adding the snippet you'll see real sessions streaming in, each with its decision and risk.
**Tip:** Turn on **monitor mode** for your first week. Verify answers `"allow"` to everything while it's on, no visitor is challenged, and the Console shows what we would have done. You see how your real visitors fare before it affects anyone, and you switch to enforcing without a deploy.
When we can't tell yet
Most sessions are clearly a person by the time you ask for a token. When one isn't, botardo.token() challenges the visitor first and resolves once they pass. We keep asking until we're sure. Your code doesn't change: the await just takes a little longer for that visitor.
If the visitor closes the challenge, botardo.token() rejects, so the request never goes out.
If botardo.token() runs somewhere it can't show anything to the visitor, it rejects with a challenge-required error instead. Call botardo.challenge() where it fits in your UI. It shows the same challenge and resolves with the token.
let token;
try {
token = await botardo.token();
} catch (err) {
if (err.code !== "challenge-required") throw err;
token = await botardo.challenge(); // when the visitor is looking
}
Protecting forms
Form protection is the hands-off route. Turn it on and we look after your forms for you, so you never have to write the token code from the steps above for them.
<script
src="https://cdn.botardo.com/v1.js"
data-site="YOUR_SITE_KEY"
data-form="true"
defer
></script>
With data-form="true" the snippet:
- attaches to every
<form>on the page, including ones added later by React, Vue, Angular or plain JavaScript; - seals the session on submit and adds the token as an
x-bot-tokenheader, or as a hidden_btfield for plain HTML posts; - challenges sessions we can't decide on yet before the submit goes through, and sends the submit once the visitor passes.
Your server verifies the token exactly as in step 3. To cover only some forms, leave the attribute off and call botardo.protect(formElement) on the ones you want.
**Tip:** Detection and form protection are independent. You can score API calls with `botardo.token()` on one page and let form protection handle the signup form on another, all with the same tag.