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 int Unix timestamp in milliseconds

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.2.tar.gz (6.4 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.2-py3-none-any.whl (8.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: crovly-1.0.2.tar.gz
  • Upload date:
  • Size: 6.4 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.2.tar.gz
Algorithm Hash digest
SHA256 55b75066e94971ff869c5a789792acf3c7a6d2ff7157e1a0ec6059e533e9a409
MD5 e43c7546a6ededb7a979203167d7fabd
BLAKE2b-256 6ba6b8d0c07e923b31d4e6ac754c52029adbecefe5fcac2baea87888ce1145f1

See more details on using hashes here.

File details

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

File metadata

  • Download URL: crovly-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 8.3 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b6a1525f7974cd0c009234ebe1b64a69db60d00dcd8150a4ac909001aa205f90
MD5 a6539747490dd5e0cdb05ce7afa6bd02
BLAKE2b-256 9f97fbfda42de8e615e12d37d60e247773e8051937132c4d858e1aa7f6f7bc88

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