> For the complete documentation index, see [llms.txt](https://docs.clore.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.clore.ai/clore.ai/clore.ai-eng-de/entwickler/cli-sdk.md).

# CLI- & SDK-Leitfaden

## Übersicht

Clore.ai bietet eine **REST-API** die den vollständigen programmgesteuerten Zugriff auf den GPU-Marktplatz ermöglicht — Server auflisten, Bestellungen erstellen, Bereitstellungen überwachen und Mieten kündigen.

> **Hinweis:** Derzeit gibt es kein offizielles CLI-Binary. Die gesamte Automatisierung erfolgt direkt über die REST-API mit Tools wie `curl`, Python oder Node.js.

**Basis-URL:** `https://api.clore.ai/v1`

**Antwortformat:** JSON. Jede Antwort enthält ein `code` Feld, das den Status angibt.

***

## Authentifizierung

Erstellen Sie Ihren API-Schlüssel über das [Clore.ai-Dashboard](https://clore.ai):

1. Melden Sie sich bei Ihrem Konto an
2. Navigieren Sie zu **API** Abschnitt in den Einstellungen
3. Erstellen und kopieren Sie Ihren API-Schlüssel

**Header-Format:**

```
auth: YOUR_API_KEY
```

> ⚠️ **Wichtig:** Der auth-Header lautet `auth`, **nicht** `Authorization: Bearer`. Bei Verwendung des falschen Formats wird der Code zurückgegeben `3` (Ungültiges API-Token).

**Beispiel:**

```bash
curl -H 'auth: YOUR_API_KEY' 'https://api.clore.ai/v1/marketplace'
```

***

## Schnellstart

### Marketplace-Server auflisten

Alle verfügbaren GPU-Server durchsuchen:

```bash
curl -XGET \\
  -H 'auth: YOUR_API_KEY' \\
  'https://api.clore.ai/v1/marketplace'
```

**Antwort:**

```json
{
  "servers": [
    {
      "id": 6,
      "owner": 4,
      "mrl": 73,
      "price": {
        "on_demand": { "bitcoin": 0.00001 },
        "spot": { "bitcoin": 0.000001 }
      },
      "rented": false,
      "specs": {
        "cpu": "Intel Core i9-11900",
        "ram": 62.67,
        "gpu": "1x NVIDIA GeForce GTX 1080 Ti",
        "gpuram": 11,
        "net": { "up": 26.38, "down": 118.42, "cc": "CZ" }
      }
    }
  ],
  "my_servers": [1, 2, 4],
  "code": 0
}
```

***

### Ihre Bestellungen abrufen

```bash
curl -XGET \\
  -H 'auth: YOUR_API_KEY' \\
  'https://api.clore.ai/v1/my_orders'
```

Abgeschlossene/abgelaufene Bestellungen einschließen:

```bash
curl -XGET \\
  -H 'auth: YOUR_API_KEY' \\
  'https://api.clore.ai/v1/my_orders?return_completed=true'
```

***

### Eine Bestellung erstellen (On-Demand)

```bash
curl -XPOST \\
  -H 'auth: YOUR_API_KEY' \\
  -H 'Content-type: application/json' \\
  -d '{
    "currency": "bitcoin",
    "image": "cloreai/ubuntu20.04-jupyter",
    "renting_server": 6,
    "type": "on-demand",
    "ports": {
      "22": "tcp",
      "8888": "http"
    },
    "ssh_password": "YourSSHPassword123",
    "jupyter_token": "YourJupyterToken123"
  }' \\
  'https://api.clore.ai/v1/create_order'
```

**Antwort:**

```json
{ "code": 0 }
```

***

### Eine Spot-Bestellung erstellen

Spot-Bestellungen sind günstiger, können aber überboten werden. Sie legen Ihren Preis pro Tag fest:

```bash
curl -XPOST \\
  -H 'auth: YOUR_API_KEY' \\
  -H 'Content-type: application/json' \\
  -d '{
    "currency": "bitcoin",
    "image": "cloreai/ubuntu20.04-jupyter",
    "renting_server": 6,
    "type": "spot",
    "spotprice": 0.000005,
    "ports": {
      "22": "tcp",
      "8888": "http"
    },
    "ssh_password": "YourSSHPassword123"
  }' \\
  'https://api.clore.ai/v1/create_order'
```

***

### Bestellstatus prüfen

```bash
curl -XGET \\
  -H 'auth: YOUR_API_KEY' \\
  'https://api.clore.ai/v1/my_orders'
```

Aktive Bestellungen enthalten `pub_cluster` (Hostnamen) und `tcp_ports` für SSH-Zugriff:

```json
{
  "id": 38,
  "pub_cluster": ["n1.c1.clorecloud.net", "n2.c1.clorecloud.net"],
  "tcp_ports": ["22:10000"],
  "http_port": "8888",
  "expired": false
}
```

Per SSH auf Ihren gemieteten Server zugreifen:

```bash
ssh root@n1.c1.clorecloud.net -p 10000
```

***

### Eine Bestellung stornieren

```bash
curl -XPOST \\
  -H 'auth: YOUR_API_KEY' \\
  -H 'Content-type: application/json' \\
  -d '{
    "id": 38
  }' \\
  'https://api.clore.ai/v1/cancel_order'
```

Optional ein Problem mit dem Server melden:

```bash
curl -XPOST \\
  -H 'auth: YOUR_API_KEY' \\
  -H 'Content-type: application/json' \\
  -d '{
    "id": 38,
    "issue": "Die GPU überhitzte und drosselte die Leistung"
  }' \\
  'https://api.clore.ai/v1/cancel_order'
```

***

## Python-SDK

Ein leichtgewichtiger Wrapper, der die `requests` Bibliothek verwendet. Installieren Sie ihn mit:

```bash
pip install requests
```

### CloreClient-Klasse

```python
import requests
import time

class CloreClient:
    """Ein einfaches Python-SDK für die Clore.ai-REST-API."""
    
    BASE_URL = "https://api.clore.ai/v1"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({"auth": api_key})
    
    def _get(self, endpoint: str, params: dict = None) -> dict:
        url = f"{self.BASE_URL}/{endpoint}"
        response = self.session.get(url, params=params)
        response.raise_for_status()
        data = response.json()
        if data.get("code") != 0:
            raise CloreAPIError(data)
        return data
    
    def _post(self, endpoint: str, body: dict) -> dict:
        url = f"{self.BASE_URL}/{endpoint}"
        self.session.headers.update({"Content-type": "application/json"})
        response = self.session.post(url, json=body)
        response.raise_for_status()
        data = response.json()
        if data.get("code") != 0:
            raise CloreAPIError(data)
        return data
    
    def list_servers(self) -> list:
        """Alle verfügbaren Server auf dem Marktplatz abrufen."""
        data = self._get("marketplace")
        return data["servers"]
    
    def get_server_details(self, server_id: int) -> dict:
        """Spot-Marktplatzinformationen für einen bestimmten Server abrufen."""
        data = self._get("spot_marketplace", params={"market": server_id})
        return data["market"]
    
    def create_order(
        self,
        server_id: int,
        image: str,
        order_type: str = "on-demand",
        currency: str = "bitcoin",
        spotprice: float = None,
        ports: dict = None,
        ssh_password: str = None,
        ssh_key: str = None,
        jupyter_token: str = None,
        env: dict = None,
        command: str = None,
    ) -> dict:
        """
        Eine On-Demand- oder Spot-Bestellung erstellen.
        
        Argumente:
            server_id: ID des zu mietenden Servers
            image: Docker-Image (z. B. 'cloreai/ubuntu20.04-jupyter')
            order_type: 'on-demand' oder 'spot'
            currency: 'bitcoin' (Standard)
            spotprice: Erforderlich für Spot-Bestellungen — Preis pro Tag in BTC
            ports: Portweiterleitung, z. B. {"22": "tcp", "8888": "http"}
            ssh_password: SSH-Passwort (nur alphanumerische Zeichen)
            ssh_key: Öffentlicher SSH-Schlüssel
            jupyter_token: Jupyter-Notebook-Token
            env: Dict der Umgebungsvariablen
            command: Shell-Befehl, der nach dem Containerstart ausgeführt wird
        """
        if order_type == "spot" and spotprice is None:
            raise ValueError("spotprice ist für Spot-Bestellungen erforderlich")
        
        body = {
            "currency": currency,
            "image": image,
            "renting_server": server_id,
            "type": order_type,
        }
        
        if spotprice is not None:
            body["spotprice"] = spotprice
        if ports:
            body["ports"] = ports
        if ssh_password:
            body["ssh_password"] = ssh_password
        if ssh_key:
            body["ssh_key"] = ssh_key
        if jupyter_token:
            body["jupyter_token"] = jupyter_token
        if env:
            body["env"] = env
        if command:
            body["command"] = command
        
        return self._post("create_order", body)
    
    def get_orders(self, include_completed: bool = False) -> list:
        """Ihre aktiven (und optional abgeschlossenen) Bestellungen abrufen."""
        params = {"return_completed": "true"} if include_completed else {}
        data = self._get("my_orders", params=params)
        return data["orders"]
    
    def cancel_order(self, order_id: int, issue: str = None) -> dict:
        """Eine Bestellung stornieren. Optional ein Problem melden."""
        body = {"id": order_id}
        if issue:
            body["issue"] = issue
        return self._post("cancel_order", body)
    
    def get_wallets(self) -> list:
        """Ihre Wallets und Guthaben abrufen."""
        data = self._get("wallets")
        return data["wallets"]
    
    def get_my_servers(self) -> list:
        """Server abrufen, die Sie dem Marktplatz bereitstellen."""
        data = self._get("my_servers")
        return data["servers"]


class CloreAPIError(Exception):
    """Wird ausgelöst, wenn die Clore-API einen von Null verschiedenen Code zurückgibt."""
    
    ERROR_CODES = {
        0: "Normal",
        1: "Datenbankfehler",
        2: "Ungültige Eingabedaten",
        3: "Ungültiges API-Token",
        4: "Ungültiger Endpunkt",
        5: "Rate-Limit überschritten (1 Anfr./Sek.)",
        6: "Fehler (siehe Fehlerfeld)",
    }
    
    def __init__(self, response: dict):
        self.code = response.get("code")
        self.error = response.get("error", "")
        message = self.ERROR_CODES.get(self.code, f"Unbekannter Code {self.code}")
        if self.error:
            message = f"{message}: {self.error}"
        super().__init__(f"Clore-API-Fehler {self.code}: {message}")
```

### Vollständiges funktionierendes Beispiel

```python
import os
from clore_client import CloreClient, CloreAPIError

# Client initialisieren
client = CloreClient(api_key=os.environ["CLORE_API_KEY"])

# 1. Marktplatz durchsuchen
servers = client.list_servers()
print(f"{len(servers)} Server auf dem Marktplatz gefunden")

# 2. Verfügbare RTX-4090-Server filtern
rtx4090_servers = [
    s for s in servers
    if "4090" in s["specs"].get("gpu", "") and not s["rented"]
]

if not rtx4090_servers:
    print("Keine verfügbaren RTX-4090-Server gefunden")
    exit(1)

# 3. Den günstigsten auswählen
cheapest = min(rtx4090_servers, key=lambda s: s["price"]["on_demand"]["bitcoin"] )
print(f"Günstigste RTX 4090: Server-ID {cheapest['id']}, "
      f"Preis {cheapest['price']['on_demand']['bitcoin']:.8f} BTC/Tag")

# 4. Eine Bestellung erstellen
try:
    client.create_order(
        server_id=cheapest["id"],
        image="cloreai/ubuntu20.04-jupyter",
        order_type="on-demand",
        currency="bitcoin",
        ports={"22": "tcp", "8888": "http"},
        ssh_password="SecurePass123",
        jupyter_token="MyToken123",
    )
    print("Bestellung erfolgreich erstellt!")
except CloreAPIError as e:
    print(f"Fehler beim Erstellen der Bestellung: {e}")
    exit(1)

# 5. Ihre Bestellungen prüfen
orders = client.get_orders()
for order in orders:
    if not order.get("expired"):
        cluster = order.get("pub_cluster", [])
        tcp = order.get("tcp_ports", [])
        print(f"Bestellung {order['id']}: Server {order['si']}")
        if cluster and tcp:
            ssh_port = tcp[0].split(":")[1]
            print(f"  SSH: ssh root@{cluster[0]} -p {ssh_port}")
```

***

## Node.js-Beispiel

Mit der nativen `fetch` API (Node.js 18+):

```javascript
const BASE_URL = 'https://api.clore.ai/v1';

class CloreClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.defaultHeaders = {
      'auth': apiKey,
    };
  }

  async get(endpoint, params = {}) {
    const url = new URL(`${BASE_URL}/${endpoint}`);
    Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
    
    const res = await fetch(url.toString(), {
      headers: this.defaultHeaders,
    });
    
    const data = await res.json();
    if (data.code !== 0) {
      throw new Error(`Clore-API-Fehler ${data.code}: ${data.error || 'unbekannt'}`);
    }
    return data;
  }

  async post(endpoint, body) {
    const res = await fetch(`${BASE_URL}/${endpoint}`, {
      method: 'POST',
      headers: {
        ...this.defaultHeaders,
        'Content-type': 'application/json',
      },
      body: JSON.stringify(body),
    });
    
    const data = await res.json();
    if (data.code !== 0) {
      throw new Error(`Clore-API-Fehler ${data.code}: ${data.error || 'unbekannt'}`);
    }
    return data;
  }

  async listServers() {
    const data = await this.get('marketplace');
    return data.servers;
  }

  async getOrders(includeCompleted = false) {
    const params = includeCompleted ? { return_completed: 'true' } : {};
    const data = await this.get('my_orders', params);
    return data.orders;
  }

  async createOrder({ serverId, image, type = 'on-demand', currency = 'bitcoin', spotprice, ports, sshPassword, jupyterToken, env, command }) {
    const body = {
      currency,
      image,
      renting_server: serverId,
      type,
      ...(spotprice && { spotprice }),
      ...(ports && { ports }),
      ...(sshPassword && { ssh_password: sshPassword }),
      ...(jupyterToken && { jupyter_token: jupyterToken }),
      ...(env && { env }),
      ...(command && { command }),
    };
    return this.post('create_order', body);
  }

  async cancelOrder(orderId, issue = null) {
    const body = { id: orderId };
    if (issue) body.issue = issue;
    return this.post('cancel_order', body);
  }
}

// Verwendungsbeispiel
const client = new CloreClient(process.env.CLORE_API_KEY);

async function main() {
  // Marktplatz auflisten
  const servers = await client.listServers();
  console.log(`Der Marktplatz hat ${servers.length} Server`);

  // Verfügbare RTX-4090-Server finden
  const available = servers.filter(
    s => s.specs.gpu.includes('4090') && !s.rented
  );
  console.log(`Verfügbare RTX-4090-Server: ${available.length}`);

  // Aktuelle Aufträge abrufen
  const orders = await client.getOrders();
  const activeOrders = orders.filter(o => !o.expired);
  console.log(`Aktive Aufträge: ${activeOrders.length}`);

  for (const order of activeOrders) {
    const host = order.pub_cluster?.[0];
    const portMapping = order.tcp_ports?.[0];
    if (host && portMapping) {
      const sshPort = portMapping.split(':')[1];
      console.log(`Auftrag ${order.id} → ssh root@${host} -p ${sshPort}`);
    }
  }
}

main().catch(console.error);
```

***

## Häufige Arbeitsabläufe

### Die günstigste RTX 4090 finden und mieten

```python
from clore_client import CloreClient

client = CloreClient(api_key="YOUR_API_KEY")

def rent_cheapest_rtx4090(image="cloreai/ubuntu20.04-jupyter", ssh_password="SecurePass123"):
    servers = client.list_servers()
    
    # Filter: verfügbare RTX 4090
    candidates = [
        s for s in servers
        if "4090" in s["specs"].get("gpu", "")
        and not s["rented"]
    ]
    
    if not candidates:
        raise RuntimeError("Keine verfügbaren RTX-4090-Server gefunden")
    
    # Nach On-Demand-BTC-Preis sortieren
    candidates.sort(key=lambda s: s["price"]["on_demand"]["bitcoin"] )
    best = candidates[0]
    
    price_btc = best["price"]["on_demand"]["bitcoin"]
    print(f"Miete Server {best['id']}: {best['specs']['gpu']} @ {price_btc:.8f} BTC/Tag")
    
    client.create_order(
        server_id=best["id"],
        image=image,
        order_type="on-demand",
        currency="bitcoin",
        ports={"22": "tcp"},
        ssh_password=ssh_password,
    )
    
    print("Fertig! Prüfe deine Aufträge auf SSH-Verbindungsdetails.")
    return best["id"]

rent_cheapest_rtx4090()
```

***

### Meine Aufträge überwachen

```python
import time
from clore_client import CloreClient

client = CloreClient(api_key="YOUR_API_KEY")

def monitor_orders(poll_interval_seconds=60):
    """Aufträge abfragen und Statusaktualisierungen ausgeben."""
    print(f"Überwache Aufträge (Abfrage alle {poll_interval_seconds}s). Mit Strg+C beenden.\n")
    
    while True:
        orders = client.get_orders(include_completed=False)
        active = [o for o in orders if not o.get("expired")]
        
        print(f"--- {len(active)} aktive(r) Auftrag/Aufträge ---")
        for order in active:
            cluster = order.get("pub_cluster", [])
            tcp = order.get("tcp_ports", [])
            spend = order.get("spend", 0)
            
            ssh_info = ""
            if cluster and tcp:
                port = tcp[0].split(":")[1]
                ssh_info = f" | SSH: {cluster[0]}:{port}"
            
            print(f"  Auftrag {order['id']}: Server {order['si']}"
                  f" | ausgegeben {spend:.8f} BTC{ssh_info}")
        
        if not active:
            print("  Keine aktiven Aufträge.")
        
        print()
        time.sleep(poll_interval_seconds)

monitor_orders()
```

***

### Automatisch mieten, wenn der Preis unter X fällt

```python
import time
from clore_client import CloreClient

client = CloreClient(api_key="YOUR_API_KEY")

def auto_rent_on_price_drop(
    gpu_model: str = "RTX 4090",
    max_price_btc: float = 0.00015,
    image: str = "cloreai/ubuntu20.04-jupyter",
    ssh_password: str = "SecurePass123",
    check_interval_seconds: int = 120,
):
    """
    Beobachte den Marktplatz und miete automatisch eine GPU, wenn der Preis unter den Schwellenwert fällt.
    
    Argumente:
        gpu_model: Name der zu suchenden GPU (Groß-/Kleinschreibung wird nicht beachtet)
        max_price_btc: Maximal akzeptabler Preis pro Tag in BTC
        image: Bereitzustellendes Docker-Image
        ssh_password: SSH-Passwort für den Container
        check_interval_seconds: Wie oft geprüft wird (Rate-Limits beachten!)
    """
    print(f"Suche nach {gpu_model} für ≤ {max_price_btc:.8f} BTC/Tag...")
    
    while True:
        servers = client.list_servers()
        
        for server in servers:
            gpu = server["specs"].get("gpu", "")
            if gpu_model.lower() not in gpu.lower():
                continue
            if server["rented"]:
                continue
            
            price = server["price"]["on_demand"]["bitcoin"]
            if price <= max_price_btc:
                print(f"🎯 Treffer gefunden! Server {server['id']}: {gpu} @ {price:.8f} BTC/Tag")
                
                try:
                    client.create_order(
                        server_id=server["id"],
                        image=image,
                        order_type="on-demand",
                        currency="bitcoin",
                        ports={"22": "tcp"},
                        ssh_password=ssh_password,
                        required_price=price,  # Preis hier festschreiben
                    )
                    print(f"✅ Auftrag für Server {server['id']} erstellt!")
                    return server["id"]
                except Exception as e:
                    print(f"Auftrag konnte nicht erstellt werden: {e}. Neuer Versuch...")
        
        print(f"Noch kein Treffer. Erneute Prüfung in {check_interval_seconds}s...")
        time.sleep(check_interval_seconds)

auto_rent_on_price_drop(gpu_model="4090", max_price_btc=0.00012)
```

***

## WebSocket

Die Clore.ai REST-API stellt derzeit keinen WebSocket-Endpunkt bereit. Für die Echtzeitüberwachung verwende Polling mit einem vernünftigen Intervall (siehe Rate-Limits unten).

***

## Rate-Limits

| Endpunkt                            | Limit                       |
| ----------------------------------- | --------------------------- |
| Die meisten Endpunkte               | **1 Anfrage/Sekunde**       |
| `create_order`                      | **1 Anfrage/5 Sekunden**    |
| `set_spot_price` (Preisreduzierung) | Einmal pro **600 Sekunden** |

**Antwort bei Rate-Limit (Code 5):**

```json
{ "code": 5 }
```

**Bewährte Praktiken:**

* Füge `time.sleep(1)` zwischen aufeinanderfolgenden API-Aufrufen hinzu
* Für `create_order`, warte mindestens 5 Sekunden zwischen Anfragen
* Verwende für Überwachungsschleifen Polling-Intervalle von 60+ Sekunden
* Cache die Marktplatzdaten lokal, wenn du sie häufig abfragen musst

**Python-Hilfsfunktion:**

```python
import time

def safe_api_call(fn, *args, delay=1.1, **kwargs):
    """Umhülle einen API-Aufruf mit Rate-Limit-Sicherheit."""
    result = fn(*args, **kwargs)
    time.sleep(delay)
    return result
```

***

## Fehlerbehandlung

Jede API-Antwort enthält ein `code` Feld. Ein Wert von `0` bedeutet Erfolg.

### Fehlercodes

| Code | Bedeutung                                 | Aktion                                      |
| ---- | ----------------------------------------- | ------------------------------------------- |
| `0`  | Erfolg                                    | —                                           |
| `1`  | Datenbankfehler                           | Nach einer Verzögerung erneut versuchen     |
| `2`  | Ungültige Eingabedaten                    | Prüfe deinen Request-Body/deine Parameter   |
| `3`  | Ungültiges API-Token                      | Überprüfe deinen API-Schlüssel im Dashboard |
| `4`  | Ungültiger Endpunkt                       | Prüfe die Endpunkt-URL                      |
| `5`  | Rate-Limit überschritten (1 Anfrage/Sek.) | Füge Verzögerungen zwischen Anfragen hinzu  |
| `6`  | Anwendungsfehler (siehe `Fehler` Feld)    | Lies das `Fehler` Feld für Details          |

### Unterfehler von Code 6

| `Fehler` Wert                 | Bedeutung                                                                        |
| ----------------------------- | -------------------------------------------------------------------------------- |
| `exceeded_max_step`           | Die Spot-Preisreduzierung ist zu groß; prüfe `max_step` Feld                     |
| `can_lower_every_600_seconds` | Du musst warten, bevor du den Spot-Preis erneut senkst; prüfe `time_to_lowering` |

### Python-Beispiel zur Fehlerbehandlung

```python
from clore_client import CloreClient, CloreAPIError
import time

client = CloreClient(api_key="YOUR_API_KEY")

def create_order_with_retry(server_id, image, max_retries=3):
    for attempt in range(max_retries):
        try:
            return client.create_order(
                server_id=server_id,
                image=image,
                order_type="on-demand",
                currency="bitcoin",
                ports={"22": "tcp"},
                ssh_password="SecurePass123",
            )
        except CloreAPIError as e:
            if e.code == 5:  # Rate-Limit
                print(f"Rate-Limit erreicht. Warte 5s... (Versuch {attempt+1}/{max_retries})")
                time.sleep(5)
            elif e.code == 3:  # Ungültiger API-Schlüssel
                print("Ungültiger API-Schlüssel! Prüfe deinen CLORE_API_KEY.")
                raise
            elif e.code == 2:  # Ungültige Eingabe
                print(f"Ungültige Anfrage: {e}")
                raise
            else:
                print(f"API-Fehler: {e}. Neuer Versuch in 3s...")
                time.sleep(3)
    
    raise RuntimeError(f"Fehlgeschlagen nach {max_retries} Versuchen")
```

### JavaScript-Beispiel zur Fehlerbehandlung

```javascript
async function createOrderWithRetry(client, serverConfig, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.createOrder(serverConfig);
    } catch (err) {
      const code = parseInt(err.message.match(/error (\d+)/)?.[1]);
      
      if (code === 5) {
        console.log(`Rate-Limit erreicht. Warte 5s... (Versuch ${attempt + 1}/${maxRetries})`);
        await new Promise(r => setTimeout(r, 5000));
      } else if (code === 3) {
        throw new Error('Ungültiger API-Schlüssel');
      } else {
        console.log(`API-Fehler: ${err.message}. Neuer Versuch in 3s...`);
        await new Promise(r => setTimeout(r, 3000));
      }
    }
  }
  throw new Error(`Fehlgeschlagen nach ${maxRetries} Versuchen`);
}
```

***

## Verfügbare Docker-Images

Clore.ai bietet vorgefertigte Images, optimiert für GPU-Workloads:

| Image                         | Beschreibung              |
| ----------------------------- | ------------------------- |
| `cloreai/ubuntu20.04-jupyter` | Ubuntu 20.04 + JupyterLab |
| `cloreai/ubuntu22.04-jupyter` | Ubuntu 22.04 + JupyterLab |

Du kannst auch jedes öffentliche Docker-Hub-Image verwenden. Für GPU-Zugriff verwende CUDA-fähige Images, z. B.:

```
nvidia/cuda:12.1.0-devel-ubuntu22.04
```

***

## Portweiterleitung

Beim Erstellen eines Auftrags gib die freizugebenden Ports an:

```json
{
  "ports": {
    "22": "tcp",
    "8888": "http",
    "6006": "http"
  }
}
```

* **`"tcp"`** — Direkte TCP-Portweiterleitung (für SSH, benutzerdefinierte Server)
* **`"http"`** — HTTPS-Proxy (für Jupyter, Web-UIs). Pro `http_port` Feld ist nur ein HTTP-Port erlaubt.

Nach der Auftragserstellung erscheinen die Verbindungsdetails in `my_orders`:

```json
{
  "pub_cluster": ["n1.c1.clorecloud.net", "n2.c1.clorecloud.net"],
  "tcp_ports": ["22:10000"],
  "http_port": "8888"
}
```

Verbinde dich via SSH:

```bash
ssh root@n1.c1.clorecloud.net -p 10000
```

Greife per Browser auf Jupyter zu: `https://n1.c1.clorecloud.net` verwendet das `http_port`)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://docs.clore.ai/clore.ai/clore.ai-eng-de/entwickler/cli-sdk.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
