# Python SDK (clore-ai)

Complete API reference for the official [`clore-ai`](https://pypi.org/project/clore-ai/) Python SDK — the recommended way to interact with the Clore.ai GPU marketplace from Python and the command line.

***

## Installation

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

**Requirements:** Python 3.9+

> 📚 New to the SDK? Read [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/) for a hands-on tutorial.

**From source:**

```bash
git clone https://gitlab.com/cloreai/clore-ai-sdk.git
cd clore-ai
pip install -e .
```

***

## Authentication

The SDK resolves your API key in this order:

1. **Constructor argument** — `CloreAI(api_key="...")`
2. **Environment variable** — `CLORE_API_KEY`
3. **Config file** — `~/.clore/config.json` (set via `clore config set api_key YOUR_KEY`)

Get your key from [clore.ai](https://clore.ai).

***

## `CloreAI` — Synchronous Client

### Constructor

```python
from clore_ai import CloreAI

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

| Parameter     | Type          | Default                   | Description                                                                           |
| ------------- | ------------- | ------------------------- | ------------------------------------------------------------------------------------- |
| `api_key`     | `str \| None` | `None`                    | Clore.ai API key. If omitted, reads `CLORE_API_KEY` env var or `~/.clore/config.json` |
| `base_url`    | `str \| None` | `https://api.clore.ai/v1` | API base URL                                                                          |
| `timeout`     | `float`       | `30.0`                    | Request timeout (seconds)                                                             |
| `max_retries` | `int`         | `3`                       | Max retries on transient / rate-limit errors                                          |

**Context manager support:**

```python
with CloreAI(api_key="...") as client:
    servers = client.marketplace()
# client.close() called automatically
```

***

### `wallets()`

Get all wallet balances for the authenticated account.

```python
wallets: List[Wallet] = client.wallets()
```

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

**Raises:** `AuthError` if no API key is set.

**Example:**

```python
from clore_ai import CloreAI

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

***

### `marketplace()`

Browse available GPU servers. This is a **public endpoint** — no API key required.

```python
servers: List[MarketplaceServer] = client.marketplace(
    gpu: str | None = None,
    min_gpu_count: int | None = None,
    min_ram_gb: float | None = None,
    max_price_usd: float | None = None,
    available_only: bool = True,
)
```

| Parameter        | Type            | Default | Description                                                         |
| ---------------- | --------------- | ------- | ------------------------------------------------------------------- |
| `gpu`            | `str \| None`   | `None`  | Filter by GPU model substring (case-insensitive), e.g. `"RTX 4090"` |
| `min_gpu_count`  | `int \| None`   | `None`  | Minimum number of GPUs per server                                   |
| `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 not currently rented                            |

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

> **Note:** Filtering happens client-side. The API returns all servers; the SDK filters the results based on your criteria.

**Example:**

```python
# Find cheap RTX 4090 servers
servers = client.marketplace(gpu="RTX 4090", max_price_usd=0.50)
servers.sort(key=lambda s: s.price_usd or float("inf"))

for s in servers[:5]:
    print(f"Server {s.id}: {s.gpu_count}x {s.gpu_model} — ${s.price_usd:.4f}/h")
```

***

### `my_servers()`

List servers you host on the Clore.ai marketplace.

```python
servers: List[MyServer] = client.my_servers()
```

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

> **Note:** `my_servers()` returns `MyServer` objects, not `MarketplaceServer`. `MyServer` has a `.status` property that returns `"Online"`, `"Offline"`, `"Disconnected"`, or `"Not Working"`.

**Raises:** `AuthError` if no API key is set.

**Example:**

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

***

### `server_config()`

Get the configuration for one of your hosted servers.

```python
config: ServerConfig = client.server_config(server_name: str)
```

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

**Returns:** `ServerConfig`

**Example:**

```python
cfg = client.server_config("MyGPU")
print(f"Available: {cfg.availability}")
print(f"Min rental: {cfg.mrl}h")
print(f"On-demand: {cfg.on_demand_price}")
print(f"Spot: {cfg.spot_price}")
```

***

### `my_orders()`

List your rental orders.

```python
orders: List[Order] = client.my_orders(include_completed: bool = False)
```

| Parameter           | Type   | Default | Description                            |
| ------------------- | ------ | ------- | -------------------------------------- |
| `include_completed` | `bool` | `False` | Also return completed/cancelled orders |

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

**Example:**

```python
orders = client.my_orders()
for o in orders:
    print(f"Order {o.id}: {o.type} — {o.status} @ {o.pub_cluster}")
```

***

### `create_order()`

Create a new GPU rental order.

```python
order: Order = client.create_order(
    server_id: int,
    image: str,
    type: Literal["on-demand", "spot"],
    currency: str,
    ssh_password: str | None = None,
    ssh_key: str | None = None,
    ports: Dict[str, str] | None = None,
    env: Dict[str, str] | None = None,
    jupyter_token: str | None = None,
    command: str | None = None,
    spot_price: float | None = None,
    required_price: float | None = None,
    autossh_entrypoint: str | None = None,
)
```

| Parameter            | Type                     | Description                                               |
| -------------------- | ------------------------ | --------------------------------------------------------- |
| `server_id`          | `int`                    | Server ID from marketplace                                |
| `image`              | `str`                    | Docker image, e.g. `"cloreai/ubuntu22.04-cuda12"`         |
| `type`               | `"on-demand" \| "spot"`  | Order type                                                |
| `currency`           | `str`                    | Payment currency: `"bitcoin"`, `"CLORE-Blockchain"`, etc. |
| `ssh_password`       | `str \| None`            | SSH password for the instance                             |
| `ssh_key`            | `str \| None`            | SSH public key (mutually exclusive with `ssh_password`)   |
| `ports`              | `Dict[str, str] \| None` | Port mappings, e.g. `{"22": "tcp", "8888": "http"}`       |
| `env`                | `Dict[str, str] \| None` | Environment variables                                     |
| `jupyter_token`      | `str \| None`            | Jupyter access token                                      |
| `command`            | `str \| None`            | Custom startup command                                    |
| `spot_price`         | `float \| None`          | Bid price for spot orders                                 |
| `required_price`     | `float \| None`          | Required price                                            |
| `autossh_entrypoint` | `str \| None`            | Auto SSH entrypoint                                       |

**Returns:** `Order`

**Raises:** `AuthError`, `InvalidInputError`, `RateLimitError`

> **Rate limit:** `create_order` has a special 5-second cooldown between calls, enforced by the built-in rate limiter.

**Example:**

```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"},
    env={"NVIDIA_VISIBLE_DEVICES": "all"},
)
print(f"Order {order.id} created @ {order.pub_cluster}")
```

***

### `cancel_order()`

Cancel an active order.

```python
result: Dict[str, Any] = client.cancel_order(
    order_id: int,
    issue: str | None = None,
)
```

| Parameter  | Type          | Description                  |
| ---------- | ------------- | ---------------------------- |
| `order_id` | `int`         | Order ID to cancel           |
| `issue`    | `str \| None` | Optional cancellation reason |

**Returns:** `Dict[str, Any]` — API response

**Example:**

```python
client.cancel_order(order_id=38, issue="Job complete")
```

***

### `spot_marketplace()`

Get spot market data for a specific server.

```python
market: SpotMarket = client.spot_marketplace(server_id: int)
```

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

**Returns:** `SpotMarket` — object with `offers` (list of `SpotOffer`), `server` info, and `currency_rates_in_usd`

**Example:**

```python
market = client.spot_marketplace(server_id=6)
if market.offers:
    for offer in market.offers:
        print(f"Order {offer.order_id}: price={offer.price}")
if market.currency_rates_in_usd:
    print(f"Currency rates: {market.currency_rates_in_usd}")
```

***

### `set_spot_price()`

Update the spot price for one of your orders.

```python
result: Dict[str, Any] = client.set_spot_price(
    order_id: int,
    price: float,
)
```

| Parameter  | Type    | Description        |
| ---------- | ------- | ------------------ |
| `order_id` | `int`   | Active order ID    |
| `price`    | `float` | New spot bid price |

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

**Example:**

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

***

### `set_server_settings()`

Update settings for one of your hosted servers.

```python
result: Dict[str, Any] = client.set_server_settings(
    name: str,
    availability: bool | None = None,
    mrl: int | None = None,
    on_demand: float | None = None,
    spot: float | None = None,
)
```

| Parameter      | Type            | Description                    |
| -------------- | --------------- | ------------------------------ |
| `name`         | `str`           | Server name                    |
| `availability` | `bool \| None`  | Toggle availability on/off     |
| `mrl`          | `int \| None`   | Minimum rental length in hours |
| `on_demand`    | `float \| None` | On-demand price                |
| `spot`         | `float \| None` | Spot price                     |

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

**Example:**

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

***

### `close()`

Close the underlying HTTP client. Called automatically when using the context manager.

```python
client.close()
```

***

## `AsyncCloreAI` — Asynchronous Client

The async client mirrors every method of `CloreAI` but returns coroutines. It uses `httpx.AsyncClient` under the hood.

### Constructor

```python
from clore_ai import AsyncCloreAI

client = AsyncCloreAI(
    api_key: str | None = None,
    base_url: str | None = None,
    timeout: float = 30.0,
    max_retries: int = 3,
)
```

Parameters are identical to `CloreAI`.

### Context Manager (recommended)

```python
import asyncio
from clore_ai import AsyncCloreAI

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

asyncio.run(main())
```

### Methods

All methods have the same signatures as the sync client. Prefix calls with `await`:

```python
wallets = await client.wallets()
servers = await client.marketplace(gpu="A100", max_price_usd=2.0)
my_servers = await client.my_servers()
config = await client.server_config("MyGPU")
orders = await client.my_orders(include_completed=True)
order = await client.create_order(server_id=123, image="...", type="on-demand", currency="bitcoin")
await client.cancel_order(order_id=38)
market = await client.spot_marketplace(server_id=6)
await client.set_spot_price(order_id=39, price=0.000003)
await client.set_server_settings(name="MyGPU", availability=True)
await client.close()
```

### Concurrent Operations

The main advantage of the async client is running multiple API calls in parallel:

```python
import asyncio
from clore_ai import AsyncCloreAI

async def compare_gpus():
    async with AsyncCloreAI() as client:
        rtx4090, a100, rtx3090 = await asyncio.gather(
            client.marketplace(gpu="RTX 4090"),
            client.marketplace(gpu="A100"),
            client.marketplace(gpu="RTX 3090"),
        )
        print(f"RTX 4090: {len(rtx4090)} | A100: {len(a100)} | RTX 3090: {len(rtx3090)}")

asyncio.run(compare_gpus())
```

***

## Models

All models are [Pydantic](https://docs.pydantic.dev/) `BaseModel` subclasses with `extra="allow"` (so they won't break when the API adds new fields) and `populate_by_name=True`.

### `Wallet`

```python
class Wallet(BaseModel):
    name: str                      # e.g. "bitcoin", "CLORE-Blockchain", "USD-Blockchain"
    deposit: str | None            # Deposit address
    balance: float | None          # Current balance
    withdrawal_fee: float | None   # Withdrawal fee
```

### `MarketplaceServer`

Returned by `marketplace()`. Has a nested structure with `specs`, `price`, and `rating` sub-objects, plus convenience properties for easy access.

```python
class MarketplaceServer(BaseModel):
    id: int
    owner: int | None
    rented: bool | None
    specs: ServerSpecs | None      # Hardware specs (CPU, RAM, GPU, disk, network)
    price: ServerPrice | None      # Nested price object with on_demand/spot/usd
    reliability: float | None
    allowed_coins: List[str] | None
    rating: ServerRating | None
    gpu_array: List[Any] | None
    cuda_version: str | None
    mrl: int | None                # Minimum rental length

    # Convenience properties (read-only):
    @property gpu_model -> str | None       # e.g. "1x NVIDIA GeForce RTX 4090" (from specs.gpu)
    @property gpu_count -> int              # Number of GPUs (from gpu_array length)
    @property ram_gb -> float | None        # RAM in GB (from specs.ram)
    @property price_usd -> float | None     # On-demand price in USD (from price.usd.on_demand_usd)
    @property spot_price_usd -> float | None  # Spot price in USD (from price.usd.spot)
    @property available -> bool             # True if not rented
    @property location -> str | None        # Country code (from specs.net.cc)
```

> **Note:** `Server` is a backward-compatible alias for `MarketplaceServer`.

### `ServerSpecs`

Nested under `MarketplaceServer.specs`.

```python
class ServerSpecs(BaseModel):
    mb: str | None                 # Motherboard
    cpu: str | None                # CPU model
    cpus: str | None               # "cores/threads" e.g. "16/32"
    ram: float | None              # RAM in GB
    disk: str | None               # Disk description
    disk_speed: float | None       # Disk speed MB/s
    gpu: str | None                # e.g. "1x NVIDIA GeForce RTX 4090"
    gpuram: float | None           # GPU VRAM in GB
    net: NetworkSpecs | None       # Network info (cc, down, up)
    pcie_rev: int | None
    pcie_width: int | None
```

### `ServerPrice`

Nested under `MarketplaceServer.price`.

```python
class ServerPrice(BaseModel):
    on_demand: PriceTier | None    # Per-currency on-demand prices
    spot: PriceTier | None         # Per-currency spot prices
    usd: PriceUSD | None           # USD-equivalent prices
    original_usd: Dict[str, OriginalUSDEntry] | None

class PriceTier(BaseModel):        # Per payment method
    bitcoin: float | None
    CLORE_Blockchain: float | None  # alias: "CLORE-Blockchain"
    USD_Blockchain: float | None    # alias: "USD-Blockchain"

class PriceUSD(BaseModel):         # USD equivalents
    on_demand_btc: float | None
    on_demand_clore: float | None
    on_demand_usd: float | None
    spot: float | None
```

### `MyServer`

Returned by `my_servers()`. Different from `MarketplaceServer` — represents servers you host.

```python
class MyServer(BaseModel):
    id: int
    name: str | None
    connected: bool | None
    visibility: str | None         # "public" / "private"
    pricing: Dict[str, float] | None
    online: bool | None
    gpu_array: List[Any] | None
    specs: ServerSpecs | None
    working_properly: bool | None

    # Convenience properties:
    @property gpu_model -> str | None   # Primary GPU description
    @property ram_gb -> float | None    # RAM in GB
    @property status -> str             # "Online", "Offline", "Disconnected", or "Not Working"
```

### `ServerConfig`

Returned by `server_config()`.

```python
class ServerConfig(BaseModel):
    id: int | None
    name: str | None
    connected: bool | None
    visibility: str | None
    pricing: Dict[str, float] | None
    usd_pricing: Dict[str, CurrencyPricing] | None
    mrl: int | None                # Min rental length
    online: bool | None
    specs: ServerSpecs | None
    background_job: BackgroundJob | None

    # Convenience properties:
    @property gpu_model -> str | None
    @property on_demand_price -> float | None   # First available on-demand USD price
    @property spot_price -> float | None        # First available spot USD price
```

### `Order`

```python
class Order(BaseModel):
    id: int
    server_id: int | None          # Alias: "renting_server"
    type: str | None               # "on-demand" or "spot"
    status: str | None
    image: str | None
    currency: str | None
    price: float | None
    created_at: str | None
    pub_cluster: str | None        # Public IP / hostname
    tcp_ports: Dict[str, int] | None  # Port mappings
    http_port: int | None
    env: Dict[str, str] | None
    ssh_password: str | None
    ssh_key: str | None
    jupyter_token: str | None
    command: str | None
```

### `SpotMarket`

Returned by `spot_marketplace()`.

```python
class SpotMarket(BaseModel):
    currency_rates_in_usd: Dict[str, float] | None
    offers: List[SpotOffer] | None
    server: SpotServerInfo | None

class SpotOffer(BaseModel):
    order_id: int | None
    price: float | None
    server_id: int | None

class SpotServerInfo(BaseModel):
    min_pricing: Dict[str, float] | None
    mrl: int | None
    visibility: str | None
    online: bool | None
```

### `APIResponse`

```python
class APIResponse(BaseModel):
    code: int
    message: str | None
    data: Any | None
```

***

## Exceptions

All exceptions inherit from `CloreAPIError`.

### Hierarchy

```
CloreAPIError (base)
├── DBError            # Code 1 — database error
├── InvalidInputError  # Code 2 — bad request parameters
├── AuthError          # Code 3 — invalid or missing API key
├── InvalidEndpointError # Code 4 — endpoint does not exist
├── RateLimitError     # Code 5 — rate limit exceeded
└── FieldError         # Code 6 — error in a specific field
```

### `CloreAPIError`

```python
class CloreAPIError(Exception):
    code: int | None       # API error code (1–6)
    response: dict | None  # Raw API response
```

### Error Code Table

| Code | Exception Class        | Meaning                                  |
| ---- | ---------------------- | ---------------------------------------- |
| 0    | *(success)*            | No error                                 |
| 1    | `DBError`              | Database / internal error                |
| 2    | `InvalidInputError`    | Invalid input parameters                 |
| 3    | `AuthError`            | Authentication failed                    |
| 4    | `InvalidEndpointError` | Unknown endpoint                         |
| 5    | `RateLimitError`       | Rate limit exceeded                      |
| 6    | `FieldError`           | Error in a specific field (see response) |

### Handling Errors

```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("Check your CLORE_API_KEY")
except InvalidInputError as e:
    print(f"Bad request: {e} (code={e.code})")
except RateLimitError:
    print("Slow down — rate limit hit (auto-retry exhausted)")
except CloreAPIError as e:
    print(f"API error: {e} (code={e.code}, response={e.response})")
```

***

## Rate Limiter

The SDK includes a built-in `RateLimiter` that enforces:

* **1 request/second** for all endpoints (configurable via `requests_per_second`)
* **5-second cooldown** between `create_order` calls (configurable via `create_order_delay`)
* **Exponential backoff** on rate-limit errors (code 5): starts at 1 s, doubles each retry up to `max_retries`

### How It Works

```
Request 1 ─── 1s gap ─── Request 2 ─── 1s gap ─── Request 3
                                                        ↓
                                               Rate limit (code 5)
                                                        ↓
                                               Backoff 1s → retry
                                                        ↓
                                               Backoff 2s → retry
                                                        ↓
                                               Backoff 4s → retry (or raise)
```

The rate limiter is automatic — you don't need to configure anything. If you want to adjust:

```python
client = CloreAI()
client.rate_limiter.min_interval = 2.0        # 0.5 req/sec
client.rate_limiter.create_order_delay = 10.0  # 10s between create_order calls
```

***

## Configuration

### Environment Variables

| Variable        | Description                          |
| --------------- | ------------------------------------ |
| `CLORE_API_KEY` | API key (preferred over config file) |

### Config File (`~/.clore/config.json`)

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

Set via CLI:

```bash
clore config set api_key YOUR_API_KEY
clore config get api_key
clore config show
```

### Priority Order

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

***

## CLI Reference

The `clore` command-line tool is installed with the package. It uses [Rich](https://rich.readthedocs.io/) for formatted terminal output.

### Commands

| Command                               | Description                   |
| ------------------------------------- | ----------------------------- |
| `clore search`                        | Search GPU marketplace        |
| `clore orders`                        | List your orders              |
| `clore deploy <server_id>`            | Create a new order            |
| `clore cancel <order_id>`             | Cancel an order               |
| `clore wallets`                       | Show wallet balances          |
| `clore servers`                       | List your hosted servers      |
| `clore server-config <name>`          | Get server configuration      |
| `clore spot <server_id>`              | View spot market for a server |
| `clore spot-price <order_id> <price>` | Set spot price                |
| `clore ssh <order_id>`                | SSH into an order             |
| `clore config set <key> <value>`      | Set config value              |
| `clore config get <key>`              | Get config value              |
| `clore config show`                   | Show all config               |

### `clore search`

```bash
clore search [OPTIONS]

Options:
  --gpu TEXT          Filter by GPU model (e.g. "RTX 4090")
  --min-gpu INTEGER   Minimum GPU count
  --min-ram FLOAT     Minimum RAM in GB
  --max-price FLOAT   Maximum price in USD/hour
  --sort [price|gpu|ram]  Sort results (default: price)
  --limit INTEGER     Limit results (default: 20)
```

### `clore deploy`

```bash
clore deploy SERVER_ID [OPTIONS]

Options:
  --image TEXT           Docker image (required)
  --type [on-demand|spot]  Order type (required)
  --currency TEXT        Payment currency (required)
  --ssh-password TEXT    SSH password
  --ssh-key TEXT         SSH public key
  --port TEXT            Port mapping, repeatable (e.g. 22:tcp)
  --env TEXT             Environment variable, repeatable (KEY=VALUE)
  --spot-price FLOAT     Spot price (for spot orders)
```

### `clore ssh`

```bash
clore ssh ORDER_ID [OPTIONS]

Options:
  --user TEXT  SSH user (default: root)
```

***

## See Also

* [SDK Quick Start](https://docs.clore.ai/dev/getting-started/python-sdk-quickstart) — step-by-step tutorial
* [Automation Recipes](https://docs.clore.ai/dev/advanced-use-cases/sdk-automation-recipes) — production patterns
* [CI/CD with clore-ai SDK](https://docs.clore.ai/dev/devops-and-automation/cicd-clore-sdk) — pipeline integration
* [Clore Client Reference](https://docs.clore.ai/dev/reference/clore-client) — legacy `requests`-based client
