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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| Code | Name | Description |
|---|---|---|
| MKT | Market | Executes immediately at the current market price. |
| LMT | Limit | Executes only at the specified price or better. |
| STP | Stop | Becomes a market order when the stop price is reached. |
| STPLMT | Stop-Limit | Becomes a limit order when the stop price is reached. |
| Code | Name | Description |
|---|---|---|
| DAY | Day | Active until end of the regular trading day. |
| GTC | Good Till Cancel | Active until filled or manually cancelled. |
| IOC | Immediate or Cancel | Executes immediately; any unfilled portion is cancelled. |
| FOK | Fill or Kill | Must fill the entire order immediately or it is cancelled. |
| OPEN | Market on Open | Executes at the market open. |
| CLOSE | Market on Close | Executes at the market close. |
| DAY+ | Day + Extended Hours | Active during regular and extended hours for the day. |
| GTC+ | GTC + Extended Hours | Active during regular and extended hours until filled or cancelled. |
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.
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".
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 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.
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}.
| Status | Description |
|---|---|
| pending | Submitted but not yet acknowledged by the broker. |
| working | Active and waiting to be filled. |
| partially_filled | Part of the order has been filled; remainder is still working. |
| filled | Completely filled. |
| cancelled | Cancelled. |
| rejected | Rejected by the broker. |
| expired | Expired without being filled. |
| replaced | Replaced by a modified order. |
| pending_cancel | Cancel request submitted, not yet confirmed. |
| pending_replace | Modify 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.
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.
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.
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.
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.
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.
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}.
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 are case-insensitive values used in the broker field throughout the API.
| Broker | Code |
|---|---|
| Alpaca | ALPACA |
| Bank of America | BANKOFAMERICA |
| Capital One | CAPITALONE |
| Chase | CHASE |
| Citi | CITI |
| Fidelity | FIDELITY |
| Lightspeed | LIGHTSPEED |
| PNC | PNC |
| Public | PUBLIC |
| Tastytrade | TASTYTRADE |
| TD | TD |
| TradeStation | TRADESTATION |
| TradeZero | TRADEZERO |
| US Bank | USBANK |
| Webull | WEBULL |
| Wells Fargo | WELLSFARGO |
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).
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).
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.
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()
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']}")
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']}")
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']}")
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 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']})")
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')}")
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']}")
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']}")
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}")
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']}")
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())
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)")
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}%)")
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.
API documentation overview | Interactive API reference (Redoc) | Code cookbook (Python, JavaScript, Go, Rust)
Connect your first brokerage account and place a trade in five steps. Examples are provided in Python, JavaScript, Go, and Rust.
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()
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();
}
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",
}
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()
}
user = post("/users", {"user_id": "my-first-user"})
user_secret = user["user_secret"]
print(f"User created! Save this user secret: {user_secret}")
const user = await post("/users", { user_id: "my-first-user" });
const userSecret = user.user_secret;
console.log(`User created! Save this user secret: ${userSecret}`);
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"])
let user = post("/users", &serde_json::json!({
"user_id": "my-first-user"
})).await;
println!("User created! Save this user secret: {}", user["user_secret"]);
HEADERS = {
"client-id": "YOUR_CLIENT_ID",
"client-secret": "YOUR_CLIENT_SECRET",
"user-id": "my-first-user",
"user-secret": user_secret,
}
const HEADERS = {
"client-id": "YOUR_CLIENT_ID",
"client-secret": "YOUR_CLIENT_SECRET",
"user-id": "my-first-user",
"user-secret": userSecret,
};
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),
}
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
}
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']}")
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}`);
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"])
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"]);
accounts = get("/accounts")
for acct in accounts:
print(f"{acct['institution_name']} — {acct['account_number']} (id: {acct['account_id']})")
const accounts = await get("/accounts");
for (const acct of accounts) {
console.log(`${acct.institution_name} — ${acct.account_number} (id: ${acct.account_id})`);
}
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"])
}
let accounts = get("/accounts", &[]).await;
for acct in accounts.as_array().unwrap() {
println!("{} — {} (id: {})",
acct["institution_name"], acct["account_number"], acct["account_id"]);
}
balances = get("/balances")
for bal in balances:
print(f"Account {bal['account_number']}: cash={bal['cash']}, buying_power={bal['buying_power']}")
const balances = await get("/balances");
for (const bal of balances) {
console.log(`Account ${bal.account_number}: cash=${bal.cash}, buying_power=${bal.buying_power}`);
}
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"])
}
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"]);
}
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']}")
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}`);
}
}
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"])
}
}
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"]);
}
}
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']}")
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}`);
}
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"])
}
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"]);
}
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']}")
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}`);
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"])
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"]);