# Python SDK (clore-ai)

Das **clore-ai** Paket ist das offizielle Python-SDK für das [Clore.ai](https://clore.ai) GPU-Marktplatz. Es kapselt die gesamte REST-API in einer sauberen, typsicheren Schnittstelle mit integrierter Ratenbegrenzung, automatischen Wiederholungen und strukturierter Fehlerbehandlung — damit Sie sich auf das Mieten von GPUs konzentrieren können und nicht auf HTTP-Details.

***

## Installation

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

**Anforderungen:** Python 3.9+

Das Paket installiert sowohl das Python-SDK als auch die [`clore` CLI](https://docs.clore.ai/clore.ai/clore.ai-eng-de/entwickler/cli-guide).

***

## Authentifizierung

Holen Sie sich Ihren API-Schlüssel vom [Clore.ai-Dashboard](https://clore.ai) → **API** Abschnitt.

### Option 1: Umgebungsvariable (empfohlen)

```bash
export CLORE_API_KEY=your_api_key_here
```

Das SDK liest `CLORE_API_KEY` automatisch — keine Codeänderungen erforderlich.

### Option 2: CLI-Konfigurationsdatei

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

Dies speichert den Schlüssel in `~/.clore/config.json`.

### Option 3: Direkt im Code übergeben

```python
from clore_ai import CloreAI

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

> ⚠️ **Wichtig:** Die Clore.ai-API verwendet den `auth` Header für die Authentifizierung, **nicht** `Authorization: Bearer`. Das SDK kümmert sich automatisch darum.

***

## Schnellstart

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

***

## Synchroner Client (`CloreAI`)

### Konstruktor

```python
CloreAI(
    api_key: str | None = None,       # Fällt auf CLORE_API_KEY env / config zurück
    base_url: str | None = None,       # Standard: https://api.clore.ai/v1
    timeout: float = 30.0,             # Anfrage-Timeout in Sekunden
    max_retries: int = 3               # Anzahl der Wiederholungsversuche bei Rate-Limit- / Netzwerkfehlern
)
```

Der Client unterstützt Kontextmanager für automatische Aufräumarbeiten:

```python
with CloreAI() as client:
    wallets = client.wallets()
    # client.close() wird automatisch aufgerufen
```

***

### `wallets()`

Holen Sie Ihre Wallet-Salden und Einzahlungsadressen.

```python
wallets = client.wallets()

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

**Gibt zurück:** `List[Wallet]`

| Feld             | Typ             | Beschreibung                                                               |
| ---------------- | --------------- | -------------------------------------------------------------------------- |
| `name`           | `str`           | Währungsname (z. B. `"bitcoin"`, `"CLORE-Blockchain"`, `"USD-Blockchain"`) |
| `balance`        | `float \| None` | Aktueller Saldo                                                            |
| `deposit`        | `str \| None`   | Einzahlungsadresse                                                         |
| `withdrawal_fee` | `float \| None` | Auszahlungsgebühr                                                          |

***

### `marketplace()`

Durchsuchen Sie den GPU-Marktplatz mit optionalen clientseitigen Filtern.

```python
# Alle verfügbaren Server
servers = client.marketplace()

# Nach GPU-Modell und Max-Preis filtern
servers = client.marketplace(
    gpu="RTX 4090",
    max_price_usd=5.0
)

# Multi-GPU-Rigs mit viel RAM
servers = client.marketplace(
    min_gpu_count=4,
    min_ram_gb=128.0
)
```

**Parameter:**

| Parameter        | Typ             | Standard | Beschreibung                                                     |
| ---------------- | --------------- | -------- | ---------------------------------------------------------------- |
| `gpu`            | `str \| None`   | `None`   | Nach GPU-Modell filtern (case-insensitiver Teilstring-Vergleich) |
| `min_gpu_count`  | `int \| None`   | `None`   | Minimale Anzahl von GPUs                                         |
| `min_ram_gb`     | `float \| None` | `None`   | Minimale RAM-Größe in GB                                         |
| `max_price_usd`  | `float \| None` | `None`   | Maximaler Preis pro Stunde in USD                                |
| `available_only` | `bool`          | `True`   | Nur Server zurückgeben, die zur Miete verfügbar sind             |

**Gibt zurück:** `List[MarketplaceServer]`

Jeder `MarketplaceServer` bietet komfortable Eigenschaften für die gebräuchlichsten Felder sowie Zugriff auf die vollständigen verschachtelten Daten:

| Eigenschaft      | Typ             | Beschreibung                                                    |
| ---------------- | --------------- | --------------------------------------------------------------- |
| `id`             | `int`           | Eindeutige Server-ID                                            |
| `gpu_model`      | `str \| None`   | Primäre GPU-Beschreibung (z. B. `"1x NVIDIA GeForce RTX 4090"`) |
| `gpu_count`      | `int`           | Anzahl der GPUs (aus `gpu_array`)                               |
| `ram_gb`         | `float \| None` | RAM in GB                                                       |
| `price_usd`      | `float \| None` | On-Demand-Preis in USD                                          |
| `spot_price_usd` | `float \| None` | Spot-Preis in USD                                               |
| `available`      | `bool`          | Ob der Server verfügbar ist (nicht vermietet)                   |
| `location`       | `str \| None`   | Ländercode aus den Netzwerkspezifikationen                      |

Für fortgeschrittene Anwendungsfälle können Sie auf die vollständige verschachtelte Struktur zugreifen:

| Feld          | Typ                    | Beschreibung                                                                                                   |
| ------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------- |
| `specs`       | `ServerSpecs \| None`  | Vollständige Hardware-Spezifikationen (`specs.gpu`, `specs.ram`, `specs.cpu`, `specs.disk`, `specs.net`, usw.) |
| `price`       | `ServerPrice \| None`  | Vollständiges Preisobjekt (`price.usd.on_demand_usd`, `price.usd.spot`, `price.on_demand`, usw.)               |
| `rented`      | `bool \| None`         | Ob der Server derzeit vermietet ist                                                                            |
| `reliability` | `float \| None`        | Server-Zuverlässigkeitswert                                                                                    |
| `rating`      | `ServerRating \| None` | Serverbewertung (`rating.avg`, `rating.cnt`)                                                                   |

> **Hinweis:** Das `marketplace()` Der Endpunkt ist öffentlich — er funktioniert ohne API-Schlüssel.

***

### `my_servers()`

Liste der Server, die Sie auf dem Clore.ai-Marktplatz bereitstellen.

```python
my_servers = client.my_servers()

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

**Gibt zurück:** `List[MyServer]`

| Eigenschaft  | Typ             | Beschreibung                                                                                   |
| ------------ | --------------- | ---------------------------------------------------------------------------------------------- |
| `id`         | `int`           | Server-ID                                                                                      |
| `name`       | `str \| None`   | Servername                                                                                     |
| `gpu_model`  | `str \| None`   | Primäre GPU-Beschreibung                                                                       |
| `ram_gb`     | `float \| None` | RAM in GB                                                                                      |
| `status`     | `str`           | Menschlich lesbarer Status: `"Online"`, `"Offline"`, `"Getrennt"`, oder `"Funktioniert nicht"` |
| `connected`  | `bool \| None`  | Ob der Server verbunden ist                                                                    |
| `online`     | `bool \| None`  | Ob der Server online ist                                                                       |
| `visibility` | `str \| None`   | `"public"` oder `"private"`                                                                    |

***

### `server_config(server_name)`

Holen Sie die Konfiguration eines bestimmten Servers, den Sie hosten.

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

print(f"Server: {config.name}")
print(f"GPU: {config.gpu_model}")
print(f"Minimale Mietdauer: {config.mrl}h")
print(f"On-Demand: ${config.on_demand_price}")
print(f"Spot: ${config.spot_price}")
```

**Parameter:**

| Parameter     | Typ   | Beschreibung     |
| ------------- | ----- | ---------------- |
| `server_name` | `str` | Name des Servers |

**Gibt zurück:** `ServerConfig`

| Eigenschaft       | Typ                   | Beschreibung                              |
| ----------------- | --------------------- | ----------------------------------------- |
| `name`            | `str \| None`         | Servername                                |
| `gpu_model`       | `str \| None`         | Primäre GPU-Beschreibung                  |
| `mrl`             | `int \| None`         | Minimale Mietdauer in Stunden             |
| `on_demand_price` | `float \| None`       | Erster verfügbarer On-Demand-Preis in USD |
| `spot_price`      | `float \| None`       | Erster verfügbarer Spot-Preis in USD      |
| `specs`           | `ServerSpecs \| None` | Vollständige Hardware-Spezifikationen     |
| `connected`       | `bool \| None`        | Ob der Server verbunden ist               |
| `visibility`      | `str \| None`         | `"public"` oder `"private"`               |

***

### `my_orders(include_completed)`

Holen Sie Ihre aktuellen Bestellungen, optional einschließlich abgeschlossener/abgelaufener.

```python
# Nur aktive Bestellungen
orders = client.my_orders()

# Abgeschlossene Bestellungen einbeziehen
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}")
```

**Parameter:**

| Parameter           | Typ    | Standard | Beschreibung                                        |
| ------------------- | ------ | -------- | --------------------------------------------------- |
| `include_completed` | `bool` | `False`  | Abgeschlossene/abgelaufene Bestellungen einbeziehen |

**Gibt zurück:** `List[Order]`

| Feld          | Typ             | Beschreibung                             |
| ------------- | --------------- | ---------------------------------------- |
| `id`          | `int`           | Eindeutige Bestell-ID                    |
| `server_id`   | `int \| None`   | Server-ID                                |
| `type`        | `str`           | `"on-demand"` oder `"spot"`              |
| `status`      | `str \| None`   | Bestellstatus                            |
| `image`       | `str \| None`   | Docker-Image                             |
| `currency`    | `str \| None`   | Zahlungswährung                          |
| `price`       | `float \| None` | Bestellpreis pro Tag                     |
| `pub_cluster` | `str \| None`   | Öffentlicher Hostname/IP für den Zugriff |
| `tcp_ports`   | `dict \| None`  | TCP-Portzuordnungen                      |

***

### `spot_marketplace(server_id)`

Spotmarktangebote für einen bestimmten Server anzeigen.

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

**Parameter:**

| Parameter   | Typ   | Beschreibung          |
| ----------- | ----- | --------------------- |
| `server_id` | `int` | Zu prüfende Server-ID |

**Gibt zurück:** `SpotMarket`

| Feld                    | Typ                        | Beschreibung                                                     |
| ----------------------- | -------------------------- | ---------------------------------------------------------------- |
| `offers`                | `List[SpotOffer] \| None`  | Liste der Spot-Angebote (`order_id`, `price`, `server_id`)       |
| `server`                | `SpotServerInfo \| None`   | Serverinformationen (Mindestpreise, Sichtbarkeit, Online-Status) |
| `currency_rates_in_usd` | `Dict[str, float] \| None` | Währungswechselkurse in USD                                      |

***

### `create_order(...)`

Erstellen Sie eine neue On-Demand- oder Spot-Bestellung. So mieten Sie eine GPU.

#### On-Demand-Bestellung

```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 erstellt: {order.id}")
print(f"Verbinden: {order.pub_cluster}")
```

#### Spot-Bestellung

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

**Parameter:**

| Parameter            | Typ     | Erforderlich | Beschreibung                                                    |
| -------------------- | ------- | ------------ | --------------------------------------------------------------- |
| `server_id`          | `int`   | Ja           | Zu mietende Server-ID                                           |
| `image`              | `str`   | Ja           | Docker-Image (z. B. `"cloreai/ubuntu22.04-cuda12"`)             |
| `type`               | `str`   | Ja           | `"on-demand"` oder `"spot"`                                     |
| `currency`           | `str`   | Ja           | Zahlungswährung (z. B. `"bitcoin"`)                             |
| `ssh_password`       | `str`   | Nein         | SSH-Passwort (alphanumerisch, max. 32 Zeichen)                  |
| `ssh_key`            | `str`   | Nein         | SSH Public Key (max. 3072 Zeichen)                              |
| `ports`              | `dict`  | Nein         | Portzuordnungen, z. B. `{"22": "tcp", "8888": "http"}`          |
| `env`                | `dict`  | Nein         | Umgebungsvariablen                                              |
| `jupyter_token`      | `str`   | Nein         | Jupyter-Notebook-Token (max. 32 Zeichen)                        |
| `command`            | `str`   | Nein         | Shell-Befehl, der nach dem Start des Containers ausgeführt wird |
| `spot_price`         | `float` | Nur Spot     | Preis pro Tag für Spot-Bestellungen                             |
| `required_price`     | `float` | Nein         | Sperren Sie einen bestimmten Preis (nur On-Demand)              |
| `autossh_entrypoint` | `str`   | Nein         | Clore.ai SSH-Entrypoint verwenden                               |

**Gibt zurück:** `Bestellung`

> **Ratenlimit:** `create_order` hat eine spezielle 5-Sekunden-Abkühlzeit zwischen Aufrufen. Das SDK erzwingt dies automatisch.

***

### `cancel_order(order_id, issue)`

Stornieren Sie eine aktive Bestellung oder ein Spot-Angebot. Optional können Sie ein Problem mit dem Server melden.

```python
# Einfache Stornierung
client.cancel_order(order_id=38)

# Stornierung mit Problemmeldung
client.cancel_order(
    order_id=38,
    issue="GPU #1 überhitzte und drosselte"
)
```

**Parameter:**

| Parameter  | Typ   | Erforderlich | Beschreibung                                           |
| ---------- | ----- | ------------ | ------------------------------------------------------ |
| `order_id` | `int` | Ja           | Zu stornierende Bestell-ID                             |
| `issue`    | `str` | Nein         | Stornierungsgrund / Problemmeldung (max. 2048 Zeichen) |

**Gibt zurück:** `Dict[str, Any]`

***

### `set_server_settings(...)`

Aktualisieren Sie die Einstellungen für einen Server, den Sie auf dem Marktplatz hosten.

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

**Parameter:**

| Parameter      | Typ     | Erforderlich | Beschreibung                       |
| -------------- | ------- | ------------ | ---------------------------------- |
| `name`         | `str`   | Ja           | Servername                         |
| `availability` | `bool`  | Nein         | Ob der Server gemietet werden kann |
| `mrl`          | `int`   | Nein         | Minimale Mietdauer in Stunden      |
| `on_demand`    | `float` | Nein         | On-Demand-Preis pro Tag            |
| `spot`         | `float` | Nein         | Minimaler Spot-Preis pro Tag       |

**Gibt zurück:** `Dict[str, Any]`

***

### `set_spot_price(order_id, price)`

Aktualisieren Sie den Preis für Ihr Spot-Angebot.

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

**Parameter:**

| Parameter  | Typ     | Beschreibung              |
| ---------- | ------- | ------------------------- |
| `order_id` | `int`   | Spot-Bestell-/Angebots-ID |
| `price`    | `float` | Neuer Preis pro Tag       |

**Gibt zurück:** `Dict[str, Any]`

> **Hinweis:** Sie können Spot-Preise nur alle 600 Sekunden und nur in begrenzten Schritten senken. Die API gibt `code: 6` mit Details zurück, wenn Sie diese Grenzen überschreiten.

***

## Asynchroner Client (`AsyncCloreAI`)

Das `AsyncCloreAI` client stellt dieselben Methoden wie `CloreAI`, aber alle geben Koroutinen zurück. Verwenden Sie ihn, wenn Sie gleichzeitige API-Aufrufe benötigen oder in einer asynchronen Anwendung arbeiten.

### Grundlegende Verwendung

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

### Gleichzeitige Operationen

Führen Sie mehrere API-Aufrufe parallel mit `asyncio.gather`:

```python
import asyncio
from clore_ai import AsyncCloreAI

async def compare_gpus():
    async with AsyncCloreAI() as client:
        # Mehrere GPU-Modelle gleichzeitig suchen
        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)} verfügbar, günstigster ${cheapest:.4f}/h")
            else:
                print(f"{name}: keine verfügbar")

asyncio.run(compare_gpus())
```

### Verfügbare Methoden

`AsyncCloreAI` unterstützt alle dieselben Methoden wie `CloreAI`:

| Methode                             | Beschreibung                      |
| ----------------------------------- | --------------------------------- |
| `await wallets()`                   | Wallet-Salden abrufen             |
| `await marketplace(...)`            | Marktplatz durchsuchen            |
| `await my_servers()`                | Ihre gehosteten Server auflisten  |
| `await server_config(name)`         | Serverkonfiguration abrufen       |
| `await my_orders(...)`              | Ihre Bestellungen auflisten       |
| `await spot_marketplace(server_id)` | Spotmarkt-Angebote abrufen        |
| `await create_order(...)`           | Neue Bestellung erstellen         |
| `await cancel_order(...)`           | Bestellung stornieren             |
| `await set_server_settings(...)`    | Servereinstellungen aktualisieren |
| `await set_spot_price(...)`         | Spot-Preis aktualisieren          |

***

## Fehlerbehandlung

Das SDK stellt strukturierte Ausnahmeklassen für jeden API-Fehlercode bereit.

```python
from clore_ai import CloreAI
from clore_ai.exceptions import (
    CloreAPIError,      # Basisklasse für alle API-Fehler
    AuthError,          # Code 3 — ungültiger API-Schlüssel
    RateLimitError,     # Code 5 — Ratenlimit überschritten
    InvalidInputError,  # Code 2 — fehlerhafte Anfrage-Daten
    DBError,            # Code 1 — Datenbankfehler
    InvalidEndpointError,  # Code 4 — ungültiger Endpunkt
    FieldError,         # Code 6 — feldspezifischer Fehler
)

client = CloreAI()

try:
    order = client.create_order(
        server_id=123,
        image="cloreai/ubuntu22.04-cuda12",
        type="on-demand",
        currency="bitcoin",
    )
except AuthError:
    print("Ungültiger API-Schlüssel. Überprüfen Sie Ihren CLORE_API_KEY.")
except RateLimitError:
    print("Rate limit erreicht. Das SDK versucht automatisch erneut, aber Sie haben die maximale Anzahl von Versuchen erreicht.")
except InvalidInputError as e:
    print(f"Ungültige Anfrage: {e}")
except FieldError as e:
    # Code-6-Fehler enthalten Details in der Antwort
    print(f"Feldfehler: {e} (Details: {e.response})")
except CloreAPIError as e:
    print(f"API-Fehler: {e} (Code: {e.code})")
```

### Fehlercodes

| Code | Ausnahme               | Beschreibung                                                        |
| ---- | ---------------------- | ------------------------------------------------------------------- |
| 0    | —                      | Erfolg                                                              |
| 1    | `DBError`              | Datenbankfehler                                                     |
| 2    | `InvalidInputError`    | Ungültige Eingabedaten                                              |
| 3    | `AuthError`            | Ungültiges API-Token                                                |
| 4    | `InvalidEndpointError` | Ungültiger Endpunkt                                                 |
| 5    | `RateLimitError`       | Rate-Limit überschritten                                            |
| 6    | `FieldError`           | Fehler in einem bestimmten Feld (siehe `error` Feld in der Antwort) |

Alle Ausnahmeklassen erben von `CloreAPIError` und enthalten:

* `e.code` — numerischer Fehlercode
* `e.response` — vollständiges API-Antwort-Objekt (wenn verfügbar)

***

## Rate Limiting

Das SDK enthält einen eingebauten Ratenbegrenzer, der die Limits von Clore.ai automatisch durchsetzt:

| Endpunkt              | Limit                    |
| --------------------- | ------------------------ |
| Die meisten Endpunkte | **1 Anfrage/Sekunde**    |
| `create_order`        | **1 Anfrage/5 Sekunden** |

Wenn die API einen Rate-Limit-Fehler (Code 5) zurückgibt, wendet das SDK **exponentielles Backoff** an und wiederholt bis zu `max_retries` Versuche (Standard: 3). Sie müssen kein `time.sleep()` zwischen Aufrufen hinzufügen.

### Funktionsweise

1. Vor jeder Anfrage wartet der Ratenbegrenzer, bis das minimale Intervall verstrichen ist.
2. `create_order` Aufrufe erzwingen eine zusätzliche 5-Sekunden-Abkühlzeit.
3. Bei Rate-Limit-Fehlern verringert das SDK die Wiederholungen exponentiell: 1s → 2s → 4s → ...
4. Nach `max_retries` fehlgeschlagenen Versuchen wird eine `RateLimitError` ausgelöst.

### Anpassen des Wiederholungsverhaltens

```python
client = CloreAI(
    max_retries=5,    # Mehr Versuche für lang laufende Skripte
    timeout=60.0      # Längere Zeitüberschreitung für langsame Verbindungen
)
```

***

## Konfiguration

### Konfigurationsdatei

Die CLI speichert die Konfiguration in `~/.clore/config.json`:

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

### Behebungsreihenfolge

Das SDK löst den API-Schlüssel in folgender Reihenfolge auf:

1. `api_key` als Argument an den Konstruktor übergeben
2. `CLORE_API_KEY` Umgebungsvariable
3. `api_key` Feld in `~/.clore/config.json`

### Umgebungsvariablen

| Variable        | Beschreibung                        |
| --------------- | ----------------------------------- |
| `CLORE_API_KEY` | API-Schlüssel zur Authentifizierung |

***

## Nächste Schritte

* [**CLI-Referenz**](https://docs.clore.ai/clore.ai/clore.ai-eng-de/entwickler/cli-guide) — Verwenden Sie Clore.ai über Ihr Terminal
* [**REST-API**](https://docs.clore.ai/clore.ai/clore.ai-eng-de/fur-hosts/api) — Rohdokumentation der API für benutzerdefinierte Integrationen
* [**On-Demand vs Spot**](https://docs.clore.ai/clore.ai/clore.ai-eng-de/fur-mieter/on-demand-vs-spot) — Preisgestaltungsmodelle verstehen
* [**Verfügbare Docker-Images**](https://docs.clore.ai/clore.ai/clore.ai-eng-de/fur-mieter/docker-images) — Vorgefertigte Images für GPU-Workloads
