# multi — the cross-platform perp DEX aggregator (full reference) > multi is the cross-platform perp DEX aggregator. One order is quoted against every major perp > venue (Hyperliquid, Aster, Lighter, Pacifica, Pear, Avantis), routed to the books that fill it > best, and reported back as a single position under one margin view — exposed as one REST API, an > MCP server, and typed SDKs (TypeScript + Python). Cross-chain routing across 70+ networks is how > you fund venue margin, not the headline. Public market data is open; execution uses a scoped > multi_sk_ agent key whose risk policy is enforced server-side before any order reaches a venue. API base: https://multi-venym-labs.vercel.app/api Streaming (SSE): https://multi-backend.fly.dev/api (target the backend directly) MCP (remote): https://multi-venym-labs.vercel.app/mcp Docs: https://multi-venym-labs.vercel.app/docs ## Authentication Public market-data endpoints need no key. Everything that reads private account state or moves funds needs a multi agent key (multi_sk_live_... / multi_sk_test_...), sent as either header: Authorization: Bearer multi_sk_live_... x-multi-key: multi_sk_live_... Scopes (a key carries a subset): market:read Public market data (a key only raises rate limits). account:read Positions, balances, open orders for the key's subject wallet. orders:execute Place/cancel perp orders, open/close positions. pairs:execute Open/close long/short pair trades. swap:quote Quote swaps + build unsigned (non-custodial) swap transactions. swap:execute Execute cross-chain swaps headlessly via a delegated wallet. Risk policy (per key, enforced before any order is sent; omitted caps use conservative defaults): executionEnabled Master switch — defaults false. No execution until set true. maxOrderUsd Per-order notional ceiling (USD). dailyNotionalUsd Rolling 24h (UTC-day) notional cap. maxLeverage Max leverage for perp orders. venuesAllow Allowlist of venues, e.g. ["hyperliquid","lighter"]. marketsAllow/Deny Symbol allow/deny lists. swapMaxUsd Per-swap value ceiling (USD). swapChainsAllow Allowlist of chain IDs for swaps. maxSlippageBps Hard slippage cap (bps); clamps any requested slippage. ipAllow Optional IP allowlist. Keys are managed by a human owner authenticated with a multi (Dynamic) session JWT. A key always trades the owner's own wallet. ## Connect an agent harness (MCP) One-command install — @multidex/agent-kit writes/merges the harness config for you: npx @multidex/agent-kit init --key multi_sk_live_... harnesses: claude-code | claude-desktop | codex | opencode | cursor | windsurf | hermes | openclaw | gemini | generic (prints snippets) flags: --remote (hosted HTTP) | --readonly | --project (project-level config) | --dry-run multi-agent doctor connectivity/key/scope checks multi-agent tools tool catalog Every harness consumes MCP over stdio (npx -y @multidex/mcp, reads MULTI_API_KEY) or remote HTTP (https://multi-venym-labs.vercel.app/mcp, Authorization: Bearer ...). Without a key only read tools appear. Claude Code — .mcp.json: { "mcpServers": { "multi": { "type": "stdio", "command": "npx", "args": ["-y","@multidex/mcp"], "env": { "MULTI_API_KEY": "multi_sk_live_..." } } } } Codex CLI — ~/.codex/config.toml: [mcp_servers.multi] command = "npx" args = ["-y", "@multidex/mcp"] [mcp_servers.multi.env] MULTI_API_KEY = "multi_sk_live_..." OpenCode — opencode.json: { "mcp": { "multi": { "type": "local", "command": ["npx","-y","@multidex/mcp"], "enabled": true, "environment": { "MULTI_API_KEY": "multi_sk_live_..." } } } } Hermes Agent — ~/.hermes/config.yaml: mcp_servers: multi: command: "npx" args: ["-y", "@multidex/mcp"] env: { MULTI_API_KEY: "multi_sk_live_..." } enabled: true OpenClaw — ~/.openclaw/openclaw.json: { "mcp": { "servers": { "multi": { "command": "npx", "args": ["-y","@multidex/mcp"], "env": { "MULTI_API_KEY": "multi_sk_live_..." } } } } } Cursor — ~/.cursor/mcp.json (or project .cursor/mcp.json): { "mcpServers": { "multi": { "command": "npx", "args": ["-y","@multidex/mcp"], "env": { "MULTI_API_KEY": "multi_sk_live_..." } } } } Windsurf — ~/.codeium/windsurf/mcp_config.json: same stdio shape; remote uses "serverUrl". Gemini CLI — ~/.gemini/settings.json: same stdio shape; remote uses "httpUrl" + headers. ## MCP tools Public (no key): get_markets(search?) List tradable perp markets. get_ticker(symbol,exchange?) Latest price + 24h stats. get_orderbook(symbol,full?) Aggregated bids/asks across every venue. get_candles(symbol,interval?,limit?) OHLCV candles. get_trades(symbol,limit?) Recent prints. get_routing(symbol,side?) Best venue for an order + the alternatives it beat. get_market_stats(symbol) 24h volume, funding, OI. watch_trades(symbol,durationMs?,max?) Bounded (<=10s) burst of live trades. watch_orderbook(symbol,durationMs?,max?) Bounded (<=10s) burst of book updates. Account (scope account:read): get_positions() get_balances() get_orders() get_portfolio() One-call cross-venue portfolio: positions, PnL, funding, risk. list_strategies(status?) get_strategy(id) Execution (scopes orders:execute / pairs:execute): execute_order(symbol,side,type,quantity,price?,reduceOnly?,preferredExchange?) cancel_order(venue,orderId) batch_orders(orders[]) Up to 10 orders, per-order policy checks. open_position(symbol,direction,size,leverage?,orderType,limitPrice?,takeProfitPrice?,stopLossPrice?) close_position(symbol,direction,size,orderType?,limitPrice?) create_twap_order(symbol,side,totalQuantity,sliceCount,intervalMs,reduceOnly?,preferredExchange?) create_scaled_order(symbol,side,totalQuantity,sliceCount,priceLow,priceHigh,reduceOnly?,preferredExchange?) cancel_strategy(id) Stops future TWAP slices. open_pair_position(longSymbol,shortSymbol,notionalUsd,leverage,slippage?,venue?) close_pair_position(id) Swaps (scopes swap:quote / swap:execute): swap_quote(fromChain,toChain,fromToken,toToken,fromAmount,fromAddress?,toAddress?) swap_prepare(quoteId) -> UNSIGNED tx, sign yourself (non-custodial) swap_execute(quoteId) -> headless via delegated wallet get_swap_status(swapId) Resources: multi://llms.txt, multi://markets, multi://ticker/{symbol} Prompts: analyze_market(symbol), plan_swap(fromToken,toToken,amount), review_portfolio(), plan_position(symbol) ## REST API Responses use { success, data, timestamp } (a few data endpoints return a raw array). Execution endpoints accept an Idempotency-Key header (strongly recommended for retries). Market data (public): GET /token-pairs GET /aggregated/book?symbol=BTC GET /aggregated/candles?symbol=BTC&interval=1h&limit=200 (raw array) GET /aggregated/routing?symbol=BTC&side=BUY GET /orderbook/:symbol (+ /full, /metrics) GET /trades/:symbol?limit=100 (+ /metrics) GET /charts/:symbol/candles?interval=1h GET /market-data/symbols | /trending | /search | /:symbol/stats GET /trading/:exchange/ticker/:symbol Streaming (SSE; events: book | bar | trade): GET /aggregated/stream?symbol=BTC GET /aggregated/stream/candles?symbol=BTC&interval=1m GET /aggregated/stream/trades?symbol=BTC Account (scope account:read): GET /agent/positions GET /agent/balances GET /agent/orders GET /agent/pair GET /agent/portfolio One-call cross-venue portfolio: normalized positions[] (venue, symbol, direction, size, entry/mark price, notionalUsd, unrealizedPnlUsd, leverage, liquidationPrice + distance %, fundingRate, estFundingUsdPerDay), pairPositions[], summary, byVenue[], bySymbol[], errors Orders & positions (scope orders:execute): POST /agent/orders { symbol, side, type, quantity, price?, triggerPrice?, reduceOnly?, preferredExchange? } POST /agent/orders/batch { orders: [<=10 of PlaceOrder] } -> { results[], total, succeeded, failed } (sequential, per-order policy checks; one failure doesn't abort the rest) POST /agent/positions/open { symbol, direction, size, leverage?, orderType, limitPrice?, preferredExchange?, takeProfitPrice?, stopLossPrice? } -> with TP/SL, server places reduce-only TP (limit) / SL (stop-market) after the entry fills; response gains bracket {...} POST /agent/positions/close { symbol, direction, size, orderType?, limitPrice? } DELETE /agent/orders/:venue/:orderId Execution strategies (create/cancel: orders:execute; reads: account:read; full notional policy-checked up-front; SSE events stream emits strategy_update): POST /agent/strategies { kind: "twap"|"scaled", symbol, side, totalQuantity, sliceCount (2-50), intervalMs (twap, 5000-3600000), priceLow/priceHigh (scaled), reduceOnly?, preferredExchange? } twap = server-run timed market slices; scaled = ladder of limit orders GET /agent/strategies?status= GET /agent/strategies/:id DELETE /agent/strategies/:id Stops future TWAP slices (cancel resting scaled rungs via DELETE /agent/orders/...) Pair trades (scope pairs:execute): POST /agent/pair/open { longSymbol, shortSymbol, notionalUsd, leverage, slippage?, venue? } POST /agent/pair/:id/close Swaps (scopes swap:quote / swap:execute): POST /agent/swap/quote { fromChain, toChain, fromToken, toToken, fromAmount, fromAddress?, toAddress? } POST /agent/swap/prepare { quoteId } -> unsigned tx POST /agent/swap/execute { quoteId } -> headless GET /agent/swap/:swapId Key management (owner JWT): POST /agent/keys { label, scopes, policy } -> raw secret returned once GET /agent/keys | /agent/keys/:id PATCH /agent/keys/:id POST /agent/keys/:id/rotate DELETE /agent/keys/:id GET /agent/keys/:id/usage ## SDK quickstart TypeScript (npm i @multidex/sdk): import { MultiClient } from "@multidex/sdk"; const multi = new MultiClient({ apiKey: process.env.MULTI_API_KEY }); const book = await multi.markets.getOrderbook("BTC"); const order = await multi.orders.place({ symbol:"BTC", side:"BUY", type:"MARKET", quantity:"0.01" }); const portfolio = await multi.portfolio.get(); const batch = await multi.orders.placeBatch([{ symbol:"BTC", side:"BUY", type:"MARKET", quantity:"0.01" }]); const twap = await multi.strategies.create({ kind:"twap", symbol:"BTC", side:"BUY", totalQuantity:"0.1", sliceCount:10, intervalMs:60000 }); // also list/get/cancel for await (const ev of multi.streams.trades("BTC")) { console.log(ev.data); break; } Python (pip install multidex-sdk): from multidex import MultiClient multi = MultiClient(api_key=os.environ["MULTI_API_KEY"]) book = multi.markets.get_orderbook("BTC") order = multi.orders.place(symbol="BTC", side="BUY", type="MARKET", quantity="0.01") portfolio = multi.portfolio.get() batch = multi.orders.place_batch([{"symbol":"BTC","side":"BUY","type":"MARKET","quantity":"0.01"}]) twap = multi.strategies.create({"kind":"twap","symbol":"BTC","side":"BUY", "totalQuantity":"0.1","sliceCount":10,"intervalMs":60000}) # also list/get/cancel ## Errors 401 UNAUTHORIZED · 403 INSUFFICIENT_SCOPE · 403 EXECUTION_DISABLED · 403 ORDER_TOO_LARGE / LEVERAGE_TOO_HIGH / VENUE_NOT_ALLOWED / MARKET_NOT_ALLOWED · 403 SWAP_TOO_LARGE / CHAIN_NOT_ALLOWED · 429 DAILY_CAP · 429 RATE_LIMITED · 400 VALIDATION_ERROR Safety: quotes and prepared swaps never move funds; execution is fail-closed (off until policy.executionEnabled is true and the scope is granted); a key only ever trades the owner's wallet; pass an Idempotency-Key so retries never double-submit.