Invisible bot protection for FastAPI applications using Roundtable.ai Proof-of-Human detection
Project description
fastapi-roundtable
Invisible bot protection for FastAPI applications using Roundtable.ai Proof-of-Human detection.
Overview
fastapi-roundtable is a FastAPI integration for Roundtable.ai Proof-of-Human bot detection.
It provides backend session validation for protecting your FastAPI endpoints from automated bots and
suspicious activity.
Roundtable uses behavioral biometrics and device analysis to detect bots with best-in-class accuracy (87% vs Google's 69% and Cloudflare's 33%). Unlike CAPTCHAs, it works invisibly in the background, providing a frictionless experience for legitimate users while blocking automated threats.
This library handles the backend validation piece, making it easy to verify session IDs generated by Roundtable's frontend JavaScript tracker.
How It Works
┌─────────────┐
│ Browser │ 1. Roundtable JS tracker monitors user behavior
│ (Frontend) │ 2. Generates session ID based on behavioral analysis
└──────┬──────┘
│ Session ID (form field, header, or body)
▼
┌─────────────┐
│ FastAPI │ 3. fastapi-roundtable validates session ID
│ (Backend) │ 4. Queries Roundtable API for risk score
└──────┬──────┘ 5. Rejects high-risk sessions automatically
│
▼
┌─────────────┐
│ Roundtable │ Returns risk score (0-100)
│ API │ Higher score = more suspicious
└─────────────┘
The result: Bots and automated attacks are blocked before they can exploit your system, while legitimate users never see a CAPTCHA or challenge.
Features
- Async session validation using aiohttp for high performance
- Multiple session ID sources - extract from form fields, HTTP headers, or request body
- Action-based validation - wait for specific user actions to ensure data freshness
- Configurable risk thresholds - set your own acceptable risk level
- Type-safe - full type hints throughout
- Environment variable support - secure API key management
- FastAPI-native - uses dependency injection for clean integration
Prerequisites
Before using this library, you need:
- A Roundtable.ai account with Site Key and Secret Key
- Roundtable's JavaScript tracker installed on your frontend pages
- A FastAPI application
Installation
pip install fastapi-roundtable
Quick Start
1. Add Roundtable tracker to your frontend
<!DOCTYPE html>
<html>
<head>
<title>Contact Form</title>
</head>
<body>
<form method="POST" action="/contact">
<!-- Hidden field for Roundtable session ID -->
<input type="hidden" name="rt_session_id" id="rt_session_id" value="">
<input type="text" name="name" placeholder="Your name" required>
<input type="email" name="email" placeholder="Your email" required>
<textarea name="message" placeholder="Your message" required></textarea>
<button type="submit">Send</button>
</form>
<!-- Load Roundtable tracker -->
<script src="https://cdn.roundtable.ai/v1/rt.js"
data-site-key="YOUR_SITE_KEY"></script>
<!-- Populate hidden field with session ID from sessionStorage -->
<script>
const interval = setInterval(() => {
if (sessionStorage.getItem('rtSessionId')) {
clearInterval(interval);
document.getElementById("rt_session_id").value = sessionStorage.getItem('rtSessionId');
}
}, 500);
</script>
</body>
</html>
The Roundtable tracker stores the session ID in sessionStorage.rtSessionId. The JavaScript code
polls for this value and populates the hidden form field when available.
2. Protect your FastAPI endpoint
from fastapi import Depends, FastAPI
from fastapi_roundtable import Roundtable
app = FastAPI()
# Initialize Roundtable validator (do this once, reuse across endpoints)
roundtable = Roundtable(
api_key="your_secret_key", # or set ROUNDTABLE_API_KEY env var
status_code=404, # HTTP status to return on validation failure
max_risk_score=50 # Maximum acceptable risk score (0-100)
)
@app.post("/contact", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
async def contact_form(name: str, email: str, message: str):
# This code only runs if session validation passes
# Bots and high-risk sessions are automatically rejected
return {"message": "Thank you for your message!"}
That's it! Your endpoint is now protected from bots.
Detailed Usage Examples
Form-Based Validation
Perfect for contact forms, signup pages, and user submissions:
from fastapi import Depends, FastAPI, Form
from fastapi_roundtable import Roundtable
app = FastAPI()
roundtable = Roundtable()
@app.post("/signup", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
async def signup(
username: str = Form(...),
email: str = Form(...),
password: str = Form(...)
):
# Only legitimate users reach this code
return {"status": "success"}
Header-Based Validation
Ideal for API endpoints and mobile applications:
@app.post("/api/submit", dependencies=[Depends(roundtable(http_header="X-Session-ID"))])
async def api_submit(data: dict):
# Validate session from custom header
return {"received": data}
Client sends request with header:
POST /api/submit
X-Session-ID: <roundtable_session_id>
Body-Based Validation
For JSON APIs where session ID is in the request payload:
@app.post("/api/create", dependencies=[Depends(roundtable(body_field="session_id"))])
async def create_resource(data: dict):
return {"created": True}
Client sends JSON:
{
"session_id": "<roundtable_session_id>",
"name": "Resource name",
"data": {...}
}
Custom Error Handling
Customize the response when validation fails:
from fastapi import HTTPException, Request
from fastapi.responses import HTMLResponse
@app.exception_handler(HTTPException)
async def custom_http_exception_handler(request: Request, exc: HTTPException):
if exc.status_code == 404:
return HTMLResponse(
content="<h1>Access Denied</h1><p>Suspicious activity detected.</p>",
status_code=404
)
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail}
)
Reusing Across Multiple Endpoints
Initialize once, use everywhere:
app = FastAPI()
roundtable = Roundtable(max_risk_score=60)
@app.post("/contact", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
async def contact(): ...
@app.post("/signup", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
async def signup(): ...
@app.post("/api/data", dependencies=[Depends(roundtable(http_header="X-Session-ID"))])
async def api_data(): ...
Ensuring Data Freshness with Validation Timeout
Frontend behavioral data and backend API requests are processed through independent channels. When a user submits a form, the frontend tracker logs the action while the form data is sent to your backend simultaneously. To ensure the risk assessment includes the most recent user activity, this library waits a configurable amount of time before validating the session.
What session_validation_timeout does:
-
Without
require_action: Waits for the specified timeout duration to give Roundtable time to process recent frontend events, then validates using the risk score. This is the most common usage pattern. -
With
require_action: Waits for a specific action to appear in the session logs. If the action appears within the timeout period, validates using the risk score. If the timeout is reached without finding the action, the request is blocked (rejected with the configured status code).
Basic Example (waiting for data to settle):
@app.post(
"/contact",
dependencies=[
Depends(
roundtable(
form_field="rt_session_id",
session_validation_timeout=5 # Wait 5 seconds for recent events to process
)
)
]
)
async def contact_form():
return {"message": "Thank you!"}
Advanced Example (requiring specific action):
@app.post(
"/contact",
dependencies=[
Depends(
roundtable(
form_field="rt_session_id",
require_action="User submitted contactForm", # Must see this action
session_validation_timeout=30 # Wait up to 30 seconds for it
)
)
]
)
async def contact_form():
return {"message": "Thank you!"}
Parameters:
session_validation_timeout(default: 30 seconds): Time to wait before validating the session. Always applied.require_action(optional): Specific action name that must appear in session logs. If specified and not found within timeout, the request is blocked.
When to use:
- Without
require_action: Most common usage - allows time for recent events to be processed - With
require_action: When you want to validate that a specific user action occurred (e.g., form submission, button click) - Shorter timeouts (5-10s): For better user experience when you expect fast event processing
- Longer timeouts (30-60s): When you need to ensure even slower events are captured
Configuration
Environment Variables
Store your API key securely:
export ROUNDTABLE_API_KEY="your_secret_key"
Then initialize without explicit key:
roundtable = Roundtable() # Reads from environment
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str | None |
None |
Roundtable API secret key. If not provided, reads from ROUNDTABLE_API_KEY environment variable. |
status_code |
int |
404 |
HTTP status code to raise when validation fails. Using 404 makes bot detection less obvious. |
max_risk_score |
int |
50 |
Maximum acceptable risk score (0-100). Sessions with higher scores are rejected. |
Risk Score Guidelines
Risk scores range from 0 (definitely human) to 100 (definitely bot):
- 0-30: Very likely human - safe to allow
- 30-50: Probably human - acceptable for most use cases
- 50-70: Uncertain - higher scrutiny recommended
- 70-100: Very likely bot - should be blocked
Recommended thresholds:
- Lenient (max_risk_score=70): Allow most traffic, block obvious bots
- Balanced (max_risk_score=50): Default setting, good for most applications
- Strict (max_risk_score=30): High security, may have false positives
Adjust based on your needs. Start with 50 and tune based on your traffic patterns.
Use Cases
Contact Form Spam Prevention
@app.post("/contact", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
Block automated spam submissions while keeping forms simple for real users.
Account Signup Protection
@app.post("/signup", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
Prevent bot-driven account creation and fake registrations.
API Rate Limit Bypass Prevention
@app.post("/api/resource", dependencies=[Depends(roundtable(http_header="X-Session-ID"))])
Stop bots from bypassing rate limits with multiple IPs or identities.
Comment/Review Spam Blocking
@app.post("/reviews", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
Keep your user-generated content authentic and spam-free.
Credential Stuffing Prevention
@app.post("/login", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
Protect login endpoints from automated credential testing attacks.
API Reference
Roundtable
A reusable session validator for FastAPI applications.
class Roundtable:
def __init__(
self,
*,
api_key: str | None = None,
status_code: int = 404,
max_risk_score: int = 50
) -> None:
"""Initialize a session validator with Roundtable.ai API credentials."""
__call__(*, form_field: str | None = None, http_header: str | None = None, body_field: str | None = None, require_action: str | None = None, session_validation_timeout: float = 30)
Creates a FastAPI dependency for session validation. Exactly one of form_field, http_header, or
body_field must be provided.
Parameters:
form_field: Name of the form field containing the session IDhttp_header: Name of the HTTP header containing the session IDbody_field: Name of the request body field containing the session IDsession_validation_timeout(default: 30): Seconds to wait before validating the session. Always applied.require_action(optional): Action name that must appear in session logs. If not found within timeout, request is blocked.
Returns: An async dependency function compatible with Depends()
Example:
# Basic validation from form field
Depends(roundtable(form_field="rt_session_id"))
# Wait for specific action before validating
Depends(roundtable(
form_field="rt_session_id",
require_action="User submitted contactForm",
session_validation_timeout=30
))
# Validate from HTTP header
Depends(roundtable(http_header="X-Session-ID"))
# Validate from request body field
Depends(roundtable(body_field="sessionId"))
async validate_session(session_id: str, require_action: str | None = None, session_validation_timeout: float = 60) -> None
Validates a session ID against the Roundtable.ai API. Raises HTTPException if validation fails or timeout is reached without finding the required action.
For advanced usage and full API documentation, see the source code.
Requirements
- Python 3.9 or higher
- fastapi
- aiohttp
- pydantic
Links
- GitHub Repository: github.com/leandropls/fastapi-roundtable
- PyPI Package: pypi.org/project/fastapi-roundtable
- Roundtable Documentation: docs.roundtable.ai
- Issue Tracker: GitHub Issues
- Roundtable Dashboard: accounts.roundtable.ai
Disclaimer
This library is maintained independently and is not an official Roundtable.ai product. While the author is employed by Roundtable.ai, this project is a community contribution and not affiliated with or endorsed by the company.
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request or open an Issue on GitHub.
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 fastapi_roundtable-0.2.0.tar.gz.
File metadata
- Download URL: fastapi_roundtable-0.2.0.tar.gz
- Upload date:
- Size: 9.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.2 CPython/3.13.2 Darwin/24.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ed55f64475353166b074b3a8ce1b3a1e169e7324b705f54926fcaec5c5ea517
|
|
| MD5 |
7f696e26fbe9b7f0652dcb8935a74b70
|
|
| BLAKE2b-256 |
d6091ca18568e691da3e5a7e69d25e6ffc6f4d5eed14aa4fafb5b1692b682591
|
File details
Details for the file fastapi_roundtable-0.2.0-py3-none-any.whl.
File metadata
- Download URL: fastapi_roundtable-0.2.0-py3-none-any.whl
- Upload date:
- Size: 10.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.1.2 CPython/3.13.2 Darwin/24.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6e1c45beb60665bb471ae64d4cf22f2eddcb273bcb04530f0f5818318f44a52
|
|
| MD5 |
f065168bdef00d62f93e35bd80b32ab5
|
|
| BLAKE2b-256 |
34eb4c090e3caf0d574e9bdedc3a9c72a55bd57a5c188211df927d23240caff3
|