# Python 🐍

The official Python client for the CardScan API provides a pythonic interface for all API operations.

## Installation

```bash
pip install cardscan-client
```

## Basic Usage

```python
from cardscan_client import CardScanApi
from cardscan_client.exceptions import ApiException

# Initialize with your API key
api_key = "sk_test_cardscan_ai_..."
client = CardScanApi(api_key=api_key)

# Generate a session token for a user
token_response = client.get_access_token(user_id="unique-user-123")
session_token = token_response["Token"]
identity_id = token_response["IdentityId"]
session_id = token_response["session_id"]

# Initialize client with session token for frontend operations
user_client = CardScanApi(session_token=session_token, live=False)
```

## Quick Start with full\_scan

The easiest way to scan cards is using the `full_scan` helper method that handles the entire workflow:

```python
from cardscan_client import CardScanApi

# Initialize the client
client = CardScanApi(api_key="sk_test_cardscan_ai_...")

# Scan a card (front and back)
async def scan_card():
    # Using file paths
    result = await client.full_scan(
        front_image="./front-card.jpg",
        back_image="./back-card.jpg",  # Optional - omit for front-only
        user_id="unique-user-123"
    )
    
    # Or using file objects
    with open("./front-card.jpg", "rb") as front:
        with open("./back-card.jpg", "rb") as back:
            result = await client.full_scan(
                front_image=front,
                back_image=back,
                user_id="unique-user-123"
            )
    
    # Access the results
    print(f"Card ID: {result['card_id']}")
    print(f"Member ID: {result['details']['member_id']}")
    print(f"Group: {result['details']['group_number']}")
    print(f"Payer: {result['details']['payer_name']}")
    
    return result

# Run the async function
import asyncio
result = asyncio.run(scan_card())
```

The `full_scan` method automatically:

* Generates a session token for the user
* Creates a card with appropriate settings
* Uploads images in the correct order
* Polls for processing completion
* Returns the completed card with all extracted data

## Manual Card Scanning Workflow

For more control over the scanning process, you can use the step-by-step approach:

### 1. Create a Card

```python
# Create a card with options
card = user_client.create_card(
    enable_backside_scan=False,
    enable_livescan=False,
    metadata={
        "patient_id": "12345",
        "visit_id": "v-67890"
    }
)

print(f"Card ID: {card['card_id']}")
print(f"State: {card['state']}")  # 'pending'
```

### 2. Generate Upload URL

```python
# Generate pre-signed upload URL
upload_data = user_client.generate_card_upload_url(
    card_id=card["card_id"],
    orientation="front",
    capture_type="manual"
)

upload_url = upload_data["upload_url"]
upload_parameters = upload_data["upload_parameters"]
```

### 3. Upload Image

```python
import requests

# Read image file
with open("insurance_card.jpg", "rb") as f:
    files = {"file": f}
    
    # Upload to S3 using pre-signed URL
    response = requests.post(
        upload_url,
        data=upload_parameters,
        files=files
    )
    response.raise_for_status()
```

### 4. Poll for Results

```python
import time

def wait_for_completion(card_id, timeout=300):
    """Poll for card completion with timeout."""
    start_time = time.time()
    
    while time.time() - start_time < timeout:
        card = user_client.get_card(card_id=card_id)
        
        if card["state"] in ["completed", "error"]:
            return card
            
        time.sleep(2)  # Wait 2 seconds between polls
    
    raise TimeoutError(f"Card {card_id} did not complete within {timeout} seconds")

completed_card = wait_for_completion(card["card_id"])
print("Insurance Info:", completed_card.get("details"))
```

## Error Handling

```python
from cardscan_client.exceptions import ApiException

try:
    card = client.get_card(card_id="invalid-id")
except ApiException as e:
    print(f"API Error: {e.status} - {e.body}")
    
    if e.status == 401:
        print("Invalid API key or session token")
    elif e.status == 404:
        print("Card not found")
    elif e.status == 429:
        print("Rate limit exceeded")
```

