Skip to main content

Python SDK for integrating CONXA Wallet payments into AI services

Project description

CONXA Python SDK

Official Python SDK for integrating CONXA Wallet payments into AI services.

Overview

CONXA is a universal wallet for AI services. This SDK enables AI providers (ChatGPT, Claude, etc.) to:

  1. Generate QR codes for users to connect their CONXA wallet
  2. Detect connections when users scan and approve
  3. Charge users for AI usage (pay-per-token)
  4. Check balances and manage sessions

Installation

# Install from PyPI (includes QR code generation)
pip install conxa-sdk

# From source
pip install .

# Development with examples (Flask, FastAPI)
pip install -e ".[dev,examples]"

Quick Start

from conxa import CONXAClient

# Initialize with your API key
client = CONXAClient(
    api_key="pk_live_your_api_key",
    provider_id="your_provider_id",
)

# 1. Generate QR code for user to scan
qr = client.create_payment_qr(
    provider_username="user@example.com",
    expires_in=10  # Optional: QR expires after 10 seconds
)
print(f"Show this QR to user: {qr.qr_base64}")

# 2. Wait for user to connect (or poll manually)
session = client.wait_for_connection(
    provider_username="user@example.com",
    timeout=120,  # Wait up to 2 minutes
    qr_data=qr,  # Pass QR data to check expiration
)
print(f"User connected! Token: {session.session_token}")

# 3. Charge for AI usage (use a unique idempotency key per logical charge)
result = client.charge(
    session_token=session.session_token,
    idempotency_key="req_abc123",  # e.g. request ID - required for retry safety
    model_name="gpt-4",
    input_tokens=1000,
    output_tokens=500,
)

if result.approved:
    print(f"Charged! New balance: {result.new_balance} tokens")
else:
    print(f"Failed: {result.error}")

Integration Flow

┌─────────────────────────────────────────────────────────────────┐
│                        YOUR AI SERVICE                          │
│                                                                 │
│  1. User visits your website                                    │
│     │                                                           │
│     ▼                                                           │
│  2. Generate QR: client.create_payment_qr(username, expires_in=10) │
│     │                                                           │
│     ▼                                                           │
│  3. Display QR code to user (expires after 10 seconds)         │
│     │                                                           │
│     │    ┌─────────────────────────────────┐                   │
│     │    │      CONXA Mobile App           │                   │
│     └───▶│  User scans QR & approves       │                   │
│          │  Connection established!         │                   │
│          └─────────────────────────────────┘                   │
│     │                                                           │
│     ▼                                                           │
│  4. Detect connection: client.get_session_status(username, qr_data=qr) │
│     │                                                           │
│     ▼                                                           │
│  5. For each AI request:                                        │
│     result = client.charge(session_token, idempotency_key, ...)  │
│     │                                                           │
│     ▼                                                           │
│  6. Tokens deducted from user's CONXA wallet                    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

API Reference

CONXAClient

Main client class for interacting with CONXA API.

client = CONXAClient(
    api_key="pk_live_xxx",           # Required: Your API key
    provider_id="your_provider_id",   # Required: Your provider ID
    base_url="https://api.conxa.in",  # Optional: API base URL
    timeout=30,                       # Optional: Request timeout
    provider_type="api",              # Optional: "api" or "web"
)

Methods

create_payment_qr(provider_username, provider_type=None, limit=None, size=300, expires_in=None)

Generate a QR code for user to scan and connect.

qr = client.create_payment_qr(
    provider_username="user@example.com",
    provider_type=None,  # Optional: "api" or "web" (default: client's provider_type)
    limit=50000,         # Optional: spending limit in tokens
    size=300,            # Optional: image size in pixels
    expires_in=10,       # Optional: QR expires after N seconds (default: None)
)

