# Python SDK (clore-ai)

The **clore-ai** package is the official Python SDK for the [Clore.ai](https://clore.ai) GPU marketplace. It wraps the entire REST API into a clean, type-safe interface with built-in rate limiting, automatic retries, and structured error handling — so you can focus on renting GPUs, not on HTTP plumbing.

***

## Installation

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

**Requirements:** Python 3.9+

The package installs both the Python SDK and the [`clore` CLI](https://docs.clore.ai/developers/cli-guide).

***

## Authentication

Get your API key from the [Clore.ai dashboard](https://clore.ai) → **API** section.

### Option 1: Environment variable (recommended)

```bash
export CLORE_API_KEY=your_api_key_here
```

The SDK reads `CLORE_API_KEY` automatically — no code changes needed.

### Option 2: CLI config file

```bash
clore config set api_key YOUR_API_KEY
```

This stores the key in `~/.clore/config.json`.

### Option 3: Pass directly in code

```python
from clore_ai import CloreAI

client = CloreAI(api_key="your_api_key_here")
```

> ⚠️ **Important:** The Clore.ai API uses the `auth` header for authentication, **not** `Authorization: Bearer`. The SDK handles this automatically.

***

## Quick Start

```python
from clore_ai import CloreAI

client = CloreAI()
servers = client.marketplace(gpu="RTX 4090", max_price_usd=5.0)
for s in servers:
    print(f"Server {s.id}: {s.gpu_model} — ${s.price_usd:.4f}/h")
```

***

## Sync Client (`CloreAI`)

### Constructor

```python
CloreAI(
    api_key: str | None = None,       # Falls back to CLORE_API_KEY env / config
    base_url: str | None = None,       # Default: https://api.clore.ai/v1
    timeout: float = 30.0,             # Request timeout in seconds
    max_retries: int = 3               # Retry attempts on rate-limit / network errors
)
```

The client supports context managers for automatic cleanup:

```python
with CloreAI() as client:
    wallets = client.wallets()
    # client.close() called automatically
```

***

### `wallets()`

Get your wallet balances and deposit addresses.

```python
wallets = client.wallets()

for wallet in wallets:
    print(f"{wallet.name}: {wallet.balance:.8f}")
    if wallet.deposit:
        print(f"  Deposit: {wallet.deposit}")
```

**Returns:** `List[Wallet]`

| Field            | Type            | Description                                                                |
| ---------------- | --------------- | -------------------------------------------------------------------------- |
| `name`           | `str`           | Currency name (e.g. `"bitcoin"`, `"CLORE-Blockchain"`, `"USD-Blockchain"`) |
| `balance`        | `float \| None` | Current balance                                                            |
| `deposit`        | `str \| None`   | Deposit address                                                            |
| `withdrawal_fee` | `float \| None` | Withdrawal fee                                                             |

***

### `marketplace()`

Search the GPU marketplace with optional client-side filters.

```python
# All available servers
servers = client.marketplace()

# Filter by GPU model and max price
servers = client.marketplace(
    gpu="RTX 4090",
    max_price_usd=5.0
)

# Multi-GPU rigs with plenty of RAM
servers = client.marketplace(
    min_gpu_count=4,
    min_ram_gb=128.0
)
```

**Parameters:**

| Parameter        | Type            | Default | Description                                            |
| ---------------- | --------------- | ------- | ------------------------------------------------------ |
| `gpu`            | `str \| None`   | `None`  | Filter by GPU model (case-insensitive substring match) |
| `min_gpu_count`  | `int \| None`   | `None`  | Minimum number of GPUs                                 |
| `min_ram_gb`     | `float \| None` | `None`  | Minimum RAM in GB                                      |
| `max_price_usd`  | `float \| None` | `None`  | Maximum price per hour in USD                          |
| `available_only` | `bool`          | `True`  | Only return servers that are available to rent         |

**Returns:** `List[MarketplaceServer]`

Each `MarketplaceServer` provides convenient properties for the most common fields, plus access to the full nested data:

| Property         | Type            | Description                                                   |
| ---------------- | --------------- | ------------------------------------------------------------- |
| `id`             | `int`           | Unique server ID                                              |
| `gpu_model`      | `str \| None`   | Primary GPU description (e.g. `"1x NVIDIA GeForce RTX 4090"`) |
| `gpu_count`      | `int`           | Number of GPUs (from `gpu_array`)                             |
| `ram_gb`         | `float \| None` | RAM in GB                                                     |
| `price_usd`      | `float \| None` | On-demand price in USD                                        |
| `spot_price_usd` | `float \| None` | Spot price in USD                                             |
| `available`      | `bool`          | Whether the server is available (not rented)                  |
| `location`       | `str \| None`   | Country code from network specs                               |

For advanced use cases, you can access the full nested structure:

| Field         | Type                   | Description                                                                                  |
| ------------- | ---------------------- | -------------------------------------------------------------------------------------------- |
| `specs`       | `ServerSpecs \| None`  | Full hardware specs (`specs.gpu`, `specs.ram`, `specs.cpu`, `specs.disk`, `specs.net`, etc.) |
| `price`       | `ServerPrice \| None`  | Full price object (`price.usd.on_demand_usd`, `price.usd.spot`, `price.on_demand`, etc.)     |
| `rented`      | `bool \| None`         | Whether the server is currently rented                                                       |
| `reliability` | `float \| None`        | Server reliability score                                                                     |
| `rating`      | `ServerRating \| None` | Server rating (`rating.avg`, `rating.cnt`)                                                   |

> **Note:** The `marketplace()` endpoint is public — it works without an API key.

***

### `my_servers()`

List servers you are providing to the Clore.ai marketplace.

```python
my_servers = client.my_servers()

for server in my_servers:
    print(f"{server.name}: {server.gpu_model} [{server.status}]")
```

**Returns:** `List[MyServer]`

| Property     | Type            | Description                                                                          |
| ------------ | --------------- | ------------------------------------------------------------------------------------ |
| `id`         | `int`           | Server ID                                                                            |
| `name`       | `str \| None`   | Server name                                                                          |
| `gpu_model`  | `str \| None`   | Primary GPU description                                                              |
| `ram_gb`     | `float \| None` | RAM in GB                                                                            |
| `status`     | `str`           | Human-readable status: `"Online"`, `"Offline"`, `"Disconnected"`, or `"Not Working"` |
| `connected`  | `bool \| None`  | Whether the server is connected                                                      |
| `online`     | `bool \| None`  | Whether the server is online                                                         |
| `visibility` | `str \| None`   | `"public"` or `"private"`                                                            |

***

### `server_config(server_name)`

Get the configuration of a specific server you host.

```python
config = client.server_config("MyGPU")

print(f"Server: {config.name}")
print(f"GPU: {config.gpu_model}")
print(f"Min rental: {config.mrl}h")
print(f"On-demand: ${config.on_demand_price}")
print(f"Spot: ${config.spot_price}")
```

**Parameters:**

| Parameter     | Type  | Description        |
| ------------- | ----- | ------------------ |
| `server_name` | `str` | Name of the server |

**Returns:** `ServerConfig`

| Property          | Type                  | Description                         |
| ----------------- | --------------------- | ----------------------------------- |
| `name`            | `str \| None`         | Server name                         |
| `gpu_model`       | `str \| None`         | Primary GPU description             |
| `mrl`             | `int \| None`         | Minimum rental length in hours      |
| `on_demand_price` | `float \| None`       | First available on-demand USD price |
| `spot_price`      | `float \| None`       | First available spot USD price      |
| `specs`           | `ServerSpecs \| None` | Full hardware specifications        |
| `connected`       | `bool \| None`        | Whether the server is connected     |
| `visibility`      | `str \| None`         | `"public"` or `"private"`           |

***

### `my_orders(include_completed)`

Get your current orders, optionally including completed/expired ones.

```python
# Active orders only
orders = client.my_orders()

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

for order in orders:
    print(f"Order {order.id}: {order.type} — {order.status}")
    if order.pub_cluster:
        print(f"  IP: {order.pub_cluster}")
    if order.tcp_ports:
        print(f"  Ports: {order.tcp_ports}")
```

**Parameters:**

| Parameter           | Type   | Default | Description                      |
| ------------------- | ------ | ------- | -------------------------------- |
| `include_completed` | `bool` | `False` | Include completed/expired orders |

**Returns:** `List[Order]`

| Field         | Type            | Description                   |
| ------------- | --------------- | ----------------------------- |
| `id`          | `int`           | Unique order ID               |
| `server_id`   | `int \| None`   | 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` | Order price per day           |
| `pub_cluster` | `str \| None`   | Public hostname/IP for access |
| `tcp_ports`   | `dict \| None`  | TCP port mappings             |

***

### `spot_marketplace(server_id)`

View spot market offers for a specific server.

```python
spot = client.spot_marketplace(server_id=6)

if spot.offers:
    for offer in spot.offers:
        print(f"Order {offer.order_id}: ${offer.price}/day (server {offer.server_id})")

if spot.currency_rates_in_usd:
    for coin, rate in spot.currency_rates_in_usd.items():
        print(f"  {coin}: ${rate}")
```

**Parameters:**

| Parameter   | Type  | Description        |
| ----------- | ----- | ------------------ |
| `server_id` | `int` | Server ID to check |

**Returns:** `SpotMarket`

| Field                   | Type                       | Description                                            |
| ----------------------- | -------------------------- | ------------------------------------------------------ |
| `offers`                | `List[SpotOffer] \| None`  | List of spot offers (`order_id`, `price`, `server_id`) |
| `server`                | `SpotServerInfo \| None`   | Server info (min pricing, visibility, online status)   |
| `currency_rates_in_usd` | `Dict[str, float] \| None` | Currency exchange rates in USD                         |

***

### `create_order(...)`

Create a new on-demand or spot order. This is how you rent a GPU.

#### On-demand order

```python
order = client.create_order(
    server_id=123,
    image="cloreai/ubuntu22.04-cuda12",
    type="on-demand",
    currency="bitcoin",
    ssh_password="MySecurePass123",
    ports={"22": "tcp", "8888": "http"}
)

print(f"Order created: {order.id}")
print(f"Connect: {order.pub_cluster}")
```

#### Spot order

```python
order = client.create_order(
    server_id=123,
    image="cloreai/pytorch",
    type="spot",
    currency="bitcoin",
    spot_price=0.000005,
    ssh_password="MySecurePass123",
    ports={"22": "tcp"}
)
```

**Parameters:**

| Parameter            | Type    | Required  | Description                                         |
| -------------------- | ------- | --------- | --------------------------------------------------- |
| `server_id`          | `int`   | Yes       | Server ID to rent                                   |
| `image`              | `str`   | Yes       | Docker image (e.g. `"cloreai/ubuntu22.04-cuda12"`)  |
| `type`               | `str`   | Yes       | `"on-demand"` or `"spot"`                           |
| `currency`           | `str`   | Yes       | Payment currency (e.g. `"bitcoin"`)                 |
| `ssh_password`       | `str`   | No        | SSH password (alphanumeric, max 32 chars)           |
| `ssh_key`            | `str`   | No        | SSH public key (max 3072 chars)                     |
| `ports`              | `dict`  | No        | Port mappings, e.g. `{"22": "tcp", "8888": "http"}` |
| `env`                | `dict`  | No        | Environment variables                               |
| `jupyter_token`      | `str`   | No        | Jupyter notebook token (max 32 chars)               |
| `command`            | `str`   | No        | Shell command to run after container start          |
| `spot_price`         | `float` | Spot only | Price per day for spot orders                       |
| `required_price`     | `float` | No        | Lock in a specific price (on-demand only)           |
| `autossh_entrypoint` | `str`   | No        | Use Clore.ai SSH entrypoint                         |

**Returns:** `Order`

> **Rate limit:** `create_order` has a special 5-second cooldown between calls. The SDK enforces this automatically.

***

### `cancel_order(order_id, issue)`

Cancel an active order or spot offer. Optionally report an issue with the server.

```python
# Simple cancel
client.cancel_order(order_id=38)

# Cancel with issue report
client.cancel_order(
    order_id=38,
    issue="GPU #1 was overheating and throttling"
)
```

**Parameters:**

| Parameter  | Type  | Required | Description                                         |
| ---------- | ----- | -------- | --------------------------------------------------- |
| `order_id` | `int` | Yes      | Order ID to cancel                                  |
| `issue`    | `str` | No       | Cancellation reason / issue report (max 2048 chars) |

**Returns:** `Dict[str, Any]`

***

### `set_server_settings(...)`

Update settings for a server you host on the marketplace.

```python
client.set_server_settings(
    name="MyGPU",
    availability=True,
    mrl=96,
    on_demand=0.0001,
    spot=0.00000113
)
```

**Parameters:**

| Parameter      | Type    | Required | Description                      |
| -------------- | ------- | -------- | -------------------------------- |
| `name`         | `str`   | Yes      | Server name                      |
| `availability` | `bool`  | No       | Whether the server can be rented |
| `mrl`          | `int`   | No       | Minimum rental length in hours   |
| `on_demand`    | `float` | No       | On-demand price per day          |
| `spot`         | `float` | No       | Minimum spot price per day       |

**Returns:** `Dict[str, Any]`

***

### `set_spot_price(order_id, price)`

Update the price on your spot market offer.

```python
client.set_spot_price(order_id=39, price=0.000003)
```

**Parameters:**

| Parameter  | Type    | Description         |
| ---------- | ------- | ------------------- |
| `order_id` | `int`   | Spot order/offer ID |
| `price`    | `float` | New price per day   |

**Returns:** `Dict[str, Any]`

> **Note:** You can only lower spot prices once every 600 seconds, and by a limited step size. The API returns `code: 6` with details if you exceed these limits.

***

## Async Client (`AsyncCloreAI`)

The `AsyncCloreAI` client provides the same methods as `CloreAI`, but all return coroutines. Use it when you need concurrent API calls or are working within an async application.

### Basic usage

```python
import asyncio
from clore_ai import AsyncCloreAI

async def main():
    async with AsyncCloreAI(api_key="your_key") as client:
        wallets = await client.wallets()
        for w in wallets:
            print(f"{w.name}: {w.balance:.8f}")

asyncio.run(main())
```

### Concurrent operations

Run multiple API calls in parallel with `asyncio.gather`:

```python
import asyncio
from clore_ai import AsyncCloreAI

async def compare_gpus():
    async with AsyncCloreAI() as client:
        # Search for multiple GPU models concurrently
        rtx4090, rtx3090, a100 = await asyncio.gather(
            client.marketplace(gpu="RTX 4090"),
            client.marketplace(gpu="RTX 3090"),
            client.marketplace(gpu="A100"),
        )

        for name, servers in [("RTX 4090", rtx4090), ("RTX 3090", rtx3090), ("A100", a100)]:
            if servers:
                cheapest = min(s.price_usd or float('inf') for s in servers)
                print(f"{name}: {len(servers)} available, cheapest ${cheapest:.4f}/h")
            else:
                print(f"{name}: none available")

asyncio.run(compare_gpus())
```

### Available methods

`AsyncCloreAI` supports all the same methods as `CloreAI`:

| Method                              | Description              |
| ----------------------------------- | ------------------------ |
| `await wallets()`                   | Get wallet balances      |
| `await marketplace(...)`            | Search marketplace       |
| `await my_servers()`                | List your hosted servers |
| `await server_config(name)`         | Get server configuration |
| `await my_orders(...)`              | List your orders         |
| `await spot_marketplace(server_id)` | Get spot market offers   |
| `await create_order(...)`           | Create a new order       |
| `await cancel_order(...)`           | Cancel an order          |
| `await set_server_settings(...)`    | Update server settings   |
| `await set_spot_price(...)`         | Update spot price        |

***

## Error Handling

The SDK provides structured exception classes for every API error code.

```python
from clore_ai import CloreAI
from clore_ai.exceptions import (
    CloreAPIError,      # Base class for all API errors
    AuthError,          # Code 3 — invalid API key
    RateLimitError,     # Code 5 — rate limit exceeded
    InvalidInputError,  # Code 2 — bad request data
    DBError,            # Code 1 — database error
    InvalidEndpointError,  # Code 4 — invalid endpoint
    FieldError,         # Code 6 — field-specific error
)

client = CloreAI()

try:
    order = client.create_order(
        server_id=123,
        image="cloreai/ubuntu22.04-cuda12",
        type="on-demand",
        currency="bitcoin",
    )
except AuthError:
    print("Invalid API key. Check your CLORE_API_KEY.")
except RateLimitError:
    print("Rate limited. The SDK retries automatically, but you hit max retries.")
except InvalidInputError as e:
    print(f"Bad request: {e}")
except FieldError as e:
    # Code 6 errors include details in the response
    print(f"Field error: {e} (details: {e.response})")
except CloreAPIError as e:
    print(f"API error: {e} (code: {e.code})")
```

### Error codes

| Code | Exception              | Description                                             |
| ---- | ---------------------- | ------------------------------------------------------- |
| 0    | —                      | Success                                                 |
| 1    | `DBError`              | Database error                                          |
| 2    | `InvalidInputError`    | Invalid input data                                      |
| 3    | `AuthError`            | Invalid API token                                       |
| 4    | `InvalidEndpointError` | Invalid endpoint                                        |
| 5    | `RateLimitError`       | Rate limit exceeded                                     |
| 6    | `FieldError`           | Error in specific field (see `error` field in response) |

All exception classes inherit from `CloreAPIError` and include:

* `e.code` — numeric error code
* `e.response` — full API response dict (when available)

***

## Rate Limiting

The SDK includes a built-in rate limiter that enforces Clore.ai's limits automatically:

| Endpoint       | Limit                   |
| -------------- | ----------------------- |
| Most endpoints | **1 request/second**    |
| `create_order` | **1 request/5 seconds** |

When the API returns a rate-limit error (code 5), the SDK applies **exponential backoff** and retries up to `max_retries` times (default: 3). You don't need to add `time.sleep()` between calls.

### How it works

1. Before each request, the rate limiter waits until the minimum interval has elapsed.
2. `create_order` calls enforce an additional 5-second cooldown.
3. On rate-limit errors, the SDK backs off exponentially: 1s → 2s → 4s → ...
4. After `max_retries` failed attempts, a `RateLimitError` is raised.

### Customize retry behavior

```python
client = CloreAI(
    max_retries=5,    # More retries for long-running scripts
    timeout=60.0      # Longer timeout for slow connections
)
```

***

## Configuration

### Config file

The CLI stores configuration in `~/.clore/config.json`:

```json
{
  "api_key": "your_api_key_here"
}
```

### Resolution order

The SDK resolves the API key in this order:

1. `api_key` argument passed to the constructor
2. `CLORE_API_KEY` environment variable
3. `api_key` field in `~/.clore/config.json`

### Environment variables

| Variable        | Description                |
| --------------- | -------------------------- |
| `CLORE_API_KEY` | API key for authentication |

***

## Next Steps

* [**CLI Reference**](https://docs.clore.ai/developers/cli-guide) — Use Clore.ai from your terminal
* [**REST API**](https://docs.clore.ai/for-hosts/api) — Raw API documentation for custom integrations
* [**On-Demand vs Spot**](https://docs.clore.ai/for-renters/on-demand-vs-spot) — Understand pricing models
* [**Available Docker Images**](https://docs.clore.ai/for-renters/docker-images) — Pre-built images for GPU workloads
