---
title: "Cloud Deploy"
description: "Deploy, monitor, and manage strategies on the Horizon Cloud platform directly from the SDK. Full lifecycle from code to live trading."
icon: "cloud-arrow-up"
---

Deploy strategies to the Horizon Cloud and monitor them in real time, all from Python or the MCP server. The full lifecycle (create, validate, deploy, monitor, stop) works entirely from the SDK with no browser required.

## Setup

Generate an SDK key from [horizon.mathematicalcompany.com](https://horizon.mathematicalcompany.com) under **Settings > SDK Keys**.

```bash
export HORIZON_API_KEY="hz_sdk_abc123..."
```

## Quick Start - Full Lifecycle

```python
from horizon import HorizonCloud

cloud = HorizonCloud()  # reads HORIZON_API_KEY from env

# 1. Create a strategy from code
strategy = cloud.create_strategy(
    name="Simple MM",
    code="""
import horizon as hz

def pipeline(ctx):
    mid = ctx.feeds.get("mid")
    if not mid:
        return None
    return hz.quotes(fair=mid.price, spread=0.04, size=10)

hz.run(
    pipeline=[pipeline],
    markets=["will-x-happen"],
    feeds=[hz.PolymarketBook("will-x-happen")],
)
""",
)
print(f"Created: {strategy['id']} ({strategy['status']})")

# 2. Use existing credentials or save new ones
creds = cloud.list_credentials()
if creds:
    cred_id = creds[0]["id"]  # use the first saved credential
    print(f"Using existing credential: {creds[0]['label']}")
else:
    cred = cloud.save_credentials(
        private_key="0xabc123...",
        label="Trading Key",
        exchange="polymarket",
    )
    cred_id = cred["id"]

# 3. Validate (static analysis + worker sandbox)
result = cloud.validate(strategy["id"])
assert result["valid"], result["errors"]

# 4. Deploy in paper mode
dep = cloud.deploy(
    strategy["id"],
    credential_id=cred_id,
    mode="paper",
)
print(f"Deployment: {dep['id']} - {dep['status']}")

# 5. Wait until running
dep = cloud.wait_for_running(strategy["id"], timeout=120)

# 6. Monitor
metrics = cloud.metrics(strategy["id"])
print(f"PnL: {metrics['latest']['total_pnl']}")

for log in cloud.logs(strategy["id"], limit=20):
    print(f"[{log['level']}] {log['message']}")

# 7. Stop
cloud.stop(strategy["id"])
```

## API Reference

### Constructor

```python
HorizonCloud(
    api_key: str = None,       # falls back to HORIZON_API_KEY env var
    base_url: str = None,      # falls back to HORIZON_PLATFORM_URL or default
    timeout: int = 30,         # request timeout in seconds
)
```

### Methods

| Method | Description |
|--------|-------------|
| `create_strategy(name, code, ...)` | Create a strategy from Python code |
| `validate(strategy_id)` | Validate code (static + sandbox) |
| `list_strategies()` | List all strategies |
| `get_strategy(id)` | Get strategy details |
| `save_credentials(private_key, ...)` | Save exchange credentials (encrypted) |
| `list_credentials()` | List credential metadata (no keys) |
| `deploy(id, credential_id, mode, markets)` | Deploy a strategy |
| `stop(id)` | Stop active deployments |
| `status(id)` | Current deployment status |
| `deployments(id)` | List deployment history |
| `metrics(id, limit=50)` | Performance metrics |
| `logs(id, limit=100, level, deployment_id)` | Deployment logs |
| `account()` | Plan, limits, usage |
| `wait_for_running(id, timeout=120)` | Block until running |

### Create Strategy

```python
cloud.create_strategy(
    name="My Strategy",           # required, max 200 chars
    code="def pipeline(ctx):...", # required, max 500KB
    description="...",            # optional, max 2000 chars
    params={"threshold": 0.6},    # optional strategy parameters
    risk_config={...},            # optional risk configuration
    auto_fix=True,                # auto-fix common AI mistakes (default)
)
```

**Code validation** runs automatically on creation:
- Forbidden imports blocked: `os`, `subprocess`, `socket`, `requests`, `pickle`, `ctypes`, etc.
- Forbidden builtins blocked: `eval()`, `exec()`, `compile()`, `open()`, `__import__()`, etc.
- Required SDK patterns: at least one `def ...(ctx)` pipeline function and `hz.quotes()`/`hz.run()` usage.
- Code is sanitized: BOM stripped, line endings normalized, common whitespace dedented.

If validation fails, returns `422` with detailed errors:

```python
try:
    cloud.create_strategy(name="Bad", code="import subprocess\nsubprocess.run('ls')")
except HorizonCloudError as e:
    print(e.body["validation_errors"])
    # [{"line": 1, "message": "Import \"subprocess\" is not allowed..."}]
```

### Validate

Two-phase validation:

1. **Static analysis** (platform-side, instant): forbidden patterns, import whitelist, SDK usage checks.
2. **Sandbox validation** (worker-side): AST parsing, import resolution, forbidden attribute access.

```python
result = cloud.validate("strategy-id")
if result["valid"]:
    print("Ready to deploy!")
else:
    for err in result["errors"]:
        print(f"  Line {err['line']}: {err['message']}")
```

### Save Credentials

```python
cloud.save_credentials(
    private_key="0xabc...",      # 64 hex chars, optional 0x prefix
    label="My Trading Key",      # max 100 chars
    exchange="polymarket",        # "polymarket" or "kalshi"
    wallet_address="0x123...",    # optional, 0x + 40 hex chars
)
```

**Security guarantees:**
- Private key is transmitted over **HTTPS only**.
- Encrypted at rest with **AES-256-GCM** (platform-side encryption key, not stored in DB).
- **Never returned** in any API response - not in `list_credentials`, not in `save_credentials` response.
- Decrypted **only in-memory** at deploy time, then sent to the worker over HMAC-signed HTTPS.
- Max 10 credentials per user.
- All credential operations are **critically audited** (audit log insert failure throws, preventing silent loss).

### Deploy

```python
cloud.deploy(
    strategy_id="abc-123",
    credential_id="cred-456",  # from save_credentials()
    mode="paper",              # "paper" (default) or "live"
    markets=["slug-a"],        # optional market slugs
)
```

- `mode="paper"` - dry run, no real orders.
- `mode="live"` - requires **Pro/Ultra** plan + circuit breaker enabled.
- `markets` - patches `hz.run(markets=[...])` in the strategy code.

### Logs

```python
cloud.logs(
    strategy_id="abc-123",
    limit=100,                # max entries (up to 500)
    level="error",            # optional: "info", "error", "warning"
    deployment_id="dep-789",  # optional (defaults to latest)
)
```

Returns log entries in chronological order (oldest first).

## Security Architecture

Every request goes through multiple security layers - **all enforced server-side**, never in the SDK client:

### Authentication & Authorization

| Layer | How it works |
|-------|-------------|
| **API key validation** | SHA-256 hash lookup in `sdk_keys` table. |
| **Expiration** | Keys can have an `expires_at` date. Expired keys are rejected. |
| **Rate limiting** | Per-user, per-action limits via Upstash Redis (in-memory fallback for dev). |
| **Audit logging** | Every create/deploy/stop/credential action is logged. Credential ops are **critical** (failure throws). |

### Code Security

| Layer | What it blocks |
|-------|---------------|
| **Import whitelist** | Only `horizon`, `hz`, `datetime`, `collections`, `math`, `typing`, `enum`, `statistics`, `pydantic`, `abc` |
| **Forbidden builtins** | `eval`, `exec`, `compile`, `open`, `__import__`, `globals`, `locals`, `getattr`, `setattr`, `breakpoint` |
| **Forbidden modules** | `os`, `sys`, `subprocess`, `socket`, `http`, `urllib`, `requests`, `pickle`, `ctypes`, `threading`, `multiprocessing`, `importlib`, `shutil`, `tempfile` |
| **Forbidden attributes** | `__builtins__`, `__subclasses__`, `__bases__`, `__globals__`, `__code__`, `__closure__`, `__mro__`, `__reduce__` |
| **Line continuation block** | Backslash `\` continuations blocked to prevent pattern bypass |
| **Code sanitization** | BOM stripping, line ending normalization, leading whitespace dedent |
| **Worker sandbox** | AST-level validation, import resolution check, separate process isolation |

### Credential Security

```
SDK (HTTPS) → Platform (AES-256-GCM encrypt) → Database (ciphertext + IV + auth tag)
                                                       ↓ (at deploy time only)
                                               Platform (decrypt in-memory)
                                                       ↓
                                               Worker (HMAC-signed HTTPS) → Strategy subprocess
```

- Encryption key is a 256-bit hex string stored in platform env (`ENCRYPTION_KEY`), never in the database.
- Worker communication uses Bearer token + HMAC-SHA256 signature + HTTPS-only enforcement.
- Worker URL must be `https://` in production (localhost exempted for dev).

### Deployment Security

| Check | When |
|-------|------|
| Strategy must be `validated`/`stopped`/`error`/`deployed` status | Before deploy |
| Circuit breaker required for live trading | Before deploy |
| Double-deploy guard (409 if already active) | Before deploy |
| Worker capacity check (queue if full) | Before deploy |
| Plan limits (concurrent deploys, live trading) | Before deploy |
| Risk overrides injected by `platform_runner.py` | At runtime (worker) |
| Strategy runs in isolated subprocess | At runtime (worker) |

## Plan Limits

All limits are enforced **server-side** before any action proceeds.

| Limit | Free | Pro | Ultra |
|-------|------|-----|-------|
| Max strategies | 1 | 10 | Unlimited |
| Concurrent deploys | 1 | 5 | 10 |
| Live trading | No | Yes | Yes |
| Backtests / week | 1 | 10 | Unlimited |
| Priority execution | No | No | Yes |

```python
try:
    cloud.deploy("abc-123", credential_id="cred-456", mode="live")
except HorizonCloudError as e:
    if e.status_code == 403:
        print(f"Plan limit: {e.body.get('plan')}")
```

Check your current usage:

```python
info = cloud.account()
print(f"Plan: {info['plan']}")
print(f"Limits: {info['limits']}")
print(f"Usage: {info['usage']}")
```

## Deployment Lifecycle

```
pending → starting → running → stopped
   ↓                    ↓
 queued               error
```

| Status | Meaning |
|--------|---------|
| `pending` | Created, worker being contacted |
| `queued` | Worker at capacity, waiting for a slot |
| `starting` | Worker acknowledged, process launching |
| `running` | Strategy is actively trading |
| `stopped` | Gracefully stopped (via `cloud.stop()` or platform UI) |
| `error` | Crashed or timed out |

## Rate Limits

| Action | Limit |
|--------|-------|
| Read endpoints (strategies, metrics, logs, credentials) | 30 / minute |
| Create strategy | 10 / minute |
| Validate | 10 / minute |
| Deploy | 5 / minute |
| Stop | 10 / minute |
| Save credentials | 5 / minute |

Exceeding the limit returns `429 Too Many Requests`.

## MCP Tools

When running the [MCP server](/integrations/mcp-server), 13 cloud tools are available:

| Tool | Description |
|------|-------------|
| `cloud_create_strategy` | Create strategy from code |
| `cloud_validate` | Validate strategy code |
| `cloud_save_credentials` | Save exchange credentials (encrypted) |
| `cloud_list_credentials` | List credential metadata |
| `cloud_list_strategies` | List strategies |
| `cloud_get_strategy` | Get strategy details |
| `cloud_deploy` | Deploy a strategy |
| `cloud_stop` | Stop a deployment |
| `cloud_status` | Get deployment status |
| `cloud_metrics` | Get performance metrics |
| `cloud_logs` | Get deployment logs |
| `cloud_account` | Get account info |

Example MCP usage (Claude Desktop / Claude Code):

> "Create a market making strategy, save my Polymarket key, and deploy it in paper mode"

The MCP tools accept an optional `api_key` parameter; if omitted, they use `HORIZON_API_KEY` from the environment.

## Error Handling

All API errors raise `HorizonCloudError` with `status_code` and `body`:

```python
from horizon import HorizonCloud, HorizonCloudError

cloud = HorizonCloud()

try:
    cloud.create_strategy(name="Bad", code="import os; os.system('rm -rf /')")
except HorizonCloudError as e:
    print(f"Status: {e.status_code}")  # 422
    print(f"Errors: {e.body['validation_errors']}")
```

| Status | Meaning |
|--------|---------|
| 400 | Bad request (invalid params, missing code, key format error) |
| 401 | Invalid or missing API key |
| 403 | Plan limit exceeded |
| 404 | Strategy or credential not found |
| 409 | Strategy already has an active deployment |
| 422 | Code validation failed (includes `validation_errors` list) |
| 429 | Rate limit exceeded |
| 500 | Worker or platform error |

## Architecture

```
SDK (HorizonCloud)
  │
  ▼ HTTPS (Bearer hz_sdk_...)
Platform API (v1)
  │ validates key → enforces plan limits → rate limits
  │ encrypts credentials (AES-256-GCM) → validates code (static + sandbox)
  ▼
Worker (FastAPI, HMAC-signed HTTPS)
  │ AST validation → subprocess isolation → risk overrides
  ▼
Trading Engine (Rust/PyO3)
  │ 8-point risk pipeline → paper/live exchange
  ▼
Polymarket / Kalshi / Alpaca
```

The SDK client (`HorizonCloud`) calls the Platform's v1 REST API over HTTPS using your SDK key. The Platform validates the key (SHA-256 hash lookup), enforces plan limits and rate limits, then forwards deploy requests to the Worker over HMAC-signed HTTPS. The Worker validates the code in a sandbox, spawns an isolated subprocess running your strategy with risk overrides injected, and reports metrics back via webhooks.
