Skip to main content

Official Crovly Python SDK — verify captcha tokens

Project description

Crovly Python SDK

Official Python SDK for Crovly — privacy-first, Proof of Work captcha verification.

Installation

pip install crovly

Quick Start

from crovly import Crovly

client = Crovly("crvl_secret_xxx")

# Verify a captcha token from the widget
result = client.verify(token, expected_ip=request_ip)

if result.is_human():
    # Allow the request
    print(f"Human verified (score: {result.score})")
else:
    # Block the request
    print("Verification failed")

Async Usage (FastAPI)

from fastapi import FastAPI, Request, HTTPException
from crovly import AsyncCrovly

app = FastAPI()
crovly = AsyncCrovly("crvl_secret_xxx")

@app.post("/submit")
async def submit_form(request: Request):
    data = await request.json()
    token = data.get("crovly_token")

    if not token:
        raise HTTPException(400, "Missing captcha token")

    result = await crovly.verify(token, expected_ip=request.client.host)

    if not result.is_human():
        raise HTTPException(403, "Captcha verification failed")

    return {"message": "Form submitted successfully"}

@app.on_event("shutdown")
async def shutdown():
    await crovly.close()

Or use the async context manager:

async with AsyncCrovly("crvl_secret_xxx") as client:
    result = await client.verify(token)

Django Middleware

# middleware.py
from crovly import Crovly, CrovlyError

crovly_client = Crovly("crvl_secret_xxx")

PROTECTED_PATHS = ["/contact", "/register", "/login"]

class CrovlyMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if request.method == "POST" and request.path in PROTECTED_PATHS:
            token = request.POST.get("crovly_token")
            if not token:
                from django.http import JsonResponse
                return JsonResponse({"error": "Missing captcha token"}, status=400)

            try:
                result = crovly_client.verify(
                    token,
                    expected_ip=self._get_client_ip(request),
                )
                if not result.is_human():
                    from django.http import JsonResponse
                    return JsonResponse({"error": "Captcha failed"}, status=403)
            except CrovlyError:
                from django.http import JsonResponse
                return JsonResponse({"error": "Verification error"}, status=500)

        return self.get_response(request)

    def _get_client_ip(self, request):
        xff = request.META.get("HTTP_X_FORWARDED_FOR")
        if xff:
            return xff.split(",")[0].strip()
        return request.META.get("REMOTE_ADDR")

Add to settings.py:

MIDDLEWARE = [
    # ...
    "yourapp.middleware.CrovlyMiddleware",
]

Flask

from flask import Flask, request, jsonify
from crovly import Crovly

app = Flask(__name__)
client = Crovly("crvl_secret_xxx")

@app.route("/submit", methods=["POST"])
def submit():
    token = request.form.get("crovly_token")
    if not token:
        return jsonify(error="Missing captcha token"), 400

    result = client.verify(token, expected_ip=request.remote_addr)

    if not result.is_human():
        return jsonify(error="Captcha verification failed"), 403

    return jsonify(message="Success")

Custom Threshold

The default threshold for is_human() is 0.5. You can adjust it:

# Stricter — require higher confidence
if result.is_human(threshold=0.7):
    allow()

# Lenient — allow lower scores
if result.is_human(threshold=0.3):
    allow()

# Manual score check
if result.success and result.score >= 0.8:
    allow()

Configuration

client = Crovly(
    "crvl_secret_xxx",
    api_url="https://api.crovly.com",  # API base URL
    timeout=10.0,                       # Request timeout (seconds)
    max_retries=2,                      # Retries on 5xx/network errors
)

Error Handling

from crovly import (
    Crovly,
    CrovlyError,
    AuthenticationError,
    ValidationError,
    RateLimitError,
    ApiError,
)

client = Crovly("crvl_secret_xxx")

try:
    result = client.verify(token)
except AuthenticationError:
    # Invalid or missing secret key (401)
    print("Check your secret key")
except ValidationError as e:
    # Invalid request parameters (400)
    print(f"Bad request: {e.message}")
except RateLimitError as e:
    # Rate limit exceeded (429)
    print(f"Rate limited, retry after: {e.retry_after}")
except ApiError as e:
    # Server error (5xx)
    print(f"Server error: {e.status_code}")
except CrovlyError as e:
    # Any other API error
    print(f"Error: {e.message} (code: {e.code})")

Error Classes

Class Status When
AuthenticationError 401 Invalid or missing secret key
ValidationError 400 Invalid token or request body
RateLimitError 429 Too many requests
ApiError 5xx Server error (retried automatically)
CrovlyError - Base class for all errors

VerifyResponse

Field Type Description
success bool Whether the token is valid
score float Risk score: 0.0 (bot) to 1.0 (human)
ip str IP address that solved the challenge
solved_at str ISO 8601 timestamp

Requirements

  • Python 3.9+
  • httpx

Documentation

Full documentation at docs.crovly.com.

License

MIT

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

crovly-1.0.0.tar.gz (6.3 kB view details)

Uploaded Source

Built Distribution

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

crovly-1.0.0-py3-none-any.whl (8.2 kB view details)

Uploaded Python 3

File details

Details for the file crovly-1.0.0.tar.gz.

File metadata

  • Download URL: crovly-1.0.0.tar.gz
  • Upload date:
  • Size: 6.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for crovly-1.0.0.tar.gz
Algorithm Hash digest
SHA256 a5fceec96da20dcdb46072f082c885472005c499be5d8dee28230e70b1218d90
MD5 4d8ae66315e9984fa4de90b94bdafa81
BLAKE2b-256 6306ecc412c4874b7c5b8d37cbf7da1ec96d175df727f91d6045946d992ac6d4

See more details on using hashes here.

File details

Details for the file crovly-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: crovly-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 8.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for crovly-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c8c6ffea6c73749513b566ed7ec242e0e6d6b5378360562e874af8e77ae6f74d
MD5 1efc04cf562aa49d2b5a81d236d58493
BLAKE2b-256 13ef0d7da914c4b08f6aa0f6ed04a3f0a937c49752df059214b4a3ff847cce54

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