Venues & balances
Read a user's connected venues and balances through the API. Venues are connected by the user in the hosted flow — you never collect or send exchange credentials through the API.
Connecting a venue is not an API operation
Connection happens in one of two hosted surfaces, depending on who the account belongs to:
- Your users (Connect) — send each user through the hosted Connect flow. Your backend mints a one-time link; the user enters credentials on
connect.openmarkets.ai, never through you. See the Connect integration guide. - Your own account (first-party) — connect venues yourself on the Venues screen at
app.openmarkets.ai.
Minting a hosted Connect link (the only "connect" call your backend makes):
curl -X POST https://api.openmarkets.ai/flow/v1/connect/link-sessions \
-H 'X-API-Key: om_..._live_...' \
-H 'Content-Type: application/json' \
-d '{ "external_user_id": "your-user-123",
"return_url": "https://app.yourco.com/openmarkets/done" }'
# → { "hosted_url": "https://connect.openmarkets.ai/start?token=lt_live_..." }
# Redirect the user to hosted_url. They connect their venues there.What the API gives you
Once a user has connected venues in the hosted flow, the API is read-only over them:
GET /flow/v1/partners— the catalog of venues OpenMarkets supports.GET /flow/v1/auth/account/partners— a user's connected venues + balances.
The shape:
partner— a supported venue (Kalshi, BettorEdge, Polymarket, …).account_partner— a user's connection to that venue (status, balance).credential_status—pending(linked, not yet validated),connected(validated),failed— all set by the hosted flow, never by you.
See supported venues
Read-only catalog of every venue OpenMarkets supports. (The response also carries the credential schema each venue expects — that's what the hosted flow renders; you don't build a connect form.)
curl https://api.openmarkets.ai/flow/v1/partners \
-H 'X-API-Key: om_data_live_...'{
"data": [
{
"partner_id": "p_kalshi",
"name": "Kalshi",
"status": "active",
"orders_allowed": true,
"connectable": true,
"internal_book": false,
"order_config": {
"orders_allowed": true,
"credentials_allowed": true,
"min_order_amount": 1,
"max_order_amount": null,
"amount_increment": null,
"whole_contracts": true, // amount / price must be a whole number of contracts
"full_fill_always_allowed": false,
"price_submitted": true, // we submit a price
"price_format": "cents",
"price_tick": 1, // whole cents
"min_price": 1,
"max_price": 99,
"cancel_supported": true,
"supported_order_types": ["market", "limit"] // this venue rests limit orders
}
},
{
"partner_id": "p_bettoredge",
"name": "BettorEdge",
"status": "active",
"orders_allowed": true,
"connectable": true,
"internal_book": false,
"order_config": {
"orders_allowed": true,
"credentials_allowed": true,
"min_order_amount": 1,
"max_order_amount": null,
"amount_increment": 0.01, // any cent stake
"whole_contracts": false,
"full_fill_always_allowed": true, // accept-by-id: full take waives the minimum
"price_submitted": false, // you take the offer's price; nothing to snap
"price_format": "american",
"price_tick": 1,
"min_price": null,
"max_price": null,
"cancel_supported": true,
"supported_order_types": ["market"] // immediate-only: limit 400s here
}
}
]
}Order constraints (the venue grid)
Each venue's order_config is the grid an order on that venue must satisfy. It is per-venue (not per-market), so read it once from GET /flow/v1/partners, cache it, and join it to a price by partner_id — every partner_liquidities[] entry in the liquidity feed carries the partner_id to join on.
orders_allowed—false⇒ read-only / quotes-only; don't offer an order ticket for that venue.min_order_amount/max_order_amount— USD bounds (max_order_amount: null⇒ only the position'savailablecaps the order).full_fill_always_allowed— accept-by-id venues waive the minimum when you take the whole resting offer.whole_contracts—amount / pricemust be a whole number of contracts; size the stake so it doesn't floor to zero.amount_increment— USD step when not whole-contract (e.g.0.01).price_submitted—false⇒ accept-by-id (you take the offer's price; there's nothing to validate);true⇒ we submit a price on the grid below.price_format/price_tick/min_price/max_price— the venue's native price grid (e.g. Kalshi: whole cents, 1–99). These are inprice_formatunits, not the 0–1 probability the liquidity feed quotes.cancel_supported— whether a resting order can be cancelled.supported_order_types— whichorder_typevalues/orders/buyaccepts for this venue. Every venue supports"market";"limit"is present only where the venue rests orders. Requesting a type absent here returns400 unsupported_order_type.
POST /orders/buy and returns a per-leg rejected_code (below_min_order, above_max_order, unfillable, price_out_of_band, orders_not_supported) if an order violates them. Pre-validating just gives the user the feedback before they submit.Read a user's connected venues + balances
Returns the acting account's connected venues with cached balances. On a player JWT it's the user's own account; for Connect, scope to one of your users with X-OpenMarkets-Account.
curl https://api.openmarkets.ai/flow/v1/auth/account/partners \
-H 'X-API-Key: om_..._live_...' \
-H 'X-OpenMarkets-Account: your-user-123'{
"data": {
"router_account_id": "ra_...",
"total_balance": 1234.56,
"account_partners": [
{
"account_partner_id": "ap_...",
"partner_id": "p_kalshi",
"partner_name": "Kalshi",
"status": "active",
"credential_status": "connected",
"balance": 892.10
},
{
"account_partner_id": "ap_...",
"partner_id": "p_bettoredge",
"partner_name": "BettorEdge",
"status": "active",
"credential_status": "connected",
"balance": 342.46
}
]
}
}Balances here are cached. They refresh whenever the user connects/reconnects in the hosted flow, or when you call the refresh endpoint below.
Refresh balances
Re-reads live balances from the user's already-connected venues and returns the same envelope as GET /account/partners with a refreshed_partner_ids field added. This is a balance read — it touches no credentials and connects nothing.
curl -X POST https://api.openmarkets.ai/flow/v1/auth/account/partners/balance/update \
-H 'X-API-Key: om_..._live_...' \
-H 'X-OpenMarkets-Account: your-user-123' \
-H 'Content-Type: application/json' \
-d '{}'Real money vs. play money (currency buckets)
An account can hold two kinds of money, and they are never summed together: real money (USD, held across the connected venues above) and play money (ATLAS — practice "atlas coins" on the OpenMarkets internal book, auto-provisioned at signup). Real trading and paper trading (see Placing orders → Real vs. paper) draw down the matching bucket.
GET /flow/v1/auth/account/balances returns balances partitioned by currency — one bucket per currency, plus convenience totals. Use this (rather than /account/partners) when you need to show real and practice balances side by side without blending them.
curl https://api.openmarkets.ai/flow/v1/auth/account/balances \
-H 'X-API-Key: om_..._live_...' \
-H 'X-OpenMarkets-Account: your-user-123'{
"data": {
"router_account_id": "ra_...",
"by_currency": [
{
"currency": "USD",
"is_real_money": true,
"symbol": "$",
"label": "US Dollars",
"total": 1234.56, // sum of partners[].balance in this currency
"partners": [
{ "partner_id": "p_kalshi", "partner_name": "Kalshi", "balance": 892.10, "internal_book": false },
{ "partner_id": "p_bettoredge", "partner_name": "BettorEdge", "balance": 342.46, "internal_book": false }
]
},
{
"currency": "ATLAS",
"is_real_money": false,
"symbol": "🪙",
"label": "Atlas Coins",
"total": 1000,
"partners": [
{ "partner_id": "p_openmarkets", "partner_name": "OpenMarkets (Practice)", "balance": 1000, "internal_book": true }
]
}
],
"real_money_total": 1234.56, // convenience: total USD across connected venues
"atlas_balance": 1000 // convenience: the ATLAS practice wallet
}
}Not available through the API
By design, none of the following are API operations — they all live in the hosted flow (Connect for your users, the Venues screen for your own account):
- Submitting or storing exchange credentials.
- Connecting, reconnecting, or disconnecting a venue.
- Testing/validating credentials.
If you need any of these, send the user through the hosted Connect flow. Users also set their own execution limits, pause venues, and disconnect there — you're read-only over all of it.