---
title: Deployments
description: One resource to run any supported framework on any supported venue.
---

A deployment is **recurring execution**: a strategy running against a venue until you stop it. To run one immediate managed-wallet place or cancel operation, use [one-time venue executions](/runtime/executions). One create endpoint covers every framework, venue, and mode — the combination is data, not URL structure.

> Base URL `https://api.superior.trade` · every request sends your key as `x-api-key` ([authentication](/account/registration)).

### POST /runtime/deployments

**Request**

```bash
curl -X POST https://api.superior.trade/runtime/deployments \
  -H "x-api-key: st_live_…" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: 7f3d2c1a-…" \
  -d @deployment.json
```

```json
{
  "framework": "freqtrade",
  "venue": "hyperliquid",
  "region": "tokyo",
  "mode": "paper",
  "name": "btc-range-v1",
  "code": "class BtcRange(IStrategy):\n    timeframe = '4h'\n    …",
  "config": {
    "timeframe": "4h",
    "stake_amount": 100,
    "max_open_trades": 2,
    "exchange": { "name": "hyperliquid", "pair_whitelist": ["BTC/USDC:USDC"] }
  },
  "credentials": { "type": "managed" },
  "alive_until": "2026-08-04T00:00:00Z"
}
```

| Field          | Required | Notes                                                                                                                                               |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `framework`    | yes      | From [`/runtime/frameworks`](/runtime/frameworks)                                                                                                   |
| `image_digest` | no       | Pin an older listed digest; defaults to the framework's current one ([pinning](/runtime/frameworks))                                                |
| `venue`        | yes      | Combination validated against the [venue matrix](/context/venues)                                                                                   |
| `region`       | no       | Defaults to the venue's best region                                                                                                                 |
| `mode`         | yes      | `paper` or `live` — see [paper trading](/runtime/paper-trading)                                                                                     |
| `name`         | yes      | Yours; unique per account                                                                                                                           |
| `code`         | yes      | The strategy source, framework-native                                                                                                               |
| `config`       | yes      | Framework-native config; validated per framework                                                                                                    |
| `credentials`  | no       | Use `{"type": "managed"}` today. BYOK is coming soon. **Omitted ≡ `{"type": "managed"}`**; ignored entirely in [paper mode](/runtime/paper-trading) |
| `alive_until`  | no       | Auto-expiry timestamp — see [expiry semantics](#what-happens-when-aliveuntil-lapses)                                                                |
| `on_expiry`    | no       | `stop` (default) or `block_entries` — what expiry does to a bot holding positions                                                                   |

### Managed live Freqtrade capital

For a `freqtrade` deployment with `mode: "live"` and `credentials.type: "managed"` (including omitted credentials), `config.available_capital` is required. It must be a finite JSON number, at least `$50.00`, with no more than six decimal places. `stake_amount`, wallet balances, and defaults do not authorize capital.

Creation and later start reuse one durable, deployment-scoped venue setup operation. The server derives its idempotency key; callers keep using the deployment `Idempotency-Key`. A deployment response may include the safe `venue_setup` summary: `id`, `venue`, `amount_usd`, `status`, and `step`. When setup is active, start returns `409 venue_setup_in_progress` with `details.poll_url: "/wallet"`; a known failed setup returns `409 venue_not_ready`; insufficient held funds return `409 insufficient_held_balance`. Poll `GET /wallet` for setup progress.

Paper deployments and BYOK do not allocate managed capital. BYOK remains unavailable at its existing public validation boundary. Managed live Nautilus does not automatically allocate capital and returns `501 feature_not_available` because it has no capital-authorization contract. The managed Freqtrade path is available only when the venue-wallet feature rollout and its production runtime dependencies are enabled; otherwise it fails closed without moving funds.

**Retries are safe.** Send an `Idempotency-Key` header (any unique string, kept 24h): a retried create returns the first attempt's response instead of a second deployment. Without the header, `name` still protects you — it's unique per account, so an accidental duplicate returns `409 name_taken` rather than silently creating a twin.

**`code` and `config` are immutable after creation.** Changing a strategy means a new deployment — that's what makes "the strategy that backtested is the strategy that runs" checkable. The update path for a live strategy: create the new deployment (new name) → attach credentials → stop the old one → let positions close per your exit logic before the stop, or close them on the venue → start the new one → delete the old. `code` is a single strategy file; see [what's in the image](/runtime/frameworks) for available imports.

