Crovly

Python SDK

Server-side captcha token verification for Python.

Installation

pip install crovly

Supports Python 3.9+. Uses httpx for HTTP requests.

Basic Usage

from crovly import Crovly

client = Crovly("crvl_secret_YOUR_API_KEY")

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

if not result.is_human():
    return {"error": "Captcha failed"}, 403

Async Usage

from crovly import AsyncCrovly

client = AsyncCrovly("crvl_secret_YOUR_API_KEY")

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

API

Crovly(secret_key, base_url?, timeout?)

ParameterTypeDefaultDescription
secret_keystrYour API key (required)
base_urlstrhttps://api.crovly.comAPI base URL
timeoutfloat10.0Request timeout in seconds

client.verify(token, expected_ip?)

Returns a VerifyResponse:

result.success    # bool
result.score      # float (0.0–1.0)
result.ip         # str
result.solved_at  # int (Unix ms)
result.error      # str | None
result.is_human() # bool — shorthand for result.success

Django Example

# views.py
from crovly import Crovly
from django.conf import settings
from django.http import JsonResponse

crovly = Crovly(settings.CROVLY_SECRET_KEY)

def contact(request):
    if request.method != "POST":
        return JsonResponse({"error": "Method not allowed"}, status=405)

    token = request.POST.get("crovly-token", "")
    ip = request.META.get("HTTP_X_FORWARDED_FOR", "").split(",")[0] or request.META.get("REMOTE_ADDR", "")

    result = crovly.verify(token, expected_ip=ip)
    if not result.is_human():
        return JsonResponse({"error": "Verification failed"}, status=403)

    # Process the form...
    return JsonResponse({"ok": True})
# settings.py
CROVLY_SITE_KEY = env("CROVLY_SITE_KEY")
CROVLY_SECRET_KEY = env("CROVLY_SECRET_KEY")

Flask Example

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

app = Flask(__name__)
crovly = Crovly("crvl_secret_YOUR_API_KEY")

@app.post("/api/contact")
def contact():
    token = request.form.get("crovly-token", "")
    result = crovly.verify(token, expected_ip=request.remote_addr)

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

    return jsonify(ok=True)

FastAPI Example

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

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

@app.post("/api/contact")
async def contact(request: Request):
    data = await request.json()
    result = await crovly.verify(
        data["token"],
        expected_ip=request.client.host,
    )

    if not result.is_human():
        raise HTTPException(status_code=403, detail="Verification failed")

    return {"ok": True}

Error Handling

from crovly import Crovly
from crovly.errors import AuthenticationError, RateLimitError, TimeoutError

try:
    result = crovly.verify(token)
except AuthenticationError:
    # Invalid API key
except RateLimitError:
    # Too many requests
except TimeoutError:
    # Request timed out
ExceptionDescription
AuthenticationErrorInvalid or missing API key
ValidationErrorInvalid request data
RateLimitErrorRate limit exceeded
TimeoutErrorRequest timed out
NetworkErrorNetwork connectivity issue

On this page