Skip to main content

Arcjet Python SDK. Bot detection, rate limiting, email validation, attack protection for Python applications.

Project description

Arcjet Logo

Arcjet - Python SDK

PyPI badge

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

Install from PyPI with uv:

# 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.

Overriding automatic IP detection

By default, Arcjet automatically detects the client IP from the request using X-Forwarded-For. We recommend leaving this enabled in most cases and configuring trusted proxies as needed (see above).

[!WARNING] Disabling automatic IP detection is not recommended unless you have written your own IP detection logic that considers the correct parsing of IP headers. Accepting client IPs from untrusted sources can expose your application to IP spoofing attacks. See the MDN documentation for further guidance.

To disable automatic IP detection (for example, if you have your own custom logic to extract the client IP), set disable_automatic_ip_detection=True when creating the Arcjet client, and then provide the ip_src parameter to .protect(...).

from arcjet import arcjet
aj = arcjet(
    key=os.environ["ARCJET_KEY"],
    rules=[...],
    disable_automatic_ip_detection=True,
)

# ...

decision = aj.protect(
    request,
    ip_src="8.8.8.8",  # provide the client IP here
)

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:

  1. high-level helpers for common reputation checks.
  2. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

arcjet-0.3.0.tar.gz (35.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

arcjet-0.3.0-py3-none-any.whl (41.2 kB view details)

Uploaded Python 3

File details

Details for the file arcjet-0.3.0.tar.gz.

File metadata

  • Download URL: arcjet-0.3.0.tar.gz
  • Upload date:
  • Size: 35.9 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

Hashes for arcjet-0.3.0.tar.gz
Algorithm Hash digest
SHA256 8aed9d59b8b85ecb4d5a21367ebe55633540695a7292a74b7b0ce4891b485fca
MD5 2b900581412a4267c01727821c203103
BLAKE2b-256 915d3770f232d5ad4ac91f14fd5bb54d8ce30fad4c6dd078b7a1d13b61a7c1ed

See more details on using hashes here.

File details

Details for the file arcjet-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: arcjet-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 41.2 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

Hashes for arcjet-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d5cc06cc408148f0fe0528a3434b878d45ee7e72ca093f636fdd8ff687c8d93a
MD5 29f69e25958eb5d9ca559779909f914e
BLAKE2b-256 de5962f81dcc4ef8496f5b2bf7613d7941fd333066c072f3ba628e6c4662632d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page