API reference

Every endpoint takes your key in the X-Api-Key header and returns the same shape: a 0–100 risk_score, an allow | review | block verdict, and the signals behind it. New here? Start with the quickstart.

Unified Score — score a whole signup

GET/POST/v1/score/user

The endpoint the others feed into: pass any combination of ip, email, phone (+ optional phone_country), and domain. Components run in parallel and are combined into one weighted score — a hard block on any single component is never averaged away. Billed as one credit per component checked.

curl -X POST "https://www.layercall.com/v1/score/user" \
  -H "X-Api-Key: tl_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "ip": "146.70.202.5",
    "email": "ceo@mailinator.com",
    "phone": "+14155552671",
    "domain": "just-registered-shop.com"
  }'
{
  "risk_score": 71,
  "verdict": "block",
  "components_checked": ["ip", "email", "phone", "domain"],
  "top_signals": [
    "ip: commercial vpn",
    "email: disposable domain",
    "domain: registered <30 days ago"
  ],
  "components": {
    "ip":     { "risk_score": 55, "verdict": "review", "...": "full detail" },
    "email":  { "risk_score": 70, "verdict": "block",  "...": "full detail" },
    "phone":  { "risk_score": 0,  "verdict": "allow",  "...": "full detail" },
    "domain": { "risk_score": 60, "verdict": "review", "...": "full detail" }
  },
  "request_id": "req_9f2c…",
  "processing_time_sec": 0.052
}

IP Intelligence

GET/v1/score/ip

VPN, proxy, datacenter, Tor, and abuse signals with geo/ASN context. VPN/datacenter detection runs per-IP across 50k+ ranges (not just ASNs), so it catches a VPN running on a cloud provider. Egress severity: Tor > VPN > datacenter (strongest tier counts, not summed), with abuse and our own network reports stacking on top. Cached lookups return in ~0.003 sec.

curl "https://www.layercall.com/v1/score/ip?ip=185.220.101.1" \
  -H "X-Api-Key: tl_live_your_key"

Email Validation

GET/v1/verify/email

Industry-standard status / sub_status vocabulary (valid, invalid, risky, do_not_mail — drop-in compatible with ZeroBounce-style branching), plus: live SMTP mailbox verification (mailbox_exists) and catch-all detection, MX + provider identification, 4,000+ disposable domains refreshed daily, did_you_mean typo correction that also catches typo-squat domains like gamil.com, normalized_email for multi-account dedupe (strips +tags and Gmail dots), machine-generated handle detection, domain age via RDAP, high-abuse TLD flags, first_seen / times_seen velocity from our own network, and a digital_footprintobject (public Gravatar profile + optional breach history — an aged real identity lowers risk). When the mailbox can't be verified we say so (mailbox_not_verified) rather than guessing.

curl "https://www.layercall.com/v1/verify/email?email=ceo@mailinator.com" \
  -H "X-Api-Key: tl_live_your_key"
{
  "email": "test@mailinator.com",
  "normalized_email": "test@mailinator.com",
  "risk_score": 100,
  "verdict": "block",
  "status": "do_not_mail",
  "sub_status": "disposable",
  "deliverability_score": 0,
  "did_you_mean": null,
  "signals": {
    "syntax_valid": true,
    "mx_found": true,
    "is_disposable": true,
    "is_homograph": false,
    "is_role_account": true,
    "is_free_provider": false,
    "is_suspicious_handle": true,
    "is_tagged": false,
    "is_risky_tld": false,
    "is_new_domain": null,
    "mailbox_exists": null,
    "is_catch_all": null,
    "has_digital_footprint": false
  },
  "domain": "mailinator.com",
  "domain_age_days": null,
  "mx_provider": null,
  "mx_records": [
    "mail.mailinator.com.",
    "mail2.mailinator.com."
  ],
  "abuse_reports": 0,
  "digital_footprint": {
    "has_gravatar": false,
    "gravatar_profile_url": null,
    "breach_count": null,
    "seen_in_breach": null
  }
}

Phone Validation

GET/v1/lookup/phone

Validity, country, E.164/national/international formats, and line type for 240+ countries — with premium-rate risk flags. VoIP detection comes from national numbering-plan ranges, so it is available for GB, FR, NL, IT, CA and others, but not for +1 US numbers (the NANP plan has no VoIP range). Accepts E.164 directly or national format with &country=.

curl "https://www.layercall.com/v1/lookup/phone?phone=%2B14155552671" \
  -H "X-Api-Key: tl_live_your_key"
# national format also works: ?phone=4155552671&country=US
{
  "parse_status": "ok",
  "phone": "+14155552671",
  "risk_score": 0,
  "signals": {
    "syntax_valid": true,
    "is_possible": true,
    "is_voip": false,
    "is_premium_rate": false,
    "is_toll_free": false,
    "assigned_area_code": true
  },
  "number": {
    "e164": "+14155552671",
    "country": "US",
    "national": "(415) 555-2671",
    "international": "+1 415 555 2671",
    "line_type": "fixed_line_or_mobile"
  },
  "verdict": "allow"
}

Domain Intelligence

GET/v1/score/domain

Registration age via RDAP (newly-registered domains are a strong fraud signal), DNS resolution, MX/SPF/DMARC email posture, and disposable-domain detection.

