# 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](/getting-started/python-sdk-quickstart.md) — step-by-step tutorial
* [Automation Recipes](/advanced-use-cases/sdk-automation-recipes.md) — production patterns
* [CI/CD with clore-ai SDK](/devops-and-automation/cicd-clore-sdk.md) — pipeline integration
* [Clore Client Reference](/reference/clore-client.md) — legacy `requests`-based client


---

# 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://dev.clore.ai/reference/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.
