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 asx-api-key(authentication).
POST/runtime/deployments
Request
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
{
"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"
}
| Field | Required | Notes |
|---|---|---|
framework | yes | From /runtime/frameworks |
image_digest | no | Pin an older listed digest; defaults to the framework's current one (pinning) |
venue | yes | Combination validated against the venue matrix |
region | no | Defaults to the venue's best region |
mode | yes | paper or live — see 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 | Discriminated union: {"type": "managed"} | {"type": "byok", "venue": …, …} (shapes). Omitted ≡ {"type": "managed"}; ignored entirely in paper mode |
alive_until | no | Auto-expiry timestamp — see expiry semantics |
on_expiry | no | stop (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
{
"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:
{
"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.
{
"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
{
"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 asPUT /runtime/deployments/:id/statusand{ "action": "stop" }: pod down, credential retained,alive_untilcleared, 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 ownstopentry.
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.
{ "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
{ "action": "start", "alive_until": "2026-08-10T00:00:00Z", "on_expiry": "block_entries" }
{ "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:
{ "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:
{
"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.