BOTCHA Python SDK - Reverse CAPTCHA for AI agents. Challenges, JWT tokens, app management, and Trusted Agent Protocol (TAP).
Project description
BOTCHA Python SDK
Prove you're a bot. Humans need not apply.
BOTCHA is an anti-CAPTCHA system designed to keep humans out and let AI agents in. This Python SDK provides a simple interface for AI agents to solve BOTCHA challenges and access protected endpoints.
📄 Whitepaper: botcha.ai/whitepaper · 🌐 Website: botcha.ai · 📦 npm: @dupecom/botcha
Installation
pip install botcha
Quickstart
from botcha import BotchaClient
async with BotchaClient() as client:
response = await client.fetch("https://api.example.com/agent-only")
print(response.json())
That's it! The client automatically handles token acquisition, challenge solving, and authentication.
API Reference
BotchaClient
HTTP client with automatic BOTCHA challenge solving and JWT token management.
Constructor
BotchaClient(
base_url: str = "https://botcha.ai",
agent_identity: Optional[str] = None,
max_retries: int = 3,
auto_token: bool = True,
audience: Optional[str] = None,
app_id: Optional[str] = None,
app_secret: Optional[str] = None,
)
Parameters:
base_url(str): Base URL for the BOTCHA service. Default:"https://botcha.ai"agent_identity(str, optional): Custom agent identity string for User-Agent headermax_retries(int): Maximum number of retries for failed requests. Default:3auto_token(bool): Automatically acquire and attach Bearer tokens. Default:Trueaudience(str, optional): Scope tokens to a specific API/serviceapp_id(str, optional): Multi-tenant app ID for token/challenge scopingapp_secret(str, optional): App secret for app-management endpoints (verify_email,resend_verification)
Methods
async fetch(url: str, **kwargs) -> httpx.Response
Make an HTTP GET request with automatic BOTCHA handling.
Features:
- Automatically acquires and attaches Bearer token (if
auto_token=True) - Retries once on 401 (Unauthorized) with fresh token
- Solves inline challenges on 403 (Forbidden) responses
Parameters:
url(str): URL to fetch**kwargs: Additional arguments passed to httpx request
Returns: httpx.Response object
Example:
async with BotchaClient() as client:
response = await client.fetch("https://api.example.com/data")
data = response.json()
async get_token() -> str
Acquire or return cached JWT token.
Implements token caching with a buffer before expiry. If token is cached and valid, returns the cached token. Otherwise, acquires a new token via the challenge flow:
- GET
/v1/tokento receive challenge - Solve challenge problems
- POST
/v1/token/verifywith solutions - Parse and cache JWT token
Returns: JWT token string
Example:
async with BotchaClient() as client:
token = await client.get_token()
print(f"Token: {token}")
solve(problems: list[int]) -> list[str]
Solve BOTCHA challenge problems synchronously.
Parameters:
problems(list[int]): List of 6-digit integers to solve
Returns: List of 8-character hex strings (SHA256 hash prefixes)
Example:
client = BotchaClient()
answers = client.solve([123456, 789012])
print(answers) # ['8d969eef', 'ca2f2c8f']
async close() -> None
Close the underlying HTTP client. Automatically called when using async context manager.
async create_app(email: str) -> CreateAppResponse
Create a new BOTCHA app. Returns app_id and app_secret.
async verify_email(code: str, app_id: str = None, app_secret: str = None) -> VerifyEmailResponse
Verify email with 6-digit code sent to your email.
async resend_verification(app_id: str = None, app_secret: str = None) -> ResendVerificationResponse
Resend the email verification code.
async recover_account(email: str) -> RecoverAccountResponse
Request account recovery via verified email.
async rotate_secret(app_id: str = None) -> RotateSecretResponse
Rotate the app secret. Old secret is immediately invalidated.
TAP (Trusted Agent Protocol) Methods
async register_tap_agent(name, operator=None, version=None, public_key=None, signature_algorithm=None, capabilities=None, trust_level=None, issuer=None) -> TAPAgentResponse
Register an agent with TAP capabilities including cryptographic identity and capability-scoped permissions.
async get_tap_agent(agent_id: str) -> TAPAgentResponse
Get a TAP agent by ID, including public key and verification status.
async list_tap_agents(tap_only: bool = False) -> TAPAgentListResponse
List TAP agents for the current app. Set tap_only=True to filter to TAP-enabled agents only.
async create_tap_session(agent_id: str, user_context: str, intent: dict) -> TAPSessionResponse
Create a TAP session with intent validation. The intent dict should include action, and optionally resource, scope, and duration.
async get_tap_session(session_id: str) -> TAPSessionResponse
Get a TAP session by ID, including time remaining before expiry.
async rotate_agent_key(agent_id: str) -> RotateKeyResponse
Rotate the key pair for a TAP-registered agent. Returns the new public key.
Delegation Methods
async create_delegation(grantor_id: str, grantee_id: str, capabilities: list, ttl: int = 3600, parent_delegation_id: str = None) -> DelegationResponse
Delegate capabilities from one agent to another. Capabilities can only be narrowed, never expanded. Supports chained delegation via parent_delegation_id.
async get_delegation(delegation_id: str) -> DelegationResponse
Get delegation details by ID.
async list_delegations(agent_id: str, direction: str = 'both') -> DelegationListResponse
List delegations for an agent. direction can be 'in', 'out', or 'both'.
async revoke_delegation(delegation_id: str, reason: str = None) -> RevokeResponse
Revoke a delegation. Cascades to all sub-delegations.
async verify_delegation_chain(delegation_id: str) -> DelegationChainResponse
Verify the entire delegation chain and compute effective capabilities.
Attestation Methods
async issue_attestation(agent_id: str, can: list[str], cannot: list[str] = None, ttl: int = 3600, delegation_id: str = None) -> AttestationResponse
Issue a capability attestation token with action:resource permission patterns and explicit deny rules. Wildcards supported (browse:*, *:products).
async get_attestation(attestation_id: str) -> AttestationResponse
Get attestation details by ID.
async list_attestations(agent_id: str) -> AttestationListResponse
List attestations for an agent.
async revoke_attestation(attestation_id: str, reason: str = None) -> RevokeResponse
Revoke an attestation token.
async verify_attestation(token: str, action: str, resource: str) -> AttestationVerifyResponse
Verify an attestation token and check if a specific capability is allowed.
Reputation Methods
async get_reputation(agent_id: str) -> ReputationResponse
Get an agent's reputation score (0–1000) and tier (untrusted, low, neutral, good, excellent).
async record_reputation_event(agent_id: str, category: str, action: str, metadata: dict = None, source_agent_id: str = None) -> ReputationEventResponse
Record a reputation event. Categories: verification, commerce, compliance, social, security, governance.
async list_reputation_events(agent_id: str, category: str = None, limit: int = 50) -> ReputationEventListResponse
List reputation events for an agent, optionally filtered by category.
async reset_reputation(agent_id: str) -> ResetResponse
Reset an agent's reputation to the neutral base score (admin use).
Webhook Methods
async create_webhook(url: str, events: list[str]) -> WebhookResponse
Register a webhook endpoint. Returns the signing secret once — save it.
async list_webhooks() -> WebhookListResponse
List all webhooks for the current app.
async get_webhook(webhook_id: str) -> WebhookResponse
Get webhook details by ID.
async update_webhook(webhook_id: str, url: str = None, events: list[str] = None, enabled: bool = None) -> WebhookResponse
Update webhook URL, event subscriptions, or enabled state.
async delete_webhook(webhook_id: str) -> DeleteResponse
Delete a webhook and all its delivery logs.
async test_webhook(webhook_id: str) -> WebhookTestResponse
Send a signed test event to the webhook endpoint.
async list_webhook_deliveries(webhook_id: str) -> WebhookDeliveryListResponse
List the last 100 delivery attempts for a webhook.
x402 Methods
async get_x402_info() -> X402InfoResponse
Get payment configuration: amount, currency (USDC), chain (base), and recipient address.
async get_x402_challenge(payment_proof: str = None) -> X402ChallengeResponse
Initiate the x402 payment flow. Without a proof, returns a 402 with payment terms. With a valid payment_proof, returns a BOTCHA access token.
async verify_x402_payment(payment_proof: str, chain: str = 'base') -> X402VerifyResponse
Verify a raw x402 payment proof.
ANS Methods
async get_ans_identity() -> ANSIdentityResponse
Get BOTCHA's own ANS identity record.
async resolve_ans_name(name: str) -> ANSResolveResponse
Resolve an agent name to its ANS record via DNS TXT lookup.
async discover_ans_agents() -> ANSDiscoverResponse
List all BOTCHA-verified ANS agents.
async get_ans_nonce(name: str) -> ANSNonceResponse
Get a one-time nonce for ANS ownership verification. Requires Bearer token.
async verify_ans_ownership(name: str, agent_url: str, nonce: str, proof: str) -> ANSVerifyResponse
Prove ownership of an ANS name and receive a BOTCHA-signed badge JWT.
DID / Verifiable Credential Methods
async issue_credential(subject: dict, credential_type: list[str] = None, ttl_seconds: int = 3600) -> VCResponse
Issue a W3C Verifiable Credential JWT. Requires Bearer token. The VC is signed with BOTCHA's private key and can be verified by any party who resolves did:web:botcha.ai.
async verify_credential(vc: str) -> VCVerifyResponse
Verify a BOTCHA-issued VC JWT. Public endpoint — no auth required.
async resolve_did(did: str) -> DIDDocumentResponse
Resolve a did:web DID to its DID Document.
A2A Methods
async get_agent_card() -> A2ACardResponse
Get BOTCHA's own A2A Agent Card.
async attest_agent_card(card: dict, duration_seconds: int = 86400, trust_level: str = 'verified') -> A2AAttestResponse
Submit an A2A Agent Card for BOTCHA attestation. Returns a tamper-evident trust seal. Requires Bearer token.
async verify_agent_card(card: dict) -> A2AVerifyResponse
Verify the BOTCHA trust seal on an attested agent card.
async verify_agent(agent_url: str = None, agent_card: dict = None) -> A2AVerifyResponse
Verify an agent by URL or full card with embedded attestation.
async get_trust_level(agent_url: str) -> A2ATrustLevelResponse
Get the current BOTCHA trust level for an agent URL. Returns "unverified" if no attestation exists.
async list_attested_cards() -> A2ACardListResponse
Browse the registry of all BOTCHA-attested A2A agent cards.
OIDC-A Methods
async issue_eat(agent_model: str = None, ttl_seconds: int = 900, verification_method: str = None, nonce: str = None) -> EATResponse
Issue an Entity Attestation Token (EAT / RFC 9334). Requires Bearer token. Suitable for presentation to enterprise relying parties.
async issue_oidca_claims(agent_model: str = None, agent_version: str = None, agent_capabilities: list[str] = None, agent_operator: str = None, human_oversight_required: bool = False, task_id: str = None, task_purpose: str = None, nonce: str = None) -> OIDCAClaimsResponse
Issue an OIDC-A agent claims block JWT. Suitable for inclusion in OAuth2 token responses.
async create_agent_grant(scope: str, human_oversight_required: bool = False, agent_model: str = None, agent_operator: str = None, task_id: str = None, task_purpose: str = None) -> AgentGrantResponse
Initiate an OAuth2-style agent grant flow. If human_oversight_required=True, returns a pending grant with an oversight_url for the human to approve.
async get_agent_grant_status(grant_id: str) -> AgentGrantStatusResponse
Poll the status of an agent grant.
async resolve_agent_grant(grant_id: str, decision: str) -> AgentGrantResolveResponse
Approve or reject an agent grant. decision is "approved" or "rejected".
async get_oidc_userinfo() -> OIDCUserInfoResponse
Get OIDC-A UserInfo claims for the currently authenticated agent.
solve_botcha(problems: list[int]) -> list[str]
Standalone function to solve BOTCHA speed challenges without needing a client instance.
Parameters:
problems(list[int]): List of 6-digit integers to solve
Returns: List of 8-character hex strings (SHA256 hash prefixes)
Example:
from botcha import solve_botcha
answers = solve_botcha([123456, 789012])
print(answers) # ['8d969eef', 'ca2f2c8f']
Usage Examples
Basic Usage with Auto-Token
The simplest way to use BOTCHA - the client handles everything automatically:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient(base_url="https://botcha.ai") as client:
# Client automatically acquires token and solves challenges
response = await client.fetch("https://api.example.com/agent-only")
print(response.json())
asyncio.run(main())
Manual Token Acquisition
If you need more control over token management:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient(auto_token=False) as client:
# Manually acquire token
token = await client.get_token()
print(f"Acquired token: {token}")
# Use token in custom requests
response = await client.fetch(
"https://api.example.com/data",
headers={"Authorization": f"Bearer {token}"}
)
print(response.json())
asyncio.run(main())
Standalone Solver
Use the solver without creating a client instance:
from botcha import solve_botcha
# Solve challenges independently
problems = [123456, 789012, 456789]
answers = solve_botcha(problems)
print(f"Problems: {problems}")
print(f"Answers: {answers}")
# Problems: [123456, 789012, 456789]
# Answers: ['8d969eef', 'ca2f2c8f', 'c888c9ce']
Custom Agent Identity
Set a custom User-Agent header for your bot:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient(agent_identity="MyBot/1.0") as client:
response = await client.fetch("https://api.example.com/data")
print(response.json())
asyncio.run(main())
Inline Challenge Handling
The client automatically handles inline challenges (403 responses):
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient() as client:
# If the endpoint returns a 403 with a BOTCHA challenge,
# the client automatically solves it and retries
response = await client.fetch("https://api.example.com/protected")
# You get the successful response without manual intervention
print(response.json())
asyncio.run(main())
Error Handling
Handle errors gracefully:
import asyncio
import httpx
from botcha import BotchaClient
async def main():
try:
async with BotchaClient() as client:
response = await client.fetch("https://api.example.com/data")
response.raise_for_status()
print(response.json())
except httpx.HTTPError as e:
print(f"Request failed: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
asyncio.run(main())
TAP (Trusted Agent Protocol)
Enterprise-grade cryptographic agent authentication:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient(app_id="app_abc123") as client:
# Register a TAP agent
agent = await client.register_tap_agent(
name="my-agent",
operator="Acme Corp",
trust_level="verified",
capabilities=[{"action": "browse", "scope": ["products"]}],
)
print(f"Agent ID: {agent.agent_id}")
# Create a TAP session
session = await client.create_tap_session(
agent_id=agent.agent_id,
user_context="user-hash",
intent={"action": "browse", "resource": "products", "duration": 3600},
)
print(f"Session expires: {session.expires_at}")
asyncio.run(main())
Delegation Chains
Delegate capabilities between agents with auditable, revocable chains:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient(app_id="app_abc123") as client:
# Agent A delegates to Agent B
delegation = await client.create_delegation(
grantor_id="agent_aaa",
grantee_id="agent_bbb",
capabilities=[{"action": "browse", "resource": "products"}],
ttl=3600,
)
# Verify the full chain
chain = await client.verify_delegation_chain(delegation.delegation_id)
print(f"Effective capabilities: {chain.effective_capabilities}")
# Revoke (cascades to all sub-delegations)
await client.revoke_delegation(delegation.delegation_id, reason="Session ended")
asyncio.run(main())
Capability Attestation
Fine-grained action:resource permission tokens with deny rules:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient(app_id="app_abc123") as client:
# Issue attestation
att = await client.issue_attestation(
agent_id="agent_abc123",
can=["read:invoices", "browse:*"],
cannot=["purchase:*"],
ttl=3600,
)
print(f"Token: {att.token}")
# Verify capability
check = await client.verify_attestation(att.token, "read", "invoices")
print(f"Allowed: {check.allowed}") # True
asyncio.run(main())
Agent Reputation
Track agent trust scores over time:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient(app_id="app_abc123") as client:
# Get reputation
rep = await client.get_reputation("agent_abc123")
print(f"Score: {rep.score}, Tier: {rep.tier}")
# Record positive event
await client.record_reputation_event(
agent_id="agent_abc123",
category="verification",
action="challenge_solved",
)
# Endorsement from another agent
await client.record_reputation_event(
agent_id="agent_abc123",
category="social",
action="endorsement_received",
source_agent_id="agent_def456",
)
asyncio.run(main())
DID / Verifiable Credentials
Issue and verify portable W3C credential JWTs:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient() as client:
# Issue a verifiable credential
vc_result = await client.issue_credential(
subject={"agentType": "llm", "operator": "Acme Corp"},
credential_type=["VerifiableCredential", "BotchaVerification"],
ttl_seconds=3600,
)
print(f"VC JWT: {vc_result.vc}")
# Anyone can verify — no auth required
verified = await client.verify_credential(vc_result.vc)
print(f"Valid: {verified.valid}")
print(f"Issuer: {verified.payload['iss']}") # did:web:botcha.ai
asyncio.run(main())
A2A Agent Card Attestation
Get a BOTCHA trust seal for your A2A Agent Card:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient() as client:
# Attest your agent's card
result = await client.attest_agent_card(
card={
"name": "My Commerce Agent",
"url": "https://myagent.example",
"version": "1.0.0",
"capabilities": {"streaming": False},
"skills": [{"id": "browse", "name": "Browse"}],
},
trust_level="verified",
)
print(f"Trust seal: {result.attestation.token}")
# Verify an attested card
check = await client.verify_agent_card(result.attested_card)
print(f"Valid: {check.valid}")
asyncio.run(main())
OIDC-A Attestation
Enterprise agent auth chains with EAT tokens and agent grants:
import asyncio
from botcha import BotchaClient
async def main():
async with BotchaClient() as client:
# Issue an Entity Attestation Token (EAT)
eat = await client.issue_eat(
agent_model="gpt-5",
ttl_seconds=900,
verification_method="speed-challenge",
)
print(f"EAT token: {eat.token}")
# Initiate an agent grant (OAuth2-style)
grant = await client.create_agent_grant(
scope="agent:read openid",
human_oversight_required=True,
agent_model="gpt-5",
task_purpose="invoice reconciliation",
)
print(f"Grant status: {grant.status}")
if grant.oversight_url:
print(f"Human oversight required: {grant.oversight_url}")
asyncio.run(main())
How It Works
BOTCHA is a speed challenge designed to prove computational capability:
- Challenge Generation: Server generates a list of 6-digit integers
- Solving: Client computes SHA256 hash of each integer and returns the first 8 hex characters
- Verification: Server validates solutions within the time limit (typically 10 seconds)
- Token Issuance: Upon successful verification, server issues a JWT token
Why it works:
- Fast for bots: Modern computers can solve thousands of challenges per second
- Slow for humans: Manual calculation is impractical
- Simple to implement: Standard SHA256 hashing, no complex cryptography
- Stateless: No session management required
Security properties:
- Solutions cannot be precomputed (random challenges)
- Time-limited to prevent delayed solving
- JWT tokens expire to limit attack windows
- No brute-force protection needed (humans self-exclude)
Type Hints
This package includes type hints (PEP 484) and ships with a py.typed marker for full type checking support in IDEs and tools like mypy.
Requirements
- Python >= 3.9
- httpx >= 0.27
Development
# Clone the repository
git clone https://github.com/dupe-com/botcha.git
cd botcha/packages/python
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest tests/ -v
# Run type checking
mypy src/botcha
Links
- Website: https://botcha.ai
- Repository: https://github.com/dupe-com/botcha
- Issues: https://github.com/dupe-com/botcha/issues
- PyPI: https://pypi.org/project/botcha/
License
MIT License - see LICENSE for details.
Author
Ramin ramin@dupe.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file botcha-0.21.1.tar.gz.
File metadata
- Download URL: botcha-0.21.1.tar.gz
- Upload date:
- Size: 32.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48e79d43aa56a9db9051af729be1836cc02e058f3ad8ce93092e5907b9116f89
|
|
| MD5 |
72e59070469c79d2ebfd3d16eaa97156
|
|
| BLAKE2b-256 |
276c937150121c8c5e197deefbab3ae924aafaaa126e4195f12a8e2471c2cc1b
|
Provenance
The following attestation bundles were made for botcha-0.21.1.tar.gz:
Publisher:
publish-pypi.yml on dupe-com/botcha
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
botcha-0.21.1.tar.gz -
Subject digest:
48e79d43aa56a9db9051af729be1836cc02e058f3ad8ce93092e5907b9116f89 - Sigstore transparency entry: 1692381201
- Sigstore integration time:
-
Permalink:
dupe-com/botcha@c74542757264511a44fc0aeaae31ad9741f01ffe -
Branch / Tag:
refs/heads/main - Owner: https://github.com/dupe-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@c74542757264511a44fc0aeaae31ad9741f01ffe -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file botcha-0.21.1-py3-none-any.whl.
File metadata
- Download URL: botcha-0.21.1-py3-none-any.whl
- Upload date:
- Size: 21.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8dcb5929f76105ab20192d0aa2adf30b8bd4c7c73ecaf15f3a4844e72d2128fc
|
|
| MD5 |
2d8d7a3531eaeee5307e27a3336dbbbd
|
|
| BLAKE2b-256 |
9101598490c0b6ec74f5c6934bf5b8cf8ff13b61a046c71cb28a8bd656e1cef4
|
Provenance
The following attestation bundles were made for botcha-0.21.1-py3-none-any.whl:
Publisher:
publish-pypi.yml on dupe-com/botcha
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
botcha-0.21.1-py3-none-any.whl -
Subject digest:
8dcb5929f76105ab20192d0aa2adf30b8bd4c7c73ecaf15f3a4844e72d2128fc - Sigstore transparency entry: 1692381361
- Sigstore integration time:
-
Permalink:
dupe-com/botcha@c74542757264511a44fc0aeaae31ad9741f01ffe -
Branch / Tag:
refs/heads/main - Owner: https://github.com/dupe-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@c74542757264511a44fc0aeaae31ad9741f01ffe -
Trigger Event:
workflow_dispatch
-
Statement type: