Connect Trade API Documentation

The Connect Trade API connects fintech applications to retail brokerage accounts through a single unified platform. It provides a normalized REST API and two WebSocket streaming APIs covering trading, account management, and real-time market data across 21 brokers through one integration.

Getting Started

The Connect Trade API provides three core capability areas:

To obtain API credentials, request access at connecttrade.com/contact. Upon approval you will receive a client-id and client-secret.

Overview

API Protocols

The Connect Trade API is JSON over REST and WebSocket. REST request methods can be GET, POST, DELETE, or PATCH and return JSON. WebSocket connections exchange two-way JSON messages.

All REST endpoints require client-id and client-secret headers. Most endpoints also require user-id and user-secret.

API Field Formats

All fields returned by the API are strings or booleans. Prices may be returned with up to 9 digits of precision to support cryptocurrency trading.

Creating and Authenticating Your First User

Step 1: Create a new user by sending a POST request to https://api.connecttrade.com/users with a user_id in the request body. Save the returned user-secret securely.

Step 2: Retrieve an authentication URL by sending a POST request to https://api.connecttrade.com/connections with the user-id, user-secret, connection type, and redirect_url. Each connection is user-broker specific. Connections are long-lived and do not need to be re-created each time a user logs in.

Step 3: Display the authentication URL to the user. When the user opens it, they see a list of available brokers, log in, select their account, and are redirected to your redirect_url. Error details are passed as URL parameters when applicable.

Endpoints

Single Environment

Connect Trade provides a single production environment for all API access. There are no separate sandbox, staging, or QA environments. For development and testing, connect a paper trading account at a supported broker.

REST API

Base URL: https://api.connecttrade.com. Protocol: HTTPS (JSON over REST). Methods: GET, POST, PATCH, DELETE. Authentication: client-id and client-secret headers on all endpoints; user-scoped endpoints also require user-id and user-secret.

The REST API is used for all account management, trading, and data retrieval operations including user creation, connection management, order placement, and querying balances, positions, and transactions.

Streaming Broker API (WebSocket)

Base URL: wss://stream.connecttrade.com. Protocol: WebSocket (JSON messages). Authentication: client_id and client_secret sent via an authenticate message. Each client_id can have up to 10 concurrent sessions.

The Streaming Broker API provides real-time push updates for orders, positions, and account balances. Each session must subscribe to the topics and platform users whose updates it should receive.

Streaming Market Data API (WebSocket)

Base URL: wss://mdstream.connecttrade.com. Protocol: WebSocket (JSON messages). Authentication: per-user access token from GET /connections/{connection_id}/token, valid 24 hours.

The Streaming Market Data API provides real-time streaming market data sourced from the user's connected broker, including Level 1 quotes, trades, OHLCV bars, depth of book (Level 2), and options chains.

Paper Trading

Most brokers offer paper trading (simulated) accounts. Paper trading accounts behave identically to production accounts from the Connect Trade API perspective: same OAuth flow, request/response formats, headers, endpoints, and error handling. Executions are simulated and may not perfectly reflect live market behavior.

No special flag or parameter is required to indicate paper versus production. When the user opens the OAuth URL, both paper and production accounts appear side by side. The distinction is made entirely by the account the user selects during OAuth. Any code written and tested against a paper account works without modification when switching to production.

Trading

Prerequisites

Before placing orders: you need a client-id and client-secret; a user created via POST /users; a brokerage connection with connection type trade; and trading entitlement on your Connect Trade account.

Placing Orders

All orders are placed via POST /orders (HTTP 201). Common parameters: account_id, cl_order_id (unique per order per day), broker (e.g. ALPACA, WEBULL), side, order_type, order_tif, symbol, and optionally instrument. For equities, futures, and crypto, use BUY, SELL, or SELL_SHORT; explicit side variants are also accepted. Options support open/close side variants.

Futures symbols use [ROOT][M][YY] format with a two-digit year (e.g. ESU26, ZNH27, MESZ26). Continuous contracts and spread symbols are not accepted. Connect Trade does not validate expiration or per-product broker availability; the broker determines whether a contract is tradable.

Futures limit order example:

{
  "account_id": "ABC123",
  "cl_order_id": "futures-order-001",
  "broker": "TASTYTRADE",
  "side": "BUY",
  "instrument": "FUT",
  "order_qty": "1",
  "order_type": "LMT",
  "order_limit_price": "7740.25",
  "order_tif": "DAY",
  "symbol": "ESZ26"
}

Crypto market order example:

{
  "account_id": "ABC123",
  "cl_order_id": "crypto-market-001",
  "broker": "ALPACA",
  "side": "BUY",
  "instrument": "CRYPTO",
  "order_qty": "0.001",
  "order_type": "MKT",
  "order_tif": "GTC",
  "symbol": "BTC/USD"
}

Creating, replacing, or cancelling orders through a BROKEN connection returns HTTP 409 Conflict. Re-authenticate by creating a new connection for the same broker, then retry the operation.

Order Types

CodeNameDescription
MKTMarketExecutes immediately at the current market price.
LMTLimitExecutes only at the specified price or better.
STPStopBecomes a market order when the stop price is reached.
STPLMTStop-LimitBecomes a limit order when the stop price is reached.

