Clore.ai provides a REST API that enables full programmatic access to the GPU marketplace — listing servers, creating orders, monitoring deployments, and canceling rentals.
Note: There is no official CLI binary at this time. All automation is done directly via the REST API using tools like curl, Python, or Node.js.
Base URL:https://api.clore.ai/v1
Response format: JSON. Every response includes a code field indicating the status.
Spot orders are cheaper but can be outbid. You set your price per day:
Check Order Status
Active orders include pub_cluster (hostnames) and tcp_ports for SSH access:
SSH into your rented server:
Cancel an Order
Optionally report an issue with the server:
Python SDK
A lightweight wrapper using the requests library. Install it with:
CloreClient Class
Full Working Example
Node.js Example
Using the native fetch API (Node.js 18+):
Common Workflows
Find Cheapest RTX 4090 and Rent It
Monitor My Orders
Auto-Rent When Price Drops Below X
WebSocket
The Clore.ai REST API does not currently expose a WebSocket endpoint. For real-time monitoring, use polling with a reasonable interval (see rate limits below).
Rate Limits
Endpoint
Limit
Most endpoints
1 request/second
create_order
1 request/5 seconds
set_spot_price (price reduction)
Once per 600 seconds
Rate limit response (code 5):
Best practices:
Add time.sleep(1) between consecutive API calls
For create_order, wait at least 5 seconds between requests
Use polling intervals of 60+ seconds for monitoring loops
Cache marketplace data locally if you need to query it frequently
Python helper:
Error Handling
Every API response includes a code field. A value of 0 means success.
Error Codes
Code
Meaning
Action
0
Success
—
1
Database error
Retry after a delay
2
Invalid input data
Check your request body/parameters
3
Invalid API token
Verify your API key in the dashboard
4
Invalid endpoint
Check the endpoint URL
5
Rate limit exceeded (1 req/sec)
Add delays between requests
6
Application error (see error field)
Read the error field for details
Code 6 Sub-errors
error value
Meaning
exceeded_max_step
Spot price reduction too large; check max_step field
can_lower_every_600_seconds
Must wait before lowering spot price again; check time_to_lowering
Python Error Handling Example
JavaScript Error Handling Example
Available Docker Images
Clore.ai provides pre-built images optimized for GPU workloads:
Image
Description
cloreai/ubuntu20.04-jupyter
Ubuntu 20.04 + JupyterLab
cloreai/ubuntu22.04-jupyter
Ubuntu 22.04 + JupyterLab
You can also use any public Docker Hub image. For GPU access, use CUDA-enabled images, e.g.:
Port Forwarding
When creating an order, specify ports to expose:
"tcp" — Direct TCP port forwarding (for SSH, custom servers)
"http" — HTTPS proxy (for Jupyter, web UIs). Only one HTTP port allowed per http_port field.
After order creation, connection details appear in my_orders:
Connect via SSH:
Access Jupyter via browser: https://n1.c1.clorecloud.net (uses the http_port)
import requests
import time
class CloreClient:
"""Simple Python SDK for 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:
"""Get all available servers on the marketplace."""
data = self._get("marketplace")
return data["servers"]
def get_server_details(self, server_id: int) -> dict:
"""Get spot marketplace info for a specific server."""
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:
"""
Create an on-demand or spot order.
Args:
server_id: ID of server to rent
image: Docker image (e.g. 'cloreai/ubuntu20.04-jupyter')
order_type: 'on-demand' or 'spot'
currency: 'bitcoin' (default)
spotprice: Required for spot orders — price per day in BTC
ports: Port forwarding, e.g. {"22": "tcp", "8888": "http"}
ssh_password: SSH password (alphanumeric chars only)
ssh_key: SSH public key
jupyter_token: Jupyter notebook token
env: Environment variables dict
command: Shell command to run after container start
"""
if order_type == "spot" and spotprice is None:
raise ValueError("spotprice is required for spot orders")
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:
"""Get your active (and optionally completed) orders."""
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:
"""Cancel an order. Optionally report an issue."""
body = {"id": order_id}
if issue:
body["issue"] = issue
return self._post("cancel_order", body)
def get_wallets(self) -> list:
"""Get your wallets and balances."""
data = self._get("wallets")
return data["wallets"]
def get_my_servers(self) -> list:
"""Get servers you are providing to the marketplace."""
data = self._get("my_servers")
return data["servers"]
class CloreAPIError(Exception):
"""Raised when the Clore API returns a non-zero code."""
ERROR_CODES = {
0: "Normal",
1: "Database error",
2: "Invalid input data",
3: "Invalid API token",
4: "Invalid endpoint",
5: "Rate limit exceeded (1 req/sec)",
6: "Error (see error field)",
}
def __init__(self, response: dict):
self.code = response.get("code")
self.error = response.get("error", "")
message = self.ERROR_CODES.get(self.code, f"Unknown code {self.code}")
if self.error:
message = f"{message}: {self.error}"
super().__init__(f"Clore API error {self.code}: {message}")
import os
from clore_client import CloreClient, CloreAPIError
# Initialize client
client = CloreClient(api_key=os.environ["CLORE_API_KEY"])
# 1. Browse marketplace
servers = client.list_servers()
print(f"Found {len(servers)} servers on marketplace")
# 2. Filter available RTX 4090 servers
rtx4090_servers = [
s for s in servers
if "4090" in s["specs"].get("gpu", "") and not s["rented"]
]
if not rtx4090_servers:
print("No available RTX 4090 servers found")
exit(1)
# 3. Pick the cheapest one
cheapest = min(rtx4090_servers, key=lambda s: s["price"]["on_demand"]["bitcoin"])
print(f"Cheapest RTX 4090: server ID {cheapest['id']}, "
f"price {cheapest['price']['on_demand']['bitcoin']:.8f} BTC/day")
# 4. Create an order
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("Order created successfully!")
except CloreAPIError as e:
print(f"Failed to create order: {e}")
exit(1)
# 5. Check your orders
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"Order {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}")
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: available RTX 4090
candidates = [
s for s in servers
if "4090" in s["specs"].get("gpu", "")
and not s["rented"]
]
if not candidates:
raise RuntimeError("No available RTX 4090 servers found")
# Sort by on-demand BTC price
candidates.sort(key=lambda s: s["price"]["on_demand"]["bitcoin"])
best = candidates[0]
price_btc = best["price"]["on_demand"]["bitcoin"]
print(f"Renting server {best['id']}: {best['specs']['gpu']} @ {price_btc:.8f} BTC/day")
client.create_order(
server_id=best["id"],
image=image,
order_type="on-demand",
currency="bitcoin",
ports={"22": "tcp"},
ssh_password=ssh_password,
)
print("Done! Check your orders for SSH connection details.")
return best["id"]
rent_cheapest_rtx4090()
import time
from clore_client import CloreClient
client = CloreClient(api_key="YOUR_API_KEY")
def monitor_orders(poll_interval_seconds=60):
"""Poll orders and print status updates."""
print(f"Monitoring orders (polling every {poll_interval_seconds}s). Ctrl+C to stop.\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)} active order(s) ---")
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" Order {order['id']}: server {order['si']}"
f" | spent {spend:.8f} BTC{ssh_info}")
if not active:
print(" No active orders.")
print()
time.sleep(poll_interval_seconds)
monitor_orders()
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,
):
"""
Watch the marketplace and auto-rent a GPU when price drops below threshold.
Args:
gpu_model: GPU name to search for (case-insensitive)
max_price_btc: Maximum acceptable price per day in BTC
image: Docker image to deploy
ssh_password: SSH password for the container
check_interval_seconds: How often to check (respect rate limits!)
"""
print(f"Watching for {gpu_model} at ≤ {max_price_btc:.8f} BTC/day...")
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"🎯 Found match! Server {server['id']}: {gpu} @ {price:.8f} BTC/day")
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, # Lock in this price
)
print(f"✅ Order created for server {server['id']}!")
return server["id"]
except Exception as e:
print(f"Failed to create order: {e}. Will retry...")
print(f"No match yet. Checking again in {check_interval_seconds}s...")
time.sleep(check_interval_seconds)
auto_rent_on_price_drop(gpu_model="4090", max_price_btc=0.00012)
{ "code": 5 }
import time
def safe_api_call(fn, *args, delay=1.1, **kwargs):
"""Wrap an API call with rate limit safety."""
result = fn(*args, **kwargs)
time.sleep(delay)
return result
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 limited. Waiting 5s... (attempt {attempt+1}/{max_retries})")
time.sleep(5)
elif e.code == 3: # Bad API key
print("Invalid API key! Check your CLORE_API_KEY.")
raise
elif e.code == 2: # Bad input
print(f"Invalid request: {e}")
raise
else:
print(f"API error: {e}. Retrying in 3s...")
time.sleep(3)
raise RuntimeError(f"Failed after {max_retries} attempts")
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 limited. Waiting 5s... (attempt ${attempt + 1}/${maxRetries})`);
await new Promise(r => setTimeout(r, 5000));
} else if (code === 3) {
throw new Error('Invalid API key');
} else {
console.log(`API error: ${err.message}. Retrying in 3s...`);
await new Promise(r => setTimeout(r, 3000));
}
}
}
throw new Error(`Failed after ${maxRetries} attempts`);
}