proofage — ProofAge for Python
Python client for ProofAge, the age and identity verification API. It
gives you a sync and an async client for every /v1 endpoint, typed Pydantic models, typed errors
and webhook verification — enough to go from pip install to a verified result in a few minutes,
from a backend, a Telegram bot or an AI agent.
Install
pip install proofage
# or
uv add proofage
Quick start
Set PROOFAGE_API_KEY and PROOFAGE_SECRET_KEY (from your workspace in the ProofAge console),
then:
from proofage import ProofAge
with ProofAge() as client:
verification = client.verifications.create(
external_id="candidate-42",
callback_url="https://your-app.example/verified",
)
print(verification.url) # send the person here
# later, or when the webhook arrives
print(client.verifications.get(verification.id).status)
The person opens verification.url on their phone, ProofAge runs the checks, and you learn the
outcome from a webhook (below) or by calling get().
Async
AsyncProofAge has the same methods, awaited — the natural choice in aiogram bots, FastAPI and
other asyncio code:
from proofage import AsyncProofAge
async with AsyncProofAge() as client:
verification = await client.verifications.create(external_id="tg-12345")
Receiving results (webhooks)
ProofAge POSTs the outcome to your workspace's webhook URL. Verify it with the raw request body — not a re-serialised copy of its JSON — and answer 2xx.
FastAPI:
from fastapi import FastAPI, Request, Response
from proofage import WebhookVerificationError, verify_webhook
app = FastAPI()
@app.post("/webhooks/proofage")
async def proofage_webhook(request: Request) -> Response:
try:
event = verify_webhook(await request.body(), request.headers)
except WebhookVerificationError as error:
return Response(status_code=error.http_status)
print(event.verification_id, event.status, event.reason)
return Response(status_code=200)
Django:
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from proofage import WebhookVerificationError, verify_webhook
@csrf_exempt
@require_POST
def proofage_webhook(request):
try:
event = verify_webhook(request.body, request.headers)
except WebhookVerificationError as error:
return HttpResponse(status=error.http_status)
...
return HttpResponse(status=200)
A delivery can arrive more than once: de-duplicate on event.delivery_id, which stays the same
on every automatic retry. Ready-made FastAPI, Django and Flask integrations are coming in 0.1.x.
Statuses
event.status and verification.status are VerificationStatus members (APPROVED,
DECLINED, RESUBMISSION_REQUESTED, REVIEW, …). A status this version does not know yet
arrives as a plain string instead of raising, so compare with ==:
from proofage import VerificationStatus
if event.status == VerificationStatus.APPROVED:
...
Unknown fields are kept too, in model.model_extra; model.model_dump(mode="json") gives a plain
dict.
Errors
from proofage import PaymentRequiredError, ProofAgeError, RateLimitError, ValidationError
try:
client.verifications.create(external_id="x" * 300)
except ValidationError as error:
print(error.errors) # {"external_id": ["..."]}
except PaymentRequiredError:
print("Add a payment method to the workspace")
except RateLimitError as error:
print("Try again in", error.retry_after, "seconds")
except ProofAgeError as error:
print(error.status_code, error.code, error.message)
AuthenticationError (401), PaymentRequiredError (402), PermissionDeniedError (403),
NotFoundError (404), ValidationError (422), RateLimitError (429), ServerError (5xx) and
TransportError (no response) all extend ProofAgeError. ConfigurationError is raised when a
client is built with missing or invalid settings. A response whose shape this SDK version does not
recognise raises ProofAgeError naming the fields (never their values).
Configuration
| Argument | Environment | Default |
|---|---|---|
api_key |
PROOFAGE_API_KEY |
required |
secret_key |
PROOFAGE_SECRET_KEY |
required |
base_url |
PROOFAGE_BASE_URL |
https://api.proofage.xyz |
version |
PROOFAGE_VERSION |
v1 |
timeout |
PROOFAGE_TIMEOUT (seconds) |
30.0 |
retry_attempts |
PROOFAGE_RETRY_ATTEMPTS |
3 |
retry_delay |
PROOFAGE_RETRY_DELAY (milliseconds) |
1.0 seconds |
download_retry_attempts |
PROOFAGE_DOWNLOAD_RETRY_ATTEMPTS |
1 |
http_client |
— | an owned httpx client |
The environment variables use the same names and units as the ProofAge Laravel and PHP SDKs, so
one .env serves all of them (the Node SDK reads PROOFAGE_TIMEOUT in milliseconds). Constructor
arguments are seconds.
timeout limits each network operation (connecting, each read, each write), not the whole
request. Pass your own httpx.Client / httpx.AsyncClient as http_client for proxies or custom
TLS; the SDK never closes a client you pass in.
Retries
- A GET is retried on 408, 429, 5xx, timeouts and connection failures.
- A POST is retried only on 429 and when the connection never opened. It is never retried on a 5xx or once sending began, because the server may already have created the verification.
- A 429 waits for its
Retry-After, up to 60 seconds; a longerRetry-AfterraisesRateLimitErrorat once (withretry_afterset) instead of blocking your thread. Otherwise the wait grows byretry_delayper attempt. - Media downloads never retry an HTTP status; run them from a queue and let its backoff wait.
Media
document = client.verifications.document(verification_id)
for item in document.media:
if item.url is not None: # None once purged or past retention
client.verifications.download_media_to(verification_id, item.id, f"{item.id}.jpg")
Hosted flow first
accept_consent, upload_media and submit exist for custom capture flows. Most integrations
only create a session, send the person to its url, and read the result.
For packages that wrap this SDK
A plugin or bot template built on this SDK can name itself in the X-ProofAge-Sdk header, which
helps ProofAge support tell integrations apart:
ProofAge(sdk_tokens=["telegram-bot/1.2.0"]) # X-ProofAge-Sdk: telegram-bot/1.2.0 python/0.1.0
user_agent= replaces the default ProofAge-Python/<version> (Python <x.y.z>) User-Agent.
Supported versions
A Python version stays supported for 12 months after its upstream end of life, or until httpx or Pydantic stop supporting it, whichever comes first. A version leaves only in a minor release, announced one release ahead in the changelog.
| Python | Upstream end of life | Supported by proofage until |
|---|---|---|
| 3.10 | 2026-10 | 2027-10 |
| 3.11 | 2027-10 | 2028-10 |
| 3.12 | 2028-10 | 2029-10 |
| 3.13 | 2029-10 | 2030-10 |
| 3.14 | 2030-10 | 2031-10 |
AI agents
The package ships AGENTS.md next to its code: the full request and response contract of every
method, written for coding agents. Agents that speak MCP can also use the ProofAge MCP server
directly.
License
MIT
Metadata
Release files for proofage 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| proofage-0.1.0.tar.gz | 50.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| proofage-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 92.1 kB
Release files / proofage-0.1.0.tar.gz
| Download URL | proofage-0.1.0.tar.gz |
|---|---|
| Size | 50.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9ff3b24178319c60c92cd8d142d4702c757e623020bafbadb4d0260c0d1256be
|
|
BLAKE2b-256 checksum How to use checksums |
6e10e2ffbb755fcf14ebd41a1ebd80777badd4c8b97eaef9b2409984eccbcd17
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / proofage-0.1.0-py3-none-any.whl
| Download URL | proofage-0.1.0-py3-none-any.whl |
|---|---|
| Size | 41.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7f4d513e638bb39aa080f6f99be0adcf3d6ebe767dd9016b78bc9506e4e0595d
|
|
BLAKE2b-256 checksum How to use checksums |
bc340ab6eeb425b6eafc4482a921a2b64d39a957eca1b7d03e76788c4b16cb00
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency log