# Gambit > Gambit (gambit.trading) is an AI-native trading platform for Hyperliquid: > research and market context first — live market data, computed quant > features, the Today's Market opportunity feed, sentiment/news/equity-options > context, portfolio updates, and an agent that produces trade > recommendations. Trading happens through an explicit propose → confirm > flow (api:trade scope, agent wallet only, server-side guardrails and > hourly caps). Clients are REQUIRED to show the proposal to a human and > obtain approval before confirming — but that approval is a client-side > obligation, not something the server can verify, so treat an api:trade > key or OAuth grant as a live trading credential: it can execute trades if > misused or compromised. Keys and grants without api:trade cannot trade on > Hyperliquid at all (brokerage stock orders are a separate api:stock_trade > grant, admin-only and off for now). Reads cover native perps, spot, and HIP-3 builder-DEX perps > (equities/commodities like xyz:TSLA); trading covers native and HIP-3 > perps, not spot. Connect: add https://api.gambit.trading/mcp to any MCP > client and sign in with OAuth; headless agents use a scoped API key. ## Connect your agent (MCP / API) There are two ways in: 1. **Sign in with OAuth** — for interactive MCP clients (Claude, ChatGPT, Claude Code, Codex, Cursor, VS Code, …). Add the URL, sign in to Gambit in the browser, pick the scopes. No key to create, copy, or paste. Start here. 2. **API key** — for headless agents, scripts, cron jobs, and plain-JSON HTTPS calls where no human is around to sign in. See "Headless agents and scripts: API key" below. ### Sign in with OAuth (interactive MCP clients — start here) Add `https://api.gambit.trading/mcp` as a remote (Streamable HTTP) MCP server. The client discovers everything else: the first unauthenticated call returns 401 with `WWW-Authenticate: Bearer ... resource_metadata=...`, the client registers itself, and it opens the Gambit consent page (https://app.gambit.trading/oauth/authorize) in the browser. The human signs in and chooses which scopes to grant (`api:read` is always included; `api:trade` is only granted if they tick it and its trading confirmation; `api:stock_trade` needs its own, separate stock-trading confirmation — one never covers the other). - Claude Code: claude mcp add --transport http gambit https://api.gambit.trading/mcp then run `/mcp` inside Claude Code, pick gambit, and sign in in the browser. - Claude (web and desktop): Customize → Connectors → + Add → Add custom connector. Name it Gambit, paste the URL, continue, and sign in when prompted. Turn it on per chat from the + menu → Connectors. On Team and Enterprise, an owner adds it first under Organization settings → Connectors; members then click Connect. - ChatGPT: open chatgpt.com/plugins → + → Add custom MCP server. Paste the URL as the public endpoint, keep the discovered authentication, and select Create as a plugin; sign in when prompted. Availability depends on your plan and workspace policy. - Codex CLI: codex mcp add gambit --url https://api.gambit.trading/mcp codex mcp login gambit - Cursor: add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project); Cursor prompts you to sign in: { "mcpServers": { "gambit": { "url": "https://api.gambit.trading/mcp" } } } - VS Code: add to `.vscode/mcp.json` (or run MCP: Open User Configuration), start the server, and sign in when VS Code prompts: { "servers": { "gambit": { "type": "http", "url": "https://api.gambit.trading/mcp" } } } - Any other MCP client: if it supports remote (Streamable HTTP) servers with OAuth, the URL is all it needs. Vendor docs for each client. The steps above were checked against these on 2026-10-08; menus move, so follow the vendor page if the two differ: - Claude Code: https://code.claude.com/docs/en/mcp - Claude (web and desktop): https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp - ChatGPT: https://developers.openai.com/plugins/deploy/connect-chatgpt - Codex CLI: https://learn.chatgpt.com/docs/extend/mcp?surface=cli - Cursor: https://cursor.com/docs/mcp - VS Code: https://code.visualstudio.com/docs/agent-customization/mcp-servers - Any other client (MCP authorization spec): https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization OAuth discovery, for client implementers: - Protected resource metadata: `https://api.gambit.trading/mcp/.well-known/oauth-protected-resource` - Authorization server metadata: `https://api.gambit.trading/.well-known/oauth-authorization-server` — OAuth 2.1, authorization code + PKCE (S256 only), refresh tokens, Dynamic Client Registration at `https://api.gambit.trading/oauth/register`, and Client ID Metadata Documents (an `https://` client_id) supported - Consent page: `https://app.gambit.trading/oauth/authorize` - Server card: `https://api.gambit.trading/mcp/server.json` (MCP registry name `trading.gambit/gambit`) An OAuth grant is held to the same rules as a key: it only does what its scopes allow, and a grant that includes `api:trade` is a live trading credential — `propose_trade`, show the preview to the human, and `confirm_trade` only after their explicit approval. ### Headless agents and scripts: API key No Gambit account yet? Start a device-code signup instead of asking the user to paste a key into chat: 1. `POST https://api.gambit.trading/api/auth/device/start` with `{}`. 2. Show the returned `verification_uri` to the user. They sign in and approve there; while Gambit is invite-only, that page also handles the invite. 3. Poll `POST https://api.gambit.trading/api/auth/device/token` with `{"device_code":"..."}` at the returned `interval`. Keep polling on `authorization_pending`, wait longer on `slow_down`, and restart on `expired_token`. 4. When `status` is `complete`, store the one-time `gk_live_` value in a secret manager. A later response with `key_already_issued: true` contains no recoverable secret. 5. Connect with that key, then call `get_setup_status` to see the remaining browser and account-setup steps. No browser and no human at sign-in time? Use a scoped API key. Create one in Settings → API (https://www.gambit.trading/settings/api), then (Claude Code shown; any MCP client that accepts a custom header works the same way): read -rs GAMBIT_API_KEY && export GAMBIT_API_KEY claude mcp add --transport http gambit https://api.gambit.trading/mcp \ --header 'Authorization: Bearer ${GAMBIT_API_KEY}' Public endpoints: - MCP: `https://api.gambit.trading/mcp` - REST base: `https://api.gambit.trading` - Legacy alias: `https://api.hypergambit.xyz` (same backend; keeps serving indefinitely — no need to migrate existing configs) - Human-readable API / MCP page (ungated): `https://www.gambit.trading/mcp` - Signed-in API reference: `https://www.gambit.trading/settings/api/docs/reference` Key handling (for agents): have the user supply the key via a silent local prompt (`read -rs GAMBIT_API_KEY && export GAMBIT_API_KEY`) or a secret manager. Never ask the user to paste the key into the conversation, and never write it into files, configs, logs, shell history, or command arguments. Keys are scoped (see Scopes below) and revocable in Settings. A key only does what its scopes grant — only `api:trade` keys can place trades, and only from the user's agent wallet, never the main wallet. For plain REST, exchange the key for a 5-minute token at `POST /api/auth/api-token` (send the key as `Authorization: Bearer`), then call the endpoints below with the returned token as the bearer. **Exchange immediately before each call or batch** — the token is deliberately short-lived; the exchange is cheap and rate-limited generously (30/5min per key). ### No MCP client? Call /mcp as plain JSON The MCP endpoint runs in stateless JSON mode: each `POST /mcp` is an ordinary HTTPS request carrying one JSON-RPC message, authenticated with your API key directly — no MCP SDK, no session, and **no token exchange or 5-minute expiry to manage**. This is the easiest path for an agent that can make HTTP calls but does not speak the MCP protocol, and the responses are shaped for LLM consumption (lean envelopes, self-correction hints): The auth header is piped via stdin (`-H @-`) so the key never appears in process arguments or shell history, and the `Accept` header must offer BOTH content types (the MCP transport rejects JSON-only accepts with a 406; the response still arrives as plain JSON): printf 'Authorization: Bearer %s\n' "$GAMBIT_API_KEY" | curl -sS \ https://api.gambit.trading/mcp -H @- \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' printf 'Authorization: Bearer %s\n' "$GAMBIT_API_KEY" | curl -sS \ https://api.gambit.trading/mcp -H @- \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_opportunities","arguments":{"view":"for_you","limit":10}}}' ## Scopes - `api:read` — market data, features, sentiment, news, Today's Market opportunities, portfolio updates, your Hyperliquid balances / positions / orders / fills / performance, your wallets, your recommendations, your Gambits and saved Gambits, Gambit's view of any ticker (when enabled), symbol research, the Market Brief, calendars, your news feed, order books, liquidation maps, your signals, your plan and usage, watchlist, preferences, and lessons - `api:portfolio` — your connected brokerage holdings and cash (Robinhood and any other broker linked to Gambit through SnapTrade) via `get_holdings`, and your linked brokerages via `list_brokerages`. Read-only. A separate grant from `api:read` because brokerage balances are more sensitive than market data. It gates the direct holdings endpoint and tool; an `api:chat` key can still discuss your account — including brokerage holdings — with your Gambit agent, as the in-app chat does - `api:respond` — record your accept/reject responses to recommendations - `api:recommend` — ask your Gambit agent for a fresh recommendation, evaluate an opportunity, or review an evaluated trade ticket (each of these uses one run of your monthly recommendation quota), and, when enabled, plan a trade on one ticker from your own limits with `plan_gambit` (it uses no quota). None of them places a trade - `api:chat` — chat with your Gambit agent (uses your AI token allowance; advisory only — the agent cannot execute trades from this channel), upload chat images, and read, rename, or delete your conversations - `api:trade` — Hyperliquid trades from your agent wallet via two-step preview → confirm flows with server-side guardrails (price-drift checks, notional/leverage caps, hourly caps): `propose_trade`, `execute_recommendation`, `confirm_trade`, `close_all_positions`, `resync_protection`, and `adjust_margin`; never touches the main wallet - `api:stock_trade` — stock orders at your own linked brokerage (`propose_stock_order` → `confirm_stock_order`, `get_stock_order`). A separate grant from `api:trade`: neither one allows the other, in a key or at OAuth consent. **Admin-only and switched off for now**: every route answers 403 `trading_disabled` unless the server's brokerage-trading flag is on and the account is on the admin allowlist - `api:account` — account setup and your own Gambit state, never trading: onboarding survey, setup status, key rotation, product feedback, the agent-wallet approval poll, the brokerage connect portal and unlinking a brokerage, your Gambits and saved Gambits (create, track, monitoring, edit, delete), the facts you add about a ticker and the views your own model forms of it (your personal layer, when enabled), your signals and opportunity votes, watchlist, Market Brief and trade-routing preferences, lesson read state, and news sources. It does not place trades or change execution permissions, risk caps, or the plan. Available on Settings keys, device-code signup keys, and OAuth grants alike Over OAuth the human picks these on the consent page; for an API key they are fixed when the key is created. A key or grant missing the scope an endpoint or tool needs gets a 403. ## What the API offers (read tools) - Market scanner ranked by funding/volume/OI/price-change across all Hyperliquid perps incl. HIP-3 DEX assets - Prices, OHLCV bars, computed features (returns, realized vol, z-scores, funding, OI deltas, market regime) — snapshot, timeseries, outliers - Live hot-cache snapshots per instrument - Sentiment indices (CNN Fear&Greed, Crypto F&G), news headlines, US equity options positioning (GEX/IV/dark pool) for major tickers - Compute and hardware research: stored GPU-hour, model-token and retail hardware prices (`get_compute_pricing`), and a semiconductor supply-chain world model with companies, fab and packaging sites, supplier flows, device bills of material and a DRAM supply/demand model (`get_semis_world`) - Today's Market opportunities: a ranked, evidence-backed feed of market observations (funding extremes, unusual flow, analyst actions, big equity moves, technical breaks), with a personalized For You view, per-card detail (evidence, model-written reads, price history, market stats), and a Daily Moves equity-session screen - Portfolio updates (rolling out — flag-gated server-side; a 401 that persists with a fresh, valid token means the deployment gate rather than your key): what changed for the instruments you hold or watch — symbol-tagged news with model-written digests, notable price moves, and upcoming earnings. Only symbols, coarse weight buckets, and PnL directions can cross the wire — sizes, dollar values, and account labels have no representation in the request schema. - Your Hyperliquid balances, open perp positions, and resting orders — native perps, spot, and every HIP-3 builder DEX you hold funds or positions on - Your connected brokerage holdings (needs `api:portfolio`; rolling out — flag-gated server-side like portfolio updates): Robinhood and any other broker linked to Gambit, stocks/ETFs and cash, read on demand from the brokerage through a short server cache and never persisted - Your agent's trade recommendations (pending + history) and the ability to record your accept/reject response - Gambit's view of any ticker it covers (switched off by default; when enabled): one shared, versioned view per ticker, with what changed since you last looked, its history, and the changes on the tickers you follow ## MCP tool inputs and REST routes All REST routes below are relative to `https://api.gambit.trading` and use the 5-minute token as `Authorization: Bearer`. Account and agent routes derive the user from that token — never send a wallet address, user id, or agent id. - `whoami()` — MCP-only; returns the key's scopes - `get_compute_pricing(view?, series_id?, kind?, search?, detail?, start?, end?, interval?, max_points?, symbol?, lag_days?)` → `GET /api/user/compute-pricing` (equivalent reference route: `GET /api/v1/info/reference/compute-pricing`), scope `api:read`. Views: catalog (default), history, correlation. Stored observations only; default 90-day window, maximum 366 days, day/hour buckets, 1–1000 points (default 500). Returns source status, timestamps, methodology and membership. OpenRouter prices are USD/Mtok, paid-price medians exclude free models; publisher families do not verify open-weight licensing or frontier capability. GPU-hour rental prices are USD/GPU-hour per GPU family and tier (`gpuhour:{source}:{family}:{on_demand|interruptible}`) from GetDeploying's CC BY 4.0 dataset (weekly, about a year of history) and Azure retail prices; metadata has min/p25/p75/max and offer counts. The composite (`gpuhour:composite:h100:on_demand` etc.) is the equal-weight median of fresh source medians. Listed prices, not fills. ORNN data (history and live quotes) requires explicit collection/redistribution rights, not only a key, and is off by default. Retail GPU series are Pricify CC BY 4.0 data (vga.live + rtx.live). Memory, SSD, hard drive and CPU prices are in `components` (kind `component`, products and weekly size or model closes), and daily retail indices such as `pricify-index:ddr5` are in `retail_indexes` (kind `retail_index`); both need `kind`, `search` or `detail=summary`, and a bare full catalog leaves them out. Reserved GPU-hour prices by term (1 to 36 months, such as `gputerm:getdeploying:h100:12mo`) are in `gpu_terms` (kind `gpu_term`) on the same terms: listed prices, often from few providers. The separate `retail_forecasts` array is Pricify's forecast record, not observed prices. While its `metadata.method` is `naive`, p50 is today's price: a no-change baseline, not a prediction of direction. `view=forecast_record` reads Gambit's sealed, log-only forecast ledger: the methods, the hash chain's `head_digest`, and a scoreboard per method, group and horizon. `skill_vs_no_change` compares a method with no-change on the same forecasts, and `claim_ready` stays false until 26 independent starts. Add `series_id` (such as `pricify-index:ddr5`) for that target's latest forecasts. Not a recommendation and no claim of skill. Semiconductor fundamentals are kind `fundamental`, history from when collection started (SEC quarters back to 2022). `semis:twse:{company}:revenue` is monthly revenue in NT$ thousand for about 27 Taiwan supply-chain companies (TSMC, Nanya, Winbond, ASE, Quanta, Wiwynn, ASUS and others), from the TWSE OpenAPI under the Open Government Data License Taiwan v1.0. `semis:sec:{company}:{metric}` is quarterly revenue, gross_profit, operating_income, capex or inventory for about 27 US filers (Micron, NVIDIA, AMD, the hyperscalers and others), from SEC EDGAR XBRL (public domain). `metadata.basis` says whether a quarter was reported or derived from year-to-date filings, and a loss has value null with the number in `metadata.signed_value`. Find them with `kind=fundamental` and `search` (such as `nanya` or `micron`). Retail observations may have unknown prices and stock. No forward filling. For correlation, positive lag_days means compute leads equity (−30 to +30); at least 10 aligned daily returns are required. Returns/backtest hit rates are fractions (0.05 = 5%); hypothetical gross backtests exclude costs and establish no predictive claim. - `get_semis_world(view?, id?, search?, stage?, country?, kind?, company?, since?, limit?)` → `GET /api/user/semis-world` (equivalent reference route: `GET /api/v1/info/reference/semis-world`), scope `api:read`. Gambit's semiconductor world model, research and log-only: who makes what, where, and for whom across the AI hardware supply chain. Views: `overview` (default; counts and the DRAM model's fit), `companies` (filter by `stage` such as memory, foundry, packaging, equipment, materials, odm or cloud; `country`; `search`), `company` (`id` required: the record, its sites, who it sells to and buys from, its dated events and products), `facilities` (fabs, HBM stacking, advanced packaging, substrate, wafer, assembly and AI datacenter sites with coordinates, status and capacity; `kind` is the site type such as `dram_fab` or `adv_packaging`), `flows` (supplier links; `company`, `kind` = product such as `hbm`), `events` (`company`, `kind`, `since` YYYY-MM-DD, `search`), `products` (device bills of material: memory type and GB, foundry, node, packaging, launch and street prices), `series` (curated time series: without `id` a catalog with rows, first and last period and units, such as Korea chip exports since 2015, the Bank of Korea DRAM export price index, TrendForce contract prints back to 2008, accelerator volumes and EUV shipments; with `id` that series' rows, newest kept when truncated; a series can mix units or statistical bases, so read each row's unit and note), `model` (a DRAM supply/demand model: wafer capacity, HBM share of wafers, demand by segment, channel inventory and a contract-price path with a 10–90% range, its fit against no-change and a holdout, plus each device's memory cost along that path) and `catalog` (free official data series with a rights verdict). `limit` 1–200 (default 50); lists report `total` and `truncated`. Every number carries its own `source`, `as_of` and `basis` (`reported`, `estimated` or `derived`); unknown values are null. The model is a structural scenario fitted to about 14 quarters of contract prints, not a forecast with demonstrated skill, and nothing here is a recommendation. A refused query returns 400 with a message saying why. - `scan_markets(sort_by?, top?, min_volume?, reverse?, include_hip3?)` → `GET /api/v1/info/scan` (`sort_by` is named `sort` in REST) - `get_prices(symbols?)` → `GET /api/v1/info/prices?resolve_spot=true&include_hip3=true`; native perps, spot pairs (`HYPE/USDC`), and HIP-3 perps keyed by their DEX prefix (`xyz:TSLA`). `symbols` filters on both MCP and REST (comma-separated; absent symbols are named in `missing_symbols`, except a symbol on a HIP-3 DEX that could not be read right now, which is named in `unavailable_symbols` — its price is unknown, not nonexistent — because its DEX is in `hip3.failed_dexes` or `hip3.included` is false); omit for all prices - `get_bars(instrument_id, interval, start?, end?, max_points?)` → `GET /api/v1/raw/bars`; intervals are 1m/5m/15m/1h/1d. An omitted range defaults to the last 24h on both MCP and REST; a single supplied boundary anchors the other (end-24h / start+24h). - `get_feature(mode, feature_name, …)` → `GET /api/v1/transformed/{snapshot|timeseries|outliers}`. Optional route-specific inputs are instrument_id, universe_id, start, end, and n. Valid features: ret_1h, ret_4h, ret_1d, ret_1h_z, rv_1d, rv_1d_z, rv_7d, ewma_vol, vol_regime, range_expansion_z, trend_strength, drawdown_current, drawdown_max_30d, ma20h_zscore, ma50h_zscore, ma20d_zscore, ma50d_zscore, corr_btc_7d, corr_btc_30d, beta_btc_7d, beta_btc_30d, skew_30d, kurt_30d, cum_ret_7d, cum_ret_30d, ret_1h_pctl, rv_1d_pctl, funding_rate, funding_z_30d, oi_chg_1h, oi_chg_1h_z, oi_chg_4h, oi_chg_1d, oi_chg_1d_z, oi_price_divergence. - `get_live_snapshot(instrument_id? | instrument_ids?)` → `GET /api/v1/live/snapshot` or `GET /api/v1/live/batch` - `get_market_regime()` → `GET /api/v1/live/regime` - `get_sentiment()` → `GET /api/v1/sentiment/latest` - `get_news_headlines(hours?, symbol?, priority?, limit?)` → `GET /api/v1/news/headlines` - `get_equity_flow(kind, ticker, start?, end?, limit?)` → `GET /api/v1/equities/{gex|iv|darkpool}`; kind is gex, iv, or darkpool, and darkpool requires start + end - `get_opportunities(view?, categories?, ticker?, limit?, sort?, min_market_cap_usd?)` → `GET /api/v1/market-opportunities`; view is market (shared ranking, default), for_you (personalized to YOUR watchlist/cases/mandates), or saved; categories is an MCP array / REST CSV of trending, analyst_desk, idiosyncrasy, unusual_flow, prediction_markets, technical, institutional; limit caps at 24. Feed refreshes every ~10 minutes — do not poll faster. for_you backfills with market-ranked cards once your personal matches run out (by design — each item's `section` says personalized vs market_fallback). - `get_opportunity_detail(opportunity_id)` → `GET /api/v1/market-opportunities/{opportunity_id}`; ids are `mo_` + 32 hex from get_opportunities. Retired cards return with expired=true — historical, not a current signal. - `get_daily_moves(view?, limit?, min_abs_z?, min_session_dollar_volume_usd?, min_market_cap_usd?, market_session?)` → `GET /api/v1/market-opportunities/daily-moves`; view=compact (MCP default) returns qualifying US-equity session movers, view=all includes non-qualifying rows with their rejection reasons; scoped callers are capped at 500 rows. - `get_portfolio_updates(window?, limit?, holdings?, watchlist?)` → `POST /api/portfolio/updates` (note: not under /api/v1; rolling out — flag-gated server-side). Over MCP, omitting both lists derives holdings from your open positions; over REST send a JSON body with at least one instrument — entries are bare symbols or `{ id, weightBucket?, pnlDirection? }` objects. Only symbols, coarse weight buckets, and PnL directions can be sent — sizes, dollar values, and account labels have no representation in the schema. - `get_account_balances()` → `GET /api/v1/account/balances` - `get_positions()` → `GET /api/v1/account/positions` - `get_open_orders()` → `GET /api/v1/account/orders` These three cover the main wallet and the agent deposit wallet, and include HIP-3 builder DEXes. Each wallet's balances report `native_perp`, spot tokens, and `hip3_perp` — one margin account per DEX with its own `collateral_token` (e.g. USDC, USDH, USDe, USDT0; null when unknown), so never add a DEX's numbers into `native_perp`. Positions and orders list native rows first, then HIP-3 rows with a `dex` field and a dex-prefixed symbol. `meta.quality` is `partial` when HIP-3 data is missing: `hip3.failed_dexes` names DEXes that could not be read, and `hip3.included` false means no HIP-3 DEX was read. HIP-3 rows can be up to ~2 s old. - `get_holdings(view?, symbol?)` → `GET /api/portfolio/stocks/holdings` (needs `api:portfolio`). Your connected brokerage holdings — stocks/ETFs and cash, not crypto. `view=compact` (default over MCP) returns total value, `cashUsd` and `buyingPowerUsd` (the web app's numbers: USD-labelled or unlabelled balances only, other currencies not summed, and null — not 0 — when the total is zero or negative, so a CAD-only account or a negative margin balance reads null), each connected account, and `topHoldings` — the 10 largest account-level lots by absolute market value per currency (a symbol held in two accounts appears twice; shorts rank by exposure) with `holdingsCount` so you know what the cap hid. `symbol=NVDA` (compact only) adds `activeSymbolPosition`: the aggregate of that exact ticker across accounts (summed quantity and value, weighted cost, `lotCount`, `lots`), null when not held; tickers match exactly, so `BRK.B` never resolves to `BRK.A` and `BRK` matches neither (a `dex:` prefix like `xyz:TSLA` is stripped). `view=full` returns the raw snapshot instead — `holdings` (every lot), `totalCash`, `accounts` — with none of the compact-only fields, so do not read a missing `topHoldings` as an empty portfolio. No FX: amounts are never compared across currencies (lots with no currency code form an unranked sample — last, ordered by symbol, under the same 10-row cap; `caps_applied.top_holdings.unknown_currency_truncated` says if that sample dropped rows, and `per_currency` refers to known currencies only); a spotlight whose lots span currencies or lack a currency code reports null `marketValue`/`marketPrice`/`averageCost`/`currency` (quantity still sums), and one mixing long and short lots keeps net `quantity`, net `marketValue` and `currency` but reports null `marketPrice` and `averageCost`; the spotlight's `marketPrice` is the quantity-weighted average of the lots' own prices. `meta.data_freshness.status` is `fresh` inside the ~60s server cache and `stale` when a cached snapshot was served past it — brokerages sync roughly daily, so stale is fine for reading, not for sizing a trade. `meta.caps_applied` is non-null only when the top-10 cap dropped rows. `meta.degraded_reason` `brokerage_not_connected` means the user has not linked a brokerage in Gambit (Portfolio → Connect brokerage); an empty list is never silent. Read-only. Brokerage stock orders are a separate scope, `api:stock_trade` (admin-only and off for now; see Stock orders). - `list_recommendations(status, limit?)` → `GET /api/v1/recommendations/{pending|history}`; row ids are the same ids `generate_recommendation` returns as `recommendation_id`. Not every generated id is listed: a watch mints none, and a run whose `ledger.status` is `recorded` (a different recommendation on the same instrument/direction was already pending) has a respondable id that is not in the pending list — the pending one is `ledger.existing_pending_recommendation_id` - `respond_to_recommendation(recommendation_id, response)` → `POST /api/v1/recommendations/{recommendation_id}/response`; response is accepted, rejected, or modified and does not execute a trade; pass the `recommendation_id` from `generate_recommendation` or a pending row — never `snapshot_id` - `generate_recommendation(watch_instrument?, all_universe?)` → `POST /api/v1/account/agent/recommend`. The answer is `user_facing_recommendation` with `state` recommend or watch (a reasoned no-trade). A recommend is `expression_kind` single_instrument (`instrument_id` + `direction`, plus `sizing` / `trade_ticket` when the desk could size it) or pair_trade (long/short `legs[{instrument_id, side}]`, no top-level instrument or direction). `recommendation_id` is the ledger id when the run produced a single-instrument recommend, and `respond_to_recommendation` takes it; `ledger.status` says how it lists: `pending` (in `list_recommendations(pending)`; when the same recommendation was already pending it is that row's id, `ledger.reason` `existing_pending_primary`) or `recorded` (a different recommendation on the same instrument/direction is already pending — this id is respondable but not listed; the pending one is `ledger.existing_pending_recommendation_id`). It is `null` when nothing was persisted (every watch, and a pair_trade for now), with the reason under `ledger.reason`. Only a `ledger.status` `pending` outcome can be reconciled from the pending list: a completed watch leaves no row, and a `recorded` outcome leaves the list unchanged, so `list_recommendations` cannot prove a timed-out run failed — wait out the run window (~2 min) before re-running; each run spends quota. `no_recommendation_reason.retryable` tells an operational failure (retry now) from a desk judgement (do not re-run). `snapshot_id` is the run's audit snapshot, not a recommendation id. Gambit answers (switched off by default; while off, every `watch_instrument` run takes the normal flow): when enabled for this deployment, a `watch_instrument` that Gambit covers is answered from Gambit's view of it (`user_facing_recommendation.gambit`: `subject_id`, `version`, `view`, `plan`) plus a plan sized from your own limits, and no model runs. A real lean (`|shown_lean|` at least 1) on a Hyperliquid listing gives a recommend (`decision_source` and `setup_family` `gambit_view`): the direction follows the lean, the entry is the current mark, the stop comes from realized volatility, `invalidation_rule` is the view's first dated wrong-if condition, and `sizing` / `trade_ticket` come from your trade policy. No edge gives a watch (`gambit_no_edge`) whose reason is the no-edge map; no view yet gives a watch (`gambit_building`) and queues the ticker; a view that is out of date or cannot be priced or confirmed on this run gives a watch with `pin_status` `unavailable` (retryable). A `ref:eq:TICKER` id of a US stock with no Hyperliquid listing gets the view as a watch with no ticket and no ledger row - `ask_gambit_agent(message, new_conversation?, image_urls?)` → `POST /api/v1/account/agent/chat` (SSE; advisory only). To show the agent a chart, upload each image first with `POST /api/chat/images` (REST only, `api:chat`): the raw bytes as the body and the image type as `Content-Type` (PNG, JPEG, WebP or GIF, one image per request, up to 10 MB). It returns `{ success, url, key, mime }`; pass up to 24 `url` values as `image_urls` (over REST the chat body takes `images: [{ url }]`). API callers may upload 30 images / 100 MB per UTC day (429 with `Retry-After` after that) - `propose_trade(symbol, side, notional_usd?, leverage?)` → `POST /api/v1/account/trade/propose`; side is long, short, or close. `symbol` is a native perp (`BTC`) or a HIP-3 perp (`xyz:TSLA`); a bare symbol prefers the native perp, then a single HIP-3 listing; with no native perp and several DEX listings it returns 422 `ambiguous_symbol` with the candidates, so ask the user which one. Spot is not traded - `confirm_trade(proposal_id, decision, asset?, side?, size_usd?)` → `POST /api/v1/account/trade/confirm`; execute requires the exact `confirm_echo` fields returned by propose, after explicit human approval; cancel needs no echo fields Over MCP, many tools below also return `app_url`: the Gambit app screen that shows the answer, so you can send the user there to see or act on it. No trade tool links to a confirm page. ### Account setup (`api:account`) - `get_setup_status()` → `GET /api/user/setup-status`; read `data.next` (the remaining steps), `agent_setup_url`, `onboarding_completed`, `wallet_linked`, `trading_profile`, `agent_approved`, `brokerage_connected`. The user signs the Hyperliquid agent approval at `open_agent_setup.url`; an agent cannot sign it - `submit_onboarding(q1, q2, q3, q4, q5?, q6?)` → `POST /api/user/onboarding-survey` with `{ version: 4, answers }` (`GET` on the same path reads it back, REST only). `q3=no_risk` stores the answer but provisions no agent - `rotate_api_key()` → `POST /api/user/api-keys/current/rotate`; revokes the key in use and returns a new `gk_live_` key once - `start_brokerage_connect(broker?)` → `POST /api/portfolio/stocks/session`; returns a SnapTrade portal URL for the user, then poll `get_setup_status` - `leave_feedback(category, message, area?, severity?, client?)` → `POST /api/feedback`; product feedback for the Gambit team. `category` is `bug | feature_request | ux | data_quality | praise | other`; `message` is plain text, 10-4000 characters (what you did, what you expected, what happened). Secrets and personal details are redacted before storage (`data.redacted` lists the kinds); markdown, links and HTML are not rendered. 5 per hour, 20 per day; the same text within 24 hours returns the existing id with `duplicate: true` ### Your Gambits and saved Gambits Reads need `api:read`; writes need `api:account` (`plan_gambit` needs `api:recommend`). None of them places a trade. Gambit's view (reads by symbol, switched off by default): Gambit keeps ONE shared, versioned view per ticker, the same for every user, with a new version only when the view changes. A read by symbol works for any ticker Gambit covers (a Hyperliquid listing or a US stock), also when you have no Gambit on it. When enabled for this deployment, these reads answer as described below; while off they answer 503 `gambit_reads_disabled`. A bare ticker that names a Hyperliquid listing and also a US stock with no Hyperliquid listing reads the Hyperliquid one: check `subject_type` and `display_symbol`, and pass `ref:eq:TICKER` (for example `ref:eq:IP`) for the US stock. A ticker that stays ambiguous → 422 `ambiguous_symbol` with `candidates` (subject ids such as `asset:crypto:IP`); call again with one of them. Unknown → 404 `unknown_symbol`. To report a view that looks wrong, use `leave_feedback` with category `data_quality`, area `gambits`, and the `subject_id` and `version`. - `list_gambits(limit?, offset?)` → `GET /api/user/cases`; each Gambit with its state, monitoring status, latest observation, and current judgment (stance recommend | watch | stand_aside, conviction high | medium | low) - `get_tracking_updates(limit?, cursor?, include_gambit_changes?)` → `GET /api/user/cases/tracking-updates`; what changed across all your Gambits, newest first (captured opportunities and reassessments). `include_gambit_changes` (default false; when enabled) also returns `gambit_changes` `{ items, has_more }` from `GET /api/user/gambits/updates?limit=&since=`: the changes in Gambit's view of the tickers you follow (the assets of your live Gambits, and only those; a live Gambit is open or watching with tracking on. A read by symbol with `mark_seen` true, the default, records the seen version on a ticker you already follow and never starts a Gambit; `create_gambit` starts one. A Gambit on the ticker in any other state, including stopped, dismissed, executed or expired, ends the follow, and a later read does not restart it) since you last looked, each with `subject_id`, `display_symbol`, `version`, `as_of`, `headline`, `first_look`, `change_lines` and `cause_items`. The answer is then `{ tracking_updates, gambit_changes }`; if that read fails, `gambit_changes` is null with `gambit_changes_error`. `first_look` true means you follow the ticker but never marked it seen (a read by symbol with `mark_seen` true, or opening its Gambit in the app): its changes count from when you started to follow it and come back on every call until it is marked seen, so do not call them new - `get_gambit(case_id | symbol, mark_seen?)` → `GET /api/user/cases/:caseId` or `GET /api/user/gambits/:symbol?mark_seen=true|false` (when enabled). By case_id: one Gambit in full, with its monitoring question and judgment log, plus a `gambit` block (the view of its asset, `since_you_last_looked`) when symbol reads are enabled and Gambit has a view; a case_id read never marks it seen. Not yours → 404. By symbol: `since_you_last_looked` (what changed since you last read this ticker and why, or `first_look`) — lead your answer with it — then `view` (headline, the view against what is priced, positioning, reasons with sources, the case against, what would prove it wrong with dates, next events, how likely it is to be wrong, what it can't judge, the no-edge map), `new_since_view` (news after the view was formed), `freshness`, and `what_would_change_our_view` (test a user's pushback against it). `mark_seen` records that you saw this version on a ticker you already follow (MCP default true; REST default false) and never starts a Gambit. `followed` says whether you follow the ticker; when it is false, offer `create_gambit`. A ticker with no view yet answers `status` `building` and is queued - Mark a case_id read's `gambit` block seen (REST only, `api:account`, when enabled): `POST /api/user/gambits/seen` with `{ subject_id, version }` from the block → `{ subject_id, version_seen }`. `since_you_last_looked` then counts from the version recorded; the recorded version only moves forward, so `version_seen` is higher when a later version was already recorded. 400 for any other body or a subject_id that is not an asset subject id; 404 `unknown_version` when the ticker has no accepted version with that number; 503 `gambit_reads_disabled` while symbol reads are off - `get_gambit_history(case_id | symbol, view?, entry_id?, observation_id?, limit?, cursor?, before_version?)` → `GET /api/user/cases/:caseId/{history|entries}`, `/entries/:entryId`, `/observations/:observationId`, or (when enabled) `GET /api/user/gambits/:symbol/history?limit=&before_version=`: the view's accepted versions, newest first, each with `version`, `as_of`, `headline`, `change_lines` and `no_change`; page with `next_before_version`. `view`, `entry_id`, `observation_id` and `cursor` are case_id reads only The chain on one ticker (switched off by default; when enabled): add data → your view → what if → plan. Everything it stores stays in YOUR personal layer and never changes the shared view. Over the API and MCP, Gambit runs no model for you: your own model does the personal work. Every chain route can answer 409 `gambit_building` (no view of the ticker yet; a read by symbol queues it), 404 `unknown_symbol`, or 422 `ambiguous_symbol`; while off it answers 503 `gambit_reads_disabled` or, for evidence, views and plan, 503 `gambit_personal_disabled`. Evidence, views and plan have daily limits (429 `rate_limited` with `retry_after`). - `add_gambit_evidence(symbol, claim, url?, check_by?)` (`api:account`) → `POST /api/user/gambits/:symbol/evidence`; a fact you know (≤ 2000 chars; an instruction inside it is treated as text), stored as unconfirmed until an official or wire source that states it confirms it (the `url`, or one web search); `check_by` (default 14 days) is when an unconfirmed fact expires. Returns `user_item`, `confirmation`, `duplicate`, and `your_view` (`form_with_your_model`: next call `get_gambit_pack`, or `same_as_shared`) - `get_gambit_pack(symbol)` (`api:read`) → `GET /api/user/gambits/:symbol/pack`; `based_on_version`, the `pack` (items with `item_id`, your facts as kind user with negative ids, market facts, allowed reason ids, open checks), `rules` and `output_schema`, for your own model to form a view - `submit_gambit_view(symbol, view, model, based_on_version)` (`api:account`) → `POST /api/user/gambits/:symbol/views` (body ≤ 64 KB); Gambit checks the view with the validator it uses for its own views (no price levels or targets, no market timing, dated wrong-if conditions) and stores it labelled with `model`: `status` accepted (now your view) or rejected (fix each of `problems` and submit again). 409 `pack_changed` → get the pack again - `what_if_gambit(symbol, question, watch_id?)` (`api:read`) → `POST /api/user/gambits/:symbol/what-if`; a question that matches a condition the view lists (or its `watch_id`) gets a precomputed answer (`lean_if`, `condition`, `check_by`, `explanation`); otherwise the answer is the pack with the event added as kind assumption, for your own model. It never changes a stored view - `plan_gambit(symbol, use, risk_pct?, account_value?)` (`api:recommend`) → `POST /api/user/gambits/:symbol/plan`; `use` shared or mine. Entry (latest price), stop (realized volatility), `size_usd` / `risk_usd` from your limits, exits (the view's dated wrong-if conditions plus the stop); `no_trade` when there is no real lean. Without `account_value` there is no size (Gambit does not read your portfolio for it). Not a recommendation: it places no order, writes no trade record, runs no model, and uses no recommendation quota - `list_mandates(status?)` → `GET /api/user/mandates`; `get_mandate(mandate_id)` → `GET /api/user/mandates/:mandateId` - `get_mandate_activity(limit?)` → `GET /api/user/recommendations/mandate-activity`; what your saved Gambits found (limit 1-20) - `get_my_opportunity_marks(opportunity_ids)` → `POST /api/market/opportunities/personal-state` (a read); your votes and saves on up to 100 cards - `get_home_insight(observations)` → `POST /api/user/home-insights/cache` (a read); cached Home wording only, never runs a model. `missing` means no summary for exactly that packet - `create_gambit(symbol, sentence?)` → `POST /api/user/cases`. Opens a Gambit on a symbol (`BTC`, `GOOGL`, or an exact id such as `xyz:GOOGL`); `sentence` (≤ 280 chars) becomes the monitoring question. An open Gambit on the same asset is returned unchanged instead of a second (`created` false, `already_existed` true). It starts with a neutral watch judgment and no direction. Known limit: Track in the app does not join a Gambit opened here, so the user can end up with two on one symbol; the next `create_gambit` then answers 409 `gambit_choice_required` with choices. 10 creates per user per hour (429 `gambit_create_limit`). Other errors: 422 `unknown_symbol`, `ambiguous_symbol` (with candidates), `unsupported_symbol`, `invalid_sentence`; 409 `gambit_create_conflict` (retry once); 503 when the symbol registry or the limit cannot be read - `track_opportunity(opportunity_id, target_case_id?, scenario_id?, idempotency_key?)` → `POST /api/market/opportunities/:id/track` (REST: `Idempotency-Key` header, body `selected_scenario_id`); the app's Track. Monitoring starts paused. More than one Gambit on the subject → 409 `tracking_target_required` with choices; call again with `target_case_id` - `update_gambit_monitoring(case_id, active?, monitor_interval_minutes?, update_categories?)` → `PATCH /api/user/cases/:caseId/monitoring`; `active=false` stops tracking (confirm with the user first). Intervals 15 | 30 | 60 | 120 | 240 - `save_mandate(action, …)` → create `POST /api/user/mandates`, update `PATCH /api/user/mandates/:mandateId`, supersede `POST /api/user/mandates/:mandateId/supersede`, refine `POST /api/user/mandates/refine`, compile_scope `POST /api/user/mandates/scope/compile` (refine and compile_scope save nothing). An active or paused saved Gambit is never edited in place: supersede, then activate the successor. `autonomy_level` 0-3 only. Plan limit → 403 `tier_limit_exceeded`; a refused lifecycle rule → 422 `mandate_rule_violation` - `delete_mandate(mandate_id)` → `DELETE /api/user/mandates/:mandateId`; permanent — confirm with the user first; pausing is the reversible option ### Research and context (`api:read`) - `search_symbols(q, limit?)` → `GET /api/research/reference-symbols/search`; US-equity tickers only (crypto and HIP-3 perps: `scan_markets`) - `get_symbol_research(symbol, view?, …)` → `GET /api/research/symbols/:symbol/{snapshot|bars|return|trade-baseline|context|relationship-profile}`. bars take interval 1m/5m/15m/30m/1h/1d/1w, start, end, max_points (202 pending = retry shortly); return needs venue (reference | hyperliquid), start and end; trade_baseline needs observed_at - `get_exposures(instrument_ids)` → `GET /api/research/portfolio-exposures` (comma-separated, up to 50); groups listings of one underlying (`same_exposure` + `group_id`). Taxonomy only; never reads your account - `get_daily_move_plan(security_id, session_date)` → `GET /api/market/opportunities/daily-moves/:securityId/plan`; a display-only example plan, never an order or a recommendation - `get_daily_move_context(symbol)` → `GET /api/market/opportunities/daily-moves/:symbol/context`; stored context for a big move, never runs a model - `get_opportunity_news(opportunity_id)` → `GET /api/market/opportunities/:id/related-news` - `get_news_feed(view?, limit?, before?, before_id?, since?, since_id?, item_id?)` → `GET /api/user/news/feed` (REST cursor names `beforeId`, `sinceId`), `GET /api/user/news/sources`, or `GET /api/user/news/items/:id/related-opportunities` - `get_market_brief(view?, symbols?, crypto?, period?, market?, limit?, symbol?, kind?)` → `GET /api/market/{brief|sentiment/latest|sentiment/breadth|equity-changes|quotes|returns|series|yields|news|asset-description}`. The brief is shared and cached (503 while it regenerates); `symbols` and `crypto` are comma-separated over REST — never mix equities and perps - `get_calendar(kind, …)` → `GET /api/calendar/macro`, `/api/calendar/earnings`, `/api/calendar/macro/take`, or `/api/calendar/earnings/priority-context`. days 1-90 (default 30), back 0-14, or from/to (both, ≤ 90 days); earnings take `symbols`, `scope` major | all, and `per_day` (REST `perDay`) - `get_orderbook(symbol)` → `GET /api/market/orderbook`; a perp, a HIP-3 market, or a spot pair id (`@107`) - `get_liquidation_map(coin, view?, bucket_pct?, max_distance_pct?, queue_side?)` → `GET /api/market/liquidations`; view levels (default) or adl - `get_fills(start_time?, end_time?)` → `GET /api/user/fills`; your main and agent wallet fills, native, spot and HIP-3. Default last 24 h, at most 90 days, 30 calls a minute. `truncated` means narrow the window ### Signals and opportunity votes - `list_signals(view?, status?, cron_id?)` (`api:read`) → `GET /api/user/crons`, `/api/user/crons/:cronId`, or `/api/user/crons/catalog` (what a signal can read, to build a predicate) - `get_fired_signals(limit?)` (`api:read`) → `GET /api/user/crons/fired-signals` - `save_signal(action, cron_id?, …)` (`api:account`) → create `POST /api/user/crons`, update `PATCH /api/user/crons/:cronId`, pause / resume `POST /api/user/crons/:cronId/{pause|resume}`, delete `DELETE /api/user/crons/:cronId`. create needs name, interval_minutes (1-59, or whole hours to 1440), query or queries (≤ 8), and a predicate `{field, op, value}` or `{operator: AND | OR, conditions}`. Your plan caps the count (403 at the cap). A signal only notifies; it never trades - `respond_to_signal(execution_id, response)` (`api:account`) → `POST /api/user/crons/fired-signals/:executionId/response`; accepted or rejected, never a trade. Already answered → 409 - `rate_opportunity(opportunity_id, rating?, saved?)` (`api:account`) → `POST /api/market/opportunities/:id/seen`; votes are feedback for Gambit's team and do not change the ranking. `data.recorded` false → retry once ### Plan, watchlist, preferences, lessons, news sources, wallets, brokerages Reads need `api:read` (brokerages: `api:portfolio`); changes need `api:account`. None of them trades or changes execution permission. - `get_plan_usage(view?)` → `GET /api/user/subscription`, `/subscription/usage`, or `/subscription/tiers`. Read-only - `get_watchlist()` → `GET /api/user/asset-favorites`; `update_watchlist(action, symbol)` → `POST` / `DELETE` on the same path with `{ symbol }`; idempotent - `get_preferences(kind)` → `GET /api/user/trading-profile`, `/market-brief-prefs`, or `/trade-routing-prefs`. The trading profile is read-only here - `update_preferences(kind, market_brief? | trade_routing?)` → `PUT /api/user/market-brief-prefs` or `/trade-routing-prefs`. Each REPLACES THE WHOLE SET: read it first, change only what the user asked, send it all back. Trade routing only preselects the ticket's destination - `get_lesson(view?, slug?)` → `GET /api/user/education/daily-card`, `/archive`, or `/lessons/:slug`. Reading the daily card claims today's card (one per UTC day) - `mark_lesson_read(served_on, slug)` → `POST /api/user/education/daily-card/read`; 409 `card_changed` when the slug no longer matches the date - `manage_news_sources(action, …)` → `/api/user/x-handles/me`, `/api/user/x-handles/suggest`, `/api/user/telegram-groups/me`, and `POST /api/user/news/sources/:handle/toggle`. List and suggest need `api:read`. A full plan cap → 403 `tier_limit_exceeded`; adding a Telegram group joins and screens it first (~20 s) - `get_wallets(view?)` → `GET /api/agent/identity`, `/api/agent/wallet`, `/api/agent/wallet/balance`, or `/api/agent/wallet/abstraction`. Addresses and approval state only, never keys; 404 = no agent wallet yet - `get_portfolio_performance(view?, wallet?, start_time?, end_time?)` → `GET /api/trading/me/{portfolio|summary|fees|abstraction|ledger}`; `wallet` main (default) or agent. The ledger defaults to the last 30 days (REST `startTime` / `endTime`) - `list_brokerages()` (`api:portfolio`) → `GET /api/portfolio/stocks/session`; your SnapTrade links with `brokerCanTrade` / `canTrade` - `unlink_brokerage(connection_id? | all?)` (`api:account`) → `DELETE /api/portfolio/stocks/connections/:id` or `DELETE /api/portfolio/stocks/session` (all). Only when the user asks; never sells holdings; relinking needs a broker login ### Opportunity evaluation (`api:recommend`) Each call runs the recommendation pipeline (up to ~45 s; model calls, except when the answer comes from Gambit's view), uses one run of the SAME monthly quota `generate_recommendation` uses (403 `tier_limit_exceeded` when spent), and one of 5 calls per minute that the two tools share (429 `rate_limited` with `Retry-After`). Neither places an order. - `evaluate_opportunity(opportunity_id?, scenario_id? | security_id?, session_date?)` → `POST /api/market/opportunities/:id/evaluate` (REST body `selected_scenario_id`) or `POST /api/market/opportunities/daily-moves/evaluations` (a Daily Move that is a Big Move). `data.evaluation.outcome` is actionable, watch, pass, or insufficient; an actionable one records a recommendation (`ledger_id`). Gambit answers (switched off by default; when enabled for this deployment): a card on an asset Gambit covers is evaluated from Gambit's view plus a plan sized from your own limits — `decision_analysis` is the rendered view, `decisive_evidence` the sources of its reasons, and `gambit` the view block (`subject_id`, `version`, `view`, `plan`). actionable needs a real lean, a Hyperliquid listing, and a current view and price; watch means no edge, no listing to trade, or no ticket on this run; insufficient means no view yet (the ticker is queued). Its recommendation is advisory, so `execute_recommendation` refuses it (409 `advisory_not_executable`) - `review_opportunity_trade(opportunity_id, evaluation_id, scenario_id?, advisory_fingerprint, origin_ticket_fingerprint, source_snapshot_fingerprint)` → `POST /api/market/opportunities/:id/review-trade`; refreshes an actionable card evaluation's ticket. `data.status` ready or unavailable with `reason_code`. A preview only. An evaluation from Gambit's view (when enabled) is re-sized from the same view at the current mark; `evaluation_materially_changed` then means a new version, direction or listing, and `gambit_view_not_current`, `plan_levels_unavailable` or `instrument_authority_rejected` mean retry later ### Conversations (`api:chat`) - `list_conversations(source?, include_archived?, limit?)` → `GET /api/v1/account/agent/conversations` - `get_conversation(conversation_id, limit?)` → `GET /api/v1/account/agent/conversations/{conv_id}` - `manage_conversation(conversation_id, action, title?)` → `PATCH` (rename) or `DELETE` (permanent; only when the user asks) on the same path ### More agent-wallet money paths (`api:trade`) Same rules as propose/confirm: the agent wallet only, never the main wallet; show the preview to the human and act only after explicit approval. - `execute_recommendation(recommendation_id, leverage?, slippage_bps?)` → `POST /api/user/recommendations/:recommendationId/execute`; stages a pending recommendation's plan (TP/SL included, leverage default 1x) and places nothing — `confirm_trade` executes it. Advisory rows (every `generate_recommendation` result, and, when Gambit answers are enabled, the recommendation an `evaluate_opportunity` answer from Gambit's view records) → 409 `advisory_not_executable` - `close_all_positions(positions?)` → `POST /api/agent/close-all`. Call once with no body to preview; only after approval call again with `positions` = `confirm_echo.positions`. The server refuses with 409 `positions_changed` if they moved, else cancels each symbol's TP/SL and places reduce-only market closes - `resync_protection(symbol)` → `POST /api/agent/positions/resync-tpsl`; rescales existing TP/SL orders to the current size (cancels and re-places them). Only when the user asks - `adjust_margin(symbol, margin_usd)` → `POST /api/agent/positions/adjust-margin`; ± USD isolated margin (non-zero, |value| ≤ 10,000,000). Removing margin moves the liquidation price closer. Cross-margin → 409 `not_isolated` ### Stock orders (`api:stock_trade` — admin-only, off for now) A separate money path at the user's own linked brokerage. Every route answers 403 `trading_disabled` unless the server's brokerage-trading flag is on and the account is on the admin allowlist. Rules: US-listed stocks, ETFs and ADRs; whole shares; Market or Limit, Day only; a SELL cannot exceed the shares held; a per-order dollar cap; per-minute limits on checks and placements for API callers (429 `rate_limited`). - `propose_stock_order(account_id, symbol, action, order_type, units, limit_price?)` → `GET /api/portfolio/stocks/quote?accountId=&symbol=` then `POST /api/portfolio/stocks/orders/check` `{ accountId, symbol, action, orderType, units, limitPrice? }`. Places nothing; returns quote, preview (`check_id`, `expires_at`, masked account, `estimated_cost`, `commission`, `buying_power_after`) and `confirm_echo`. The check expires after about 5 minutes. `account_id` comes from `get_holdings` (`api:portfolio`) - `confirm_stock_order(check_id, account_id, symbol, action, units, order_type, limit_price?)` → `POST /api/portfolio/stocks/orders/place` `{ accountId, checkId, idempotencyKey, confirmEcho: { symbol, action, units, orderType, limitPrice } }` (`limitPrice` null on Market). REAL MONEY — only after the human approves that exact preview. Mismatch → 409, nothing placed; expired → 410 `check_expired`. A check places at most once: on OUTCOME UNKNOWN or a timeout, retry the SAME check, never a new proposal - `get_stock_order(account_id, order_id)` → `GET /api/portfolio/stocks/orders/:orderId?accountId=`; status pending | accepted | partially_filled | filled | rejected | cancelled | unknown ### Other REST-only reads (`api:read`) These agent-service routes accept a scoped token but have no MCP tool yet: `/api/v1/raw/{equity_bars|yields|options_flow|proxy_candles|perp_mark|perp_liquidations|short_volume}`, `/api/v1/live/scan`, `/api/v1/sentiment/{timeseries|breadth|put_call}`, `/api/v1/equities/{greek_exposure|greek_exposure/by_strike|greek_exposure/by_expiry|iv/term_structure|tide/market|tide/etf|options_agg|earnings|short_interest|short_volume}`, `/api/v1/info/{spot-assets|perps-assets|hip3-assets|all-assets|hip3-dexes|stats}`, `/api/v1/news/headlines/{count|symbols}`, `/api/v1/market-opportunities/daily-moves/{security_id}/plan`, and `/api/v1/recommendations/mandate-activity` (all `GET`). ## Agent actions (rolling out) New scoped endpoints and MCP tools are rolling out — availability is flag-gated server-side, so expect 403s (or a 400 when creating a key with a not-yet-grantable scope) until each lands: - Generate a recommendation: `POST /api/v1/account/agent/recommend` — MCP tool `generate_recommendation` (needs `api:recommend`) - Chat with your Gambit agent: `POST /api/v1/account/agent/chat` (SSE) — MCP tool `ask_gambit_agent` (needs `api:chat`) - Positions and open orders: `GET /api/v1/account/positions`, `GET /api/v1/account/orders` — MCP tools `get_positions`, `get_open_orders` - Brokerage holdings: `GET /api/portfolio/stocks/holdings` — MCP tool `get_holdings` (needs `api:portfolio`) - Trade from the agent wallet: `POST /api/v1/account/trade/propose` then `POST /api/v1/account/trade/confirm` — MCP tools `propose_trade`, `confirm_trade` (needs `api:trade`; show the proposal to the human and confirm only after explicit human approval) ## Key facts - Trading venue: Hyperliquid (mainnet). Minimum order ~$10 notional. - Instrument ids: bare Hyperliquid symbols (`BTC`, `HYPE`); HIP-3 perps use a DEX prefix (`xyz:TSLA`). Do not send `hl_perp:BTC` or `hl_spot:HYPE` — those prefixed forms do not match the public data store. Intervals: 1m/5m/15m/1h/1d. - API responses carry a `meta` envelope (generated_at, freshness, quality, caps_applied) — trust it over assumptions about data freshness. - Rate limits: data/account reads are budgeted at 120 requests/min per user, and the MCP endpoint separately enforces 120 requests/min per key plus a small concurrent-request cap. On a 429, wait the `Retry-After` seconds before retrying, and issue long calls (`ask_gambit_agent`, `generate_recommendation`) one at a time. Agent actions carry their own tighter limits (recommend 5/min plus a monthly quota; evaluate and review-trade share 5/min and use the same monthly quota; chat 10/min; fills 30/min; chat image uploads 30 per UTC day; trade executions capped hourly). - Keys and OAuth grants without `api:trade` cannot place Hyperliquid trades or move funds. With `api:trade`, trades are agent-wallet-only, two-step preview → confirm, guardrailed, and require explicit human approval before confirming. - Brokerage stock orders need `api:stock_trade`, a separate grant from `api:trade` (neither allows the other). Stock trading is admin-only and switched off for now. - `api:read` does not grant the direct brokerage-holdings endpoint or `get_holdings`; that needs `api:portfolio`. ## Learn more - App: https://app.gambit.trading - Public MCP / API page (ungated): https://www.gambit.trading/mcp - Connect guide: https://www.gambit.trading/learn/connect-mcp - Learn hub: https://www.gambit.trading/learn - FAQ: https://www.gambit.trading/learn/faq - Settings → API (create a key for headless use): https://www.gambit.trading/settings/api - Full API reference (signed-in): https://www.gambit.trading/settings/api/docs/reference