fastapi-roundtable 0.2.0
Invisible bot protection for FastAPI applications using Roundtable.ai Proof-of-Human detection
License: MIT
Requires Python: >=3.9
Keywords: fastapi, bot-detection, security, roundtable, proof-of-human, spam-prevention, api-protection, session-validation

# fastapi-roundtable

Invisible bot protection for FastAPI applications using [Roundtable.ai](https://roundtable.ai) Proof-of-Human detection.

[![PyPI version](https://badge.fury.io/py/fastapi-roundtable.svg)](https://pypi.org/project/fastapi-roundtable/)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Overview

`fastapi-roundtable` is a FastAPI integration for [Roundtable.ai](https://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:

1. A [Roundtable.ai account](https://accounts.roundtable.ai) with Site Key and Secret Key
2. Roundtable's JavaScript tracker installed on your frontend pages
3. A FastAPI application

## Installation

```bash
pip install fastapi-roundtable
```

## Quick Start

### 1. Add Roundtable tracker to your frontend

```html
<!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

```python
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:

```python
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:

```python
@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:

```python
@app.post("/api/create", dependencies=[Depends(roundtable(body_field="session_id"))])
async def create_resource(data: dict):
    return {"created": True}
```

Client sends JSON:
```json
{
  "session_id": "<roundtable_session_id>",
  "name": "Resource name",
  "data": {...}
}
```

### Custom Error Handling

Customize the response when validation fails:

```python
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:

```python
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):**

```python
@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):**

```python
@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:

```bash
export ROUNDTABLE_API_KEY="your_secret_key"
```

Then initialize without explicit key:

```python
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
```python
@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
```python
@app.post("/signup", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
```
Prevent bot-driven account creation and fake registrations.

### API Rate Limit Bypass Prevention
```python
@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
```python
@app.post("/reviews", dependencies=[Depends(roundtable(form_field="rt_session_id"))])
```
Keep your user-generated content authentic and spam-free.

### Credential Stuffing Prevention
```python
@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.

```python
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 ID
- `http_header`: Name of the HTTP header containing the session ID
- `body_field`: Name of the request body field containing the session ID
- `session_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:**
```python
# 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](https://github.com/leandropls/fastapi-roundtable).

## Requirements

- Python 3.9 or higher
- fastapi
- aiohttp
- pydantic

## Links

- **GitHub Repository**: [github.com/leandropls/fastapi-roundtable](https://github.com/leandropls/fastapi-roundtable)
- **PyPI Package**: [pypi.org/project/fastapi-roundtable](https://pypi.org/project/fastapi-roundtable)
- **Roundtable Documentation**: [docs.roundtable.ai](https://docs.roundtable.ai)
- **Issue Tracker**: [GitHub Issues](https://github.com/leandropls/fastapi-roundtable/issues)
- **Roundtable Dashboard**: [accounts.roundtable.ai](https://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.