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. To run one immediate managed-wallet place or cancel operation, use one-time venue 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 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
wget -qO- \ --method=POST \ --header='x-api-key: st_live_…' \ --header='content-type: application/json' \ --header='Idempotency-Key: 7f3d2c1a-…' \ --body-file=deployment.json \ https://api.superior.trade/runtime/deployments
$response = Invoke-RestMethod `
-Method POST `
-Uri "https://api.superior.trade/runtime/deployments" `
-Headers @{ "x-api-key" = "st_live_…"; "content-type" = "application/json"; "Idempotency-Key" = "7f3d2c1a-…" } `
-Body (Get-Content -Raw deployment.json)
$response | ConvertTo-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 |
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 | Use {"type": "managed"} today. BYOK is coming soon. 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": "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 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 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:
{ "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.
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.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.