# 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


---

# 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.clore.ai/guides/advanced/python-sdk.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.
