# 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](/clore.ai/clore.ai-ru/razrabotchiki/cli-guide.md).

***

## 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,         # Код 6 — ошибка, привязанная к конкретному полю
)

client = CloreAI()

try:
    order = client.create_order(
        server_id=123,
        image="cloreai/ubuntu22.04-cuda12",
        type="on-demand",
        currency="bitcoin",
    )
except AuthError:
    print("Недопустимый API-ключ. Проверьте ваш CLORE_API_KEY.")
except RateLimitError:
    print("Превышен лимит запросов. SDK автоматически повторяет попытки, но вы достигли максимума попыток.")
except InvalidInputError as e:
    print(f"Неверный запрос: {e}")
except FieldError as e:
    # Ошибки с кодом 6 содержат подробности в ответе
    print(f"Ошибка поля: {e} (подробности: {e.response})")
except CloreAPIError as e:
    print(f"Ошибка API: {e} (код: {e.code})")
```

### Коды ошибок

| Код | Исключение             | Description                                           |
| --- | ---------------------- | ----------------------------------------------------- |
| 0   | —                      | Успех                                                 |
| 1   | `DBError`              | Ошибка базы данных                                    |
| 2   | `InvalidInputError`    | Неверные входные данные                               |
| 3   | `AuthError`            | Недействительный API-токен                            |
| 4   | `InvalidEndpointError` | Неверный endpoint                                     |
| 5   | `RateLimitError`       | Превышен лимит запросов                               |
| 6   | `FieldError`           | Ошибка в конкретном поле (см. `ошибка` поле в ответе) |

Все классы исключений наследуются от `CloreAPIError` и включают:

* `e.code` — числовой код ошибки
* `e.response` — полный словарь ответа API (когда доступен)

***

## Ограничение частоты запросов

SDK включает встроенный ограничитель частоты, который автоматически соблюдает лимиты Clore.ai:

| Endpoint              | Лимит                 |
| --------------------- | --------------------- |
| Большинство endpoints | **1 запрос/секунду**  |
| `create_order`        | **1 запрос/5 секунд** |

Когда API возвращает ошибку лимита (код 5), SDK применяет **экспоненциальное затухание** и повторяет попытки до `max_retries` раз (по умолчанию: 3). Вам не нужно добавлять `time.sleep()` между вызовами.

### Как это работает

1. Перед каждым запросом ограничитель частоты ждёт, пока не пройдет минимальный интервал.
2. `create_order` вызовы накладывают дополнительную паузу в 5 секунд.
3. При ошибках лимита SDK увеличивает паузы экспоненциально: 1с → 2с → 4с → ...
4. После `max_retries` неудачных попыток, `RateLimitError` возникает исключение.

### Настройка поведения повторных попыток

```python
client = CloreAI(
    max_retries=5,    # Больше попыток для длительных скриптов
    timeout=60.0      # Более длительный таймаут для медленных соединений
)
```

***

## Конфигурация

### Файл конфигурации

CLI сохраняет конфигурацию в `~/.clore/config.json`:

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

### Порядок разрешения

SDK определяет API-ключ в следующем порядке:

1. `api_key` аргумент, переданный в конструктор
2. `CLORE_API_KEY` переменная окружения
3. `api_key` поле в `~/.clore/config.json`

### Environment variables

| Переменная      | Description                 |
| --------------- | --------------------------- |
| `CLORE_API_KEY` | API-ключ для аутентификации |

***

## Следующие шаги

* [**Справочник CLI**](/clore.ai/clore.ai-ru/razrabotchiki/cli-guide.md) — Использование Clore.ai из терминала
* [**REST API**](/clore.ai/clore.ai-ru/dlya-khostov/api.md) — Документация по сырому API для кастомных интеграций
* [**On-Demand vs Spot**](/clore.ai/clore.ai-ru/dlya-arendatorov/on-demand-vs-spot.md) — Понимание моделей ценообразования
* [**Доступные Docker-образы**](/clore.ai/clore.ai-ru/dlya-arendatorov/docker-images.md) — Предсобранные образы для задач на GPU


---

# 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/clore.ai/clore.ai-ru/razrabotchiki/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.
