kaidn
Official Python client for Kaidn, the fraud and abuse scoring API.
Send one user action, get back allow, review or block, with the reasons attached.
pip install kaidn
from kaidn import KaidnClient
client = KaidnClient() # reads $KAIDN_API_KEY
r = client.score(event="signup", ip=ip, email=email)
if r.blocked:
raise Denied(r.reason_text)
Zero runtime dependencies. This runs in your signup and checkout path, so every dependency it carried would be one more thing that can break your deploy or turn up in your vulnerability scanner. It uses the standard library and nothing else.
Requires Python 3.9+. Server-side only: it holds your secret key, so never ship it to a
browser. The browser half is @kaidn/fp and
uses a separate publishable key.
Score an event
event is the only required field, and the name is yours to choose. Send whatever else
you already collect; the answer sharpens as you send more.
r = client.score(
event="signup",
user_id=user.id,
ip=request.remote_addr,
email=form["email"],
device_id=form.get("kaidn_device_id"), # from @kaidn/fp, if installed
)
r.verdict # "allow" | "review" | "block"
r.reasons # ["datacenter_ip", "disposable_email"]
r.reason_text # a sentence you could send to the customer
r.score # 0-100. Bookkeeping, not a probability: branch on the verdict
Branching, with the three cases people actually use:
if r.blocked:
return deny() # generic message: a specific one teaches the next attempt
if r.needs_review:
create_account(hold_rewards=True) # they can use the product, they just cannot earn yet
flag_for_review(r.event_id, r.reason_text)
else:
create_account()
Read the evidence
Every verdict shows its work. key is the config key you would edit to retune that
check, so a decision tells you how to change it next time.
for c in r.checks:
print(c.reason, c.weight, c.key, c.evidence)
# datacenter_ip 45 datacenterIp {'asn': '16509'}
Recognise a returning device
A browser fingerprint is not a person: on production traffic one iOS Safari fingerprint
covers 2.30 different people. So use resolved_id, not id, and weigh it with
collision_risk.
d = r.device
if d:
d.resolved_id # the identity. Link visits on this
d.collision_risk # measured P(covers more than one person)
d.account_count # includes fingerprint collisions
d.account_count_same_network # the number you can defend to an angry user
Store r.device_token as a first-party cookie on your own domain and pass it back as
device_token next time. The identity then becomes deterministic: remembered rather
than inferred.
Dedupe one inbox, not one address
bob+1@gmail.com, b.o.b@gmail.com and bob@googlemail.com are one mailbox.
if r.identity and User.exists(email_canonical=r.identity.email_canonical):
return reject("an account already uses this inbox")
Check an identifier on its own
No event recorded, useful at the form or when cleaning a list.
client.check.email("x9f2kq@mailinator.com").fraud_score # 75
client.check.ip("3.5.140.1").report.get("is_datacenter") # True
client.check.phone("+14155550123", country="US")
Report what really happened
Feedback is what sharpens scoring. legit marks your own false positive and never
lowers anyone else's risk.
client.label(label="chargeback", event_id=r.event_id)
client.label(label="legit", event_id=r.event_id)
Errors
Everything raises KaidnError, with the API's own message.
from kaidn import KaidnError
try:
r = client.score(event="signup", email=email)
except KaidnError as err:
if err.status == 429:
notify_ops("Kaidn quota exhausted")
raise
Network failures, timeouts, 429s and 5xx are retried automatically (2 extra attempts by
default, honouring Retry-After). A 4xx is not: a bad key fails identically the second
time, and retrying it just spends quota and delays the error reaching whoever can fix it.
Set a timeout and fail open. A fraud vendor that can take down your signup form is a worse problem than the fraud:
try:
r = client.score(event="signup", email=email)
except KaidnError:
r = None # create the account. Do not let our outage become yours.
Fields we have not named yet
Every response keeps what this version does not recognise, so a signal the API ships next week reaches code running the library you installed last year.
r.get("a_field_added_after_this_release")
r.device.get("some_new_signal")
r.extra # everything unrecognised
Requests work the same way: any extra keyword to score() is passed through untouched.
Configuration
KaidnClient(
api_key="kdn_live_...", # default: $KAIDN_API_KEY
base_url="https://api.kaidn.io",
timeout=10.0, # seconds per attempt
retries=2, # extra attempts on a transient failure
)
Links
MIT
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 kaidn-1.0.0.tar.gz.
File metadata
- Download URL: kaidn-1.0.0.tar.gz
- Upload date:
- Size: 12.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
25c99765b2866ff09632fbba08041ff37bf69b838c5157c1d262b60979b9d681
|
|
| MD5 |
3c55aeeb4915583474d26aefcd0dcfe9
|
|
| BLAKE2b-256 |
2ebbf8c492aaafd8a2ebe4929c509e2660a276f05df53494dac6eca29b03f41a
|
File details
Details for the file kaidn-1.0.0-py3-none-any.whl.
File metadata
- Download URL: kaidn-1.0.0-py3-none-any.whl
- Upload date:
- Size: 13.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b25125332c3331acb6dd9b26c630469f10a30f5b37a530ce0068b227b88273c
|
|
| MD5 |
e9484fdb37f8440f618b11b885e45f3d
|
|
| BLAKE2b-256 |
dd5475fece071a135af7df3f0c63c69f112d12124df4dbac2189c8f6b748fd6c
|