# SDK de Python (clore-ai)

El **clore-ai** paquete es el SDK oficial de Python para el [Clore.ai](https://clore.ai) mercado de GPU. Envuelve toda la API REST en una interfaz limpia y con tipos seguros con limitación de tasa incorporada, reintentos automáticos y manejo estructurado de errores — para que puedas concentrarte en alquilar GPUs, no en la plomería HTTP.

***

## Instalación

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

**Requisitos:** Python 3.9+

El paquete instala tanto el SDK de Python como el [`clore` CLI](https://docs.clore.ai/clore.ai/clore.ai-eng-es/desarrolladores/cli-guide).

***

## Autenticación

Obtén tu clave de API desde el [panel de control de Clore.ai](https://clore.ai) → **API** sección.

### Opción 1: Variable de entorno (recomendado)

```bash
export CLORE_API_KEY=tu_clave_api_aqui
```

El SDK lee `CLORE_API_KEY` automáticamente — no se necesitan cambios en el código.

### Opción 2: Archivo de configuración del CLI

```bash
clore config set api_key TU_API_KEY
```

Esto almacena la clave en `~/.clore/config.json`.

### Opción 3: Pasar directamente en el código

```python
from clore_ai import CloreAI

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

> ⚠️ **Importante:** La API de Clore.ai usa la `auth` cabecera para la autenticación, **no** `Authorization: Bearer`. El SDK lo maneja automáticamente.

***

## Inicio rápido

```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")
```

***

## Cliente sincrónico (`CloreAI`)

### Constructor

```python
CloreAI(
    api_key: str | None = None,       # Recurre a la variable de entorno CLORE_API_KEY / config
    base_url: str | None = None,       # Predeterminado: https://api.clore.ai/v1
    timeout: float = 30.0,             # Tiempo de espera de la solicitud en segundos
    max_retries: int = 3               # Intentos de reintento en errores de límite de tasa / red
)
```

El cliente soporta gestores de contexto para limpieza automática:

```python
with CloreAI() as client:
    wallets = client.wallets()
    # client.close() llamado automáticamente
```

***

### `wallets()`

Obtén los saldos de tus wallets y las direcciones de depósito.

```python
wallets = client.wallets()

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

**Devuelve:** `List[Wallet]`

| Campo            | Tipo            | Descripción                                                                        |
| ---------------- | --------------- | ---------------------------------------------------------------------------------- |
| `name`           | `str`           | Nombre de la moneda (p. ej. `"bitcoin"`, `"CLORE-Blockchain"`, `"USD-Blockchain"`) |
| `balance`        | `float \| None` | Saldo actual                                                                       |
| `deposit`        | `str \| None`   | Dirección de depósito                                                              |
| `withdrawal_fee` | `float \| None` | Tarifa de retiro                                                                   |

***

### `marketplace()`

Busca en el mercado de GPU con filtros opcionales del lado del cliente.

```python
# Todos los servidores disponibles
servers = client.marketplace()

# Filtrar por modelo de GPU y precio máximo
servers = client.marketplace(
    gpu="RTX 4090",
    max_price_usd=5.0
)

# Rigs con múltiples GPUs y mucha RAM
servers = client.marketplace(
    min_gpu_count=4,
    min_ram_gb=128.0
)
```

**Parámetros:**

| Parámetro        | Tipo            | Predeterminado | Descripción                                                                        |
| ---------------- | --------------- | -------------- | ---------------------------------------------------------------------------------- |
| `gpu`            | `str \| None`   | `None`         | Filtrar por modelo de GPU (coincidencia de subcadena sin distinción de mayúsculas) |
| `min_gpu_count`  | `int \| None`   | `None`         | Número mínimo de GPUs                                                              |
| `min_ram_gb`     | `float \| None` | `None`         | RAM mínima en GB                                                                   |
| `max_price_usd`  | `float \| None` | `None`         | Precio máximo por hora en USD                                                      |
| `available_only` | `bool`          | `True`         | Solo devolver servidores que están disponibles para alquilar                       |

**Devuelve:** `List[MarketplaceServer]`

Cada `MarketplaceServer` proporciona propiedades convenientes para los campos más comunes, además de acceso a los datos anidados completos:

| Propiedad        | Tipo            | Descripción                                                             |
| ---------------- | --------------- | ----------------------------------------------------------------------- |
| `id`             | `int`           | ID único del servidor                                                   |
| `gpu_model`      | `str \| None`   | Descripción de la GPU principal (p. ej. `"1x NVIDIA GeForce RTX 4090"`) |
| `gpu_count`      | `int`           | Número de GPUs (de `gpu_array`)                                         |
| `ram_gb`         | `float \| None` | RAM en GB                                                               |
| `price_usd`      | `float \| None` | Precio bajo demanda en USD                                              |
| `spot_price_usd` | `float \| None` | Precio spot en USD                                                      |
| `available`      | `bool`          | Si el servidor está disponible (no alquilado)                           |
| `location`       | `str \| None`   | Código de país según las especificaciones de la red                     |

Para casos de uso avanzados, puedes acceder a la estructura anidada completa:

| Campo         | Tipo                   | Descripción                                                                                                     |
| ------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------- |
| `specs`       | `ServerSpecs \| None`  | Especificaciones completas de hardware (`specs.gpu`, `specs.ram`, `specs.cpu`, `specs.disk`, `specs.net`, etc.) |
| `price`       | `ServerPrice \| None`  | Objeto de precio completo (`price.usd.on_demand_usd`, `price.usd.spot`, `price.on_demand`, etc.)                |
| `rented`      | `bool \| None`         | Si el servidor está actualmente alquilado                                                                       |
| `reliability` | `float \| None`        | Puntuación de fiabilidad del servidor                                                                           |
| `rating`      | `ServerRating \| None` | Calificación del servidor (`rating.avg`, `rating.cnt`)                                                          |

> **Nota:** El `marketplace()` el endpoint es público — funciona sin clave de API.

***

### `my_servers()`

Enumera los servidores que estás proporcionando al mercado de Clore.ai.

```python
my_servers = client.my_servers()

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

**Devuelve:** `List[MyServer]`

| Propiedad    | Tipo            | Descripción                                                                              |
| ------------ | --------------- | ---------------------------------------------------------------------------------------- |
| `id`         | `int`           | ID del servidor                                                                          |
| `name`       | `str \| None`   | Nombre del servidor                                                                      |
| `gpu_model`  | `str \| None`   | Descripción de la GPU principal                                                          |
| `ram_gb`     | `float \| None` | RAM en GB                                                                                |
| `status`     | `str`           | Estado legible por humanos: `"Online"`, `"Offline"`, `"Disconnected"`, o `"Not Working"` |
| `connected`  | `bool \| None`  | Si el servidor está conectado                                                            |
| `online`     | `bool \| None`  | Si el servidor está en línea                                                             |
| `visibility` | `str \| None`   | `"public"` o `"private"`                                                                 |

***

### `server_config(server_name)`

Obtén la configuración de un servidor específico que alojas.

```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}")
```

**Parámetros:**

| Parámetro     | Tipo  | Descripción         |
| ------------- | ----- | ------------------- |
| `server_name` | `str` | Nombre del servidor |

**Devuelve:** `ServerConfig`

| Propiedad         | Tipo                  | Descripción                                  |
| ----------------- | --------------------- | -------------------------------------------- |
| `name`            | `str \| None`         | Nombre del servidor                          |
| `gpu_model`       | `str \| None`         | Descripción de la GPU principal              |
| `mrl`             | `int \| None`         | Duración mínima de alquiler en horas         |
| `on_demand_price` | `float \| None`       | Primer precio disponible bajo demanda en USD |
| `spot_price`      | `float \| None`       | Primer precio spot disponible en USD         |
| `specs`           | `ServerSpecs \| None` | Especificaciones completas de hardware       |
| `connected`       | `bool \| None`        | Si el servidor está conectado                |
| `visibility`      | `str \| None`         | `"public"` o `"private"`                     |

***

### `my_orders(include_completed)`

Obtén tus órdenes actuales, opcionalmente incluyendo las completadas/expiradas.

```python
# Solo órdenes activas
orders = client.my_orders()

# Incluir órdenes completadas
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}")
```

**Parámetros:**

| Parámetro           | Tipo   | Predeterminado | Descripción                           |
| ------------------- | ------ | -------------- | ------------------------------------- |
| `include_completed` | `bool` | `False`        | Incluir órdenes completadas/expiradas |

**Devuelve:** `List[Order]`

| Campo         | Tipo            | Descripción                     |
| ------------- | --------------- | ------------------------------- |
| `id`          | `int`           | ID único de la orden            |
| `server_id`   | `int \| None`   | ID del servidor                 |
| `type`        | `str`           | `"on-demand"` o `"spot"`        |
| `status`      | `str \| None`   | Estado de la orden              |
| `image`       | `str \| None`   | Imagen de Docker                |
| `currency`    | `str \| None`   | Moneda de pago                  |
| `price`       | `float \| None` | Precio de la orden por día      |
| `pub_cluster` | `str \| None`   | Hostname/IP público para acceso |
| `tcp_ports`   | `dict \| None`  | Mapeos de puertos TCP           |

***

### `spot_marketplace(server_id)`

Ver ofertas del mercado spot para un servidor específico.

```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}")
```

**Parámetros:**

| Parámetro   | Tipo  | Descripción                 |
| ----------- | ----- | --------------------------- |
| `server_id` | `int` | ID del servidor a verificar |

**Devuelve:** `SpotMarket`

| Campo                   | Tipo                       | Descripción                                                              |
| ----------------------- | -------------------------- | ------------------------------------------------------------------------ |
| `offers`                | `List[SpotOffer] \| None`  | Lista de ofertas spot (`order_id`, `price`, `server_id`)                 |
| `server`                | `SpotServerInfo \| None`   | Información del servidor (precios mínimos, visibilidad, estado en línea) |
| `currency_rates_in_usd` | `Dict[str, float] \| None` | Tasas de cambio de monedas en USD                                        |

***

### `create_order(...)`

Crea una nueva orden on-demand o spot. Así es como alquilas una GPU.

#### Orden on-demand

```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}")
```

#### Orden spot

```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"}
)
```

**Parámetros:**

| Parámetro            | Tipo    | Requerido | Descripción                                                  |
| -------------------- | ------- | --------- | ------------------------------------------------------------ |
| `server_id`          | `int`   | Sí        | ID del servidor a alquilar                                   |
| `image`              | `str`   | Sí        | Imagen de Docker (p. ej. `"cloreai/ubuntu22.04-cuda12"`)     |
| `type`               | `str`   | Sí        | `"on-demand"` o `"spot"`                                     |
| `currency`           | `str`   | Sí        | Moneda de pago (p. ej. `"bitcoin"`)                          |
| `ssh_password`       | `str`   | No        | Contraseña SSH (alfanumérica, máximo 32 caracteres)          |
| `ssh_key`            | `str`   | No        | Clave pública SSH (máx. 3072 caracteres)                     |
| `ports`              | `dict`  | No        | Mapeos de puertos, p. ej. `{"22": "tcp", "8888": "http"}`    |
| `env`                | `dict`  | No        | Variables de entorno                                         |
| `jupyter_token`      | `str`   | No        | Token de Jupyter notebook (máx. 32 caracteres)               |
| `command`            | `str`   | No        | Comando de shell a ejecutar después de iniciar el contenedor |
| `spot_price`         | `float` | Solo spot | Precio por día para órdenes spot                             |
| `required_price`     | `float` | No        | Asegurar un precio específico (solo on-demand)               |
| `autossh_entrypoint` | `str`   | No        | Usar el punto de entrada SSH de Clore.ai                     |

**Devuelve:** `Orden`

> **Límite de tasa:** `create_order` tiene un tiempo de espera especial de 5 segundos entre llamadas. El SDK lo aplica automáticamente.

***

### `cancel_order(order_id, issue)`

Cancelar una orden activa u oferta spot. Opcionalmente reportar un problema con el servidor.

```python
# Cancelación simple
client.cancel_order(order_id=38)

