Skip to content

AI app abuse protection for Python + FastAPI

Automated clients – scrapers, data harvesters, and script-based attackers - treat AI features as free compute. Without bot protection, every request from a bot reaches your AI provider and inflates your costs.

Arcjet bot detection runs inside your application, before the AI call, so denied requests never reach your provider. It classifies known bots, verifies good bots, and detects emerging threats in real time so you can control access per route with full application context (identity, subscription level, session state).

In this example we use LangChain to create a simple AI chat server with FastAPI, and Arcjet to protect it from abuse. The same principles can be applied to any AI application, including those built with other frameworks.

Set up the environment and install dependencies (uses uv, but you can also use pip to install the Arcjet Python SDK):

Terminal window
# Export your Arcjet API key from https://console.arcjet.com
export ARCJET_KEY="ajkey_..."
export ARCJET_ENV=development
# Export your OpenAI API key (used by LangChain)
export OPENAI_API_KEY="sk-..."
# Install dependencies
uv add arcjet fastapi uvicorn langchain langchain-openai

Create the chat server:

main.py
import logging
import os
from arcjet import Mode, arcjet, detect_bot, shield
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel
app = FastAPI()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
arcjet_key = os.getenv("ARCJET_KEY")
if not arcjet_key:
raise RuntimeError("ARCJET_KEY is required. Get one at https://console.arcjet.com")
openai_api_key = os.getenv("OPENAI_API_KEY")
if not openai_api_key:
raise RuntimeError(
"OPENAI_API_KEY is required. Get one at https://platform.openai.com"
)
llm = ChatOpenAI(model="gpt-4o-mini", api_key=openai_api_key)
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful assistant."),
("human", "{message}"),
]
)
chain = prompt | llm | StrOutputParser()
class ChatRequest(BaseModel):
message: str
# Create a single Arcjet client at startup and reuse it across requests
aj = arcjet(
key=arcjet_key, # Get your key from https://console.arcjet.com
rules=[
# Shield protects against common web attacks e.g. SQL injection
shield(mode=Mode.LIVE),
# Block all automated clients — bots inflate AI costs
detect_bot(
mode=Mode.LIVE, # Blocks requests. Use Mode.DRY_RUN to log only
allow=[
"CURL", # Allow curl so we can test it (see README)
# 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
],
),
],
)
@app.post("/chat")
async def chat(request: Request, body: ChatRequest):
decision = await aj.protect(request)
if decision.is_denied():
if decision.reason_v2.type == "BOT":
return JSONResponse(
{"error": "Automated clients are not permitted"}, status_code=403
)
return JSONResponse({"error": "Forbidden"}, status_code=403)
# Arcjet approved — proceed with the AI call
reply = await chain.ainvoke({"message": body.message})
return {"reply": reply}

Then run the server:

Terminal window
uv run uvicorn main:app --reload

And send a message to the API endpoint:

Terminal window
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{"message": "What is the capital of France?"}'

Requests appear in your Arcjet dashboard in real time.

allow: [] blocks all automated clients. This is the recommended default for AI routes where no bot traffic is legitimate.

To allow specific categories or named bots from our list of known bots, add them to the allow list:

detectBot({
mode: "LIVE",
allow: [
"CURL", // Allow curl-based scripts
"CATEGORY:MONITOR", // Uptime monitoring services
"CATEGORY:PREVIEW", // Link previewers (Slack, Discord, etc.)
],
})

Bot protection controls who can call your AI features. To also control how much each user can consume, combine it with AI budget control:

rules: [
detectBot({ mode: "LIVE", allow: [] }),
tokenBucket({ // Token bucket rate limiting is best for AI budget control
mode: "LIVE",
characteristics: ["userId"], // Link limits to users
refillRate: 2_000, // Refill 2000 tokens per interval
interval: "1h", // Refill interval
capacity: 5_000, // Max tokens
}),
]

The get started guide shows the combined pattern.

Bot protection controls who can call your AI features, but legitimate users can still submit malicious prompts. Combine bot detection with prompt injection detection to also block jailbreaks, role-play escapes, and instruction overrides before they reach your AI model.