curl "https://www.layercall.com/v1/score/domain?domain=stripe.com" \
  -H "X-Api-Key: tl_live_your_key"
{
  "domain": "github.com",
  "risk_score": 0,
  "verdict": "allow",
  "signals": {
    "resolves": true,
    "mx_found": true,
    "has_spf": true,
    "has_dmarc": true,
    "is_disposable": false,
    "is_homograph": false,
    "is_free_provider": false,
    "is_risky_tld": false,
    "newly_registered": false
  },
  "registration": {
    "created_at": "2007-10-09T18:20:50Z",
    "age_days": 6865,
    "registrar": "MarkMonitor Inc."
  }
}

Device Fingerprint — the signal that survives a VPN

POST/v1/score/device

A fraudster you block switches to a VPN and a disposable inbox — both free, both already detected above. What they don’t change is the machine in front of them. Drop fp.js on your signup page to get a stable device_id, then score it. No cookie is set and nothing is written to the device, so clearing storage or opening incognito returns the same id — which is the entire point.

<!-- 1. On your signup page -->
<script src="https://www.layercall.com/fp.js"></script>
<script>
  LayerCall.fingerprint().then(fp => {
    // POST fp.device_id (and optionally fp.signals / fp.automation)
    // to YOUR backend along with the rest of the form
  });
</script>
# 2. From your backend
curl -X POST "https://www.layercall.com/v1/score/device" \
  -H "X-Api-Key: tl_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "ff48aa64a243c1b9e07d5c2f8a1b4e6d…",
    "ip": "185.220.101.5",
    "automation": { "webdriver": false }
  }'
{
  "device_id": "ff48aa64a243c1b9e07d5c2f8a1b4e6d…",
  "risk_score": 45,
  "verdict": "review",
  "bot_probability": 0,
  "signals": {
    "is_bot": false,
    "is_automated": false,
    "is_headless": false,
    "timezone_mismatch": true,
    "repeat_device": true
  },
  "first_seen": "2026-07-02T09:14:22Z",
  "times_seen": 38,
  "abuse_reports": 1,
  "processing_time_sec": 0.004
}

bot_probability is separate from risk_scoreon purpose: “this is a script” and “this is dangerous” are different questions, and plenty of automated traffic is perfectly legitimate. Automation markers are collected but deliberately not hashed into the id, so a bot that patches navigator.webdriver cannot also shed the reputation it has built up.

You can also pass device_id straight to /v1/score/user to fold it into a whole-signup score. Privacy: the id is hashed in the browser and hashed again before storage, so the stored value cannot be reversed to a device — not even by us. Raw browser characteristics are used for bot scoring only and are never persisted.

Custom Rules — your lists always win

GET/POST/DELETE/v1/rules

Exclude your own infrastructure or block known bad actors: add allow or block rules for any ip, cidr range, email, domain (subdomains included), phone, or asn. Rules override the computed score on every endpoint — an allow rule forces 0/allow, a block rule forces 100/block, and the response shows rule_applied so you always know why. Block rules take precedence over allow rules. Kind is auto-detected per value. Rule management calls are free.

# Never flag our office network or corporate domain
curl -X POST "https://www.layercall.com/v1/rules" \
  -H "X-Api-Key: tl_live_your_key" -H "Content-Type: application/json" \
  -d '{ "action": "allow", "values": ["203.0.113.0/24", "ourcompany.com"] }'

# List, delete one, or clear all
curl "https://www.layercall.com/v1/rules" -H "X-Api-Key: …"
curl -X DELETE "https://www.layercall.com/v1/rules/<rule-id>" -H "X-Api-Key: …"
curl -X DELETE "https://www.layercall.com/v1/rules?confirm=true" -H "X-Api-Key: …"

Report Fraud — grow the shared network

POST/v1/report

Caught a fraudster? Report the IP, email, domain, or phone and it feeds our reputation network — so the next time anyone (including you) scores that value, it carries the abuse history. This is our own data, built from reports like yours: no third-party licenses, no stale lists. Reported values raise abuse_reports and recent_abuse on every future score. Free — reporting makes the product better for everyone.

curl -X POST "https://www.layercall.com/v1/report" \
  -H "X-Api-Key: tl_live_your_key" -H "Content-Type: application/json" \
  -d '{ "values": ["45.9.20.14", "scammer@example.com"] }'

# → { "reported": 2, "results": [ { "kind": "ip", "abuse_reports": 7 }, … ] }

Migration Import — switch providers in one call

POST/v1/rules/import

Switching from another provider? Export your allowlist/blocklist there and POST the file here as-is — plain text or CSV, one entry per line. We auto-detect IPs, CIDR ranges, emails, domains, phones, and ASNs, skip headers and junk columns, dedupe, and report exactly what was imported and what was skipped. Up to 10,000 entries per call, free.

curl -X POST "https://www.layercall.com/v1/rules/import?action=block" \
  -H "X-Api-Key: tl_live_your_key" \
  -H "Content-Type: text/csv" \
  --data-binary @old-provider-blocklist.csv
{
  "action": "block",
  "imported": 4210,
  "duplicates": 12,
  "by_kind": { "ip": 3900, "cidr": 180, "domain": 130 },
  "skipped": [ { "line": 1, "value": "ip_address,date_added", "reason": "no ip/cidr/email/domain/phone/asn recognized" } ],
  "processing_time_sec": 0.19
}