# Cancelar con reporte de problema
client.cancel_order(
    order_id=38,
    issue="GPU #1 was overheating and throttling"
)
```

**Parámetros:**

| Parámetro  | Tipo  | Requerido | Descripción                                                          |
| ---------- | ----- | --------- | -------------------------------------------------------------------- |
| `order_id` | `int` | Sí        | ID de la orden a cancelar                                            |
| `issue`    | `str` | No        | Razón de la cancelación / reporte de problema (máx. 2048 caracteres) |

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

***

### `set_server_settings(...)`

Actualizar la configuración de un servidor que alojas en el mercado.

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

**Parámetros:**

| Parámetro      | Tipo    | Requerido | Descripción                          |
| -------------- | ------- | --------- | ------------------------------------ |
| `name`         | `str`   | Sí        | Nombre del servidor                  |
| `availability` | `bool`  | No        | Si el servidor puede ser alquilado   |
| `mrl`          | `int`   | No        | Duración mínima de alquiler en horas |
| `on_demand`    | `float` | No        | Precio por día bajo demanda          |
| `spot`         | `float` | No        | Precio mínimo spot por día           |

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

***

### `set_spot_price(order_id, price)`

Actualizar el precio de tu oferta en el mercado spot.

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

**Parámetros:**

| Parámetro  | Tipo    | Descripción                |
| ---------- | ------- | -------------------------- |
| `order_id` | `int`   | ID de la orden/oferta spot |
| `price`    | `float` | Nuevo precio por día       |

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

> **Nota:** Solo puedes bajar los precios spot una vez cada 600 segundos, y por un tamaño de paso limitado. La API devuelve `code: 6` con detalles si excedes estos límites.

***

## Cliente asíncrono (`AsyncCloreAI`)

El `AsyncCloreAI` client proporciona los mismos métodos que `CloreAI`, pero todos devuelven corrutinas. Úsalo cuando necesites llamadas de API concurrentes o estés trabajando dentro de una aplicación async.

### Uso básico

```python
import asyncio
from clore_ai import AsyncCloreAI

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