**Response — `201`**

```json
{
  "id": "dep_01j8xv…",
  "status": "created",
  "mode": "paper",
  "framework": { "id": "freqtrade", "image_digest": "sha256:beb99f2e…" },
  "venue": "hyperliquid",
  "created_at": "2026-07-28T11:20:00Z"
}
```

Creation validates and provisions but does not start trading — starting is explicit.

**Validation errors — `400`** carry the framework's own diagnostics:

```json
{
  "error": {
    "code": "strategy_invalid",
    "message": "Strategy failed to compile",
    "details": [{ "line": 14, "text": "NameError: name 'ta' is not defined" }]
  }
}
```

### GET /runtime/deployments

List, with filters: `?venue=`, `?framework=`, `?mode=`, `?status=`, plus `limit`/`cursor`.

```json
{
  "deployments": [
    {
      "id": "dep_01j8xv…",
      "name": "btc-range-v1",
      "status": "running",
      "…": "…"
    }
  ],
  "next_cursor": null
}
```

Same envelope as every list endpoint (`next_cursor: null` = last page). Deleted deployments stay listable with `status: "deleted"` for 30 days, then are removed.

### GET /runtime/deployments/:id

The full resource **with embedded status** — no separate status endpoint.

**Response — `200`**

```json
{
  "id": "dep_01j8xv…",
  "name": "btc-range-v1",
  "mode": "live",
  "status": "running",
  "framework": { "id": "freqtrade", "image_digest": "sha256:beb99f2e…" },
  "venue": "hyperliquid",
  "region": "tokyo",
  "credentials": { "attached": true, "type": "managed", "status": "ready" },
  "venue_account": {
    "funded": true,
    "balance_usd": 141.2,
    "min_deposit_usd": 5
  },
  "health": { "last_heartbeat": "2026-07-28T11:41:02Z", "restarts": 0 },
  "alive_until": "2026-08-04T00:00:00Z",
  "created_at": "2026-07-28T11:20:00Z",
  "started_at": "2026-07-28T11:25:12Z"
}
```

`status` is one of `created → starting → running → stopping → stopped`, plus `deleting`, `deleted`, and `error` (with an `error` object explaining why).

For an account-owned Freqtrade deployment created earlier through `apps/api`, known-ID status and log reads can observe it when the active `apps/api` database row stores an exact Kubernetes Deployment name and an allowlisted namespace, and the named workload's legacy identity labels verify. Observation is read-only and is not persisted. `GET /runtime/deployments/:id/logs` uses the verified workload coordinates and returns its real Cloud Logging entries. This compatibility does not refresh list results or enable legacy metrics, metadata updates, lifecycle actions, credentials, or deletion. Future mutation support must use the active `apps/api` database schema and fenced controller; `product-db` is never the runtime authority.

`venue_account` is the pre-start readiness probe: an agent checks `funded` here instead of starting-and-hoping. For paper deployments it reads `{ "funded": null }` — there is nothing to fund. It's a cached venue read (~15s), refreshed on every lifecycle action.

## What happens when `alive_until` lapses

Expiry is a stop, and a stop **never market-closes positions** — so a plain expiry on a bot holding a position leaves that position unmanaged (no stoploss logic, no exits) until you act. Two ways to handle it:

