API reference
Base URL: your BurnGuard host, e.g. https://YOUR-BURNGUARD-HOST. All endpoints accept and return JSON (UTF-8). Request bodies are capped at 16 KB.
Authentication
Send your secret key as a bearer token. Secret keys belong on your server only. Never ship them to a browser or mobile app.
Authorization: Bearer bg_sk_your_secret_key
POST /v1/check
Score a signup, login or trial start. Counts as one check against your monthly quota.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
email |
string | yes | The address the user typed. We normalize it for you. |
event |
string | yes | signup, login or trial_start. |
ip |
string | recommended | The end user's IP (IPv4 or IPv6), not your server's. |
device_token |
string | recommended | The bg_device value from the SDK. |
user_agent |
string | optional | The end user's User-Agent header. |
external_id |
string | optional | Your user id. Lets you link usage to this check. |
Optional header Idempotency-Key: <unique string> returns the original result if you retry the same request.
curl https://YOUR-BURNGUARD-HOST/v1/check \
-H "Authorization: Bearer $BURNGUARD_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"event":"signup","email":"j.o.h.n+42@gmail.com","ip":"203.0.113.7","device_token":"dv_3f9a…"}'
Response
{
"id": "chk_9d1c…",
"decision": "allow",
"would_decision": "limit",
"mode": "monitor",
"score": 41,
"recommended_credit_fraction": 0.1,
"reasons": [
{ "code": "plus_or_dot_alias", "weight": 15, "detail": "Gmail dots/+tag removed: john@gmail.com" },
{ "code": "datacenter_ip", "weight": 40, "detail": "IP belongs to a hosting provider (DigitalOcean)" }
],
"normalized_email": "john@gmail.com",
"cluster_size": 3
}
| Field | Notes |
|---|---|
decision |
What to act on: allow, limit, review or block. In monitor mode this is always allow. |
would_decision |
What BurnGuard decides with your thresholds, regardless of mode. |
mode |
monitor or enforce (set per project in the dashboard). |
score |
0–100. Signals combine so several weak ones add up but never exceed 100. |
recommended_credit_fraction |
0–1. How much of your free tier to grant now. 1 for allow, a fraction for limit, 0 for review and block. |
reasons |
Every signal that fired, with its weight and a human-readable detail. |
normalized_email |
Lowercased, Gmail dots and +tags removed. Use it to spot duplicates in your own data. |
cluster_size |
Distinct accounts linked to this one by normalized email, device or IP in the last 30 days, including this one. |
quota_exceeded |
Present and true when you are over your plan's monthly checks. The decision is then always allow. |
Decisions and default thresholds
| Decision | Default score | Suggested handling |
|---|---|---|
allow |
below 30 | Full free tier. |
limit |
30 to 49 | Grant recommended_credit_fraction of the credits; unlock the rest after card, phone or account age. |
review |
50 to 74 | Create the account but require verification before any credits. |
block |
75 and above | Refuse the signup with a neutral message. |
Thresholds are configurable per project. Allowlist matches always return allow; blocklist matches always return block.
Reason codes
| Code | Meaning |
|---|---|
disposable_domain |
Known throwaway inbox provider. |
no_mx |
The domain can't receive email (no MX record). |
random_local_part |
Random-looking username on a free or disposable provider. |
plus_or_dot_alias |
Gmail dots or a +tag were removed during normalization. |
normalized_email_reuse |
The same normalized email already signed up in this project. |
datacenter_ip |
IP belongs to a cloud or hosting provider. |
vpn_or_proxy_ip |
IP is in a known commercial VPN range. |
tor_exit |
IP is a Tor exit node. |
ip_velocity / subnet_velocity |
Many signups from the same IP or /24 in the last hour. |
device_reuse |
The same device token is linked to several different emails. |
device_velocity |
Several signups from one device in the last hour. |
automation_user_agent |
Headless browser, curl, python-requests and similar. |
missing_device |
No device token although this project normally sends one. |
allowlist / blocklist |
Matched one of your lists. |
POST /v1/feedback
Tell BurnGuard whether a check was right. Free (doesn't count against quota).
{ "check_id": "chk_9d1c…", "label": "legit" }
label is fraud or legit. legit adds the normalized email to the project's allowlist; fraud adds the device and normalized email to the blocklist. Returns { "ok": true }.
POST /v1/usage
Report credits a user consumed so the dashboard can show dollars burned by flagged accounts. Free (doesn't count against quota).
{ "external_id": "user_123", "cost_cents": 42, "units": 2100 }
| Field | Notes |
|---|---|
external_id |
The same id you sent on /v1/check. |
cost_cents |
What this usage cost you, in cents (integer ≥ 0). |
units |
Optional: tokens or credits, for your own reference. |
Without usage reports, the dashboard estimates savings as flagged signups × your cost per free account (set in project settings).
Errors
Errors use one shape:
{ "error": { "code": "unauthorized", "message": "Missing or invalid API key" } }
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request |
Missing or malformed fields. |
| 401 | unauthorized |
Missing, invalid or revoked secret key. |
| 404 | not_found |
Unknown check id (feedback). |
| 429 | rate_limited |
Bursting above the per-key rate limit. Back off and retry. |
| 503 | db_unavailable |
Temporary storage issue on our side. |
| 500 | internal |
Something broke on our side. |
Fail open
BurnGuard should never be the reason a real user can't sign up. In every integration:
- Set a short timeout (1–1.5 seconds).
- Treat timeouts, network errors,
429and5xxasallow. - Log the failure so you notice.
Going over your monthly quota doesn't break anything either: the API keeps answering allow with quota_exceeded: true.
Free tool endpoints
The public tools on this site use GET /v1/public/email?email= and GET /v1/public/ip?ip=. They need no key, are heavily rate-limited per visitor, store nothing and only cover email and network signals. Use /v1/check in production.