asyncio.run(main())
```

### Operaciones concurrentes

Ejecuta múltiples llamadas a la API en paralelo con `asyncio.gather`:

```python
import asyncio
from clore_ai import AsyncCloreAI

async def compare_gpus():
    async with AsyncCloreAI() as client:
        # Buscar múltiples modelos de GPU concurrentemente
        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())
```

### Métodos disponibles

`AsyncCloreAI` soporta todos los mismos métodos que `CloreAI`:

| Method                              | Descripción                              |
| ----------------------------------- | ---------------------------------------- |
| `await wallets()`                   | Obtener saldos de wallets                |
| `await marketplace(...)`            | Buscar en el mercado                     |
| `await my_servers()`                | Listar tus servidores alojados           |
| `await server_config(name)`         | Obtener configuración del servidor       |
| `await my_orders(...)`              | Listar tus órdenes                       |
| `await spot_marketplace(server_id)` | Obtener ofertas del mercado spot         |
| `await create_order(...)`           | Crear una nueva orden                    |
| `await cancel_order(...)`           | Cancelar una orden                       |
| `await set_server_settings(...)`    | Actualizar la configuración del servidor |
| `await set_spot_price(...)`         | Actualizar el precio spot                |

***

## Manejo de errores

El SDK proporciona clases de excepción estructuradas para cada código de error de la API.

```python
from clore_ai import CloreAI
from clore_ai.exceptions import (
    CloreAPIError,      # Clase base para todos los errores de la API
    AuthError,          # Código 3 — clave de API inválida
    RateLimitError,     # Código 5 — límite de tasa excedido
    InvalidInputError,  # Código 2 — datos de solicitud incorrectos
    DBError,            # Código 1 — error de base de datos
    InvalidEndpointError,  # Código 4 — endpoint inválido
    FieldError,         # Código 6 — error específico de campo
)