## Async Support

The client supports async operations using `asyncio`:

```python
import asyncio
from cardscan_client import AsyncCardScanApi

async def scan_card_async():
    async_client = AsyncCardScanApi(api_key="sk_test_cardscan_ai_...")
    
    # All methods are available as async versions
    token_response = await async_client.get_access_token(
        user_id="unique-user-123"
    )
    
    card = await async_client.create_card(
        session_token=token_response["Token"],
        enable_backside_scan=False
    )
    
    return card

# Run async function
card = asyncio.run(scan_card_async())
```

## WebSocket Support

For real-time updates using WebSocket:

```python
import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    
    if data["type"] == "card.processing":
        print("Card processing started")
    elif data["type"] == "card.completed":
        print(f"Card completed: {data['card_id']}")
    elif data["type"] == "card.error":
        print(f"Card error: {data['error']}")

def on_error(ws, error):
    print(f"WebSocket error: {error}")

# Connect to WebSocket
ws_url = f"wss://sandbox.cardscan.ai/v1/ws?token={session_token}"
ws = websocket.WebSocketApp(
    ws_url,
    on_message=on_message,
    on_error=on_error
)

# Run in a separate thread
import threading
wst = threading.Thread(target=ws.run_forever)
wst.daemon = True
wst.start()
```

## Eligibility Verification

```python
# Create eligibility request
eligibility = client.create_eligibility(
    card_id=completed_card["card_id"],
    eligibility={
        "provider": {
            "first_name": "John",
            "last_name": "Smith",
            "npi": "1234567890"
        },
        "subscriber": {
            "first_name": "Jane",
            "last_name": "Smith",
            "date_of_birth": "1980-01-01"
        }
    }
)

# Poll for eligibility results
eligibility_result = wait_for_eligibility_completion(
    eligibility["eligibility_id"]
)

# Access eligibility information
if eligibility_result["state"] == "completed":
    summary = eligibility_result["eligibility_summarized_response"]
    print(f"Coverage Active: {summary['coverage_active']}")
    print(f"Copay: ${summary['copay']}")
```

## Batch Operations

Process multiple cards efficiently:

```python
def process_cards_batch(image_paths):
    """Process multiple insurance cards in parallel."""
    cards = []
    
    # Create cards
    for path in image_paths:
        card = user_client.create_card(enable_backside_scan=False)
        cards.append(card)
    
    # Upload images in parallel
    import concurrent.futures
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        futures = []
        
        for card, image_path in zip(cards, image_paths):
            future = executor.submit(
                upload_and_process_card,
                card["card_id"],
                image_path
            )
            futures.append(future)
        
        # Wait for all uploads to complete
        results = [f.result() for f in futures]
    
    return results
```

## Configuration Options

```python
from cardscan_client import CardScanApi

# Custom configuration
client = CardScanApi(
    api_key="sk_test_cardscan_ai_...",
    base_url="https://sandbox.cardscan.ai/v1",
    retries=3,
    timeout=30,
    verify_ssl=True
)
```

## Type Hints

The client includes comprehensive type hints:

```python
from typing import Dict, Optional
from cardscan_client.models import (
    CardApiResponse,
    CreateCardRequest,
    EligibilityApiResponse
)

def process_card(card: CardApiResponse) -> Optional[Dict[str, str]]:
    """Extract key information from a completed card."""
    if card.state == "completed" and card.details:
        return {
            "member_id": card.details.member_id,
            "group_number": card.details.group_number,
            "payer_name": card.details.payer_name
        }
    return None
```

## Logging

Enable detailed logging for debugging:

```python
import logging

# Enable debug logging
logging.basicConfig(level=logging.DEBUG)

# The client will now log all requests and responses
client = CardScanApi(api_key="sk_test_cardscan_ai_...")
```

## Source Code

View the source code and contribute: [GitHub](https://github.com/CardScan-ai/api-clients/tree/main/clients/cardscan-python)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.cardscan.ai/api-clients/python.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
