Connection Guide
This guide explains how to establish a connection between your strategy and the Tektii Trading Gateway, configure market data subscriptions, and handle the event stream.
Connection Endpoints
The gateway exposes both WebSocket and REST over a single HTTP port. Your strategy uses the WebSocket channel for the event stream and the REST API for requests such as placing orders or reading account state.
| Protocol | Port | URL |
|---|---|---|
| WebSocket | 8080 | ws://localhost:8080/v1/ws |
| REST | 8080 | http://localhost:8080/v1/… |
Every REST endpoint is prefixed with /v1/. The only exceptions are the unversioned health and metrics endpoints (/health, /livez, /readyz, /metrics). In production, these URLs are supplied to your strategy via environment variables or configuration.
Host and Port Configuration
Point your strategy at a remote gateway via the GATEWAY_URL environment variable:
GATEWAY_URL=http://gateway.example.com:8080
Or use the localhost default for local development.
Authentication
When the gateway is started with the GATEWAY_API_KEY environment variable set (minimum 32 characters), every REST and WebSocket request must carry the key in one of two headers. If both are present, Authorization wins precedence.
| Header | Value |
|---|---|
Authorization | Bearer <key> |
X-API-Key | <key> |
The same headers are sent on the WebSocket upgrade request — there is no separate WebSocket auth handshake or first-message auth.
When GATEWAY_API_KEY is not set, the gateway runs without authentication and logs a warning at startup. Anyone with network access to the port can place, modify, and cancel orders. Do not deploy this way outside a trusted local environment.
Public endpoints — always unauthenticated: /health, /livez, /readyz, /metrics. Every /v1/* path requires the key when GATEWAY_API_KEY is set.
import websockets
headers = {"Authorization": f"Bearer {api_key}"}
async with websockets.connect("ws://localhost:8080/v1/ws", extra_headers=headers) as ws:
...
Note: this is gateway auth. The Tektii CLI, which talks to the Tektii management API at
api.tektii.com, uses a separateTEKTII_API_KEY— see CLI Authentication Setup.
Establishing a Connection
WebSocket Handshake
- Open a WebSocket connection to the gateway
- Wait for the
CONNECTEDconnection event - Begin receiving market data events
import websockets
import json
async def connect():
async with websockets.connect("ws://localhost:8080/v1/ws") as ws:
# Wait for connection confirmation
msg = await ws.recv()
event = json.loads(msg)
if event["type"] == "connection" and event["event"] == "CONNECTED":
print("Connected to server")
# Start receiving events
async for message in ws:
handle_event(json.loads(message))
Connection Events
The server sends connection status events ("type": "connection", with the variant in the event field):
| Event | Description |
|---|---|
CONNECTED | Successfully connected to provider |
DISCONNECTING | Connection closing gracefully |
RECONNECTING | Attempting to reconnect after failure |
BROKER_DISCONNECTED | Upstream broker link dropped |
BROKER_RECONNECTED | Upstream broker link restored after an outage; gap_duration_ms reports the gap |
BROKER_CONNECTION_FAILED | Initial broker connect failed, or a reconnect attempt failed terminally |
The BROKER_* variants carry an optional broker field (the upstream provider, e.g. "alpaca-paper"); BROKER_RECONNECTED also carries gap_duration_ms. See Connection Events for the full field reference.
Event Subscription
Configuring Subscriptions
Subscriptions are configured on the gateway at startup via the SUBSCRIPTIONS environment variable, not via WebSocket messages. The value is a JSON array; each entry names a broker platform, an instrument, and the events to stream for it.
SUBSCRIPTIONS='[
{"platform": "alpaca-paper", "instrument": "AAPL", "events": ["quote"]},
{"platform": "alpaca-paper", "instrument": "MSFT", "events": ["quote", "candle_1m"]}
]'
Each entry has three required fields:
| Field | Type | Description |
|---|---|---|
platform | string | Broker identifier — see below |
instrument | string | Symbol or instrument identifier used by the broker |
events | string[] | Event names to subscribe to — see below |
Event Names
The following event names are valid in the events array:
| Event Name | Description |
|---|---|
quote | Level-1 bid/ask/last price updates |
trade | Public trade prints from the venue |
candle_1m | 1-minute OHLCV bars |
candle_5m | 5-minute OHLCV bars |
candle_1h | 1-hour OHLCV bars |
candle_* | Wildcard — every timeframe the broker supports |
order_update | Changes to your own orders |
position_update | Changes to your own positions |
account_update | Account balance / equity updates |
trade_update | Fills on your own orders |
option_greeks | Greeks stream for option instruments |
Platform Values
platform identifies the upstream broker + mode. Valid values include alpaca-paper, alpaca-live, binance-spot-testnet, binance-spot-live, binance-futures-testnet, binance-futures-live, binance-margin-testnet, binance-margin-live, binance-coin-futures-testnet, binance-coin-futures-live, oanda-practice, oanda-live, saxo-sim, saxo-live, and mock. See the gateway README's "Supported Brokers" section for the canonical list as providers are added.
instrument strings are passed through to the broker verbatim — use the ticker, contract, or pair the broker itself expects (for example AAPL on Alpaca, BTCUSDT on Binance). There is no Tektii-internal prefix scheme.
Handling Events
Message Format
All WebSocket messages use JSON with a "type" field for routing:
{"type": "candle", "bar": {...}, "timestamp": "2025-01-01T00:00:00Z"}
{"type": "quote", "quote": {...}, "timestamp": "2025-01-01T00:00:00Z"}
{"type": "order", "event": "ORDER_FILLED", "order": {...}}
{"type": "ping", "timestamp": "2025-01-01T00:00:00Z"}
Event Types
| Type | Direction | Description |
|---|---|---|
candle | Server → Client | OHLCV bar data |
quote | Server → Client | Bid/ask/last price |
order | Server → Client | Order state changes |
position | Server → Client | Position state changes |
account | Server → Client | Account state changes |
trade | Server → Client | Trade execution |
connection | Server → Client | Connection status |
rate_limit | Server → Client | Rate limit warnings |
data_staleness | Server → Client | Per-symbol market-data went STALE/FRESH |
error | Server → Client | Error notifications |
ping | Server → Client | Heartbeat ping |
pong | Client → Server | Heartbeat response |
event_ack | Client → Server | Event acknowledgment |
Ping/Pong Heartbeat
The server sends periodic ping messages. Respond with pong to maintain the connection:
// Server sends
{"type": "ping", "timestamp": "2025-01-01T00:00:00Z"}
// Client responds
{"type": "pong"}
Event Acknowledgment
Backtest Mode (Required)
In backtest mode, time simulation is controlled by your strategy. After processing a batch of events, send an acknowledgment to advance time:
{
"type": "event_ack",
"correlation_id": "550e8400-e29b-41d4-a716-446655440000",
"events_processed": ["evt_123", "evt_124"],
"timestamp": 1700000000000
}
| Field | Type | Description |
|---|---|---|
correlation_id | string | Unique ID for this acknowledgment |
events_processed | string[] | List of event IDs that were processed |
timestamp | number | Unix timestamp (ms) when events were processed |
Without acknowledgments, the backtest engine will not advance time.
Trading Gateway Mode (Optional)
When using the Trading Gateway (paper or live mode), acknowledgments are informational only. The market continues regardless of whether you acknowledge events. However, sending them helps with debugging and monitoring.
Error Handling
Error Event Envelope
The server sends error events with a stable three-field envelope. code is the machine-readable discriminator, message is a human-readable description, and details carries per-code structured context (may be omitted for some codes).
{
"type": "error",
"code": "ORDER_REJECTED",
"message": "Order rejected: insufficient margin",
"details": { "reject_code": "INSUFFICIENT_MARGIN" },
"timestamp": "2025-01-01T00:00:00Z"
}
The same envelope is returned as the body of REST error responses (minus the type / timestamp fields). See REST Endpoints — Error Codes and Retry Strategy for the full code list and per-endpoint error tables.
Common Error Codes
code | Example message | Retryable? |
|---|---|---|
INVALID_REQUEST | Missing required field symbol | No — terminal, fix the request |
INVALID_VALUE | Invalid value for quantity: must be positive | No — terminal |
VALIDATION_ERROR | Validation failed: 2 errors | No — terminal |
UNAUTHORIZED | API key missing or invalid | No — terminal, fix credentials |
ORDER_NOT_FOUND | Order not found: ord_abc123 | No — terminal |
POSITION_NOT_FOUND | Position not found: pos_xyz789 | No — terminal |
SYMBOL_NOT_FOUND | Symbol not found: ZZZZ | No — terminal |
ORDER_NOT_MODIFIABLE | Order already filled | No — terminal |
ORDER_REJECTED | Order rejected by broker (see details.reject_code) | No for classification codes like INSUFFICIENT_FUNDS; only retry when message or details.reject_code contains self-trade / wash (the two transient rejection classes the Gateway recognises) |
RATE_LIMITED | Rate limit exceeded | Yes — honour details.retry_after_seconds / details.reset_at |
PROVIDER_UNAVAILABLE | Provider unavailable: upstream timeout | Yes — exponential backoff |
PROVIDER_ERROR | Provider error: 5xx from upstream | Yes — exponential backoff |
SHUTTING_DOWN | Gateway is shutting down | No during this instance; retry against the replacement once /readyz is green |
INTERNAL_ERROR | Internal error | No — surface to operators |
UNSUPPORTED_OPERATION | modify_order not supported on alpaca-paper | No — terminal for that provider |
A rate_limit event ("type": "rate_limit") may also fire as a warning before a hard RATE_LIMITED rejection — treat it as a signal to throttle.
Reconnection Strategy
Tektii clients should assume the WebSocket connection can drop at any time (network hiccup, upstream provider cycle, Gateway restart). Plan for reconnection from day one.
Server-driven signals:
DISCONNECTING— connection closing gracefully (Gateway shutdown, config reload). Stop sending new orders; wait forCONNECTEDon the replacement.RECONNECTING— the Gateway is re-establishing an upstream provider session. The WebSocket to your strategy stays open; market-data events may pause briefly.BROKER_DISCONNECTED— the upstream broker feed dropped. Market data and order updates may stall until it returns; pause trading on affected symbols.BROKER_RECONNECTED— the broker feed is back after an outage;gap_duration_msreports how long it was gone. Reconcile any state that may have changed during the gap.BROKER_CONNECTION_FAILED— the broker connection failed terminally (gave up after max retries). Surface to operators; the Gateway will not recover this session on its own.
Client-side reconnect loop — exponential backoff with full jitter:
| Setting | Recommended value |
|---|---|
| Initial delay | 1 second |
| Factor | 2× per failed attempt |
| Maximum delay | 30 seconds |
| Jitter | full (delay = random_between(0, current_delay)) |
| Max attempts | unlimited (alert operators after N consecutive failures) |
Reset the delay counter to the initial value after the next successful CONNECTED event — otherwise a brief flap will slow every subsequent reconnect.
import asyncio
import random
import websockets
INITIAL_DELAY = 1.0
MAX_DELAY = 30.0
async def run_with_reconnect(url, headers):
delay = INITIAL_DELAY
while True:
try:
async with websockets.connect(url, extra_headers=headers) as ws:
delay = INITIAL_DELAY # reset on successful connect
async for message in ws:
handle_event(message)
except (websockets.ConnectionClosed, OSError) as exc:
wait = random.uniform(0, delay) # full jitter
print(f"Disconnected ({exc}); reconnecting in {wait:.1f}s")
await asyncio.sleep(wait)
delay = min(delay * 2, MAX_DELAY)
if __name__ == "__main__":
headers = {"Authorization": f"Bearer {api_key}"}
asyncio.run(run_with_reconnect("ws://localhost:8080/v1/ws", headers))
Non-retryable errors (everything marked "terminal" above) do not justify reconnection — fix the request or credentials and resume. If UNAUTHORIZED keeps firing on reconnect, the API key has been rotated out-of-band; stop the reconnect loop and surface the failure to operators rather than looping indefinitely.
Next Steps
- Review the Concepts documentation for an overview of the unified interface
- See REST Endpoints for the canonical error-code table and per-endpoint recovery guidance