# Returns QRCodeData:
# - qr.qr_data: JSON string to encode
# - qr.qr_image: PIL Image object
# - qr.qr_base64: Base64 PNG for HTML <img> tag (validated and guaranteed valid)
# - qr.created_at: Timestamp when QR was created
# - qr.expires_at: Timestamp when QR expires (if expires_in is set)
# - qr.is_expired(): Method to check if QR has expired
# - qr.is_valid_base64(): Method to check if base64 data is valid
# - qr.to_json_safe_dict(): Get validated dict safe for API responses
# - qr.get_html_img_tag(): Get ready-to-use HTML img tag

create_payment_qr_svg(provider_username, provider_type=None, limit=None, size=300)

Generate a QR code as an SVG string (no expiration; useful for server-rendered SVG).

svg_string = client.create_payment_qr_svg(
    provider_username="user@example.com",
    provider_type=None,  # Optional: "api" or "web"
    limit=50000,
    size=300,
)
# Returns: str (SVG markup)

QR Code Expiration:

If expires_in is set, the QR code will automatically expire after the specified number of seconds. This is useful for security and ensuring users generate fresh QR codes:

# Generate QR that expires after 10 seconds
qr = client.create_payment_qr(
    provider_username="user@example.com",
    expires_in=10
)

# Check if QR has expired
if qr.is_expired():
    print("QR code expired! Generate a new one.")
    qr = client.create_payment_qr(provider_username="user@example.com", expires_in=10)

# Validate QR code before using in HTML (optional - validation is automatic)
if qr.is_valid_base64():
    html = f'<img src="{qr.qr_base64}" alt="QR Code" />'
else:
    print("Error: Invalid QR code generated")

QR Code Validation:

The SDK automatically validates all QR codes during generation to prevent browser errors. If validation fails, a ValueError is raised with a clear error message:

try:
    qr = client.create_payment_qr(provider_username="user@example.com")
    # QR code is guaranteed to be valid at this point
    # qr.qr_base64 is always a valid data URL
except ValueError as e:
    print(f"QR generation failed: {e}")
    # Handle error appropriately

You can also manually check validation using is_valid_base64():

qr = client.create_payment_qr(provider_username="user@example.com")
if qr.is_valid_base64():
    # Safe to use in HTML
    display_qr(qr.qr_base64)

Helper Methods for Integration:

For API responses, use to_json_safe_dict() which validates before serialization:

@app.post("/connect")
def connect():
    qr = client.create_payment_qr(provider_username=user_id)
    return qr.to_json_safe_dict()  # Validated and safe for JSON

For server-side HTML generation, use get_html_img_tag():

qr = client.create_payment_qr(provider_username=user_id)
html = qr.get_html_img_tag()  # Returns: <img src="data:image/png;base64,..." />

get_session_status(provider_username, retry_on_rate_limit=True, max_retries=3, qr_data=None)

Check if user has connected their wallet. Automatically handles rate limiting with exponential backoff.

qr = client.create_payment_qr("user@example.com", expires_in=10)
status = client.get_session_status(
    "user@example.com",
    qr_data=qr,  # Optional: Check if QR has expired
    retry_on_rate_limit=True,  # Auto-retry on rate limits
    max_retries=3,  # Max retry attempts
)

# Returns SessionStatus:
# - status.status: "pending", "active", "expired", "not_found"
# - status.session_token: Token for charges (if active)
# - status.expires_at: Session expiration time
# - status.is_active: True if session is active and ready for charges
# - status.is_pending: True if waiting for user to connect
# - status.is_expired: True if session has expired

# Raises SessionExpiredError if QR code has expired

Rate Limit Handling:

The SDK automatically retries on rate limit errors (429) with exponential backoff. If qr_data is provided and the QR has expired, it will raise SessionExpiredError instead of polling.

wait_for_connection(provider_username, timeout=120, poll_interval=2, on_pending=None, rate_limit_timeout=10, qr_data=None)

Block until user connects or timeout. Automatically handles rate limiting and QR expiration.

qr = client.create_payment_qr("user@example.com", expires_in=10)

session = client.wait_for_connection(
    provider_username="user@example.com",
    timeout=120,              # Maximum wait time in seconds
    poll_interval=2,         # Time between status checks
    on_pending=lambda s: print("Waiting..."),  # Callback on each poll
    rate_limit_timeout=10,   # Close QR after N seconds of rate limiting
    qr_data=qr,             # Optional: Check QR expiration
)