Time in Force

CodeNameDescription
DAYDayActive until end of the regular trading day.
GTCGood Till CancelActive until filled or manually cancelled.
IOCImmediate or CancelExecutes immediately; any unfilled portion is cancelled.
FOKFill or KillMust fill the entire order immediately or it is cancelled.
OPENMarket on OpenExecutes at the market open.
CLOSEMarket on CloseExecutes at the market close.
DAY+Day + Extended HoursActive during regular and extended hours for the day.
GTC+GTC + Extended HoursActive during regular and extended hours until filled or cancelled.

Order Side

For equities, futures, and crypto: BUY, SELL, or SELL_SHORT. TradeStation uses BUY_TO_COVER for equity buy orders that close a short position.

For options (single-leg and multi-leg): BUY_TO_OPEN, BUY_TO_CLOSE, SELL_TO_OPEN, SELL_TO_CLOSE.

Market Order Example

Buy 1 share of MSFT at the current market price: POST to https://api.connecttrade.com/orders with body: account_id, cl_order_id, broker "ALPACA", side "BUY", order_qty "1", order_type "MKT", order_tif "DAY", instrument "STK", symbol "MSFT".

Single-Leg Option Order

Options symbols use OCC OSI format. Example: buy 1 TSLA call contract (strike $437.50, expiring Oct 31 2025) at limit $13.50: broker "WEBULL", side "BUY_TO_OPEN", order_qty "1", order_type "LMT", order_limit_price "13.50", order_tif "GTC", instrument "OPT", symbol "TSLA251031C00437500".

Multi-Leg Options Orders

Multi-leg orders allow executing options strategies with 2 to 4 legs. Set instrument to MLEG_OPT and symbol to null. Each leg in the legs array specifies symbol (OCC format), action, and ratio. Supported strategies include bull call spreads, bear put spreads, long straddles, iron condors, and ratio spreads.

Modifying and Cancelling Orders

To modify an open order: PATCH /accounts/{account_id}/orders/{order_id} with a new new_cl_order_id, broker, and the fields to update (order_qty, order_notional, order_limit_price, stop_price).

To cancel an open order: DELETE /accounts/{account_id}/orders/{order_id}.

Order Status Reference

StatusDescription
pendingSubmitted but not yet acknowledged by the broker.
workingActive and waiting to be filled.
partially_filledPart of the order has been filled; remainder is still working.
filledCompletely filled.
cancelledCancelled.
rejectedRejected by the broker.
expiredExpired without being filled.
replacedReplaced by a modified order.
pending_cancelCancel request submitted, not yet confirmed.
pending_replaceModify request submitted, not yet confirmed.

These are normalized statuses. Connect Trade maps each broker's native statuses into this common set. The original broker status is also available in the broker_status field.

Account Details

Connections and Health Status

GET /connections lists the authenticated user's brokerage connections and optionally accepts status=ACTIVE or status=BROKEN. Every connection object has a required status field. ACTIVE means usable; BROKEN means the broker definitively rejected its credentials (for example, revoked or expired credentials). Recover by creating a new connection for the same broker and completing OAuth.

GET /platform/connections lists connections across all users on the platform and requires only client-id and client-secret. Results are sorted newest first. Filter by status, and paginate with limit (1–1000, default 100) and either next_cursor or prev_cursor (never both). The response contains data, next_cursor, and prev_cursor.

Use GET /accounts to retrieve all brokerage accounts for a user (filter by connection_id or broker). The returned account_id is used to query balances, positions, orders, and transactions.

Use GET /balances to retrieve cash, buying power, and margin information. Filter by account_id or connection_id. All numeric values are returned as strings.

Use GET /positions to retrieve current holdings. Filter by account_id, connection_id, or comma-separated symbols, including equity, OCC option, crypto, and dated futures symbols. URL-encode the slash in crypto pairs as %2F when necessary (most HTTP clients do this automatically). Each position includes unrealized_pl, daily_pl, and total_pl; futures positions identify themselves with instrument: "FUT".

Futures position example:

{
  "connection_id": "be573d69-eaad-4ed9-b864-645d43eccd24",
  "account_id": "9a091a87-c499-466c-9793-786d6b71ec93",
  "user_id": "newuse011",
  "account_number": "CCC992771",
  "broker_position_id": "6JZ26",
  "symbol": "6JZ25",
  "quantity": "1.000000000",
  "last_price": "0.006389500",
  "position_average_price": "0.006389500",
  "unrealized_pl": "-79868.750000000",
  "daily_pl": "0",
  "total_pl": "-79868.750000000",
  "instrument": "FUT",
  "system_time": "2025-01-01T12:00:00.000000Z"
}

Use GET /orders to retrieve order history. Filter by connection_id, account_id, broker, status, comma-separated symbols (including futures and crypto; encode crypto slashes as %2F when needed), side, and start_time/end_time. Sort with field:direction, for example entry_time:desc; sortable fields are entry_time, last_modified_time, and last_exec_time. Limit is 1 to 500 (default 50); use next_cursor and prev_cursor for pagination. Defaults to open orders from midnight UTC of the current day when no parameters are specified.

