Arcjet Python SDK. Bot detection, rate limiting, email validation, attack protection for Python applications.
Project description
Arcjet - Python SDK
Arcjet helps developers protect their apps in just a few lines of code. Bot detection. Rate limiting. Email validation. Attack protection. A developer-first approach to security.
This is the monorepo containing various Arcjet open source packages for Python.
Features
Arcjet security features for protecting Python apps:
- 🤖 Bot protection - manage traffic by automated clients and bots.
- 🛑 Rate limiting - limit the number of requests a client can make.
- 🛡️ Shield WAF - protect your application against common attacks.
- 📧 Email validation - prevent users from signing up with fake email addresses.
- 📝 Signup form protection - combines rate limiting, bot protection, and email validation to protect your signup forms.
[!NOTE] The Arcjet Python SDK currently doesn't support sensitive information detection or request filters. These features are planned for a future release when local analysis will be supported.
Get help
Join our Discord server or reach out for support.
Installation
# With a uv project
uv add arcjet
# With an existing pip managed project
uv pip install arcjet
Or with pip:
pip install arcjet
Usage
Read the docs at docs.arcjet.com
Quick start example
This example implements Arcjet bot protection, rate limiting, email validation, and Shield WAF in a FastAPI application. Requests from bots not in the allow list will be blocked with a 403 Forbidden response.
The example email is invalid so an error will be returned - change the email to see different results.
FastAPI
An asynchronous example using FastAPI with the Arcjet async client.
# main.py
import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from arcjet import (
arcjet,
shield,
detect_bot,
token_bucket,
Mode,
BotCategory,
)
app = FastAPI()
aj = arcjet(
key=os.environ["ARCJET_KEY"], # Get your key from https://app.arcjet.com
rules=[
# Shield protects your app from common attacks e.g. SQL injection
shield(mode=Mode.LIVE),
# Create a bot detection rule
detect_bot(
mode=Mode.LIVE, allow=[
BotCategory.SEARCH_ENGINE, # Google, Bing, etc
# Uncomment to allow these other common bot categories
# See the full list at https://arcjet.com/bot-list
# BotCategory.MONITOR", // Uptime monitoring services
# BotCategory.PREVIEW", // Link previews e.g. Slack, Discord
]
),
# Create a token bucket rate limit. Other algorithms are supported
token_bucket(
# Tracked by IP address by default, but this can be customized
# See https://docs.arcjet.com/fingerprints
# characteristics: ["ip.src"],
mode=Mode.LIVE,
refill_rate=5, # Refill 5 tokens per interval
interval=10, # Refill every 10 seconds
capacity=10 # Bucket capacity of 10 tokens
),
],
)
@app.get("/")
async def hello(request: Request):
# Call protect() to evaluate the request against the rules
decision = await aj.protect(
request, requested=5 # Deduct 5 tokens from the bucket
)
# Handle denied requests
if decision.is_denied():
status = 429 if decision.reason.is_rate_limit() else 403
return JSONResponse(
{"error": "Denied", "reason": decision.reason.to_dict()},
status_code=status,
)
# Check IP metadata (VPNs, hosting, geolocation, etc)
if decision.ip.is_hosting():
# Requests from hosting IPs are likely from bots, so they can usually be
# blocked. However, consider your use case - if this is an API endpoint
# then hosting IPs might be legitimate.
# https://docs.arcjet.com/blueprints/vpn-proxy-detection
return JSONResponse(
{"error": "Denied from hosting IP"},
status_code=403,
)
return {"message": "Hello world", "decision": decision.to_dict()}
Flask
A synchronous example using Flask with the sync client.
# main.py
from flask import Flask, request, jsonify
import os
from arcjet import (
arcjet_sync,
shield,
detect_bot,
token_bucket,
validate_email,
is_spoofed_bot,
Mode,
BotCategory,
EmailType,
)
app = Flask(__name__)
aj = arcjet_sync(
key=os.environ["ARCJET_KEY"],
rules=[
shield(mode=Mode.LIVE),
detect_bot(
mode=Mode.LIVE, allow=[BotCategory.SEARCH_ENGINE, "OPENAI_CRAWLER_SEARCH"]
),
token_bucket(mode=Mode.LIVE, refill_rate=5, interval=10, capacity=10),
validate_email(
mode=Mode.LIVE,
deny=[EmailType.DISPOSABLE, EmailType.INVALID, EmailType.NO_MX_RECORDS],
),
],
)
@app.route("/")
def hello():
# requested is optional; only relevant for token bucket rules (default: 1)
# email is only required if validate_email() is configured
decision = aj.protect(request, requested=1, email="example@arcjet.com")
if decision.is_denied():
status = 429 if decision.reason.is_rate_limit() else 403
return jsonify(error="Denied", reason=decision.reason.to_dict()), status
if decision.ip.is_hosting():
return jsonify(error="Hosting IP blocked"), 403
if any(is_spoofed_bot(r) for r in decision.results):
return jsonify(error="Spoofed bot"), 403
return jsonify(message="Hello world", decision=decision.to_dict())
if __name__ == "__main__":
app.run(debug=True)
Custom characteristics
Each client is tracked by IP address by default. To customize client fingerprinting you can configure custom characteristics:
# main.py
from flask import Flask, request, jsonify
import os
import logging
from arcjet import (
arcjet_sync,
shield,
detect_bot,
token_bucket,
Mode,
BotCategory,
EmailType,
)
app = Flask(__name__)
aj = arcjet_sync(
key=os.environ["ARCJET_KEY"], # Get your key from https://app.arcjet.com
rules=[
# Shield protects your app from common attacks e.g. SQL injection
shield(mode=Mode.LIVE),
# Create a bot detection rule
detect_bot(
mode=Mode.LIVE,
allow=[
BotCategory.SEARCH_ENGINE, # Google, Bing, etc
# Uncomment to allow these other common bot categories
# See the full list at https://arcjet.com/bot-list
# BotCategory.MONITOR", // Uptime monitoring services
# BotCategory.PREVIEW", // Link previews e.g. Slack, Discord
],
),
# Create a token bucket rate limit. Other algorithms are supported
token_bucket(
# Tracked by IP address by default, but this can be customized
# See https://docs.arcjet.com/fingerprints
# characteristics: ["ip.src"],
mode=Mode.LIVE,
refill_rate=5, # Refill 5 tokens per interval
interval=10, # Refill every 10 seconds
capacity=10, # Bucket capacity of 10 tokens
),
],
)
@app.route("/")
def hello():
# Call protect() to evaluate the request against the rules
decision = aj.protect(
request,
requested=5, # Deduct 5 tokens from the bucket
)
# Handle denied requests
if decision.is_denied():
status = 429 if decision.reason.is_rate_limit() else 403
return jsonify(error="Denied", reason=decision.reason.to_dict()), status
# Check IP metadata (VPNs, hosting, geolocation, etc)
if decision.ip.is_hosting():
# Requests from hosting IPs are likely from bots, so they can usually be
# blocked. However, consider your use case - if this is an API endpoint
# then hosting IPs might be legitimate.
# https://docs.arcjet.com/blueprints/vpn-proxy-detection
return jsonify(error="Hosting IP blocked"), 403
return jsonify(message="Hello world", decision=decision.to_dict())
if __name__ == "__main__":
app.run(debug=True)
Trusted proxies
When your app runs behind one or more reverse proxies or a load balancer, pass
their IPs or CIDR ranges so Arcjet can correctly resolve the real client IP from
X-Forwarded-For and similar headers.
from arcjet import arcjet
aj = arcjet(
key=os.environ["ARCJET_KEY"],
rules=[...],
proxies=["10.0.0.0/8", "192.168.0.1"],
)
Only globally routable IPs are accepted for client identification; private,
loopback, link-local, and addresses matching proxies are ignored during IP
extraction.
Logging
Enable debug logging to troubleshoot issues with Arcjet integration.
import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s %(levelname)s %(name)s %(message)s"
)
Arcjet logging can be controlled directly by setting the ARCJET_LOG_LEVEL
environment variable e.g. export ARCJET_LOG_LEVEL=debug.
Accessing decision details
Arcjet returns per-rule rule_results and a top-level decision.reason. To make a simple decision about allowing or denying a request you can check if decision.is_denied():. For more details, inspect the rule results.
Getting bot detection details
To find out which bots were detected (if any):
if decision.reason and decision.reason.is_bot():
denied = (decision.reason.to_dict() or {}).get(
decision.reason.which(), {}).get("denied", [])
print("Denied bots:", ", ".join(denied) if denied else "none")
Future releases of the Python SDK will provide more helpers to access this without needing to convert to a dict.
IP analysis
Arcjet returns an ip_details object as part of a Decision from aj.protect(...). There are two ways to inspect that data:
- high-level helpers for common reputation checks.
- the full raw fields via
Decision.to_dict().
IP analysis helpers
For common checks (is this IP a VPN, proxy, Tor exit node, or a hosting provider) use the IpInfo helpers exposed at decision.ip:
# high level booleans
if decision.ip.is_hosting():
# likely a cloud / hosting provider — often suspicious for bots
do_block()
if decision.ip.is_vpn() or decision.ip.is_proxy() or decision.ip.is_tor():
# treat according to your policy
do_something_else()
IP analysis fields
To access all the fields, convert the decision to a dict and then access the fields directly. A future SDK release will include more helpers to access this data:
d = decision.to_dict()
ip = d.get("ip_details")
if ip:
lat = ip.get("latitude")
lon = ip.get("longitude")
asn = ip.get("asn")
asn_name = ip.get("asn_name")
service = ip.get("service") # may be missing
else:
# ip details not present
These are the available fields, although not all may be present for every IP:
- Geolocation:
latitude,longitude,accuracy_radius,timezone,postal_code,city,region,country,country_name,continent,continent_name - ASN / network:
asn,asn_name,asn_domain,asn_type(isp, hosting, business, education),asn_country - Reputation / service: service name (when present) and boolean indicators for
vpn,proxy,tor,hosting,relay
Support
This repository follows the Arcjet Support Policy.
Security
This repository follows the Arcjet Security Policy.
Compatibility
Packages maintained in this repository are compatible with Python 3.10 and above.
License
Licensed under the Apache License, Version 2.0.
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 arcjet-0.2.3.tar.gz.
File metadata
- Download URL: arcjet-0.2.3.tar.gz
- Upload date:
- Size: 35.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e331a0f0183c8c1f26d791309e0a0e84239487d5418f1aa11f2ae75ac7c16b80
|
|
| MD5 |
c2d190babfdc2fab0cc1253bbd37e71d
|
|
| BLAKE2b-256 |
fead6847f2fd2e34565bd4b20f2f5096ca8762ccf8a266061538f301d4b64aed
|
File details
Details for the file arcjet-0.2.3-py3-none-any.whl.
File metadata
- Download URL: arcjet-0.2.3-py3-none-any.whl
- Upload date:
- Size: 40.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
23c3fe56a920e51f048acb78aeaee3600ce085e291a17d0fce09dde6863a492f
|
|
| MD5 |
2a813337ee49ff0bccc3b62b5db8d2e4
|
|
| BLAKE2b-256 |
de714523b73760d8648b19a2c417d9ab06e1b95488e83f9109c1220f251896b5
|