# Raises:
# - ConnectionTimeoutError: If user doesn't connect within timeout
# - SessionExpiredError: If QR expires or rate limited for too long

Features:

  • Automatic retry on rate limit errors with exponential backoff
  • QR expiration checking (if qr_data provided)
  • Rate limit timeout protection (closes QR after extended rate limiting)

charge(session_token, idempotency_key, model_name, input_tokens, output_tokens)

Charge user for AI usage. Use a unique idempotency_key per logical charge (e.g. request ID) for exactly-once semantics on retries.

result = client.charge(
    session_token="ps_xxx",
    idempotency_key="req_abc123",  # Required: unique per logical charge
    model_name="gpt-4",
    input_tokens=1000,
    output_tokens=500,
)

# Returns ChargeResult:
# - result.approved: Boolean
# - result.new_balance: Remaining tokens
# - result.error_code: Optional backend code (e.g. HARD_CAP_VIOLATION, NO_CHARGE_PERMISSION)
# - result.error: Error message (if failed)

get_wallet_balance(wallet_id)

Get wallet token balance (public endpoint).

balance = client.get_wallet_balance("1234567890123456")
# Returns: WalletBalance(wallet_id, tokens)

Exception Handling

from conxa import (
    CONXAError,
    AuthenticationError,
    InsufficientBalanceError,
    SessionExpiredError,
    ConnectionTimeoutError,
    SessionNotFoundError,
    ProviderNotFoundError,
    ForbiddenError,
    IdempotencyConflictError,
    RateLimitError,
    APIError,
    ValidationError,
)

try:
    # Generate QR with expiration
    qr = client.create_payment_qr("user@example.com", expires_in=10)
    
    # Wait for connection
    session = client.wait_for_connection("user@example.com", qr_data=qr)
    
    # Charge user
    result = client.charge(...)
    
except SessionExpiredError as e:
    print("QR code or session expired - user needs to reconnect")
    # Generate new QR code
    qr = client.create_payment_qr("user@example.com", expires_in=10)
    
except InsufficientBalanceError as e:
    print(f"User has insufficient balance: {e.current_balance} tokens")
    
except RateLimitError as e:
    print(f"Rate limit exceeded. Retry after: {e.retry_after} seconds")
    # SDK automatically retries, but you can handle manually if needed
    
except ConnectionTimeoutError:
    print("User did not connect within timeout period")
    
except AuthenticationError:
    print("Invalid API key")
    
except IdempotencyConflictError:
    print("Charge with this idempotency key already in progress - do not retry")
    
except ForbiddenError:
    print("Forbidden (e.g. spending limit exceeded or no charge permission)")
    
except CONXAError as e:
    print(f"CONXA error: {e}")

Examples

See the examples/ directory for complete integration examples:

  • basic_integration.py - Simple command-line example
  • flask_integration.py - Flask web app with QR display
  • fastapi_integration.py - FastAPI app with WebSocket support

Web Framework Integration

Flask

from flask import Flask, session, jsonify
from conxa import CONXAClient

app = Flask(__name__)
client = CONXAClient(api_key="pk_live_xxx", provider_id="xxx")

@app.route("/connect")
def connect():
    # Generate QR that expires after 10 seconds
    qr = client.create_payment_qr(
        provider_username=session["user_email"],
        expires_in=10
    )
    # Store QR data in session to check expiration
    session["qr_data"] = {
        "created_at": qr.created_at.isoformat() if qr.created_at else None,
        "expires_at": qr.expires_at.isoformat() if qr.expires_at else None,
    }
    return render_template("connect.html", qr_base64=qr.qr_base64)