Use GET /transactions to retrieve historical transaction activity. Filter by account_id, connection_id, and date range using start_date and end_date.

Streaming Overview

Connect Trade provides two separate WebSocket streaming APIs. The Streaming Market Data API (wss://mdstream.connecttrade.com) pushes live market data from the user's connected broker — designed to connect from the client side using a short-lived access token. The Streaming Broker API (wss://stream.connecttrade.com) pushes live account updates for platform users selected by per-session subscriptions and is authenticated with platform credentials. A client_id may have up to 10 concurrent sessions.

Streaming Broker API

The Streaming Broker API delivers real-time order, position, and balance updates. Connect to wss://stream.connecttrade.com, receive a welcome message, authenticate with client_id and client_secret within 30 seconds, subscribe to the desired topics, then subscribe separately to each platform user whose updates this session should receive.

Authenticate each session:

{
  "action": "authenticate",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "version": "v1.0"
}

Subscribe to topics, then send a per-user subscription for each desired platform user:

{
  "action": "subscribe",
  "topics": ["orders", "positions", "balances"]
}
{
  "action": "subscribe_user",
  "user_id": "YOUR_USER_ID"
}

A successful per-user request returns user_subscribed. The same session/user subscription is idempotent. Each session can subscribe to 500 distinct users; attempting to exceed the limit returns subscription_failed. To cover more users, open additional sessions and split the user subscriptions. An unsubscribe_user request is also idempotent. Disconnecting releases that session's user subscriptions, and delivery continues until the last subscribed session unsubscribes or disconnects.

Updates are incremental, not full snapshots. Each update contains only the changed order, position, or balance. Fetch initial snapshots via the REST API, then apply streaming updates. On every reconnect, re-authenticate, replay the topic subscription, and replay subscribe_user for every user this session should continue to receive.

Streaming Market Data API

Market data is sourced directly from the user's connected broker with no additional licensing fees from Connect Trade, as long as the user is authorized for the data at their broker. Crypto symbols use BASE/QUOTE; futures use [ROOT][M][YY] with a two-digit year. Streaming example: subscribe to ESU26 bars with interval 4h. Supported case-sensitive bar interval units are m, h, d, w, M, and y; 4h equals 240m.

Important restrictions: (1) one broker connection at a time per user for market data, (2) all WebSocket subscriptions for a user's connection must originate from the same IP address due to market data licensing requirements. The recommended architecture is to connect directly from the user's browser using a short-lived access token generated on the server.

Generate an access token: GET https://api.connecttrade.com/connections/{connection_id}/token. Tokens are valid for 24 hours; only one token is active per connection at a time. Connect to wss://mdstream.connecttrade.com and authenticate within 30 seconds.

Available subscription channels: quotes (Level 1 real-time bid/ask), trades (time and sales with session filter), bars (OHLCV candlestick data with configurable interval and optional historical backfill), depth (Level 2 order book with configurable price levels), and options_chain (real-time options chain data with optional Greeks). Crypto symbols use BASE/QUOTE (e.g. BTC/USD); futures symbols use [ROOT][M][YY] with a two-digit year (e.g. ESU26, ZNH27, MESZ26).

Bar intervals are a number followed by a case-sensitive unit: m (minute), h (hour), d (day), w (week), M (month), or y (year). For example, 4h and 240m are equivalent.

Example futures bars subscription:

{
  "action": "subscribe",
  "channel": "bars",
  "symbol": "ESU26",
  "interval": "4h",
  "request_id": "unique-request-id"
}

Historical bar snapshots respond with type snapshot_response. Each bar's ts is its start time in Unix nanoseconds, st is c (closed) or o (open), ev is b, and sym contains the symbol.

{
  "type": "snapshot_response",
  "request_id": "unique-request-id",
  "symbol": "AAPL",
  "data_type": "bars",
  "interval": "1m",
  "start_time": "2025-06-30T09:30:00Z",
  "end_time": "2025-06-30T16:00:00Z",
  "data": [
    {
      "o": "200",
      "h": "201",
      "l": "199.5",
      "c": "200.37",
      "v": "1500",
      "ts": "1751275800000000000",
      "st": "c",
      "ev": "b",
      "sym": "AAPL"
    }
  ]
}

Manage subscriptions with list_subscriptions and unsubscribe actions. Request historical bar snapshots without a persistent subscription using the snapshot action.

Common Workflows

Calculate total unrealized P&L: fetch all positions with GET /positions and sum the unrealized_pl field, parsing values from strings to floats. Handle null values per broker.

List partially filled orders: use GET /orders?status=partially_filled. Each order includes a fills array with individual executions (time, quantity, price, fill_id).

Calculate average fill price: retrieve orders with GET /orders filtered by status and symbol. Compute the volume-weighted average fill price from the fills array.

Get a daily P&L summary: combine GET /balances for cash and buying power with GET /positions for per-symbol daily P&L via the daily_pl field.

Debugging OAuth Issues

Most OAuth issues during development are caused by the redirect_url not being reachable from the user's browser. Common causes: redirect URL points to localhost or an internal address, domain not yet deployed, HTTP instead of HTTPS, or firewall blocking inbound traffic.

To isolate OAuth from redirect issues: create a test user via POST /users, create a connection with redirect_url set to https://www.google.com, complete the OAuth flow in a browser, and inspect the resulting URL. A connection_id query parameter confirms OAuth succeeded. An error parameter with error_details identifies the OAuth failure. Clean up test users with DELETE /users/{user_id}.

Error Responses

All REST API errors return JSON with a message field. Status codes: 200 (success), 201 (connection or order created), 400 Bad Request (malformed request or invalid data), 401 Unauthorized (authentication issue or entitlement issue), 404 Not Found (resource does not exist), 409 Conflict (resource already exists or order operation through a BROKEN connection), 422 Validation Error (invalid status, cursor, or limit), 500 Internal Server Error (Connect Trade side issue).

Broker Codes

Broker codes are case-insensitive values used in the broker field throughout the API.

BrokerCode
AlpacaALPACA
Bank of AmericaBANKOFAMERICA
Capital OneCAPITALONE
ChaseCHASE
CitiCITI
FidelityFIDELITY
LightspeedLIGHTSPEED
PNCPNC
PublicPUBLIC
TastytradeTASTYTRADE
TDTD
TradeStationTRADESTATION
TradeZeroTRADEZERO
US BankUSBANK
WebullWEBULL
Wells FargoWELLSFARGO

Symbology

Equities (CQS)

Connect Trade uses the NYSE Consolidated Quote System (CQS) symbology for US equities. Common stocks use standard ticker symbols (e.g. AAPL, MSFT, TSLA). Other equity classes use suffixes: preferred stock uses a "p" suffix (e.g. IBMp), class shares use a slash (e.g. IBM/B), rights use "r" (e.g. IBMr), warrants use "/WS" (e.g. IBM/WS), and when-issued shares use "w" (e.g. IBMw).

Options (OCC OSI)

Connect Trade uses the OCC Options Symbology Initiative (OSI) format for US options. The format encodes the underlying symbol, expiration date, put/call type, and strike price into a single string: {root}{YYMMDD}{C|P}{strike padded to 8 digits with 3 decimal places}.

Examples: TSLA251031C00437500 (TSLA call, Oct 31 2025, $437.50 strike), AAPL241018P00190000 (AAPL put, Oct 18 2024, $190.00 strike), SPY241018C00430000 (SPY call, Oct 18 2024, $430.00 strike).

API Cookbook

The Connect Trade API Cookbook provides ready-to-run code samples in Python, JavaScript, Go, and Rust for the most common integration tasks. All samples use the base URL https://api.connecttrade.com and authenticate via client-id, client-secret, user-id, and user-secret headers.

Setup

Configure credentials and create reusable HTTP helpers for GET, POST, PATCH, and DELETE requests.

import requests

BASE_URL = "https://api.connecttrade.com"

HEADERS = {
    "client-id": "YOUR_CLIENT_ID",
    "client-secret": "YOUR_CLIENT_SECRET",
    "user-id": "YOUR_USER_ID",
    "user-secret": "YOUR_USER_SECRET",
}

def get(path, params=None):
    r = requests.get(f"{BASE_URL}{path}", headers=HEADERS, params=params)
    r.raise_for_status()
    return r.json()

def post(path, body):
    r = requests.post(f"{BASE_URL}{path}", headers={**HEADERS, "Content-Type": "application/json"}, json=body)
    r.raise_for_status()
    return r.json()

def delete(path):
    r = requests.delete(f"{BASE_URL}{path}", headers=HEADERS)
    r.raise_for_status()
    return r.json()

def patch(path, body):
    r = requests.patch(f"{BASE_URL}{path}", headers={**HEADERS, "Content-Type": "application/json"}, json=body)
    r.raise_for_status()
    return r.json()

Get Account Balances

Retrieve cash, buying power, and margin information across all connected brokerage accounts using GET /balances.

balances = get("/balances")
for bal in balances:
    print(f"Account {bal['account_number']}: "
          f"cash={bal['cash']}, buying_power={bal['buying_power']}")

Get Positions

Retrieve current holdings including quantity, last price, and unrealized P&L for each position using GET /positions.

positions = get("/positions")
for pos in positions:
    print(f"{pos['symbol']}: {pos['quantity']} shares @ {pos['last_price']}, "
          f"unrealized P&L: {pos['unrealized_pl']}")

Get Open Orders

List all working orders filtered by status using GET /orders?status=open. Each order includes symbol, side, quantity, order type, and normalized status.

orders = get("/orders", params={"status": "open"})
for order in orders:
    print(f"{order['symbol']} {order['side']} {order['order_qty']} "
          f"({order['order_type']}) — {order['normalized_status']}")

Get Filled Orders

Retrieve recently filled orders sorted by execution time. Each order includes a fills array with per-execution quantity and price.

orders = get("/orders", params={"status": "filled", "sort": "last_exec_time:desc", "limit": "20"})
for order in orders:
    fills = order.get("fills", [])
    total_filled = sum(float(f["quantity"]) for f in fills)
    print(f"{order['symbol']} {order['side']} filled {total_filled} shares")

List Connected Accounts

List all brokerage accounts linked to the user, including institution name, account number, and account ID using GET /accounts.

accounts = get("/accounts")
for acct in accounts:
    print(f"{acct['institution_name']} — {acct['account_number']} (id: {acct['account_id']})")

Get Recent Transactions

Retrieve historical transaction activity filtered by date range using GET /transactions.

txns = get("/transactions", params={
    "start_date": "2025-06-01",
    "end_date": "2025-06-30"
})
for txn in txns:
    print(f"{txn['transaction_type']}: {txn.get('symbol', 'N/A')} — {txn.get('amount', 'N/A')}")

Place a Market Order

Submit a market order using POST /orders. Required fields include account_id, a unique cl_order_id, broker code, side, quantity, order type, time in force, instrument, and symbol.

import uuid

order = post("/orders", {
    "account_id": "YOUR_ACCOUNT_ID",
    "cl_order_id": str(uuid.uuid4())[:30],
    "broker": "ALPACA",
    "side": "BUY",
    "order_qty": "10",
    "order_type": "MKT",
    "order_tif": "DAY",
    "instrument": "STK",
    "symbol": "AAPL"
})
print(f"Order {order['order_id']} — status: {order['normalized_status']}")

Cancel All Open Orders

Fetch all open orders and cancel each one using DELETE /accounts/{account_id}/orders/{order_id}.

orders = get("/orders", params={"status": "open"})
for order in orders:
    result = delete(f"/accounts/{order['account_id']}/orders/{order['order_id']}")
    print(f"Cancelled {order['symbol']} {order['side']} — {result['message']}")

Sum Unrealized P&L

Calculate total unrealized profit and loss across all positions by summing the unrealized_pl field from GET /positions.

positions = get("/positions")
total_pl = sum(
    float(p["unrealized_pl"])
    for p in positions
    if p.get("unrealized_pl") is not None
)
print(f"Total unrealized P&L: ${total_pl:,.2f}")

Fills from Today

Retrieve all order fills from the current trading day by filtering orders by start time and flattening the fills array from each order.

from datetime import datetime, timezone

today = datetime.now(timezone.utc).strftime("%Y-%m-%dT00:00:00Z")
orders = get("/orders", params={
    "status": "all",
    "start_time": today,
    "sort": "last_exec_time:desc",
    "limit": "500"
})

fills = []
for order in orders:
    for fill in order.get("fills", []):
        fills.append({
            "symbol": order["symbol"],
            "side": order["side"],
            "price": fill["price"],
            "quantity": fill["quantity"],
            "time": fill["time"],
        })

print(f"Total fills today: {len(fills)}")
for f in fills[:10]:
    print(f"  {f['time']} {f['symbol']} {f['side']} {f['quantity']} @ {f['price']}")

Momentum Signal

Calculate a 20-day price momentum signal for a symbol using the Streaming Market Data API. Connect to wss://mdstream.connecttrade.com, authenticate with a short-lived access token, and subscribe to daily bars with historical backfill.

import asyncio, json, uuid
import websockets

async def spy_momentum():
    token_resp = get("/connections/YOUR_CONNECTION_ID/token")
    access_token = token_resp["access_token"]

    async with websockets.connect("wss://mdstream.connecttrade.com") as ws:
        await ws.send(json.dumps({
            "action": "authenticate",
            "access_token": access_token,
            "request_id": str(uuid.uuid4()),
            "version": "v1.0"
        }))
        auth = json.loads(await ws.recv())
        assert auth["type"] == "auth_success"

        await ws.send(json.dumps({
            "action": "subscribe",
            "channel": "bars",
            "symbol": "SPY",
            "interval": "1d",
            "session": "USEqCore",
            "request_id": str(uuid.uuid4()),
            "backfill_nbars": 20
        }))

        bars = []
        async for raw in ws:
            msg = json.loads(raw)
            if msg.get("type") == "subscribed":
                continue
            if "c" in msg:
                bars.append(float(msg["c"]))
                if len(bars) >= 20:
                    momentum = (bars[-1] / bars[0]) - 1
                    print(f"SPY 20-day momentum: {momentum:+.2%}")
                    return

asyncio.run(spy_momentum())

Calculate VWAP

Compute the volume-weighted average price (VWAP) for a symbol's fills from the current day. Fetches filled orders for a symbol and calculates the weighted average across all executions.

from datetime import datetime, timezone

orders = get("/orders", params={
    "status": "filled",
    "symbols": "AAPL",
    "start_time": datetime.now(timezone.utc).strftime("%Y-%m-%dT00:00:00Z"),
    "limit": "500"
})

total_qty = 0
total_cost = 0
for order in orders:
    for fill in order.get("fills", []):
        qty = float(fill["quantity"])
        px = float(fill["price"])
        total_qty += qty
        total_cost += qty * px

if total_qty > 0:
    vwap = total_cost / total_qty
    print(f"AAPL execution VWAP: ${vwap:.4f} ({total_qty:.0f} shares)")

Portfolio Concentration

Calculate each position's share of total portfolio market value, sorted from largest to smallest. Uses last_price and quantity from GET /positions.

positions = get("/positions")
values = []
for pos in positions:
    if pos.get("last_price") and pos.get("quantity"):
        mv = float(pos["last_price"]) * float(pos["quantity"])
        values.append((pos["symbol"], mv))

total = sum(v for _, v in values)
values.sort(key=lambda x: -x[1])
print("Portfolio concentration:")
for sym, mv in values:
    pct = mv / total * 100 if total else 0
    print(f"  {sym}: ${mv:,.0f} ({pct:.1f}%)")

Stream Order Updates and Track P&L

For order fills, subscribe to the orders topic; for position P&L, subscribe to positions (and balances if needed). Topic subscription alone is not enough: send subscribe_user for each target platform user. Repeat authentication, topic subscription, and all desired user subscriptions after every reconnect.

{
  "action": "authenticate",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET",
  "version": "v1.0"
}

Order updates:

{
  "action": "subscribe",
  "topics": ["orders"]
}

Position and balance updates:

{
  "action": "subscribe",
  "topics": ["positions", "balances"]
}

For either recipe, send once for every target user:

{
  "action": "subscribe_user",
  "user_id": "YOUR_USER_ID"
}

Each client_id supports up to 10 concurrent sessions, and each session supports up to 500 distinct user subscriptions. Subscribing or unsubscribing the same session/user pair repeatedly is a no-op. Send unsubscribe_user to release a user explicitly; delivery continues until the last subscribed session unsubscribes or disconnects.

Developer Resources

API documentation overview | Interactive API reference (Redoc) | Code cookbook (Python, JavaScript, Go, Rust)

Quickstart: Five Steps to Your First Trade

Connect your first brokerage account and place a trade in five steps. Examples are provided in Python, JavaScript, Go, and Rust.

Step 1: Set Up Your HTTP Client (Python)

import requests

BASE_URL = "https://api.connecttrade.com"

HEADERS = {
    "client-id": "YOUR_CLIENT_ID",
    "client-secret": "YOUR_CLIENT_SECRET",
}

def get(path, headers=HEADERS, params=None):
    r = requests.get(f"{BASE_URL}{path}", headers=headers, params=params)
    r.raise_for_status()
    return r.json()

def post(path, body, headers=HEADERS):
    r = requests.post(
        f"{BASE_URL}{path}",
        headers={**headers, "Content-Type": "application/json"},
        json=body,
    )
    r.raise_for_status()
    return r.json()

Step 1: Set Up Your HTTP Client (JavaScript)

const BASE_URL = "https://api.connecttrade.com";

const HEADERS = {
  "client-id": "YOUR_CLIENT_ID",
  "client-secret": "YOUR_CLIENT_SECRET",
};

async function get(path, headers = HEADERS, params = {}) {
  const url = new URL(`${BASE_URL}${path}`);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
  const res = await fetch(url, { headers });
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
  return res.json();
}

async function post(path, body, headers = HEADERS) {
  const res = await fetch(`${BASE_URL}${path}`, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
  return res.json();
}

Step 1: Set Up Your HTTP Client (Go)

package main

import (
	"bytes"
	"encoding/json"
	"io"
	"net/http"
	"net/url"
)

const baseURL = "https://api.connecttrade.com"

func doRequest(method, path string, headers map[string]string, params map[string]string, body any) ([]byte, error) {
	u, _ := url.Parse(baseURL + path)
	if params != nil {
		q := u.Query()
		for k, v := range params {
			q.Set(k, v)
		}
		u.RawQuery = q.Encode()
	}
	var reqBody io.Reader
	if body != nil {
		b, _ := json.Marshal(body)
		reqBody = bytes.NewReader(b)
	}
	req, _ := http.NewRequest(method, u.String(), reqBody)
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

var clientHeaders = map[string]string{
	"client-id":     "YOUR_CLIENT_ID",
	"client-secret": "YOUR_CLIENT_SECRET",
}

Step 1: Set Up Your HTTP Client (Rust)

use reqwest::header::{HeaderMap, HeaderValue};
use serde_json::Value;

fn client_headers() -> HeaderMap {
    let mut h = HeaderMap::new();
    h.insert("client-id", HeaderValue::from_static("YOUR_CLIENT_ID"));
    h.insert("client-secret", HeaderValue::from_static("YOUR_CLIENT_SECRET"));
    h
}

fn client() -> reqwest::Client {
    reqwest::Client::builder()
        .default_headers(client_headers())
        .build()
        .unwrap()
}

async fn get(path: &str, params: &[(&str, &str)]) -> Value {
    let url = format!("https://api.connecttrade.com{}", path);
    client()
        .get(&url)
        .query(params)
        .send().await.unwrap()
        .json().await.unwrap()
}

async fn post(path: &str, body: &Value) -> Value {
    let url = format!("https://api.connecttrade.com{}", path);
    client()
        .post(&url)
        .json(body)
        .send().await.unwrap()
        .json().await.unwrap()
}

Step 2: Create a User (Python)

user = post("/users", {"user_id": "my-first-user"})
user_secret = user["user_secret"]
print(f"User created! Save this user secret: {user_secret}")

Step 2: Create a User (JavaScript)

const user = await post("/users", { user_id: "my-first-user" });
const userSecret = user.user_secret;
console.log(`User created! Save this user secret: ${userSecret}`);

Step 2: Create a User (Go)

data, _ := doRequest("POST", "/users", clientHeaders, nil, map[string]any{
	"user_id": "my-first-user",
})
var user map[string]any
json.Unmarshal(data, &user)
fmt.Printf("User created! Save this user secret: %s
", user["user_secret"])

Step 2: Create a User (Rust)

let user = post("/users", &serde_json::json!({
    "user_id": "my-first-user"
})).await;
println!("User created! Save this user secret: {}", user["user_secret"]);

Step 2: Add User Credentials to Headers (Python)

HEADERS = {
    "client-id": "YOUR_CLIENT_ID",
    "client-secret": "YOUR_CLIENT_SECRET",
    "user-id": "my-first-user",
    "user-secret": user_secret,
}

Step 2: Add User Credentials to Headers (JavaScript)

const HEADERS = {
  "client-id": "YOUR_CLIENT_ID",
  "client-secret": "YOUR_CLIENT_SECRET",
  "user-id": "my-first-user",
  "user-secret": userSecret,
};

Step 2: Add User Credentials to Headers (Go)

allHeaders := map[string]string{
	"client-id":     "YOUR_CLIENT_ID",
	"client-secret": "YOUR_CLIENT_SECRET",
	"user-id":       "my-first-user",
	"user-secret":   user["user_secret"].(string),
}

Step 2: Add User Credentials to Headers (Rust)

fn all_headers(user_secret: &str) -> HeaderMap {
    let mut h = client_headers();
    h.insert("user-id", HeaderValue::from_static("my-first-user"));
    h.insert("user-secret", HeaderValue::from_str(user_secret).unwrap());
    h
}

Step 3: Connect a Brokerage Account (OAuth) (Python)

connection = post("/connections", {
    "broker": "ALPACA",
    "connection_type": "trade",
    "redirect_url": "https://yourapp.com/callback",
})
print(f"Send the user to this URL: {connection['oauth_url']}")

Step 3: Connect a Brokerage Account (OAuth) (JavaScript)

const connection = await post("/connections", {
  broker: "ALPACA",
  connection_type: "trade",
  redirect_url: "https://yourapp.com/callback",
});
console.log(`Send the user to this URL: ${connection.oauth_url}`);

Step 3: Connect a Brokerage Account (OAuth) (Go)

data, _ = doRequest("POST", "/connections", allHeaders, nil, map[string]any{
	"broker":          "ALPACA",
	"connection_type": "trade",
	"redirect_url":    "https://yourapp.com/callback",
})
var connection map[string]any
json.Unmarshal(data, &connection)
fmt.Printf("Send the user to this URL: %s
", connection["oauth_url"])

Step 3: Connect a Brokerage Account (OAuth) (Rust)

let connection = post("/connections", &serde_json::json!({
    "broker": "ALPACA",
    "connection_type": "trade",
    "redirect_url": "https://yourapp.com/callback"
})).await;
println!("Send the user to this URL: {}", connection["oauth_url"]);

Step 4: List Connected Accounts (Python)

accounts = get("/accounts")
for acct in accounts:
    print(f"{acct['institution_name']} — {acct['account_number']} (id: {acct['account_id']})")

Step 4: List Connected Accounts (JavaScript)

const accounts = await get("/accounts");
for (const acct of accounts) {
  console.log(`${acct.institution_name} — ${acct.account_number} (id: ${acct.account_id})`);
}

Step 4: List Connected Accounts (Go)

data, _ = doRequest("GET", "/accounts", allHeaders, nil, nil)
var accounts []map[string]any
json.Unmarshal(data, &accounts)
for _, acct := range accounts {
	fmt.Printf("%s — %s (id: %s)
",
		acct["institution_name"], acct["account_number"], acct["account_id"])
}

Step 4: List Connected Accounts (Rust)

let accounts = get("/accounts", &[]).await;
for acct in accounts.as_array().unwrap() {
    println!("{} — {} (id: {})",
        acct["institution_name"], acct["account_number"], acct["account_id"]);
}

Step 4: Get Account Balances (Python)

balances = get("/balances")
for bal in balances:
    print(f"Account {bal['account_number']}: cash={bal['cash']}, buying_power={bal['buying_power']}")

Step 4: Get Account Balances (JavaScript)

const balances = await get("/balances");
for (const bal of balances) {
  console.log(`Account ${bal.account_number}: cash=${bal.cash}, buying_power=${bal.buying_power}`);
}

Step 4: Get Account Balances (Go)

data, _ = doRequest("GET", "/balances", allHeaders, nil, nil)
var balances []map[string]any
json.Unmarshal(data, &balances)
for _, bal := range balances {
	fmt.Printf("Account %s: cash=%s, buying_power=%s
",
		bal["account_number"], bal["cash"], bal["buying_power"])
}

Step 4: Get Account Balances (Rust)

let balances = get("/balances", &[]).await;
for bal in balances.as_array().unwrap() {
    println!("Account {}: cash={}, buying_power={}",
        bal["account_number"], bal["cash"], bal["buying_power"]);
}

Step 4: Get Positions (Python)

positions = get("/positions")
if not positions:
    print("No open positions yet.")
else:
    for pos in positions:
        print(f"{pos['symbol']}: {pos['quantity']} shares @ {pos['last_price']}")

Step 4: Get Positions (JavaScript)

const positions = await get("/positions");
if (positions.length === 0) {
  console.log("No open positions yet.");
} else {
  for (const pos of positions) {
    console.log(`${pos.symbol}: ${pos.quantity} shares @ ${pos.last_price}`);
  }
}

Step 4: Get Positions (Go)

data, _ = doRequest("GET", "/positions", allHeaders, nil, nil)
var positions []map[string]any
json.Unmarshal(data, &positions)
if len(positions) == 0 {
	fmt.Println("No open positions yet.")
} else {
	for _, pos := range positions {
		fmt.Printf("%s: %s shares @ %s
",
			pos["symbol"], pos["quantity"], pos["last_price"])
	}
}

Step 4: Get Positions (Rust)

let positions = get("/positions", &[]).await;
let pos_arr = positions.as_array().unwrap();
if pos_arr.is_empty() {
    println!("No open positions yet.");
} else {
    for pos in pos_arr {
        println!("{}: {} shares @ {}", pos["symbol"], pos["quantity"], pos["last_price"]);
    }
}

Step 4: Get Orders (Python)

orders = get("/orders", params={"status": "all"})
for order in orders:
    print(f"{order['symbol']} {order['side']} {order['order_qty']} "
          f"({order['order_type']}) — {order['normalized_status']}")

Step 4: Get Orders (JavaScript)

const orders = await get("/orders", HEADERS, { status: "all" });
for (const order of orders) {
  console.log(`${order.symbol} ${order.side} ${order.order_qty} (${order.order_type}) — ${order.normalized_status}`);
}

Step 4: Get Orders (Go)

data, _ = doRequest("GET", "/orders", allHeaders, map[string]string{"status": "all"}, nil)
var orders []map[string]any
json.Unmarshal(data, &orders)
for _, o := range orders {
	fmt.Printf("%s %s %s (%s) — %s
",
		o["symbol"], o["side"], o["order_qty"], o["order_type"], o["normalized_status"])
}

Step 4: Get Orders (Rust)

let orders = get("/orders", &[("status", "all")]).await;
for o in orders.as_array().unwrap() {
    println!("{} {} {} ({}) — {}",
        o["symbol"], o["side"], o["order_qty"], o["order_type"], o["normalized_status"]);
}

Step 5: Place Your First Trade (Python)

import uuid

order = post("/orders", {
    "account_id": "YOUR_ACCOUNT_ID",
    "cl_order_id": str(uuid.uuid4())[:30],
    "broker": "ALPACA",
    "side": "BUY",
    "order_qty": "1",
    "order_type": "MKT",
    "order_tif": "DAY",
    "instrument": "STK",
    "symbol": "AAPL",
})
print(f"Order placed! ID: {order['order_id']}, status: {order['normalized_status']}")

Step 5: Place Your First Trade (JavaScript)

const order = await post("/orders", {
  account_id: "YOUR_ACCOUNT_ID",
  cl_order_id: crypto.randomUUID().slice(0, 30),
  broker: "ALPACA",
  side: "BUY",
  order_qty: "1",
  order_type: "MKT",
  order_tif: "DAY",
  instrument: "STK",
  symbol: "AAPL",
});
console.log(`Order placed! ID: ${order.order_id}, status: ${order.normalized_status}`);

Step 5: Place Your First Trade (Go)

body := map[string]any{
	"account_id":  "YOUR_ACCOUNT_ID",
	"cl_order_id": fmt.Sprintf("%d", time.Now().UnixNano())[:30],
	"broker":      "ALPACA",
	"side":        "BUY",
	"order_qty":   "1",
	"order_type":  "MKT",
	"order_tif":   "DAY",
	"instrument":  "STK",
	"symbol":      "AAPL",
}
data, _ = doRequest("POST", "/orders", allHeaders, nil, body)
var order map[string]any
json.Unmarshal(data, &order)
fmt.Printf("Order placed! ID: %s, status: %s
", order["order_id"], order["normalized_status"])

Step 5: Place Your First Trade (Rust)

let order = post("/orders", &serde_json::json!({
    "account_id": "YOUR_ACCOUNT_ID",
    "cl_order_id": &uuid::Uuid::new_v4().to_string()[..30],
    "broker": "ALPACA",
    "side": "BUY",
    "order_qty": "1",
    "order_type": "MKT",
    "order_tif": "DAY",
    "instrument": "STK",
    "symbol": "AAPL"
})).await;
println!("Order placed! ID: {}, status: {}", order["order_id"], order["normalized_status"]);