client = CloreAI()

try:
    order = client.create_order(
        server_id=123,
        image="cloreai/ubuntu22.04-cuda12",
        type="on-demand",
        currency="bitcoin",
    )
except AuthError:
    print("Clave de API inválida. Verifica tu CLORE_API_KEY.")
except RateLimitError:
    print("Límite de velocidad alcanzado. El SDK reintenta automáticamente, pero alcanzaste el máximo de reintentos.")
except InvalidInputError as e:
    print(f"Solicitud incorrecta: {e}")
except FieldError as e:
    # Los errores de Código 6 incluyen detalles en la respuesta
    print(f"Error de campo: {e} (detalles: {e.response})")
except CloreAPIError as e:
    print(f"Error de API: {e} (código: {e.code})")
```

### Códigos de error

| Código | Excepción              | Descripción                                                      |
| ------ | ---------------------- | ---------------------------------------------------------------- |
| 0      | —                      | Éxito                                                            |
| 1      | `DBError`              | Error de base de datos                                           |
| 2      | `InvalidInputError`    | Datos de entrada inválidos                                       |
| 3      | `AuthError`            | Token de API inválido                                            |
| 4      | `InvalidEndpointError` | Endpoint inválido                                                |
| 5      | `RateLimitError`       | Límite de velocidad excedido                                     |
| 6      | `FieldError`           | Error en un campo específico (ver `error` campo en la respuesta) |

Todas las clases de excepción heredan de `CloreAPIError` e incluyen:

* `e.code` — código numérico de error
* `e.response` — diccionario de respuesta completa de la API (cuando esté disponible)

***

## Limitación de velocidad

El SDK incluye un limitador de velocidad incorporado que aplica automáticamente los límites de Clore.ai:

| Endpoint                | Límite                     |
| ----------------------- | -------------------------- |
| La mayoría de endpoints | **1 solicitud/segundo**    |
| `create_order`          | **1 solicitud/5 segundos** |

Cuando la API devuelve un error de límite de velocidad (código 5), el SDK aplica **retroceso exponencial** y reintenta hasta `max_retries` veces (por defecto: 3). No necesitas añadir `time.sleep()` entre llamadas.

### Cómo funciona

1. Antes de cada solicitud, el limitador de velocidad espera hasta que haya transcurrido el intervalo mínimo.
2. `create_order` las llamadas imponen una pausa adicional de 5 segundos.
3. En errores por límite de velocidad, el SDK retrocede exponencialmente: 1s → 2s → 4s → ...
4. Después de `max_retries` intentos fallidos, se `RateLimitError` lanza una excepción.

### Personalizar el comportamiento de reintentos

```python
client = CloreAI(
    max_retries=5,    # Más reintentos para scripts de larga duración
    timeout=60.0      # Tiempo de espera más largo para conexiones lentas
)
```

***

## Configuración

### Archivo de configuración

El CLI almacena la configuración en `~/.clore/config.json`:

```json
{
  "api_key": "tu_api_key_aquí"
}
```

### Orden de resolución

El SDK resuelve la clave de API en este orden:

1. `api_key` argumento pasado al constructor
2. `CLORE_API_KEY` variable de entorno
3. `api_key` campo en `~/.clore/config.json`

### Variables de entorno

| Variable        | Descripción                     |
| --------------- | ------------------------------- |
| `CLORE_API_KEY` | Clave de API para autenticación |

***

## Próximos pasos

* [**Referencia CLI**](https://docs.clore.ai/clore.ai/clore.ai-eng-es/desarrolladores/cli-guide) — Usa Clore.ai desde tu terminal
* [**REST API**](https://docs.clore.ai/clore.ai/clore.ai-eng-es/para-anfitriones/api) — Documentación cruda de la API para integraciones personalizadas
* [**On-Demand vs Spot**](https://docs.clore.ai/clore.ai/clore.ai-eng-es/para-arrendatarios/on-demand-vs-spot) — Entender los modelos de precios
* [**Imágenes Docker disponibles**](https://docs.clore.ai/clore.ai/clore.ai-eng-es/para-arrendatarios/docker-images) — Imágenes preconstruidas para cargas de trabajo en GPU
