Quickstart

From an email address to a running strategy — every request shown in full.

The complete integration path, for a human at a terminal or an AI agent driving the API. Two moments need the account owner personally: relaying a six-digit code, and (for live trading) funding and authorizing their own wallet.

1. Register with your email#

bash
curl -X POST https://api.superior.trade/account/register \
  -H "content-type: application/json" \
  -d '{"email":"operator@example.com"}'
json
{ "status": "otp_sent", "expires_in": 600 }

Superior emails a six-digit code to the operator. Nothing else is in that email — no links, no dashboard required.

2. Verify the OTP, receive the API key#

The operator reads the code out; the agent (or you) exchanges it:

bash
curl -X POST https://api.superior.trade/account/verify \
  -H "content-type: application/json" \
  -d '{"email":"operator@example.com","otp":"123456"}'
json
{
  "token": "…",
  "user": { "id": "…", "email": "operator@example.com" },
  "api_key": "st_live_4f2a9c81d7e3b0…"
}

The api_key is returned once, in this response, never by email. Store it; every request from here on sends it as x-api-key.

3. Discover what you can trade#

bash
curl https://api.superior.trade/context/venues \
  -H "x-api-key: st_live_…"

The response lists every venue, which frameworks run on it, what credentials it needs, and its minimum deposit. Drill into instruments and history with /context/markets and /context/candles.

4. Backtest before anything touches capital#

This strategy is complete — it pastes and runs as-is (JSON-escape the newlines when embedding it in the request body):

python
from freqtrade.strategy import IStrategy
import talib.abstract as ta

class BtcRange(IStrategy):
    timeframe = "4h"
    minimal_roi = {"0": 0.06}
    stoploss = -0.03
    startup_candle_count = 30

    def populate_indicators(self, dataframe, metadata):
        bb = ta.BBANDS(dataframe["close"], timeperiod=20)
        dataframe["bb_lower"] = bb["lowerband"]
        dataframe["bb_upper"] = bb["upperband"]
        dataframe["rsi"] = ta.RSI(dataframe["close"], timeperiod=14)
        return dataframe

    def populate_entry_trend(self, dataframe, metadata):
        dataframe.loc[
            (dataframe["close"] < dataframe["bb_lower"]) & (dataframe["rsi"] < 30),
            "enter_long",
        ] = 1
        return dataframe

    def populate_exit_trend(self, dataframe, metadata):
        dataframe.loc[dataframe["close"] > dataframe["bb_upper"], "exit_long"] = 1
        return dataframe
bash
curl -X POST https://api.superior.trade/runtime/backtests \
  -H "x-api-key: st_live_…" \
  -H "content-type: application/json" \
  -d '{
    "framework": "freqtrade",
    "venue": "hyperliquid",
    "symbols": ["BTC/USDC:USDC"],
    "code": "<the BtcRange strategy above>",
    "config": {
      "timeframe": "4h",
      "stake_amount": 100,
      "max_open_trades": 1,
      "exchange": { "name": "hyperliquid", "pair_whitelist": ["BTC/USDC:USDC"] }
    },
    "range": { "from": "2026-01-01", "to": "2026-06-30" }
  }'

The symbols and config.exchange.pair_whitelist values are the same exact framework_symbols.freqtrade alias returned by GET /context/markets. You may omit symbols and let the runtime derive it from pair_whitelist; when both are sent, they must match exactly.

Poll GET /runtime/backtests/:id until status is finished; the result — including market_change_pct, what buy-and-hold did — is embedded in the same response.

5. Deploy paper, and start it#

Paper mode needs no credentials and no funds. Creating a deployment does not start it — starting is always an explicit action:

bash
curl -X POST https://api.superior.trade/runtime/deployments \
  -H "x-api-key: st_live_…" \
  -H "content-type: application/json" \
  -d '{
    "framework": "freqtrade",
    "venue": "hyperliquid",
    "mode": "paper",
    "name": "btc-range-paper",
    "code": "<the same strategy>",
    "config": { "timeframe": "4h", "exchange": { "name": "hyperliquid", "pair_whitelist": ["BTC/USDC:USDC"] } }
  }'
bash
curl -X PUT https://api.superior.trade/runtime/deployments/dep_01j8xv…/status \
  -H "x-api-key: st_live_…" \
  -H "content-type: application/json" \
  -d '{ "action": "start" }'

Now watch it behave with GET /runtime/deployments/:id/metrics — paper metrics carry "simulated": true so rehearsal numbers can never be mistaken for real ones.

6. Go live#

Before live trading, fund the managed wallet above the venue's min_deposit_usd. Then create the live deployment (mode is fixed at creation — live is a new deployment) and start it. Deployment creation does not pre-validate Freqtrade strategy code, so backtest it first and use deployment status and logs to diagnose startup failures. BYOK with a scoped trading key is coming soon.

bash
curl -X POST https://api.superior.trade/runtime/deployments \
  -H "x-api-key: st_live_…" \
  -H "content-type: application/json" \
  -d '{
    "framework": "freqtrade",
    "venue": "hyperliquid",
    "mode": "live",
    "name": "btc-range-live",
    "code": "<the same strategy>",
    "config": { "timeframe": "4h", "exchange": { "name": "hyperliquid", "pair_whitelist": ["BTC/USDC:USDC"] } },
    "credentials": { "type": "managed" }
  }'

Check readiness before starting — the deployment GET reports both credential and funding state, so there's no start-and-hope:

bash
curl https://api.superior.trade/runtime/deployments/dep_01j8xw… \
  -H "x-api-key: st_live_…"
# → "credentials": { "attached": true, "type": "managed", "status": "ready" },
#   "venue_account": { "funded": true, "balance_usd": 141.20 }
bash
curl -X PUT https://api.superior.trade/runtime/deployments/dep_01j8xw…/status \
  -H "x-api-key: st_live_…" \
  -H "content-type: application/json" \
  -d '{ "action": "start" }'

If something's missing, the error names it precisely — credentials_missing and account_not_funded are different problems with different fixes, and the error body carries a docs_url to the page that solves it.

That's the whole surface a working integration needs: two account calls, two context reads, a backtest, two deployments. Everything else in these docs is depth on those steps.