@app.route("/api/chat", methods=["POST"])
def chat():
    import uuid
    result = client.charge(
        session_token=session["conxa_token"],
        idempotency_key=request.json.get("request_id", "req_" + str(uuid.uuid4())),
        model_name="gpt-4",
        input_tokens=request.json["input_tokens"],
        output_tokens=request.json["output_tokens"],
    )
    if not result.approved:
        return jsonify({"error": "Insufficient balance"}), 402
    # Process AI request...

FastAPI

from fastapi import FastAPI, HTTPException
from conxa import CONXAClient

app = FastAPI()
client = CONXAClient(api_key="pk_live_xxx", provider_id="xxx")

@app.post("/connect")
async def connect(user_id: str):
    # Generate QR that expires after 10 seconds
    qr = client.create_payment_qr(
        provider_username=user_id,
        expires_in=10
    )
    return {
        "qr_base64": qr.qr_base64,
        "expires_at": qr.expires_at.isoformat() if qr.expires_at else None,
    }

@app.post("/chat")
async def chat(user_id: str, message: str, qr_data: dict = None):
    # Check if QR has expired (if provided)
    qr = None
    if qr_data:
        from conxa.models import QRCodeData
        from datetime import datetime
        qr = QRCodeData(
            qr_data="",
            created_at=datetime.fromisoformat(qr_data["created_at"]) if qr_data.get("created_at") else None,
            expires_at=datetime.fromisoformat(qr_data["expires_at"]) if qr_data.get("expires_at") else None,
        )
    
    try:
        status = client.get_session_status(user_id, qr_data=qr)
    except SessionExpiredError:
        raise HTTPException(401, "QR code expired. Please generate a new one.")
    
    if not status.is_active:
        raise HTTPException(401, "User not connected")
    
    import uuid
    result = client.charge(
        session_token=status.session_token,
        idempotency_key="req_" + str(uuid.uuid4()),  # use request ID from client in production
        model_name="gpt-4",
        input_tokens=len(message) * 4,
        output_tokens=100,
    )
    if not result.approved:
        raise HTTPException(402, "Insufficient balance")
    # Process AI request...

Configuration

Environment Variables

# API Configuration
CONXA_API_KEY=pk_live_your_api_key
CONXA_PROVIDER_ID=your_provider_id
CONXA_API_URL=https://api.conxa.in  # Optional

Using environment variables:

import os
from conxa import CONXAClient

client = CONXAClient(
    api_key=os.getenv("CONXA_API_KEY"),
    provider_id=os.getenv("CONXA_PROVIDER_ID"),
)

Advanced Features

QR Code Expiration

QR codes can be set to expire after a specified duration for security:

# Generate QR that expires after 10 seconds
qr = client.create_payment_qr(
    provider_username="user@example.com",
    expires_in=10
)

# Check expiration status
if qr.is_expired():
    # Generate new QR
    qr = client.create_payment_qr("user@example.com", expires_in=10)

# When checking status, pass QR data to validate expiration
try:
    status = client.get_session_status("user@example.com", qr_data=qr)
except SessionExpiredError:
    print("QR expired - generate a new one")

QR Code Validation

The SDK automatically validates all QR codes during generation to ensure they're valid and can be safely used in HTML. This prevents browser errors like net::ERR_INVALID_URL:

# QR codes are automatically validated during generation
try:
    qr = client.create_payment_qr(provider_username="user@example.com")
    # At this point, qr.qr_base64 is guaranteed to be valid
    # You can safely use it in HTML without additional checks
    html = f'<img src="{qr.qr_base64}" alt="QR Code" />'
except ValueError as e:
    # QR generation failed validation
    print(f"Failed to generate valid QR code: {e}")
    # Handle error appropriately

Manual Validation (Optional):

You can also manually check if a QR code is valid before using it:

qr = client.create_payment_qr(provider_username="user@example.com")

# Check if base64 data is valid (optional - validation happens automatically)
if qr.is_valid_base64():
    # Safe to use in HTML
    display_qr(qr.qr_base64)
else:
    # This should never happen if generation succeeded
    print("Warning: QR code validation failed")

What Gets Validated:

  • Image bytes are not empty
  • Base64 encoding succeeds
  • Data URL format is correct (data:image/png;base64,...)
  • PNG file signature is valid

