Deployments

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. (For run-once actions — close a position, rebalance — use a one-time execution instead.) 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).

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,
    "pair_whitelist": ["BTC/USDC:USDC"]
  },
  "credentials": { "type": "managed" },
  "alive_until": "2026-08-04T00:00:00Z"
}
FieldRequiredNotes
frameworkyesFrom /runtime/frameworks
image_digestnoPin an older listed digest; defaults to the framework's current one (pinning)
venueyesCombination validated against the venue matrix
regionnoDefaults to the venue's best region
modeyespaper or live — see paper trading
nameyesYours; unique per account
codeyesThe strategy source, framework-native
configyesFramework-native config; validated per framework
credentialsnoDiscriminated union: {"type": "managed"} | {"type": "byok", "venue": …, …} (shapes). Omitted ≡ {"type": "managed"}; ignored entirely in paper mode
alive_untilnoAuto-expiry timestamp — see expiry semantics
on_expirynostop (default) or block_entries — what expiry does to a bot holding positions

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 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": "byok", "wallet_address": "0x7e15…51f9", "scoped_key": true },
  "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).

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 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 (your strategy's exits, the venue UI, or a one-time execution running a close script), 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: plain-text lines with timestamps, last 200 by default, ?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.

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.10 },
  "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.