- `on_expiry: "stop"` (default) — the deployment stops at the timestamp, with the same semantics as `PUT /runtime/deployments/:id/status` and `{ "action": "stop" }`: pod down, credential retained, `alive_until` cleared, restartable after a metadata retime and explicit start. Use when your strategy is flat by design or you accept manual cleanup.
- `on_expiry: "block_entries"` — at the timestamp the strategy stops **opening** positions but keeps running its exit logic; the deployment stops on its own once flat. The safer default for anything that holds overnight; equivalent to freqtrade's own `stopentry`.

Either way the deployment's `status` and `alive_until: null` tell an agent it expired, and the recommendation stands: set `alive_until` beyond your strategy's max holding period, not at it.

### PATCH /runtime/deployments/:id

Metadata updates only. `code`, `config`, `framework`, `venue`, `mode`, and credentials remain immutable through this endpoint.

```json
{
  "name": "btc-range-v2",
  "alive_until": "2026-08-10T00:00:00Z",
  "on_expiry": "block_entries"
}
```

At least one metadata field is required. Lifecycle transitions use the status endpoint so validation is unambiguous.

### PUT /runtime/deployments/:id/status

```json
{
  "action": "start",
  "alive_until": "2026-08-10T00:00:00Z",
  "on_expiry": "block_entries"
}
```

```json
{ "action": "stop", "mode": "graceful" }
```

`action` is required and is either `start` or `stop`. Start accepts optional `alive_until` and `on_expiry`; when present they apply atomically before the start transition. Start on a `live` deployment requires attached [credentials](/runtime/credentials) and a funded venue account; the error taxonomy distinguishes the two (`credentials_missing` vs `account_not_funded`) so an agent knows which problem to fix. Stop accepts optional `mode`, defaulting to `graceful`: the runtime asks the framework to stop and leaves open positions untouched.

### DELETE /runtime/deployments/:id

Stops the strategy, tears down the runtime and storage, **destroys the injected credential**, and removes the record. Deletion is confirmed, not assumed: the call returns `202` and the deployment enters `deleting`; `GET` reports `deleted` only after the runtime has confirmed the process is gone and the credential material is destroyed. (Where several of your deployments share one wallet credential — the Nautilus shared-runtime case — the credential itself is destroyed when the last deployment using it is deleted; deletion of one deployment always removes it from that deployment immediately.)

Open positions are _not_ closed automatically — stop first, close positions through your strategy, the venue UI, or a typed cancel/place action where appropriate, then delete. If positions are still open, deletion returns `409 positions_open`.

Force-deleting around open positions requires a body acknowledgement; there is no query-param override:

```json
{ "force": true, "acknowledge_positions_open": true }
```

The forced response still returns `202` and a warning object naming the open positions that will become unmanaged.

### GET /runtime/deployments/:id/logs

The framework's own logs: each `logs` entry uses the canonical `<RFC3339Nano UTC timestamp> <message>` format, with the last 200 returned by default; use `?since=` (ISO 8601) and `?limit=` (max 5,000) for windows. Retention 7 days. This is a pull endpoint, not a socket — poll it; a streaming transport is a candidate alongside the [webhooks roadmap](/reference/rate-limits).

### GET /runtime/deployments/:id/metrics

Normalized trading metrics across frameworks:

```json
{
  "as_of": "2026-07-28T11:45:00Z",
  "simulated": false,
  "trades": { "total": 14, "open": 1, "wins": 8, "losses": 5 },
  "pnl": { "realized_usd": 41.35, "unrealized_usd": -2.1 },
  "exposure": {
    "positions": [
      { "symbol": "BTC/USDC:USDC", "side": "long", "size_usd": 96.4 }
    ]
  },
  "balance": { "venue_usd": 141.2 }
}
```

Same shape for freqtrade and Nautilus — the runtime does the translation so a portfolio-level agent doesn't have to. `as_of` refreshes on a ~15s cache; `balance.venue_usd` is read server-side against our venue API rate budget, not yours.