Rate Limit Handling

The SDK automatically handles rate limiting with exponential backoff:

# Automatic retry on rate limits (default behavior)
status = client.get_session_status(
    "user@example.com",
    retry_on_rate_limit=True,  # Auto-retry (default)
    max_retries=3,              # Max retry attempts
)

# Manual rate limit handling
try:
    status = client.get_session_status("user@example.com", retry_on_rate_limit=False)
except RateLimitError as e:
    print(f"Rate limited. Retry after: {e.retry_after} seconds")
    time.sleep(e.retry_after or 60)
    status = client.get_session_status("user@example.com")

Best Practices

  1. Always set QR expiration for security:

    qr = client.create_payment_qr(username, expires_in=10)
    
  2. Pass QR data when checking status to validate expiration:

    status = client.get_session_status(username, qr_data=qr)
    
  3. Handle expiration gracefully:

    try:
        session = client.wait_for_connection(username, qr_data=qr)
    except SessionExpiredError:
        # Generate new QR and try again
        qr = client.create_payment_qr(username, expires_in=10)
    
  4. Use rate limit timeout in wait_for_connection:

    session = client.wait_for_connection(
        username,
        rate_limit_timeout=10,  # Close QR after 10s of rate limiting
        qr_data=qr
    )
    
  5. QR codes are automatically validated - no need for manual checks:

    # Validation happens automatically - just use the QR code
    qr = client.create_payment_qr(username)
    html = f'<img src="{qr.qr_base64}" alt="QR Code" />'  # Safe to use
    

Testing

The SDK validates QR codes during generation. For integration testing:

  • Run the examples in examples/ (e.g. basic_integration.py, flask_integration.py, fastapi_integration.py).
  • Use qr.to_json_safe_dict() in your API to ensure responses are valid for JSON and frontend use.

All QR codes generated by the SDK are validated so they work correctly in web browsers.

Troubleshooting

If you're experiencing issues with QR code display (e.g. broken image):

  1. Verify API response: Ensure your endpoint returns qr_base64 as a valid data URL (data:image/png;base64,...). Use qr.to_json_safe_dict() so the payload is validated before returning.

  2. Browser checks: In DevTools → Network, confirm the response includes qr_base64 and that the frontend uses it as the src of an <img> (or equivalent).

  3. Handle expiration: If the QR stops working after a short time, use expires_in and handle SessionExpiredError by generating a new QR.

Support

License

MIT License - see LICENSE file for details.

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

conxa_sdk-1.3.4.tar.gz (25.8 kB view details)

Uploaded Source

Built Distribution

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

conxa_sdk-1.3.4-py3-none-any.whl (22.1 kB view details)

Uploaded Python 3

File details

Details for the file conxa_sdk-1.3.4.tar.gz.

File metadata

  • Download URL: conxa_sdk-1.3.4.tar.gz
  • Upload date:
  • Size: 25.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for conxa_sdk-1.3.4.tar.gz
Algorithm Hash digest
SHA256 68d9e008f7dc0e3e28242ea340c7a3a149665be0a7d268dfc567b5b6336f34b7
MD5 c4d538ae48e3d713ebc8530aab9a189b
BLAKE2b-256 1d375948223a684946559f0b647bdf3437ea9ed5faed8e23621fc0e97348eebb

See more details on using hashes here.

File details

Details for the file conxa_sdk-1.3.4-py3-none-any.whl.

File metadata

  • Download URL: conxa_sdk-1.3.4-py3-none-any.whl
  • Upload date:
  • Size: 22.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for conxa_sdk-1.3.4-py3-none-any.whl
Algorithm Hash digest
SHA256 5acc018bdcece8f2873856ae77bacc8ce5ea3588a07e8b91f95b1724c4bd28ba
MD5 115d8d66323e31bf164f2ef8083fa1d2
BLAKE2b-256 4ec5e6d4c1dc55a6c609be4b64890ffe917eed8360be16f6eefea121b50d7928

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