# Python SDK Guide

{% hint style="success" %}
**New to the SDK?** Start with the [5-minute quickstart](https://docs.clore.ai/guides/getting-started/python-quickstart) first.
{% endhint %}

For a quick-start tutorial with real examples, see [Clore.ai Python SDK — Automate Your GPU Workflows in 5 Minutes](https://blog.clore.ai/cloreai-python-sdk-automate-your-gpu-workflows-in-5-minutes/)

## Installation

```bash
pip install clore-ai
```

The SDK provides two clients:

* **`CloreAI`** — synchronous (simpler, good for scripts)
* **`AsyncCloreAI`** — asynchronous (faster for concurrent operations)

Both share the same methods and return the same Pydantic models.

***

## Sync vs Async — When to Use Each

| Use Case                      | Client         | Why                                |
| ----------------------------- | -------------- | ---------------------------------- |
| Simple scripts, one-off tasks | `CloreAI`      | Simpler code, no `async/await`     |
| Monitoring loops              | `CloreAI`      | Sequential checks work fine        |
| Bulk marketplace queries      | `AsyncCloreAI` | Concurrent requests = faster       |
| Batch order creation          | `AsyncCloreAI` | Create multiple orders in parallel |
| Web applications              | `AsyncCloreAI` | Non-blocking I/O                   |

### Sync Example

```python
from clore_ai import CloreAI

client = CloreAI()  # Uses CLORE_API_KEY env var

servers = client.marketplace(gpu="RTX 4090")
print(f"Found {len(servers)} servers")

client.close()  # Or use context manager
```

### Async Example

```python
import asyncio
from clore_ai import AsyncCloreAI

async def main():
    async with AsyncCloreAI() as client:
        servers = await client.marketplace(gpu="RTX 4090")
        print(f"Found {len(servers)} servers")

asyncio.run(main())
```

### Context Managers (Recommended)

Both clients support context managers for automatic cleanup:

```python
# Sync
with CloreAI() as client:
    wallets = client.wallets()

# Async
async with AsyncCloreAI() as client:
    wallets = await client.wallets()
```

***

## Client Configuration

```python
client = CloreAI(
    api_key="your_key",      # Or set CLORE_API_KEY env var
    base_url="https://api.clore.ai/v1",  # Custom API endpoint
    timeout=30.0,            # Request timeout in seconds
    max_retries=3            # Retries on rate limit / network errors
)
```

The SDK includes a built-in rate limiter:

* **General requests:** 1 request/second
* **`create_order`:** 5-second cooldown between calls
* **Rate limit errors (code 5):** Automatic exponential backoff

***

## Marketplace Filtering

The `marketplace()` method fetches all available servers and filters client-side:

```python
from clore_ai import CloreAI

client = CloreAI()

# All available servers
all_servers = client.marketplace()

# Filter by GPU model (case-insensitive substring match)
rtx_4090s = client.marketplace(gpu="RTX 4090")

# Filter by multiple criteria
budget_gpus = client.marketplace(
    gpu="RTX 4090",
    max_price_usd=1.0,       # Max $1.00/hour
    min_gpu_count=2,          # At least 2 GPUs
    min_ram_gb=64.0,          # At least 64GB system RAM
    available_only=True       # Only available servers (default)
)
```

### Advanced Filtering (Client-Side)

For filters not built into the method, filter the returned `Server` objects yourself:

```python
servers = client.marketplace(gpu="RTX 4090")

# Servers in EU with high reliability
eu_servers = [
    s for s in servers
    if s.location and s.location.upper() in ("DE", "FR", "NL", "FI")
    and s.reliability and s.reliability >= 0.95
]

# Sort by price
cheapest = sorted(servers, key=lambda s: s.price_usd or float("inf"))
print(f"Cheapest: Server {cheapest[0].id} — ${cheapest[0].price_usd:.4f}/h")
```

### Server Model Fields

Each `MarketplaceServer` object has these attributes and convenience properties:

| Field            | Type                  | Description                                                       |
| ---------------- | --------------------- | ----------------------------------------------------------------- |
| `id`             | `int`                 | Server ID (use this in `create_order`)                            |
| `gpu_model`      | `str \| None`         | GPU description from specs (property)                             |
| `gpu_count`      | `int`                 | Number of GPUs from `gpu_array` (property)                        |
| `ram_gb`         | `float \| None`       | System RAM in GB (property, from `specs.ram`)                     |
| `price_usd`      | `float \| None`       | On-demand price in USD (property, from `price.usd.on_demand_usd`) |
| `spot_price_usd` | `float \| None`       | Spot price in USD (property)                                      |
| `available`      | `bool`                | Whether the server is not rented (property)                       |
| `location`       | `str \| None`         | Country code from network specs (property)                        |
| `specs`          | `ServerSpecs \| None` | Hardware specs (cpu, ram, disk, gpu, net)                         |
| `price`          | `ServerPrice \| None` | Full pricing structure                                            |
| `rented`         | `bool \| None`        | Whether the server is currently rented                            |

***

## Order Management

### Creating Orders

```python
order = client.create_order(
    server_id=142,
    image="cloreai/ubuntu22.04-cuda12",
    type="on-demand",               # "on-demand" or "spot"
    currency="bitcoin",             # Payment currency
    ssh_password="MySecurePass",    # SSH access
    ports={"22": "tcp", "8888": "http"},  # Port mappings
    env={"HF_TOKEN": "hf_xxx"},    # Environment variables
    command="bash /start.sh",       # Custom startup command
    jupyter_token="my_token"        # Jupyter notebook token
)

print(f"Order ID: {order.id}")
print(f"IP: {order.pub_cluster}")
print(f"Ports: {order.tcp_ports}")
```

### Full `create_order` Parameters

| Parameter            | Type    | Required | Description                          |
| -------------------- | ------- | -------- | ------------------------------------ |
| `server_id`          | `int`   | ✅        | Server to rent                       |
| `image`              | `str`   | ✅        | Docker image                         |
| `type`               | `str`   | ✅        | `"on-demand"` or `"spot"`            |
| `currency`           | `str`   | ✅        | Payment currency (e.g., `"bitcoin"`) |
| `ssh_password`       | `str`   | —        | SSH password                         |
| `ssh_key`            | `str`   | —        | SSH public key                       |
| `ports`              | `dict`  | —        | Port mappings (`{"22": "tcp"}`)      |
| `env`                | `dict`  | —        | Environment variables                |
| `jupyter_token`      | `str`   | —        | Jupyter notebook token               |
| `command`            | `str`   | —        | Startup command                      |
| `spot_price`         | `float` | —        | Spot bid price                       |
| `required_price`     | `float` | —        | Required price                       |
| `autossh_entrypoint` | `str`   | —        | Auto SSH entrypoint                  |

### Listing Orders

```python
# Active orders only
active = client.my_orders()
for o in active:
    print(f"Order {o.id}: type={o.type}, IP={o.pub_cluster}, status={o.status}")

# Include completed orders
all_orders = client.my_orders(include_completed=True)
```

### Order Model Fields

| Field         | Type            | Description                           |
| ------------- | --------------- | ------------------------------------- |
| `id`          | `int`           | Order ID                              |
| `server_id`   | `int \| None`   | Rented server ID                      |
| `type`        | `str`           | `"on-demand"` or `"spot"`             |
| `status`      | `str \| None`   | Order status                          |
| `image`       | `str \| None`   | Docker image                          |
| `currency`    | `str \| None`   | Payment currency                      |
| `price`       | `float \| None` | Price                                 |
| `pub_cluster` | `str \| None`   | Public IP / hostname                  |
| `tcp_ports`   | `dict \| None`  | Port mappings (e.g., `{"22": 50022}`) |
| `created_at`  | `str \| None`   | Creation timestamp                    |

### Monitoring Orders

```python
import time

def wait_for_ready(client, order_id, timeout=120):
    """Wait for an order to get a public IP."""
    for _ in range(timeout // 10):
        orders = client.my_orders()
        order = next((o for o in orders if o.id == order_id), None)
        if order and order.pub_cluster:
            return order
        time.sleep(10)
    raise TimeoutError(f"Order {order_id} not ready after {timeout}s")

# Usage
order = client.create_order(server_id=142, image="cloreai/ubuntu22.04-cuda12", type="on-demand", currency="bitcoin")
ready = wait_for_ready(client, order.id)
print(f"SSH: ssh root@{ready.pub_cluster} -p {ready.tcp_ports.get('22', 22)}")
```

### Cancelling Orders

```python
# Cancel with optional reason
client.cancel_order(order_id=38, issue="Job complete")

# Cancel all active orders
orders = client.my_orders()
for order in orders:
    client.cancel_order(order.id, issue="Cleanup")
    print(f"Cancelled order {order.id}")
```

***

## Server Management (for Hosters)

If you host GPUs on Clore, the SDK lets you manage your servers:

### List Your Servers

```python
my_servers = client.my_servers()
for s in my_servers:
    print(f"Server {s.id}: {s.gpu_model} — {s.status}")
```

### Get Server Config

```python
config = client.server_config("MyGPU-Rig")
print(f"Name: {config.name}")
print(f"Visibility: {config.visibility}")
print(f"Online: {config.online}")
print(f"Min rental: {config.mrl}h")
print(f"On-demand price: {config.on_demand_price}")
print(f"Spot price: {config.spot_price}")
```

### Update Server Settings

```python
client.set_server_settings(
    name="MyGPU-Rig",
    availability=True,       # Make server available
    mrl=24,                  # Minimum 24h rental
    on_demand=0.0001,        # On-demand price in BTC
    spot=0.00000113          # Spot price in BTC
)
print("Settings updated")
```

***

## Spot Market

Spot orders are 30–50% cheaper but can be interrupted if someone outbids you.

### View Spot Offers

```python
offers = client.spot_marketplace(server_id=6)
for offer in offers:
    print(f"Order {offer.get('order_id')}: price={offer.get('price')}")
```

### Create a Spot Order

```python
order = client.create_order(
    server_id=142,
    image="cloreai/ubuntu22.04-cuda12",
    type="spot",
    currency="bitcoin",
    spot_price=0.0001,       # Your bid price
    ssh_password="MyPass"
)
print(f"Spot order {order.id} created")
```

### Adjust Spot Price

```python
# Raise your bid to avoid being outbid
client.set_spot_price(order_id=39, price=0.000003)
```

### Spot Bidding Strategy

```python
from clore_ai import CloreAI

client = CloreAI()

def smart_spot_bid(server_id, premium_pct=5):
    """Bid slightly above the current minimum spot price."""
    offers = client.spot_marketplace(server_id=server_id)
    if not offers:
        print("No spot offers — use on-demand price as baseline")
        return None

    min_price = min(o["price"] for o in offers)
    bid = min_price * (1 + premium_pct / 100)
    print(f"Market min: {min_price}, bidding: {bid:.8f} (+{premium_pct}%)")
    return bid

# Usage
bid = smart_spot_bid(server_id=142, premium_pct=10)
if bid:
    order = client.create_order(
        server_id=142,
        image="cloreai/ubuntu22.04-cuda12",
        type="spot",
        currency="bitcoin",
        spot_price=bid
    )
```

***

## Wallet Operations

### Check Balances

```python
wallets = client.wallets()
for w in wallets:
    print(f"{w.name}: {w.balance:.8f}")
    if w.deposit:
        print(f"  Deposit address: {w.deposit}")
```

### Low-Balance Alert

```python
from clore_ai import CloreAI

def check_balance(min_btc=0.001):
    """Alert if BTC balance is below threshold."""
    client = CloreAI()
    wallets = client.wallets()

    for w in wallets:
        if w.name.lower() == "bitcoin" and w.balance < min_btc:
            print(f"⚠️  Low BTC balance: {w.balance:.8f} (minimum: {min_btc})")
            return False

    print("✅ Balances OK")
    return True

check_balance(min_btc=0.001)
```

***

## Error Handling Best Practices

### Exception Hierarchy

```
CloreAPIError (base)
├── DBError           (code 1) — database error
├── InvalidInputError (code 2) — bad input
├── AuthError         (code 3) — invalid API key
├── InvalidEndpointError (code 4) — wrong endpoint
├── RateLimitError    (code 5) — rate limited (auto-retried)
└── FieldError        (code 6) — field-specific error
```

### Basic Error Handling

```python
from clore_ai import CloreAI
from clore_ai.exceptions import (
    CloreAPIError,
    AuthError,
    RateLimitError,
    InvalidInputError
)

client = CloreAI()

try:
    order = client.create_order(
        server_id=999999,
        image="cloreai/ubuntu22.04-cuda12",
        type="on-demand",
        currency="bitcoin"
    )
except AuthError:
    print("Invalid API key — check CLORE_API_KEY")
except InvalidInputError as e:
    print(f"Bad input: {e}")
except RateLimitError:
    print("Rate limited — the SDK retries automatically, but max retries exceeded")
except CloreAPIError as e:
    print(f"API error (code {e.code}): {e}")
```

### Retry Pattern with Backoff

The SDK has built-in retries for rate limits and network errors (`max_retries=3`). For application-level retries:

```python
import time
from clore_ai import CloreAI
from clore_ai.exceptions import CloreAPIError, RateLimitError

def retry_operation(func, max_attempts=3, base_delay=2.0):
    """Retry a Clore API operation with exponential backoff."""
    for attempt in range(max_attempts):
        try:
            return func()
        except RateLimitError:
            if attempt < max_attempts - 1:
                delay = base_delay * (2 ** attempt)
                print(f"Rate limited, retrying in {delay}s...")
                time.sleep(delay)
            else:
                raise
        except CloreAPIError as e:
            if e.code in (1,):  # DB errors may be transient
                if attempt < max_attempts - 1:
                    time.sleep(base_delay)
                    continue
            raise

# Usage
client = CloreAI()
servers = retry_operation(lambda: client.marketplace(gpu="RTX 4090"))
```

***

## Performance Tips

### 1. Reuse the Client

```python
# ❌ Bad — creates a new HTTP connection each time
for _ in range(10):
    client = CloreAI()
    client.marketplace()
    client.close()

# ✅ Good — reuses the HTTP connection
client = CloreAI()
for _ in range(10):
    client.marketplace()
client.close()
```

### 2. Use Async for Concurrent Operations

```python
import asyncio
from clore_ai import AsyncCloreAI

async def compare_gpus():
    async with AsyncCloreAI() as client:
        # Run 3 searches concurrently
        rtx4090, rtx3090, a100 = await asyncio.gather(
            client.marketplace(gpu="RTX 4090"),
            client.marketplace(gpu="RTX 3090"),
            client.marketplace(gpu="A100"),
        )

        print(f"RTX 4090: {len(rtx4090)} servers")
        print(f"RTX 3090: {len(rtx3090)} servers")
        print(f"A100: {len(a100)} servers")

asyncio.run(compare_gpus())
```

### 3. Async Batch Order Creation

```python
import asyncio
from clore_ai import AsyncCloreAI

async def batch_deploy(server_ids):
    async with AsyncCloreAI() as client:
        tasks = [
            client.create_order(
                server_id=sid,
                image="cloreai/ubuntu22.04-cuda12",
                type="on-demand",
                currency="bitcoin",
                ssh_password="BatchPass123",
                ports={"22": "tcp"}
            )
            for sid in server_ids
        ]
        orders = await asyncio.gather(*tasks, return_exceptions=True)

        for sid, result in zip(server_ids, orders):
            if isinstance(result, Exception):
                print(f"Server {sid}: FAILED — {result}")
            else:
                print(f"Server {sid}: Order {result.id} created")

        return orders

# Deploy on 3 servers at once
asyncio.run(batch_deploy([142, 305, 891]))
```

{% hint style="warning" %}
**Note:** The SDK enforces a 5-second cooldown between `create_order` calls. Even in async mode, orders are spaced out to respect rate limits.
{% endhint %}

### 4. Close Clients When Done

```python
# Context manager handles this automatically
with CloreAI() as client:
    # work...
    pass  # client.close() called automatically

# Or close manually
client = CloreAI()
try:
    # work...
    pass
finally:
    client.close()
```

***

## Complete Example: Auto-Scale GPU Workers

```python
import asyncio
import time
from clore_ai import AsyncCloreAI
from clore_ai.exceptions import CloreAPIError

async def auto_scale(
    gpu_model="RTX 4090",
    max_price=2.0,
    target_workers=3,
    image="cloreai/ubuntu22.04-cuda12"
):
    """Maintain a pool of GPU workers."""
    async with AsyncCloreAI() as client:
        # 1. Check current orders
        current_orders = await client.my_orders()
        active_count = len(current_orders)
        print(f"Active workers: {active_count}/{target_workers}")

        if active_count >= target_workers:
            print("Already at target. Nothing to do.")
            return

        # 2. Find available servers
        servers = await client.marketplace(gpu=gpu_model, max_price_usd=max_price)
        servers.sort(key=lambda s: s.price_usd or float("inf"))

        needed = target_workers - active_count
        candidates = servers[:needed]

        if len(candidates) < needed:
            print(f"Only {len(candidates)} servers available (need {needed})")

        # 3. Deploy
        for server in candidates:
            try:
                order = await client.create_order(
                    server_id=server.id,
                    image=image,
                    type="on-demand",
                    currency="bitcoin",
                    ssh_password="WorkerPass123",
                    ports={"22": "tcp"}
                )
                print(f"Deployed on server {server.id} → order {order.id}")
            except CloreAPIError as e:
                print(f"Failed to deploy on {server.id}: {e}")

asyncio.run(auto_scale())
```

***

## Next Steps

* [CLI Automation](https://docs.clore.ai/guides/advanced/cli-automation) — Bash scripts, CI/CD, batch operations
* [Batch Processing](https://docs.clore.ai/guides/advanced/batch-processing) — Process large workloads on Clore GPUs
* [API Integration](https://docs.clore.ai/guides/advanced/api-integration) — Connect AI services to your apps
