Official LayerCall client — score an IP, email, phone, domain or a whole signup for fraud in one call. Zero dependencies.
Project description
layercall
Official Python client for LayerCall — score an IP, email, phone number, domain, or a whole signup for fraud in a single call.
Zero dependencies. Standard library only. A trust check sits on your signup path, which is the worst place to add a dependency tree that has to resolve against whatever your app already pins.
pip install layercall
Quick start
import os
from layercall import LayerCall
lc = LayerCall(os.environ["LAYERCALL_API_KEY"])
result = lc.score_user(ip=request.remote_addr, email=form["email"])
if result["verdict"] == "review":
send_otp(form["email"]) # a real user clears it themselves
elif result["verdict"] == "block":
queue_for_review(result)
Get a free API key — 1,000 lookups a month, no card.
Acting on a verdict
| Verdict | Do this | Not this |
|---|---|---|
allow |
Let them through | — |
review |
Step up — OTP, SMS, 3-D Secure | Don't reject. This band includes ordinary VPN users. |
block |
Reject, or send to a human queue | Don't reject silently |
Prefer a challenge over a rejection. A real user clears it in seconds; an attacker cannot. A false positive then costs friction instead of a customer.
Already send an OTP to everyone? Passwordless products can't use an email code as a step-up — it's already mandatory. Score after sign-in and use a different lever: a limited account state, a delayed payout, or a review queue.
Methods
lc.score_ip("185.220.101.1")
lc.verify_email("someone@mailinator.com")
lc.lookup_phone("+14155552671")
lc.lookup_phone("4155552671", country="US")
lc.score_domain("example.com")
lc.score_user(ip=ip, email=email, phone=phone, device_id=device_id)
lc.batch("email", ["a@x.com", "b@y.com"]) # up to 500
Every method takes strictness (0 lenient → 3 paranoid). It moves the
verdict thresholds only; the risk score never changes, so you can re-tune
without re-scoring anything.
None means unknown, never "no"
signals = lc.score_domain("example.de")["signals"]
signals["newly_registered"] # None — .de publishes no RDAP, so age is unknown
None means we could not determine it. It is not a negative finding.
Treating it as "established" is exactly the mistake the field exists to
prevent.
Same for mailbox_exists: Gmail and Yahoo accept mail for addresses that do
not exist, so we return None rather than a guess.
Errors
from layercall import LayerCallError
try:
lc.score_ip(ip)
except LayerCallError as err:
if err.is_quota: # 402 — out of quota or spend cap reached
...
if err.is_auth: # 401/403 — bad or revoked key
...
print(err.request_id) # quote this in a support ticket
except Exception:
pass # fail open: let the signup through
429s and 5xx retry automatically (2 attempts, short backoff). Other 4xx fail fast — they will not succeed on a retry.
Server-side only
An API key in anything a user can read is public and bills to your account.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file layercall-1.1.0.tar.gz.
File metadata
- Download URL: layercall-1.1.0.tar.gz
- Upload date:
- Size: 6.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bcbd3dad7fdcf243dae946288c1ebccb5c5ed2884123e788c9e3ed928fef91fc
|
|
| MD5 |
5e68cac71ab457c06f2960d057b97020
|
|
| BLAKE2b-256 |
af6e43a453786bd46cd48d521061ce05e03f89ded3e908b9833931be72b49593
|
File details
Details for the file layercall-1.1.0-py3-none-any.whl.
File metadata
- Download URL: layercall-1.1.0-py3-none-any.whl
- Upload date:
- Size: 6.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff90eb7ce9502357be7faf36dac52824305c5cd7b978fa30a6944decd623d684
|
|
| MD5 |
affc9cad09c62e63e157e29e2e4f8192
|
|
| BLAKE2b-256 |
64bf0d662bd1241e269cb9ca9bee5fffc3bac138aa1d9e9e579eb55ceebf726e
|