# Rivo API documentation > A REST API and Server-Sent Events stream for prediction markets on Kalshi, Polymarket, Polymarket US and Gemini: markets, prices, order books, trades, market moves, trader records, backtests, paper trading and paper bots. This file holds every page of https://rivo.markets/docs as Markdown, in reading order. ## Instructions for coding agents Use the Rivo API v2. Full docs: https://rivo.markets/docs/llms-full.txt (one file) or https://rivo.markets/docs/llms.txt (an index of Markdown pages). - Base URL: https://api.rivo.markets/v2. Send `Authorization: Bearer $RIVO_API_KEY`. Read the key from the environment; never write it into code. - A success is `{ "data": ... }`, plus `nextCursor` on a list. A failure is `{ "success": false, "error", "code" }` with an HTTP status. Branch on `code`, never on `error`. - Markets and traders are named by refs. Take refs from responses and send them back unchanged. A market ref is opaque; never build or parse one. A trader ref is `platform:traderId`, such as `kalshi:apexnyc`. - Page with `limit` (up to 200) and `cursor` = the last response's `nextCursor`, with the same filters. Stop when `nextCursor` is null. There are no offsets. - Pass several values by repeating the key: `?venue=kalshi&venue=polymarket`. Unknown parameters are refused, so use only the names in the docs. - Prices are 0 to 1, money is USD, times are ISO 8601 UTC. Win rates are null under 10 settled bets. - `outcome` filters need exactly one `market`. Paper orders and market bots take a contract ref (`outcomes[].ref` of `GET /v2/markets/{ref}`), not an event ref. - Limits per key: 120 requests a minute (each `include` and each sweep value counts), 50,000 trades a day, 3 streams, trades from the last 30 days. Read `x-ratelimit-remaining`; on 429 wait `retry-after` seconds. - For anything live use `GET /v2/stream` (Server-Sent Events, events `trade` and `move`) instead of polling. After a reconnect, fill the gap with `GET /v2/trades?since=` and dedupe by `id`. - Send an `Idempotency-Key` header with `POST /v2/paper-orders` and reuse it on retries. - Do not use /v1: it is deprecated and stops answering on December 14, 2026. - Use only fields the docs list. If a field you need is not there, it does not exist. --- # rivomarkets API > A REST API and live stream for prediction markets on Kalshi, Polymarket, Polymarket US and Gemini. Source: https://rivo.markets/docs The Rivo API gives you programmatic access to prediction markets on Kalshi, Polymarket, Polymarket US and Gemini. Use it to search markets and read their prices, order books and price history, track top whale trades, follow market moves as they happen, and look up traders and their records. You can also backtest strategies, place paper trades and run paper bots. Your first call: ```bash curl "https://api.rivo.markets/v2/trades?limit=5" \ -H "Authorization: Bearer $RIVO_API_KEY" ``` ## Start here - [Quickstart](https://rivo.markets/docs/quickstart): Create a key and make your first three calls in five minutes. - [Core concepts](https://rivo.markets/docs/concepts): Learn how refs, cursors, filters, units and the response envelope work. - [Build with AI](https://rivo.markets/docs/build-with-ai): Give your coding agent llms.txt, Markdown pages and instructions to follow. - [Migrate from v1](https://rivo.markets/docs/migrate-from-v1): See where each v1 route went and when v1 stops answering. ## What you can build - [Trades](https://rivo.markets/docs/reference/list-trades): List large trades from the last 30 days and filter them by venue, kind of market, size, price and the trader's record. - [Stream](https://rivo.markets/docs/reference/stream): Receive trades and market moves the moment they land, filtered on one connection. - [Markets](https://rivo.markets/docs/reference/list-markets): Search markets on every venue, then read one with its book, whales, history and call. - [Traders](https://rivo.markets/docs/reference/list-traders): Rank traders by profit, win rate or return, overall or in one kind of market. - [Backtest](https://rivo.markets/docs/reference/backtest): Score a signal or a trader against settled history, with a holdout, a sweep or a breakdown. - [Paper and bots](https://rivo.markets/docs/reference/create-bot): Place paper trades against live order books and run trader, signal and market bots on paper. ## The basics | | | | --- | --- | | Base URL | `https://api.rivo.markets/v2` | | Auth | Send `Authorization: Bearer rivo_live_...` with a key from [Settings](https://rivo.markets/settings?tab=developer). | | Format | Requests and responses use JSON over HTTPS. The stream uses Server-Sent Events. | | Success | A success returns `{ "data": ... }`, and a list adds `nextCursor`. | | Failure | A failure returns `{ "success": false, "error": "...", "code": "..." }` with a matching HTTP status. | | Limits | Each key can make 120 requests a minute, read 50,000 trades a day and hold 3 streams open. Trades reach back 30 days. | Every Rivo subscription includes the API. A key works as long as the account that created it stays subscribed. Rivo is not an exchange. Paper orders and bots trade against live venue order books, but they never move money. --- # Quickstart > Create an API key, then list trades, open a market and backtest an idea in three calls. Source: https://rivo.markets/docs/quickstart ## 1. Create a key Create an API key in [Settings](https://rivo.markets/settings?tab=developer). Choose the `read` scope to read data, and add `write` to place paper orders or change bots. Rivo shows the key only once, so copy it into an environment variable. Never commit it to source control. ```bash export RIVO_API_KEY=rivo_live_... ``` ## 2. List recent trades This call lists the newest large buys on Kalshi from traders who win at least 60% of their bets in that kind of market. curl: ```bash curl "https://api.rivo.markets/v2/trades?venue=kalshi&minWinRate=0.6&limit=10" \ -H "Authorization: Bearer $RIVO_API_KEY" ``` Python: ```python import os import requests res = requests.get( "https://api.rivo.markets/v2/trades", headers={"Authorization": f"Bearer {os.environ['RIVO_API_KEY']}"}, params={"venue": "kalshi", "minWinRate": 0.6, "limit": 10}, ) res.raise_for_status() for trade in res.json()["data"]: print(trade["market"]["title"], trade["outcome"], trade["usd"]) ``` TypeScript: ```typescript const res = await fetch( "https://api.rivo.markets/v2/trades?venue=kalshi&minWinRate=0.6&limit=10", { headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}` } }, ); const { data } = await res.json(); for (const trade of data) console.log(trade.market.title, trade.outcome, trade.usd); ``` Response: ```json { "data": [ { "id": "f1c6b941-64bf-5911-88ef-1b5de17e9d76", "time": "2026-10-15T02:25:20.514Z", "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWEJUQzE1TS0yNk9DVDE0MjIzMC0zMA", "contract": "a2Fsc2hpOm1hcmtldDpLWEJUQzE1TS0yNk9DVDE0MjIzMC0zMA", "title": "BTC price up in next 15 mins?", "venue": "kalshi", "category": "crypto", "kind": "bitcoin" }, "outcome": "No", "action": "buy", "entry": null, "price": 0.992, "usd": 1008.86, "contracts": 1017, "trader": { "ref": "kalshi:prokingpriest", "name": "prokingpriest", "winRate": 0.974, "profit": 14118.88, "settled": 16521 }, "agreement": { "sameSide": { "trades": 9, "usd": 13130.67 }, "otherSide": { "trades": 2, "usd": 2501.92 } }, "result": "open", "profit": null } ], "nextCursor": "eyJ0IjoiMjAyNi0..." } ``` Every trade includes a `ref` for its market. Routes use refs to name markets and traders. Copy a ref from one response and send it unchanged in your next call. ## 3. Open the market Pass a market's ref to read that market. Add `include=whales` to see who bought each side, and `include=call` to see the call Rivo makes on it. Each include counts as one request toward your limit of 120 a minute, so this call uses three: one for the market and one for each include. ```bash curl "https://api.rivo.markets/v2/markets/?include=whales&include=call" \ -H "Authorization: Bearer $RIVO_API_KEY" ``` ## 4. Backtest an idea This backtest scores a signal against 90 days of settled trades. The signal picks large buys on sports markets priced between 40 and 70 cents and stakes $100 on each one. ```bash curl -X POST "https://api.rivo.markets/v2/backtest" \ -H "Authorization: Bearer $RIVO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "signal": { "category": ["sports"], "priceMin": 0.4, "priceMax": 0.7 }, "sizing": { "usd": 100 }, "days": 90 }' ``` If you like the result, send the same signal to [Create Bot](https://rivo.markets/docs/reference/create-bot). The bot starts paper trading on the next matching trade. ## Next - [Core concepts](https://rivo.markets/docs/concepts): Learn how refs, paging and filters work on every route. - [Stream](https://rivo.markets/docs/streaming): Receive trades and moves as they happen instead of polling. - [Build with AI](https://rivo.markets/docs/build-with-ai): Hand these docs to Claude, Cursor or any agent. --- # Authentication > Send your key as a bearer token. Scopes decide what it can change, and the account's subscription decides whether it works. Source: https://rivo.markets/docs/authentication Send your API key in the `Authorization` header of every request. You create keys in [Settings](https://rivo.markets/settings?tab=developer), and every key starts with `rivo_live_`. ``` Authorization: Bearer rivo_live_... ``` ## Scopes | Scope | Allows | | --- | --- | | `read` | The key can call every `GET` route: trades, positions, moves, the stream, markets, traders, the paper account and bots. It can also call `POST /v2/backtest`, which reads history and changes nothing. | | `write` | The key can do everything a `read` key can. It can also place paper orders and create, change and delete bots. | You choose a key's scopes when you create it, and you cannot change them later. To add a scope, create a new key and revoke the old one. A v1 key with the `trade` scope works as a `write` key on v2. ## Subscription A key works only while the account that created it has an active Rivo subscription. If the subscription lapses, the API answers every request with `403 SUBSCRIPTION_INACTIVE`. The same key starts working again as soon as the subscription is renewed, so you never need to replace it. ## Failures | Status | Code | Means | | --- | --- | --- | | 401 | `UNAUTHORIZED` | The request has no `Authorization` header, or the key is wrong or revoked. | | 403 | `SUBSCRIPTION_INACTIVE` | The key is valid, but its account has no active subscription. | | 403 | `SCOPE_REQUIRED` | The route changes data, and the key does not have the `write` scope. | ## Streaming to a browser Never send your API key to a browser. Anyone who opens the page can read it. Instead, your server opens the stream with the key and passes the frames on to the browser. The API accepts the key only in the `Authorization` header, never in the URL. Your server (a Next.js route handler): ```typescript // app/api/rivo-stream/route.ts export async function GET(request: Request): Promise { // Check that the visitor is signed in to your app here. const upstream = await fetch(`https://api.rivo.markets/v2/stream${new URL(request.url).search}`, { headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}` }, signal: request.signal, }); return new Response(upstream.body, { status: upstream.status, headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" }, }); } ``` The browser: ```typescript const events = new EventSource("/api/rivo-stream?channels=trades&venue=kalshi"); events.addEventListener("trade", (event) => { const trade = JSON.parse(event.data); console.log(trade.market.title, trade.outcome, trade.usd); }); ``` Each key can hold three streams open at once. If many visitors watch at the same time, open one stream on your server and send its frames to every visitor. ## Keeping keys safe - Store keys in environment variables or a secrets manager. Never put a key in client-side code or a repository. - Create one key for each app or agent, with the smallest scope it needs, so you can revoke one key without breaking the others. - Revoke a key as soon as you think it may have leaked. Revoking takes effect immediately. --- # Core concepts > Every v2 route follows the same rules for responses, refs, cursors, filters, includes and units. Source: https://rivo.markets/docs/concepts ## Responses A successful request returns an HTTP 2xx status with the result under `data`. A list also returns `nextCursor`. Successful responses have no `success` flag, because the HTTP status already tells you the request worked. One item: ```json { "data": { "ref": "a2Fsc2hpOm1hcmtldDpLWE5CQS0yNi1MQUw", "title": "Lakers vs Celtics" } } ``` A list: ```json { "data": [ { "id": "..." }, { "id": "..." } ], "nextCursor": "eyJ0IjoiMjAyNi0xMC0xNCAxODowMjoxMS4..." } ``` A failure: ```json { "success": false, "error": "minWinRate: Number must be less than or equal to 1", "code": "VALIDATION" } ``` Every failure includes a stable `code`. Write your error handling against the code, not the message. The [Errors](https://rivo.markets/docs/errors) page lists every code. ## Refs A ref is a string that names a market or a trader. Every route returns refs and accepts them, so you never need to build an id yourself. Store a ref, compare it and send it back unchanged. - **Market refs** are opaque: treat one as a token and never parse it. They come in two kinds. A *contract* ref names one tradable contract. An *event* ref names a market with several outcomes, such as an election or a championship. When you call `GET /v2/markets/{ref}` with an event ref, the response lists each outcome with its own contract ref under `outcomes[].ref`. - **Trader refs** name one trader on one venue and read as `platform:traderId`: a wallet address on Polymarket (`polymarket:0x...`) and a username on Kalshi (`kalshi:apexnyc`). Polymarket US and Gemini do not publish who made each trade, so their trades have no trader ref and return `trader: null`. - Routes that act on a single contract, such as paper orders and market bots, need a contract ref. They refuse an event ref with `VALIDATION`. ## Paging List routes return results in pages. Set `limit` to choose the page size, from 1 to 200. The default is 50. To read the next page, send the previous response's `nextCursor` as `cursor`, along with the same filters. On the last page, `nextCursor` is `null`. Read every page: ```python cursor = None while True: params = {"venue": "kalshi", "limit": 200} if cursor: params["cursor"] = cursor body = requests.get(f"https://api.rivo.markets/v2/trades", headers=headers, params=params).json() handle(body["data"]) cursor = body["nextCursor"] if cursor is None: break ``` A cursor works only on the route that returned it. If you send it to another route or change it, the API refuses the request with `VALIDATION`. The API does not support offsets. ## Filters Each filter has the same name and meaning on every route that accepts it. The API refuses unknown parameters with `VALIDATION` instead of ignoring them, so a typo fails loudly rather than returning everything. | Filter | Values | | --- | --- | | `venue` | `kalshi` (Kalshi), `polymarket` (Polymarket), `polymarket_us` (Polymarket US), `gemini` (Gemini) | | `category` | `sports`, `politics`, `crypto`, `finance`, `culture` | | `kind` | A kind of market. The table below lists every kind. | | `trader` | A trader ref. | | `market` | A market ref, for either a contract or an event. | | `outcome` | A side of the market, such as `Yes` or `Lakers`. You can also pass the side's `label` from Get Market, such as `Milwaukee`. It works only together with exactly one `market`. | To pass several values, repeat the parameter: `?venue=kalshi&venue=polymarket`. You can pass up to 50 values, and a row matches if it matches any one of them. When you combine different filters, a row must match all of them. ### Kinds of market | Category | Kinds | | --- | --- | | Sports | `nfl`, `college_football`, `nba`, `wnba`, `college_basketball`, `basketball`, `mlb`, `nhl`, `soccer`, `tennis`, `golf`, `mma`, `boxing`, `motorsport`, `cricket`, `esports`, `chess`, `rugby`, `olympics`, `parlay`, `other_sports` | | Politics | `us_elections`, `us_politics`, `world_politics`, `other_politics` | | Crypto | `bitcoin`, `ethereum`, `solana`, `xrp`, `other_crypto` | | Finance | `fed_rates`, `economic_data`, `stocks_indices`, `companies`, `commodities`, `fx`, `other_finance` | | Culture | `mentions`, `weather`, `entertainment`, `tech_ai`, `health`, `other_culture` | ## Includes Routes that return a single market, trader or bot also accept `include`, which adds extra sections to the response. You can separate includes with commas, as in `?include=book,whales`, or repeat the parameter, as in `?include=book&include=whales`. Each include counts as one request toward your limit of 120 a minute, so `?include=book,whales` uses three. Ask only for the sections you use. ## Units and time | Value | Unit | | --- | --- | | Prices | Prices run from 0 to 1. A price of `0.62` means 62 cents, or a 62% chance. | | Money | Amounts such as `usd`, `profit` and `cost` are in US dollars, sent as numbers. | | Times | Times use ISO 8601 in UTC, such as `2026-10-14T18:02:11.204Z`. Send times in the same format. | | Win rates and returns | These run from 0 to 1, so `0.58` means 58%. A win rate stays `null` until the trader has 10 settled bets. | ## History The API returns individual trades from the last 30 days. If you send a `since` older than that, the API reads from 30 days ago instead. Backtests can score up to 365 days of history, and they return scores rather than individual trades. --- # Venues > The four venues the API covers, the value you pass for each, and which ones name their traders. Source: https://rivo.markets/docs/venues The API covers four prediction market venues. Pass a venue's value in the `venue` filter on any route to narrow the results to that venue. | Venue | Value | Region | Names traders | | --- | --- | --- | --- | | Kalshi | `kalshi` | US | Yes | | Polymarket | `polymarket` | Global | Yes | | Polymarket US | `polymarket_us` | US | No | | Gemini | `gemini` | US | No | ## Venues that name their traders Kalshi and Polymarket publish who made each trade. Their trades include a `trader` with a ref and a record, and you can look those traders up with [Get Trader](https://rivo.markets/docs/reference/get-trader) or copy them with a trader bot. Polymarket US and Gemini publish trades without names. Their trades return `trader: null`, but they still appear in trades, moves, markets, backtests and signal bots. ## Polymarket and Polymarket US are separate Polymarket (`polymarket`) and Polymarket US (`polymarket_us`) are separate venues with their own markets, prices and order books. The same question on each one is a different market with its own ref. ## Calling a venue directly Rivo's API covers all four venues with one key. If you also call a venue's own API, read the [Kalshi API guide](https://rivo.markets/kalshi-api) and the [Polymarket API guide](https://rivo.markets/polymarket-api). They cover keys, request signing and rate limits. --- # Errors > Every failure returns an HTTP status and a stable error code to check. Source: https://rivo.markets/docs/errors Failure body: ```json { "success": false, "error": "cursor: not a cursor from this listing", "code": "VALIDATION" } ``` The `error` message is written for people and may change. The `code` never changes, so your error handling should check the code. A `VALIDATION` message names the field that failed, such as `signal.priceMax`. ## Codes | Code | Status | Means | Retry | | --- | --- | --- | --- | | `UNAUTHORIZED` | 401 | The request has no `Authorization` header, or the key is wrong or revoked. | No | | `SUBSCRIPTION_INACTIVE` | 403 | The key is valid, but its account has no active subscription. The key works again as soon as the subscription is renewed. | No | | `SCOPE_REQUIRED` | 403 | The route changes data, and the key does not have the `write` scope. Create a key that has it. | No | | `VALIDATION` | 400 | A parameter or the body is invalid, or the request includes an unknown parameter. The message names the field. | No | | `NOT_FOUND` | 404 | The market, trader, bot or order does not exist on this account. The API gives the same answer for something you do not own. | No | | `IDEMPOTENCY_IN_PROGRESS` | 409 | A request with the same `Idempotency-Key` is still running. Wait, then send it again with the same key. | After a wait | | `QUOTA_EXCEEDED` | 429 | You sent more than 120 requests this minute, read more than 50,000 trades today, or opened a fourth stream. The `retry-after` header says how many seconds to wait. | After a wait | | `UPSTREAM_FAILED` | 502 | A venue did not answer, for example while Rivo read its live order book. Rivo recorded nothing. | Yes | | `INTERNAL` | 500 | Something failed on Rivo's side. Retry with backoff, and contact us if it keeps happening. | Yes | ## Handling failures ```typescript const res = await fetch(url, { headers }); if (res.ok) return (await res.json()).data; const { code, error } = await res.json(); switch (code) { case "QUOTA_EXCEEDED": await sleep(Number(res.headers.get("retry-after") ?? 60) * 1000); return retry(); case "UPSTREAM_FAILED": case "INTERNAL": return retryWithBackoff(); default: // UNAUTHORIZED, SCOPE_REQUIRED, VALIDATION, NOT_FOUND: fix the request, do not resend it. throw new Error(`${code}: ${error}`); } ``` ## Retrying a paper order Send an `Idempotency-Key` header with `POST /v2/paper-orders`, and reuse the same key every time you retry that order. A retry with the same key returns the first result instead of placing the trade twice. If you send the same key with a different body, the API refuses it with `VALIDATION`. If the API refuses an order, you can use its key again for another try. --- # Rate limits > Each key can make 120 requests a minute, read 50,000 trades a day and hold three streams open at once. Source: https://rivo.markets/docs/rate-limits | Limit | Per key | Resets | | --- | --- | --- | | Requests | 120 a minute | Every minute | | Trades read | 50,000 a day, across `/v2/trades`, `include=trades` and the stream | Midnight UTC | | Streams open at once | 3, shared with `/v1/stream` | When one closes | | Trade history | The last 30 days | Rolling | ## What counts as a request - Every call is one request. - Each `include` counts as one more request. - A backtest `sweep` counts one request for each value it scores. - An open stream counts as one request for each minute it stays open. ## Headers Every response tells you how much of your limit is left, so your client can slow down before it hits the limit. | Header | Means | | --- | --- | | `x-ratelimit-limit` | The number of requests you can make each minute. | | `x-ratelimit-remaining` | The number of requests you have left this minute. | | `x-ratelimit-reset` | The number of seconds until the minute resets. | | `retry-after` | On a 429, the number of seconds to wait before you try again. | | `x-trades-limit` | On trade listings, the number of trades you can read each day. | | `x-trades-remaining` | On trade listings, the number of trades you have left today. | | `x-trades-reset` | On trade listings, the number of seconds until midnight UTC. | ## When you hit a limit When you go over a limit, the API answers `429 QUOTA_EXCEEDED`. Wait the number of seconds in `retry-after`, then send the same request again. If a stream uses up the day's trades, it sends an `error` event with the same code and then closes. ## Staying under the limits - Use the [stream](https://rivo.markets/docs/streaming) for live data instead of polling. - Set `limit=200` when you read many pages. Fewer, larger pages use fewer requests. - Filter on the server with `minWinRate`, `minSettled` and `minUsd`. The API does not send or count trades that your filters leave out. - Cache what single-market and single-trader routes return. Those records change slowly, so caching them for a few minutes is safe. --- # Streaming > The stream sends you trades and market moves the moment they land, over Server-Sent Events, with the same filters as the list routes. Source: https://rivo.markets/docs/streaming `GET /v2/stream` keeps one HTTP connection open and sends each new trade and market move the moment it lands. Each frame has the same shape as a row from [List Trades](https://rivo.markets/docs/reference/list-trades) or [List Moves](https://rivo.markets/docs/reference/list-moves). Rivo applies your filters before it sends anything, so you receive only what you asked for. ```bash curl -N "https://api.rivo.markets/v2/stream?channels=trades&venue=kalshi&minWinRate=0.6" \ -H "Authorization: Bearer $RIVO_API_KEY" ``` ## Frames | Event | Carries | | --- | --- | | `trade` | One trade, in the same shape as a `/v2/trades` row. | | `move` | One market move, in the same shape as a `/v2/moves` row. | | `error` | An object with `code` and `error`. Rivo sends it just before it closes the stream, for example when you have used up the day's trades. | | `: ping` | A comment line that Rivo sends every 25 seconds so proxies keep the connection open. You can ignore it. | ## One market, everything on it The `market` filter applies to both channels. Send `?market=` to receive every trade and every move on one market over a single connection. Repeat `market` to watch up to 50 markets. ## Reconnecting Connections drop when we deploy, when a network fails, or when a laptop goes to sleep. Save the time of the last frame you handled. When the connection drops, reconnect after a short backoff, then fetch the trades you missed from the list route with the same filters. ```typescript let lastSeen = new Date().toISOString(); async function fillGap(): Promise { const res = await fetch(`https://api.rivo.markets/v2/trades?venue=kalshi&since=${lastSeen}&limit=200`, { headers }); for (const trade of (await res.json()).data.reverse()) handle(trade); } // On every trade frame: lastSeen = trade.time // After a reconnect: await fillGap(), then keep reading the stream. ``` Remove duplicates by `id`, because a trade that arrived just before the drop can appear again when you fetch the gap. ## Limits - Each key can hold three streams open at once, including any on `/v1/stream`. The API refuses a fourth with `429 QUOTA_EXCEEDED`. - An open stream counts as one request a minute. - Each trade the stream sends counts toward your 50,000 trades a day. Moves do not count. The [Stream reference](https://rivo.markets/docs/reference/stream) lists every parameter and shows a full client in three languages. --- # Build with AI > Give Claude, Cursor, Codex or any agent the whole API in one file, every page as Markdown, and a set of instructions to follow. Source: https://rivo.markets/docs/build-with-ai We wrote these docs for both people and AI models. Every page has a Markdown version, and the whole reference fits in one file. You can also paste a set of instructions into your agent so the code it writes uses the API correctly. ## llms.txt | File | Holds | | --- | --- | | [/docs/llms.txt](https://rivo.markets/docs/llms.txt) | This file indexes every docs page, with a one-line summary and a link to the page's Markdown. Point an agent here if it can fetch pages as it needs them. | | [/docs/llms-full.txt](https://rivo.markets/docs/llms-full.txt) | This file holds every page in one Markdown file, including the guides and every endpoint with its parameters, fields and examples. Load it into your agent's context once. | These files cover only the developer docs. The site's main [/llms.txt](https://rivo.markets/llms.txt) covers the rest of Rivo, including its markets and traders. ## Every page as Markdown Add `.md` to any docs URL to get the page as Markdown, for example [/docs/reference/list-trades.md](https://rivo.markets/docs/reference/list-trades.md). The overview lives at [/docs/index.md](https://rivo.markets/docs/index.md). Every page also has a **Copy page** button, which copies the page as Markdown or opens it in Claude or ChatGPT. ## Instructions for your agent Paste this into your agent's project instructions: `CLAUDE.md` for Claude Code, `AGENTS.md` for Codex, `.cursor/rules` for Cursor, or the system prompt of your own agent. AGENTS.md: ``` # Rivo API: instructions for coding agents Use the Rivo API v2. Full docs: https://rivo.markets/docs/llms-full.txt (one file) or https://rivo.markets/docs/llms.txt (an index of Markdown pages). - Base URL: https://api.rivo.markets/v2. Send `Authorization: Bearer $RIVO_API_KEY`. Read the key from the environment; never write it into code. - A success is `{ "data": ... }`, plus `nextCursor` on a list. A failure is `{ "success": false, "error", "code" }` with an HTTP status. Branch on `code`, never on `error`. - Markets and traders are named by refs. Take refs from responses and send them back unchanged. A market ref is opaque; never build or parse one. A trader ref is `platform:traderId`, such as `kalshi:apexnyc`. - Page with `limit` (up to 200) and `cursor` = the last response's `nextCursor`, with the same filters. Stop when `nextCursor` is null. There are no offsets. - Pass several values by repeating the key: `?venue=kalshi&venue=polymarket`. Unknown parameters are refused, so use only the names in the docs. - Prices are 0 to 1, money is USD, times are ISO 8601 UTC. Win rates are null under 10 settled bets. - `outcome` filters need exactly one `market`. Paper orders and market bots take a contract ref (`outcomes[].ref` of `GET /v2/markets/{ref}`), not an event ref. - Limits per key: 120 requests a minute (each `include` and each sweep value counts), 50,000 trades a day, 3 streams, trades from the last 30 days. Read `x-ratelimit-remaining`; on 429 wait `retry-after` seconds. - For anything live use `GET /v2/stream` (Server-Sent Events, events `trade` and `move`) instead of polling. After a reconnect, fill the gap with `GET /v2/trades?since=` and dedupe by `id`. - Send an `Idempotency-Key` header with `POST /v2/paper-orders` and reuse it on retries. - Do not use /v1: it is deprecated and stops answering on December 14, 2026. - Use only fields the docs list. If a field you need is not there, it does not exist. ``` ## A good first prompt ``` Read https://rivo.markets/docs/llms-full.txt. Then write a TypeScript script that opens the Rivo v2 stream for Kalshi trades by traders with a win rate of at least 0.6, prints each trade's market title, outcome, price and dollars, and reconnects with a gap fill from /v2/trades when the connection drops. Read the API key from RIVO_API_KEY. ``` ## MCP If you want an assistant to call Rivo directly instead of writing code against it, connect the [MCP server](https://rivo.markets/docs/mcp). It uses the same API keys. --- # Migrate from v1 > v1 is deprecated and stops answering on December 14, 2026. This guide shows where each v1 route went and what to change. Source: https://rivo.markets/docs/migrate-from-v1 > **v1 stops answering:** Rivo deprecated v1 on October 15, 2026, and v1 stops answering on December 14, 2026. Your existing keys already work on v2. We built v2 around what the API does best: reading trades and market moves, looking up markets and traders, testing ideas and running them on paper. Most v1 calls have a direct replacement in v2, and several v1 calls now share a single v2 route. ## What changes in every call | | v1 | v2 | | --- | --- | --- | | Base | `/v1` | `/v2` | | Success body | `{ "success": true, "data": ... }` | `{ "data": ... }`, plus `nextCursor` on a list | | Failure body | `{ "success": false, "error", "code" }` | The same, with the same codes | | Paging | Offsets on some routes | `limit` and `cursor` everywhere | | Traders | `/{platform}/{traderId}` in the path | A trader ref, like markets | | Several values | Comma lists or arrays in a body | Repeat the query key | | Scopes | `read`, `write`, `trade` | `read` and `write`; a `trade` key works as `write` | ## Where each v1 route went | v1 | v2 | | --- | --- | | `GET /v1/feed`, `POST /v1/events` | [`GET /v2/trades`](https://rivo.markets/docs/reference/list-trades) | | `GET /v1/stream` | [`GET /v2/stream`](https://rivo.markets/docs/reference/stream) | | `GET /v1/markets/search` | [`GET /v2/markets`](https://rivo.markets/docs/reference/list-markets) | | `GET /v1/markets/{marketRef}` | [`GET /v2/markets/{ref}`](https://rivo.markets/docs/reference/get-market) | | `GET /v1/traders` | [`GET /v2/traders`](https://rivo.markets/docs/reference/list-traders) | | `GET /v1/traders/{platform}/{traderId}`, `POST /v1/traders/compare` | [`GET /v2/traders/{ref}`](https://rivo.markets/docs/reference/get-trader), called once for each trader | | `GET /v1/universe`, `POST /v1/backtest`, `/sweep`, `/compare`, `/breakdown` | [`POST /v2/backtest`](https://rivo.markets/docs/reference/backtest) with `holdout`, `sweep` or `breakdown` | | `/v1/strategies`, follows and paper autopilot | [`/v2/bots`](https://rivo.markets/docs/reference/list-bots): trader, signal and market bots on paper | | `POST /v1/paper-orders`, `GET /v1/portfolio` (paper) | [`POST /v2/paper-orders`](https://rivo.markets/docs/reference/place-paper-order), [`GET /v2/paper`](https://rivo.markets/docs/reference/get-paper-account) | | `GET /v1/alerts`, market alerts | Alerts stay in the app. Through the API, filter [trades](https://rivo.markets/docs/reference/list-trades) and [moves](https://rivo.markets/docs/reference/list-moves), or open the [stream](https://rivo.markets/docs/reference/stream). | | `POST /v1/orders`, `/v1/positions/sell`, `/v1/exits`, live autopilot, `/v1/copy-executions` | v2 does not include these. Live trading happens in the app, where you can switch a bot to live once it has proved itself on paper. | | `/v1/webhooks` | v2 does not include webhooks. The [stream](https://rivo.markets/docs/streaming) sends you trades and moves instead. | ## Find the v1 calls you missed Every v1 response includes a `Deprecation` header with the date Rivo deprecated v1, a `Sunset` header with the date v1 stops answering, and a `Link` header that points to this page. Log these headers or alert on them to find any v1 calls you missed. ```bash curl -sI "https://api.rivo.markets/v1/universe" -H "Authorization: Bearer $RIVO_API_KEY" | grep -iE "deprecation|sunset|link" ``` ## A worked example v1: ```bash curl "https://api.rivo.markets/v1/feed?platform=kalshi&limit=50&offset=100" \ -H "Authorization: Bearer $RIVO_API_KEY" ``` v2: ```bash curl "https://api.rivo.markets/v2/trades?venue=kalshi&limit=50&cursor=" \ -H "Authorization: Bearer $RIVO_API_KEY" ``` The v1 reference stays available under [v1 (legacy)](https://rivo.markets/docs/api/universe) until v1 stops answering. --- # List Trades > Lists whale trades from the last 30 days, newest or biggest first, with each trader's record and what other whales did. Source: https://rivo.markets/docs/reference/list-trades `GET /v2/trades` Scope: `read` This route returns whale trades from every venue over the last 30 days, newest first. Each row includes the trader's record in that kind of market and what other whales did on the same market in the 24 hours before it. You can filter by who traded, what they paid and the trader's record. `minWinRate` and `minSettled` test the same record that the row returns under `trader`. For example, `?minWinRate=0.6&minSettled=50` returns trades by traders who win 60% of their bets and have 50 or more settled bets in that kind of market. > **Note:** Every row the API returns counts toward your key's daily allowance of 50,000 trades. Rows that a filter leaves out do not count. See [Rate limits](https://rivo.markets/docs/rate-limits). ## Example request curl: ```bash curl "https://api.rivo.markets/v2/trades?venue=kalshi&named=true&minWinRate=0.6&minSettled=50&limit=2" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/trades" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("venue", "kalshi"), ("named", "true"), ("minWinRate", "0.6"), ("minSettled", "50"), ("limit", "2"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["venue", "kalshi"], ["named", "true"], ["minWinRate", "0.6"], ["minSettled", "50"], ["limit", "2"], ]); const res = await fetch(`https://api.rivo.markets/v2/trades?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Query parameters - `venue` (string, optional): Returns rows from these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `category` (string, optional): Returns rows in these categories: `sports`, `politics`, `crypto`, `finance`, `culture`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `kind` (string, optional): Returns rows in these kinds of market. Each kind belongs to one category: `nfl`, `college_football`, `nba`, `wnba`, `college_basketball`, `basketball`, `mlb`, `nhl`, `soccer`, `tennis`, `golf`, `mma`, `boxing`, `motorsport`, `cricket`, `esports`, `chess`, `rugby`, `olympics`, `parlay`, `other_sports`, `us_elections`, `us_politics`, `world_politics`, `other_politics`, `bitcoin`, `ethereum`, `solana`, `xrp`, `other_crypto`, `fed_rates`, `economic_data`, `stocks_indices`, `companies`, `commodities`, `fx`, `other_finance`, `mentions`, `weather`, `entertainment`, `tech_ai`, `health`, `other_culture`. See [Kinds of market](https://rivo.markets/docs/concepts#kinds-of-market). To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `trader` (trader ref, optional): Returns rows for this trader. Take the trader's `ref` from any row that names them. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `market` (market ref, optional): Returns rows for this market. The `ref` can name one contract or a market with several outcomes. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `outcome` (string, optional): Returns rows for one side of the market. Pass the side's `name` (`Yes`, `No`, `Lakers`, `Up`) or its `label` from [Get Market](https://rivo.markets/docs/reference/get-market), such as `Milwaukee`. The match ignores case. You must also pass exactly one `market`. - `action` (string, optional, default `buy`): Which trades to return: `buy`, `sell` or `all`. `buy` leaves out trims and closes, because those are exits. - `entry` (string, optional, default `all`): Which buys to return. `open` returns new positions only, `add` returns buys that add to a position, and `all` returns both. - `minUsd` (number, optional): Returns trades of at least this many dollars. - `maxUsd` (number, optional): Returns trades of at most this many dollars. - `priceMin` (number, optional): Returns trades at or above this price paid, from 0 to 1. - `priceMax` (number, optional): Returns trades at or below this price paid, from 0 to 1. - `named` (boolean, optional): Set to `true` to return only trades by a named trader. This leaves out venues that do not name their traders. - `minWinRate` (number, optional): Returns trades by traders whose win rate in that kind of market is at least this, from 0 to 1. The trader also needs at least 10 settled bets there. Trades with no named trader drop out. - `minSettled` (integer, optional): Returns trades by traders with at least this many settled bets in that kind of market, from 1 to 100,000. Trades with no named trader drop out. - `result` (string, optional): Returns trades with this result: `open`, `win` or `loss`. - `q` (string, optional): Returns trades whose market title or outcome contains every one of these words, up to five words and 200 characters. Search covers the full 30 days. - `since` (string, optional): Returns trades at or after this time, in ISO 8601 with an offset. A time older than 30 days reads as 30 days ago. - `until` (string, optional): Returns trades at or before this time, in ISO 8601 with an offset. - `sort` (string, optional, default `newest`): How to order the trades. `newest` puts the latest first and `size` puts the biggest first. With `size`, the API reads only the last 24 hours unless you set `trader` or `market`. - `limit` (integer, optional, default `50`): The number of rows on each page, from 1 to 200. - `cursor` (string, optional): Pass the `nextCursor` from the previous page to read the next one. A cursor works only on the listing that made it. See [Paging](https://rivo.markets/docs/concepts#paging). ## Example response ```json { "data": [ { "id": "f1c6b941-64bf-5911-88ef-1b5de17e9d76", "time": "2026-10-15T02:25:20.514Z", "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWEJUQzE1TS0yNk9DVDE0MjIzMC0zMA", "contract": "a2Fsc2hpOm1hcmtldDpLWEJUQzE1TS0yNk9DVDE0MjIzMC0zMA", "title": "BTC price up in next 15 mins?", "venue": "kalshi", "category": "crypto", "kind": "bitcoin" }, "outcome": "No", "action": "buy", "entry": null, "price": 0.992, "usd": 1008.86, "contracts": 1017, "trader": { "ref": "kalshi:prokingpriest", "name": "prokingpriest", "winRate": 0.974, "profit": 14118.88, "settled": 16521 }, "agreement": { "sameSide": { "trades": 9, "usd": 13130.67 }, "otherSide": { "trades": 2, "usd": 2501.92 } }, "result": "open", "profit": null }, { "id": "1ce3724a-941e-5b4a-8e84-f2e50ae89bb8", "time": "2026-10-15T02:24:53.011Z", "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "contract": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "title": "Milwaukee wins", "venue": "kalshi", "category": "sports", "kind": "mlb" }, "outcome": "Yes", "action": "buy", "entry": null, "price": 0.88, "usd": 7933.36, "contracts": 9015.18, "trader": { "ref": "kalshi:scottramirez", "name": "scottramirez", "winRate": 0.8269, "profit": 5122.43, "settled": 52 }, "agreement": { "sameSide": { "trades": 37, "usd": 95502.12 }, "otherSide": { "trades": 0, "usd": 0 } }, "result": "open", "profit": null } ], "nextCursor": "eyJ0IjoiMjAyNi0xMC0xNSAwMjoyNDo1My4wMTErMDAiLCJpZCI6IjFjZTM3MjRhLTk0MWUtNWI0YS04ZTg0LWYyZTUwYWU4OWJiOCJ9" } ``` ## Response fields - `data` (object[]): The trades on this page. - `id` (string): The trade's id. It never changes, so the same trade has the same id in every listing and on the stream. - `time` (string): When the trade happened on the venue, in ISO 8601 UTC. - `market` (object): The market this row is about. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the trader bought or sold, by its `name`. On Polymarket it is the outcome's own name, such as `Lakers`. On every other venue it is `Yes` or `No`. - `action` (string): Whether the trader bought or sold: `buy` or `sell`. - `entry` (string or null): What the trade did to the trader's position: `open`, `add`, `flip`, `trim` or `close`. It is `null` when the venue does not say. - `price` (number or null): The price paid for each contract, from 0 to 1. - `usd` (number or null): The dollars traded. - `contracts` (number or null): The number of contracts traded. - `trader` (object or null): The trader who made the trade, with their record in this kind of market, or their whole record when Rivo has none for that kind. Only `kalshi` or `polymarket` name their traders. On every other venue this is `null`. - `ref` (trader ref): The trader's ref. You can pass it to any `trader` filter or to [Get Trader](https://rivo.markets/docs/reference/get-trader). - `name` (string or null): The trader's public name, or `null` when they have none. The API never returns a wallet address as a name. - `winRate` (number or null): The share of their settled bets the trader won, from 0 to 1. It covers the kind of market this row is about, or their whole record when Rivo has none for that kind. It is `null` until they have 10 settled bets. - `profit` (number or null): The trader's profit in USD, over the same bets as `winRate`. - `settled` (integer): The number of settled bets behind `winRate` and `profit`. - `agreement` (object): The other whale trades on this market in the 24 hours before this one. - `sameSide` (object): Trades that bought the same side. - `trades` (integer): The number of whale trades. - `usd` (number): The dollars those trades spent. - `otherSide` (object): Trades that bought the other side. - `trades` (integer): The number of whale trades. - `usd` (number): The dollars those trades spent. - `result` (string): The trade's result. It is `open` until the market settles, then `win`, `loss` or `void`. - `profit` (number or null): What the trade made in USD, if held to settlement at the price paid. It is `null` while the market is open. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. ## Headers Every route sends the `x-ratelimit-*` headers. This route also sends `x-trades-limit`, `x-trades-remaining` and `x-trades-reset` for the daily trade allowance. `x-trades-reset` counts the seconds until midnight UTC. ## Errors | Code | When | | --- | --- | | `VALIDATION` | A parameter is out of range or unknown. You sent `outcome` without exactly one `market`. You asked for `sort=size` with a `since` older than 24 hours and no `trader` or `market`. Or the cursor came from another listing. | | `QUOTA_EXCEEDED` | You used up this minute's requests or today's trade allowance. The `retry-after` header says when to try again. | --- # List Positions > Lists what winning traders hold right now on markets that are still trading, biggest first. Source: https://rivo.markets/docs/reference/list-positions `GET /v2/positions` Scope: `read` This route returns the open positions of traders who have a winning record in that kind of market, on markets that are still trading. Each row covers one side of one market. It shows the biggest holder, the other holders, what they paid and what the side costs now. Some venues do not name their traders. On those venues a row shows the side that large buys went to, and `lead` is `null`. Rivo refreshes prices from the market activity it already holds and rebuilds the list every few minutes. ## Example request curl: ```bash curl "https://api.rivo.markets/v2/positions?venue=kalshi&category=sports&limit=1" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/positions" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("venue", "kalshi"), ("category", "sports"), ("limit", "1"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["venue", "kalshi"], ["category", "sports"], ["limit", "1"], ]); const res = await fetch(`https://api.rivo.markets/v2/positions?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Query parameters - `venue` (string, optional): Returns rows from these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `category` (string, optional): Returns rows in these categories: `sports`, `politics`, `crypto`, `finance`, `culture`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `kind` (string, optional): Returns rows in these kinds of market. Each kind belongs to one category: `nfl`, `college_football`, `nba`, `wnba`, `college_basketball`, `basketball`, `mlb`, `nhl`, `soccer`, `tennis`, `golf`, `mma`, `boxing`, `motorsport`, `cricket`, `esports`, `chess`, `rugby`, `olympics`, `parlay`, `other_sports`, `us_elections`, `us_politics`, `world_politics`, `other_politics`, `bitcoin`, `ethereum`, `solana`, `xrp`, `other_crypto`, `fed_rates`, `economic_data`, `stocks_indices`, `companies`, `commodities`, `fx`, `other_finance`, `mentions`, `weather`, `entertainment`, `tech_ai`, `health`, `other_culture`. See [Kinds of market](https://rivo.markets/docs/concepts#kinds-of-market). To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `trader` (trader ref, optional): Returns positions this trader holds. Take the trader's `ref` from any row that names them. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `market` (market ref, optional): Returns rows for this market. The `ref` can name one contract or a market with several outcomes. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `outcome` (string, optional): Returns rows for one side of the market. Pass the side's `name` (`Yes`, `No`, `Lakers`, `Up`) or its `label` from [Get Market](https://rivo.markets/docs/reference/get-market), such as `Milwaukee`. The match ignores case. You must also pass exactly one `market`. - `priceMin` (number, optional): Returns sides that trade at or above this price now, from 0 to 1. Rows with no recent price drop out. - `priceMax` (number, optional): Returns sides that trade at or below this price now, from 0 to 1. Rows with no recent price drop out. - `minUsd` (number, optional): Returns sides that hold at least this many dollars. - `minTraders` (integer, optional): Returns sides that at least this many winning traders hold, from 1 to 100. - `backed` (boolean, optional): Set to `true` to return only positions with a measured record of how bets like them paid. On those rows `record` is not `null`. - `sort` (string, optional, default `size`): How to order the positions. `size` sorts by dollars held, `newest` puts the latest buy first, and `move` puts the biggest price move of the last hour first, in either direction. - `limit` (integer, optional, default `50`): The number of rows on each page, from 1 to 200. - `cursor` (string, optional): Pass the `nextCursor` from the previous page to read the next one. A cursor works only on the listing that made it. See [Paging](https://rivo.markets/docs/concepts#paging). ## Example response ```json { "data": [ { "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE5GTE5GQ05PUlRILTI3LUNISQ", "contract": "a2Fsc2hpOm1hcmtldDpLWE5GTE5GQ05PUlRILTI3LUNISQ", "title": "Will Chicago win the Pro Football NFC North Division?", "venue": "kalshi", "category": "sports", "kind": "nfl" }, "outcome": "Yes", "outcomeLabel": null, "entryPrice": 0.2005, "price": 0.2, "priceAt": "2026-10-15T02:28:27.319Z", "usd": 42770.17, "openedAt": "2026-10-04T08:40:39.804Z", "lastBoughtAt": "2026-10-12T05:13:29.103Z", "lead": { "ref": "kalshi:avidsmores", "name": "avidsmores", "winRate": 0.6973, "profit": 15681.48, "settled": 935, "usd": 42770.17, "entryPrice": 0.2005 }, "others": [], "split": { "sameSideUsd": 42770.17, "otherSideUsd": 0, "otherSideTraders": 0 }, "activity": null, "record": null } ], "nextCursor": "eyJvIjoxfQ" } ``` ## Response fields - `data` (object[]): The positions on this page. - `market` (object): The market this row is about. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the traders hold: `Yes`, `No`, or a named outcome. - `outcomeLabel` (string or null): The name the market gives this side, when `Yes` or `No` alone says too little. - `entryPrice` (number or null): The average price every holder paid, from 0 to 1. Rivo divides their dollars by their contracts. - `price` (number or null): The price the side trades at now. It is `null` when nothing traded recently. - `priceAt` (string or null): When Rivo read `price`, in ISO 8601 UTC. - `usd` (number): The dollars on this side. On a venue that does not name its traders, this is the sum of the large buys. - `openedAt` (string): When the first buy happened, in ISO 8601 UTC. - `lastBoughtAt` (string): When the latest buy happened, in ISO 8601 UTC. - `lead` (object or null): The biggest holder, with their record in this kind of market. It is `null` on a venue that does not name its traders. - `ref` (trader ref): The trader's ref. You can pass it to any `trader` filter or to [Get Trader](https://rivo.markets/docs/reference/get-trader). - `name` (string or null): The trader's public name, or `null` when they have none. The API never returns a wallet address as a name. - `winRate` (number or null): The share of their settled bets the trader won, from 0 to 1. It covers the kind of market this row is about, or their whole record when Rivo has none for that kind. It is `null` until they have 10 settled bets. - `profit` (number or null): The trader's profit in USD, over the same bets as `winRate`. - `settled` (integer): The number of settled bets behind `winRate` and `profit`. - `usd` (number): The dollars this trader has put on this side. - `entryPrice` (number or null): The average price this trader paid, from 0 to 1. - `others` (object[]): The other winning holders. Each has the same fields as `lead`. - `ref` (trader ref): The trader's ref. You can pass it to any `trader` filter or to [Get Trader](https://rivo.markets/docs/reference/get-trader). - `name` (string or null): The trader's public name, or `null` when they have none. The API never returns a wallet address as a name. - `winRate` (number or null): The share of their settled bets the trader won, from 0 to 1. It covers the kind of market this row is about, or their whole record when Rivo has none for that kind. It is `null` until they have 10 settled bets. - `profit` (number or null): The trader's profit in USD, over the same bets as `winRate`. - `settled` (integer): The number of settled bets behind `winRate` and `profit`. - `usd` (number): The dollars this trader has put on this side. - `entryPrice` (number or null): The average price this trader paid, from 0 to 1. - `split` (object): How the winning traders' money splits between this side and the other sides of the market. - `sameSideUsd` (number): The dollars on this side. - `otherSideUsd` (number): The dollars on the other sides. - `otherSideTraders` (integer): The number of winning traders on the other sides. - `activity` (object or null): What this side did over the last hour. It is `null` when Rivo has no reading for the hour. - `move1h` (number or null): The price change over the hour. - `volume1h` (number or null): The dollars traded over the hour. - `record` (object or null): How settled bets like this one did at this price. It is `null` when Rivo has no measured record. - `settled` (integer): The number of settled bets in the record. - `winRate` (number): The share of those bets that won, from 0 to 1. - `maxPrice` (number): The highest price at which the record says this bet was worth buying, from 0 to 1. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. The cursor marks a place in the sorted list. If Rivo rebuilds the list between two pages, a row can shift by one place. ## Errors | Code | When | | --- | --- | | `VALIDATION` | A parameter is out of range or unknown. You sent `outcome` without exactly one `market`. Or the cursor came from another listing. | --- # List Moves > Lists what markets did over the last 24 hours: sharp price moves, changes of favorite and whales piling in. Source: https://rivo.markets/docs/reference/list-moves `GET /v2/moves` Scope: `read` This route returns market moves from the last 24 hours, newest first. There are three types: - `move` means the Yes price moved sharply within a few minutes and held there. - `flip` means the favorite changed. - `whales` means several different whales bought one side within a few minutes. Moves do not count toward the daily trade allowance. ## Example request curl: ```bash curl "https://api.rivo.markets/v2/moves?category=crypto&minPoints=10&limit=2" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/moves" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("category", "crypto"), ("minPoints", "10"), ("limit", "2"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["category", "crypto"], ["minPoints", "10"], ["limit", "2"], ]); const res = await fetch(`https://api.rivo.markets/v2/moves?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Query parameters - `venue` (string, optional): Returns rows from these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `category` (string, optional): Returns rows in these categories: `sports`, `politics`, `crypto`, `finance`, `culture`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `kind` (string, optional): Returns rows in these kinds of market. Each kind belongs to one category: `nfl`, `college_football`, `nba`, `wnba`, `college_basketball`, `basketball`, `mlb`, `nhl`, `soccer`, `tennis`, `golf`, `mma`, `boxing`, `motorsport`, `cricket`, `esports`, `chess`, `rugby`, `olympics`, `parlay`, `other_sports`, `us_elections`, `us_politics`, `world_politics`, `other_politics`, `bitcoin`, `ethereum`, `solana`, `xrp`, `other_crypto`, `fed_rates`, `economic_data`, `stocks_indices`, `companies`, `commodities`, `fx`, `other_finance`, `mentions`, `weather`, `entertainment`, `tech_ai`, `health`, `other_culture`. See [Kinds of market](https://rivo.markets/docs/concepts#kinds-of-market). To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `market` (market ref, optional): Returns rows for this market. The `ref` can name one contract or a market with several outcomes. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `type` (string, optional): Returns moves of these types: `move`, `flip` or `whales`. Repeat the key to pass several. - `minPoints` (number, optional): Returns `move` rows whose price changed by at least this many points, from 0 to 100. Flips and whales have no price change, so they always pass. - `priceMin` (number, optional): Returns moves where the Yes price ended at or above this, from 0 to 1. Use it to leave out markets that are all but decided. - `priceMax` (number, optional): Returns moves where the Yes price ended at or below this, from 0 to 1. - `q` (string, optional): Returns moves on markets whose name contains every one of these words, up to 200 characters. - `since` (string, optional): Returns moves at or after this time, in ISO 8601 with an offset. - `limit` (integer, optional, default `50`): The number of rows on each page, from 1 to 200. - `cursor` (string, optional): Pass the `nextCursor` from the previous page to read the next one. A cursor works only on the listing that made it. See [Paging](https://rivo.markets/docs/concepts#paging). ## Example response ```json { "data": [ { "id": "flip:kalshi:market:KXBTCD-26OCT1423-T80799.99:1792031312711", "time": "2026-10-15T02:28:32.711Z", "type": "flip", "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWEJUQ0QtMjZPQ1QxNDIzLVQ4MDc5OS45OQ", "contract": "a2Fsc2hpOm1hcmtldDpLWEJUQ0QtMjZPQ1QxNDIzLVQ4MDc5OS45OQ", "title": "Bitcoin price on Oct 14, 2026?", "venue": "kalshi", "category": "crypto", "kind": "bitcoin" }, "outcome": "No", "priceBefore": null, "priceAfter": 0.36, "minutes": null, "traders": null, "usd": null }, { "id": "price_move:polymarket:market:btc-updown-5m-1792031100:1792031230000", "time": "2026-10-15T02:27:10.000Z", "type": "move", "market": { "ref": "cG9seW1hcmtldDptYXJrZXQ6YnRjLXVwZG93bi01bS0xNzkyMDMxMTAw", "contract": "cG9seW1hcmtldDptYXJrZXQ6YnRjLXVwZG93bi01bS0xNzkyMDMxMTAw", "title": "Bitcoin Up or Down - October 14, 10:25PM-10:30PM ET", "venue": "polymarket", "category": "crypto", "kind": "bitcoin" }, "outcome": "Yes", "priceBefore": 0.5, "priceAfter": 0.89, "minutes": 5, "traders": null, "usd": null } ], "nextCursor": "eyJ0IjoxNzkyMDMxMjMwMDAwLCJpZCI6InByaWNlX21vdmU6cG9seW1hcmtldDptYXJrZXQ6YnRjLXVwZG93bi01bS0xNzkyMDMxMTAwOjE3OTIwMzEyMzAwMDAifQ" } ``` ## Response fields - `data` (object[]): The moves on this page. - `id` (string): The move's id. It never changes, so the listing and the stream use the same id. - `time` (string): When Rivo recorded the move, in ISO 8601 UTC. - `type` (string): What happened. `move` means the Yes price moved sharply. `flip` means the favorite changed. `whales` means several whales bought one side within minutes. - `market` (object): The market this row is about. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the move is about. On a `move` it is `Yes`, the side whose price moved. On a `flip` it is the new favorite. On `whales` it is the side the whales bought. - `priceBefore` (number or null): On a `move`, the Yes price before the move. On other types it is `null`. - `priceAfter` (number or null): The Yes price when Rivo recorded the move. It is `null` on a `whales` move when Rivo has no price yet. - `minutes` (integer or null): On a `move`, the number of minutes the move took. On other types it is `null`. - `traders` (integer or null): On `whales`, the number of whales who bought. On other types it is `null`. - `usd` (number or null): On `whales`, the dollars those whales spent between them. On other types it is `null`. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. ## Errors | Code | When | | --- | --- | | `VALIDATION` | A parameter is out of range or unknown, or the cursor came from another listing. | --- # Stream > Pushes trades and market moves to you over one server-sent events connection, with the same filters as the listings. Source: https://rivo.markets/docs/reference/stream `GET /v2/stream` Scope: `read` This route opens a server-sent events stream. Each new whale trade arrives as an `event: trade` frame with the same fields as a [List Trades](https://rivo.markets/docs/reference/list-trades) row. Each market move arrives as an `event: move` frame with the same fields as a [List Moves](https://rivo.markets/docs/reference/list-moves) row. Each connection has its own filters, and they have the same names and meanings as on the two listings. `market` narrows both channels. So `?market=` sends you every trade and every move on one market over a single connection. > **Note:** Send the key in the `Authorization` header. The stream does not accept the key in the URL. To show the stream in a browser, open it on your server and pass the frames on, as [Streaming to a browser](https://rivo.markets/docs/authentication#streaming-to-a-browser) shows. When you reconnect, call [List Trades](https://rivo.markets/docs/reference/list-trades) with `since` set to the time of the last frame you received. That fills the gap. See [Streaming](https://rivo.markets/docs/streaming). ## Example request curl: ```bash curl -N "https://api.rivo.markets/v2/stream?channels=trades&venue=kalshi&minUsd=5000" \ -H "Authorization: Bearer $RIVO_API_KEY" ``` Python: ```python import json import os import requests url = "https://api.rivo.markets/v2/stream" params = {"channels": "trades", "venue": "kalshi", "minUsd": 5000} headers = {"Authorization": f"Bearer {os.environ['RIVO_API_KEY']}"} with requests.get(url, params=params, headers=headers, stream=True, timeout=(10, None)) as res: res.raise_for_status() event = None for line in res.iter_lines(decode_unicode=True): if line.startswith("event: "): event = line[len("event: "):] elif line.startswith("data: "): frame = json.loads(line[len("data: "):]) if event == "error": raise RuntimeError(frame["error"]) print(event, frame["market"]["title"], frame.get("usd")) elif line == "": event = None ``` TypeScript: ```typescript const url = new URL("https://api.rivo.markets/v2/stream"); url.searchParams.set("channels", "trades"); url.searchParams.set("venue", "kalshi"); url.searchParams.set("minUsd", "5000"); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}` }, }); if (!res.ok || !res.body) throw new Error(`stream refused: ${res.status}`); const reader = res.body.pipeThrough(new TextDecoderStream()).getReader(); let buffer = ""; for (;;) { const { value, done } = await reader.read(); if (done) break; buffer += value; let end: number; while ((end = buffer.indexOf("\n\n")) !== -1) { const frame = buffer.slice(0, end); buffer = buffer.slice(end + 2); const event = /^event: (.+)$/m.exec(frame)?.[1]; const data = /^data: (.+)$/m.exec(frame)?.[1]; if (!event || !data) continue; // a ": ping" heartbeat console.log(event, JSON.parse(data)); } } ``` ## Query parameters - `channels` (string, optional, default `trades and moves`): Which channels to receive: `trades`, `moves`, or both. Repeat the key to pass both. - `venue` (string, optional): Returns rows from these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `category` (string, optional): Returns rows in these categories: `sports`, `politics`, `crypto`, `finance`, `culture`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `kind` (string, optional): Returns rows in these kinds of market. Each kind belongs to one category: `nfl`, `college_football`, `nba`, `wnba`, `college_basketball`, `basketball`, `mlb`, `nhl`, `soccer`, `tennis`, `golf`, `mma`, `boxing`, `motorsport`, `cricket`, `esports`, `chess`, `rugby`, `olympics`, `parlay`, `other_sports`, `us_elections`, `us_politics`, `world_politics`, `other_politics`, `bitcoin`, `ethereum`, `solana`, `xrp`, `other_crypto`, `fed_rates`, `economic_data`, `stocks_indices`, `companies`, `commodities`, `fx`, `other_finance`, `mentions`, `weather`, `entertainment`, `tech_ai`, `health`, `other_culture`. See [Kinds of market](https://rivo.markets/docs/concepts#kinds-of-market). To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `market` (market ref, optional): Returns rows for this market. The `ref` can name one contract or a market with several outcomes. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. It narrows both channels. - `priceMin` (number, optional): Sends frames at or above this price, from 0 to 1. On a trade it tests the price paid. On a move it tests the Yes price after the move. - `priceMax` (number, optional): Sends frames at or below this price, from 0 to 1. It tests the same prices as `priceMin`. - `trader` (trader ref, optional): Applies to trades only. Returns rows for this trader. Take the trader's `ref` from any row that names them. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `outcome` (string, optional): Applies to trades only. Returns rows for one side of the market. Pass the side's `name` (`Yes`, `No`, `Lakers`, `Up`) or its `label` from [Get Market](https://rivo.markets/docs/reference/get-market), such as `Milwaukee`. The match ignores case. You must also pass exactly one `market`. - `action` (string, optional, default `buy`): Applies to trades only. Choose `buy`, `sell` or `all`. `buy` leaves out trims and closes. - `entry` (string, optional, default `all`): Applies to trades only. Choose `open`, `add` or `all`. - `minUsd` (number, optional): Applies to trades only. Sends trades of at least this many dollars. - `maxUsd` (number, optional): Applies to trades only. Sends trades of at most this many dollars. - `named` (boolean, optional): Applies to trades only. Set to `true` to receive only trades by a named trader. - `minWinRate` (number, optional): Applies to trades only. Sends trades by traders whose win rate in that kind of market is at least this, from 0 to 1, over at least 10 settled bets. - `minSettled` (integer, optional): Applies to trades only. Sends trades by traders with at least this many settled bets in that kind of market. - `type` (string, optional): Applies to moves only. Choose `move`, `flip` or `whales`. Repeat the key to pass several. - `minPoints` (number, optional): Applies to moves only. Sends `move` rows whose price changed by at least this many points, from 0 to 100. ## Example response ``` : connected event: trade data: {"id":"1ce3724a-941e-5b4a-8e84-f2e50ae89bb8","time":"2026-10-15T02:24:53.011Z","market":{"ref":"a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM","title":"Milwaukee wins","venue":"kalshi","category":"sports","kind":"mlb"},"outcome":"Yes","action":"buy","entry":null,"price":0.88,"usd":7933.36,"contracts":9015.18,"trader":{"ref":"kalshi:scottramirez","name":"scottramirez","winRate":0.8269,"profit":5122.43,"settled":52},"agreement":{"sameSide":{"trades":37,"usd":95502.12},"otherSide":{"trades":0,"usd":0}},"result":"open","profit":null} event: move data: {"id":"flip:kalshi:market:KXBTCD-26OCT1423-T80799.99:1792031312711","time":"2026-10-15T02:28:32.711Z","type":"flip","market":{"ref":"a2Fsc2hpOm1hcmtldDpLWEJUQ0QtMjZPQ1QxNDIzLVQ4MDc5OS45OQ","title":"Bitcoin price on Oct 14, 2026?","venue":"kalshi","category":"crypto","kind":"bitcoin"},"outcome":"No","priceBefore":null,"priceAfter":0.36,"minutes":null,"traders":null,"usd":null} : ping ``` ## Response fields - `event: trade` (frame): One whale trade. Its `data` has the same fields as a [List Trades](https://rivo.markets/docs/reference/list-trades) row. - `id` (string): The trade's id. It never changes, so the same trade has the same id in every listing and on the stream. - `time` (string): When the trade happened on the venue, in ISO 8601 UTC. - `market` (object): The market this row is about. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the trader bought or sold, by its `name`. On Polymarket it is the outcome's own name, such as `Lakers`. On every other venue it is `Yes` or `No`. - `action` (string): Whether the trader bought or sold: `buy` or `sell`. - `entry` (string or null): What the trade did to the trader's position: `open`, `add`, `flip`, `trim` or `close`. It is `null` when the venue does not say. - `price` (number or null): The price paid for each contract, from 0 to 1. - `usd` (number or null): The dollars traded. - `contracts` (number or null): The number of contracts traded. - `trader` (object or null): The trader who made the trade, with their record in this kind of market, or their whole record when Rivo has none for that kind. Only `kalshi` or `polymarket` name their traders. On every other venue this is `null`. - `ref` (trader ref): The trader's ref. You can pass it to any `trader` filter or to [Get Trader](https://rivo.markets/docs/reference/get-trader). - `name` (string or null): The trader's public name, or `null` when they have none. The API never returns a wallet address as a name. - `winRate` (number or null): The share of their settled bets the trader won, from 0 to 1. It covers the kind of market this row is about, or their whole record when Rivo has none for that kind. It is `null` until they have 10 settled bets. - `profit` (number or null): The trader's profit in USD, over the same bets as `winRate`. - `settled` (integer): The number of settled bets behind `winRate` and `profit`. - `agreement` (object): The other whale trades on this market in the 24 hours before this one. - `sameSide` (object): Trades that bought the same side. - `trades` (integer): The number of whale trades. - `usd` (number): The dollars those trades spent. - `otherSide` (object): Trades that bought the other side. - `trades` (integer): The number of whale trades. - `usd` (number): The dollars those trades spent. - `result` (string): The trade's result. It is `open` until the market settles, then `win`, `loss` or `void`. - `profit` (number or null): What the trade made in USD, if held to settlement at the price paid. It is `null` while the market is open. - `event: move` (frame): One market move. Its `data` has the same fields as a [List Moves](https://rivo.markets/docs/reference/list-moves) row. - `id` (string): The move's id. It never changes, so the listing and the stream use the same id. - `time` (string): When Rivo recorded the move, in ISO 8601 UTC. - `type` (string): What happened. `move` means the Yes price moved sharply. `flip` means the favorite changed. `whales` means several whales bought one side within minutes. - `market` (object): The market this row is about. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the move is about. On a `move` it is `Yes`, the side whose price moved. On a `flip` it is the new favorite. On `whales` it is the side the whales bought. - `priceBefore` (number or null): On a `move`, the Yes price before the move. On other types it is `null`. - `priceAfter` (number or null): The Yes price when Rivo recorded the move. It is `null` on a `whales` move when Rivo has no price yet. - `minutes` (integer or null): On a `move`, the number of minutes the move took. On other types it is `null`. - `traders` (integer or null): On `whales`, the number of whales who bought. On other types it is `null`. - `usd` (number or null): On `whales`, the dollars those whales spent between them. On other types it is `null`. - `event: error` (frame): The server sends this just before it closes the stream, because the day's trade allowance ran out. - `code` (string): Always `QUOTA_EXCEEDED`. - `error` (string): A sentence that says what happened and when the allowance resets. - `: connected` (comment): The server sends this once when the stream opens. - `: ping` (comment): The server sends this heartbeat every 25 seconds. You can ignore it. ## Limits - A key can hold at most 3 streams at once, and `/v1/stream` counts toward the same 3. - An open stream uses one of your 120 requests every minute. - Every trade frame the server sends counts toward your daily allowance of 50,000 trades. Rivo charges them once a minute. A trade that a filter leaves out is not sent and does not count. Move frames do not count. - When the allowance runs out, the stream sends `event: error` and closes. The API refuses new streams until the allowance resets. ## Errors If the request fails, the API returns an ordinary JSON body and status before the stream opens. | Code | When | | --- | --- | | `UNAUTHORIZED` | You sent no key in the `Authorization` header, or the key is wrong or revoked. | | `SUBSCRIPTION_INACTIVE` | The account that owns the key has no active subscription. | | `VALIDATION` | A parameter is out of range or unknown, you sent `outcome` without exactly one `market`, or you put the key in the URL as `?key=`. | | `QUOTA_EXCEEDED` | The key already holds 3 streams, or you used up today's trade allowance. | --- # List Markets > Searches and browses markets on every venue. It returns open markets by default, or the markets that settled in the last 30 days. Source: https://rivo.markets/docs/reference/list-markets `GET /v2/markets` Scope: `read` This route returns markets. Without `q`, it browses: it lists the markets that traded in the last hour, then each venue's most traded open markets. With `q`, it searches the title and outcomes of every open market. `status=settled` lists markets that settled in the last 30 days and had whale trades, with the most recently settled first. `status=all` lists the open markets first and the settled ones after them. A market with several outcomes, such as a game between two teams or an election with many candidates, appears as one row. Its `outcomes` show the two sides of its most traded contract, and `outcomeCount` says how many contracts it has. [Get Market](https://rivo.markets/docs/reference/get-market) lists every contract. ## Example request curl: ```bash curl "https://api.rivo.markets/v2/markets?category=sports&sort=whale_money&limit=2" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/markets" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("category", "sports"), ("sort", "whale_money"), ("limit", "2"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["category", "sports"], ["sort", "whale_money"], ["limit", "2"], ]); const res = await fetch(`https://api.rivo.markets/v2/markets?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Query parameters - `venue` (string, optional): Returns rows from these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `category` (string, optional): Returns rows in these categories: `sports`, `politics`, `crypto`, `finance`, `culture`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `kind` (string, optional): Returns rows in these kinds of market. Each kind belongs to one category: `nfl`, `college_football`, `nba`, `wnba`, `college_basketball`, `basketball`, `mlb`, `nhl`, `soccer`, `tennis`, `golf`, `mma`, `boxing`, `motorsport`, `cricket`, `esports`, `chess`, `rugby`, `olympics`, `parlay`, `other_sports`, `us_elections`, `us_politics`, `world_politics`, `other_politics`, `bitcoin`, `ethereum`, `solana`, `xrp`, `other_crypto`, `fed_rates`, `economic_data`, `stocks_indices`, `companies`, `commodities`, `fx`, `other_finance`, `mentions`, `weather`, `entertainment`, `tech_ai`, `health`, `other_culture`. See [Kinds of market](https://rivo.markets/docs/concepts#kinds-of-market). To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `status` (string, optional, default `open`): Which markets to return: `open`, `settled` or `all`. - `q` (string, optional): Returns markets whose title or outcomes contain these words, up to 200 characters. - `priceMin` (number, optional): Returns open markets whose leading side trades at or above this price, from 0 to 1. It works on open markets only. - `priceMax` (number, optional): Returns open markets whose leading side trades at or below this price, from 0 to 1. It works on open markets only. - `series` (boolean, optional): Set to `true` to return only repeating markets. These markets reopen on a clock, such as Bitcoin up or down in the next 15 minutes. - `sort` (string, optional, default `whale_money`): How to order open markets. `whale_money` sorts by whale dollars in the last hour, `volume` by dollars traded in the last hour, `move` by the biggest price move of the last hour in either direction, and `newest` by the latest trade. Markets with no reading for the sort come last. Settled markets always come newest settled first. - `limit` (integer, optional, default `50`): The number of rows on each page, from 1 to 200. - `cursor` (string, optional): Pass the `nextCursor` from the previous page to read the next one. A cursor works only on the listing that made it. See [Paging](https://rivo.markets/docs/concepts#paging). ## Example response ```json { "data": [ { "ref": "cG9seW1hcmtldDptYXJrZXQ6d3RhLW11Y2hvdmEtc2huYWlkZS0yMDI2LTEwLTE0", "title": "Wuhan Tennis Open: Karolina Muchova vs Diana Shnaider", "venue": "polymarket", "category": "sports", "kind": "tennis", "status": "open", "outcomes": [ { "name": "Karolina Muchova", "label": "Karolina Muchova", "price": 0.6 }, { "name": "Diana Shnaider", "label": "Diana Shnaider", "price": 0.4 } ], "outcomeCount": 16, "volume1h": 197247.96, "move1h": 0.02, "whaleMoney": 339469.91, "result": null, "settledAt": null }, { "ref": "cG9seW1hcmtldDptYXJrZXQ6Y2ZiLWNvbHN0LXR4c3QtMjAyNi0xMC0xNS10b3RhbC02OXB0NQ", "title": "Colorado State vs. Texas State: O/U 69.5", "venue": "polymarket", "category": "sports", "kind": "college_football", "status": "open", "outcomes": [ { "name": "Yes", "label": "Yes", "price": 0.505 }, { "name": "No", "label": "No", "price": 0.495 } ], "outcomeCount": 228, "volume1h": null, "move1h": null, "whaleMoney": 91079.31, "result": null, "settledAt": null } ], "nextCursor": "eyJwIjoib3BlbiIsIm8iOjJ9" } ``` ## Response fields - `data` (object[]): The markets on this page. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `status` (string): The market's status: `open`, `settled` or `void`. - `outcomes` (object[]): On an open market, the two sides, or the two sides of its most traded contract. The list is empty on a settled market. - `name` (string): The side's name, the same on every route. On Polymarket it is the outcome's own name, such as `Lakers` or `Up`. On every other venue it is `Yes` or `No`. - `label` (string): What the venue calls the side, such as `Milwaukee` or `Not Milwaukee`. It equals `name` where the two agree. - `price` (number or null): The side's price now, from 0 to 1. - `outcomeCount` (integer): The number of contracts the market has. - `volume1h` (number or null): The dollars traded on the market in the last hour, counting every trade Rivo's market reader saw. It is `null` when Rivo has not seen the market trade. It comes from a different read than `whaleMoney`, so on a busy market `whaleMoney` can be larger. - `move1h` (number or null): How much the Yes price changed over the last hour. - `whaleMoney` (number or null): The dollars whales spent buying into the market in the last hour, from Rivo's whale trades. It is `null` when no whale bought in the last hour. - `result` (string or null): On a settled market, the name of the outcome that won. It is `null` while the market is open and when it is void. - `settledAt` (string or null): On a settled market, when it settled, in ISO 8601 UTC. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. ## Errors | Code | When | | --- | --- | | `VALIDATION` | A parameter is out of range or unknown. You sent `priceMin` or `priceMax` without `status=open`. You sent a `sort` other than `newest` with `status=settled`. Or the cursor came from another listing. | --- # Get Market > Reads one market with every outcome's price, bid and ask. On request it adds the order book, the whales, the price history and the newest whale trades. Source: https://rivo.markets/docs/reference/get-market `GET /v2/markets/{ref}` Scope: `read` This route returns one market with its status, its rule, and every outcome's price, bid and ask. For a market with several contracts, it lists both sides of each contract, and each side carries its contract's own `ref`. `include` adds extra sections to the response. Each include counts as a request of its own against your 120 requests a minute, so `?include=book,whales` uses 3. `trades` also counts its rows against the 50,000 trades a day. | include | Adds | | --- | --- | | `book` | Adds the bids and asks for each side, up to 20 levels. Rivo reads them from the venue and caches them for 5 seconds. It works on a single contract only and returns `null` for a market with several contracts. | | `whales` | Adds the whale dollars on each side and lists each whale on the market. For each whale it shows what they bought and sold, their prices, their record and whether they still hold. | | `call` | Adds the side with the most behind it, with a headline and up to four reasons. It is `null` when there is no call, or when the call was not ready within half a second. In that case, ask again shortly. | | `history` | Adds the price and volume over `range`. For a repeating market it also adds the last 15 windows and how often each side won them. It works on a single contract only. | | `trades` | Adds the newest 40 whale buys on the market, with the same fields as a [List Trades](https://rivo.markets/docs/reference/list-trades) row. | To trade one side on paper, call [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order). Pass the outcome's `ref` as `market` and its `name` as `outcome`. ## Example request curl: ```bash curl "https://api.rivo.markets/v2/markets/a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM?include=book%2Cwhales%2Ccall%2Chistory%2Ctrades" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/markets/a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("include", "book,whales,call,history,trades"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["include", "book,whales,call,history,trades"], ]); const res = await fetch(`https://api.rivo.markets/v2/markets/a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Path parameters - `ref` (market ref, required): The market's `ref`. You can take it from any row that names the market. ## Query parameters - `include` (string, optional): The extra sections to add: `book`, `whales`, `call`, `history` or `trades`. Separate them with commas, or repeat the key. - `range` (string, optional, default `1d`): The period that `include=history` covers: `1h`, `1d` or `7d`. ## Example response ```json { "data": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "title": "Milwaukee wins", "venue": "kalshi", "category": "sports", "kind": "mlb", "status": "open", "result": null, "settledAt": null, "rule": "If Milwaukee wins the Milwaukee vs Los Angeles D professional baseball game originally scheduled for Oct 14, 2026, then the market resolves to Yes.", "outcomes": [ { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "contract": "Milwaukee wins", "name": "Yes", "label": "Milwaukee", "price": 0.87, "bid": 0.86, "ask": 0.87 }, { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "contract": "Milwaukee wins", "name": "No", "label": "Not Milwaukee", "price": 0.13, "bid": 0.13, "ask": 0.14 } ], "otherVenues": [], "book": [ { "outcome": "Yes", "bids": [ { "price": 0.86, "size": 194965.3 }, { "price": 0.85, "size": 262306.1 } ], "asks": [ { "price": 0.87, "size": 163419.21 }, { "price": 0.88, "size": 111676.84 } ] }, { "outcome": "No", "bids": [ { "price": 0.13, "size": 163419.21 }, { "price": 0.12, "size": 111676.84 } ], "asks": [ { "price": 0.14, "size": 194965.3 }, { "price": 0.15, "size": 262306.1 } ] } ], "whales": { "sides": [ { "outcome": "Yes", "usd": 77068.91, "trades": 28 } ], "traders": [ { "ref": "kalshi:pentasir", "name": "pentasir", "winRate": 0.7791, "profit": 10958.34, "settled": 163, "outcome": "Yes", "boughtUsd": 9835.02, "soldUsd": 0, "entryPrice": 0.39, "exitPrice": null, "status": "holding", "lastTradeAt": "2026-10-14T22:49:09.451Z" } ] }, "call": null, "history": { "range": "1d", "prices": [ { "time": "2026-10-14T03:00:00.000Z", "price": 0.38, "volume": 8804.29 }, { "time": "2026-10-14T04:00:00.000Z", "price": 0.39, "volume": 25974.21 } ], "series": null }, "trades": [ { "id": "1ce3724a-941e-5b4a-8e84-f2e50ae89bb8", "time": "2026-10-15T02:24:53.011Z", "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "contract": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "title": "Milwaukee wins", "venue": "kalshi", "category": "sports", "kind": "mlb" }, "outcome": "Yes", "action": "buy", "entry": null, "price": 0.88, "usd": 7933.36, "contracts": 9015.18, "trader": { "ref": "kalshi:scottramirez", "name": "scottramirez", "winRate": 0.8269, "profit": 5122.43, "settled": 52 }, "agreement": { "sameSide": { "trades": 37, "usd": 95502.12 }, "otherSide": { "trades": 0, "usd": 0 } }, "result": "open", "profit": null } ] } } ``` ## Response fields - `data` (object): The market you asked for. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `status` (string): The market's status: `open`, `closed`, `settled` or `void`. `closed` means trading has stopped but no result is in yet. - `result` (string or null): The name of the outcome that won. It is `null` until the market settles, and when it is void. - `settledAt` (string or null): When the market settled, in ISO 8601 UTC. - `rule` (string or null): How the venue settles the market, in the venue's own words. - `outcomes` (object[]): Both sides of each contract in the market. - `ref` (market ref): The contract this side trades on. Pass it as `market` to a paper order or a market bot. - `contract` (string): The contract's own title. On a market with several contracts, it tells you which contract this side belongs to. - `name` (string): The side's name, the same name trades, whales and positions use. On Polymarket it is the outcome's own name. On every other venue it is `Yes` or `No`. Pass it as `outcome` to a paper order. - `label` (string): What the venue calls the side, such as `Milwaukee` or `Not Milwaukee`. Every route that takes an `outcome` accepts the label too. - `price` (number or null): The side's price now, from 0 to 1. - `bid` (number or null): The best bid, from 0 to 1. It is `null` when the venue has no book. - `ask` (number or null): The best ask, from 0 to 1. It is `null` when the venue has no book. - `otherVenues` (object[]): The same outcome on other venues. Rivo pairs only some markets across venues, and this list is empty for every other market. - `venue` (string): The other venue that lists this outcome. - `ref` (market ref): The contract on that venue. - `book` (object[] or null): The order book, when you ask for `include=book`. Each side lists its best price first. - `outcome` (string): The side this entry is about. - `bids` (object[]): The bids, with the best first. - `price` (number): The price at this level, from 0 to 1. - `size` (number): The number of contracts offered at this price. - `asks` (object[]): The asks, with the best first. - `price` (number): The price at this level, from 0 to 1. - `size` (number): The number of contracts offered at this price. - `whales` (object): The whales, when you ask for `include=whales`. It covers the last 30 days on a single contract and the last 24 hours on a market with several contracts. - `sides` (object[]): The whale buys on each side. - `outcome` (string): The side this entry is about. - `usd` (number): The dollars whales spent buying this side. - `trades` (integer): The number of whale buys on this side. - `traders` (object[]): One row for each named whale on each side, with the biggest buyers first. - `ref` (trader ref): The trader's ref. You can pass it to any `trader` filter or to [Get Trader](https://rivo.markets/docs/reference/get-trader). - `name` (string or null): The trader's public name, or `null` when they have none. The API never returns a wallet address as a name. - `winRate` (number or null): The share of their settled bets the trader won, from 0 to 1. It covers the kind of market this row is about, or their whole record when Rivo has none for that kind. It is `null` until they have 10 settled bets. - `profit` (number or null): The trader's profit in USD, over the same bets as `winRate`. - `settled` (integer): The number of settled bets behind `winRate` and `profit`. - `outcome` (string): The side this whale bought. - `boughtUsd` (number): The dollars this whale spent buying. - `soldUsd` (number): The dollars this whale received from selling. - `entryPrice` (number or null): The average price this whale paid. - `exitPrice` (number or null): The average price this whale sold at. - `status` (string): Whether the whale still holds: `holding`, `reduced` or `exited`. - `lastTradeAt` (string): When this whale last traded this market, in ISO 8601 UTC. - `call` (object or null): The call, when you ask for `include=call`. - `outcome` (string or null): The side with the most money and the strongest records behind it. - `action` (string): What the call suggests: `buy` or `watch`. - `headline` (string): The call, in one line. - `detail` (string): One sentence that explains the call. - `price` (number or null): The side's price now, from 0 to 1. - `maxPrice` (number or null): The highest price worth paying, when a measured record supports one. - `reasons` (string[]): Up to four reasons behind the call. - `history` (object or null): The price history, when you ask for `include=history`. - `range` (string): The period the history covers: `1h`, `1d` or `7d`. - `prices` (object[]): The data points, oldest first. - `time` (string): The time of this point, in ISO 8601 UTC. - `price` (number): The Yes price at this point. - `volume` (number): The dollars traded in this interval. - `series` (object or null): The past windows. Only repeating markets have them. - `windows` (object[]): The last 15 windows, with the newest first. - `opensAt` (string): When the window opened, in ISO 8601 UTC. - `closesAt` (string): When the window closed, in ISO 8601 UTC. - `result` (string): The side that won the window: `yes` or `no`. - `yesWon` (integer): The number of windows that Yes won. - `noWon` (integer): The number of windows that No won. - `trades` (object[]): The newest 40 whale buys, when you ask for `include=trades`. - `id` (string): The trade's id. It never changes, so the same trade has the same id in every listing and on the stream. - `time` (string): When the trade happened on the venue, in ISO 8601 UTC. - `market` (object): The market this row is about. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the trader bought or sold, by its `name`. On Polymarket it is the outcome's own name, such as `Lakers`. On every other venue it is `Yes` or `No`. - `action` (string): Whether the trader bought or sold: `buy` or `sell`. - `entry` (string or null): What the trade did to the trader's position: `open`, `add`, `flip`, `trim` or `close`. It is `null` when the venue does not say. - `price` (number or null): The price paid for each contract, from 0 to 1. - `usd` (number or null): The dollars traded. - `contracts` (number or null): The number of contracts traded. - `trader` (object or null): The trader who made the trade, with their record in this kind of market, or their whole record when Rivo has none for that kind. Only `kalshi` or `polymarket` name their traders. On every other venue this is `null`. - `ref` (trader ref): The trader's ref. You can pass it to any `trader` filter or to [Get Trader](https://rivo.markets/docs/reference/get-trader). - `name` (string or null): The trader's public name, or `null` when they have none. The API never returns a wallet address as a name. - `winRate` (number or null): The share of their settled bets the trader won, from 0 to 1. It covers the kind of market this row is about, or their whole record when Rivo has none for that kind. It is `null` until they have 10 settled bets. - `profit` (number or null): The trader's profit in USD, over the same bets as `winRate`. - `settled` (integer): The number of settled bets behind `winRate` and `profit`. - `agreement` (object): The other whale trades on this market in the 24 hours before this one. - `sameSide` (object): Trades that bought the same side. - `trades` (integer): The number of whale trades. - `usd` (number): The dollars those trades spent. - `otherSide` (object): Trades that bought the other side. - `trades` (integer): The number of whale trades. - `usd` (number): The dollars those trades spent. - `result` (string): The trade's result. It is `open` until the market settles, then `win`, `loss` or `void`. - `profit` (number or null): What the trade made in USD, if held to settlement at the price paid. It is `null` while the market is open. When you do not ask for an include, the response leaves out the fields that include would add. ## Errors | Code | When | | --- | --- | | `VALIDATION` | `ref` is not a market ref, or a parameter is unknown or out of range. | | `NOT_FOUND` | No market has that ref. | | `QUOTA_EXCEEDED` | The includes would take the key past its per-minute limit, or you asked for `include=trades` after using up today's trade allowance. | --- # List Traders > Searches and ranks traders who were active in the last 30 days, over all time or over today, this week or this month. Source: https://rivo.markets/docs/reference/list-traders `GET /v2/traders` Scope: `read` This route returns traders who traded in the last 30 days, ranked by `sort`. Only `kalshi` or `polymarket` name their traders. Venues whose public trades name nobody have no traders here. `period` ranks traders by the bets they placed in that window instead of their whole record. `category` or `kind` ranks them by their record in those markets only. Win rate and return need at least 10 settled bets. Until a trader has 10, both are `null`, and the trader sorts after everyone who has them. ## Example request curl: ```bash curl "https://api.rivo.markets/v2/traders?venue=kalshi&period=month&sort=profit&limit=2" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/traders" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("venue", "kalshi"), ("period", "month"), ("sort", "profit"), ("limit", "2"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["venue", "kalshi"], ["period", "month"], ["sort", "profit"], ["limit", "2"], ]); const res = await fetch(`https://api.rivo.markets/v2/traders?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Query parameters - `venue` (string, optional): Returns rows from these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `category` (string, optional): Ranks traders by their record in one category: `sports`, `politics`, `crypto`, `finance`, `culture`. Pass one value. - `kind` (string, optional): Ranks traders by their record in one kind of market: `nfl`, `college_football`, `nba`, `wnba`, `college_basketball`, `basketball`, `mlb`, `nhl`, `soccer`, `tennis`, `golf`, `mma`, `boxing`, `motorsport`, `cricket`, `esports`, `chess`, `rugby`, `olympics`, `parlay`, `other_sports`, `us_elections`, `us_politics`, `world_politics`, `other_politics`, `bitcoin`, `ethereum`, `solana`, `xrp`, `other_crypto`, `fed_rates`, `economic_data`, `stocks_indices`, `companies`, `commodities`, `fx`, `other_finance`, `mentions`, `weather`, `entertainment`, `tech_ai`, `health`, `other_culture`. Pass one value. If you also send `category`, the kind must belong to that category. - `q` (string, optional): Finds traders by name, handle or address, up to 100 characters. - `period` (string, optional, default `all`): The window to rank over. `today` covers the last 24 hours, `week` 7 days, `month` 30 days, and `all` the whole record. - `minSettled` (integer, optional): Returns only traders with at least this many settled bets in the chosen markets and window, from 1 to 100,000. - `sort` (string, optional, default `profit`): How to rank traders: `profit`, `win_rate`, `return`, `volume`, `trades` or `recent`. `recent` puts the latest trade first. - `limit` (integer, optional, default `50`): The number of rows on each page, from 1 to 200. - `cursor` (string, optional): Pass the `nextCursor` from the previous page to read the next one. A cursor works only on the listing that made it. See [Paging](https://rivo.markets/docs/concepts#paging). ## Example response ```json { "data": [ { "ref": "kalshi:repanzuliog", "name": "RepanzulioG", "venue": "kalshi", "winRate": 0.8423, "return": 0.1091, "profit": 191467.44, "volume": 1755478.24, "trades": 1171, "settled": 1097, "bestKind": "parlay", "lastTradeAt": "2026-09-30T03:14:23.912Z" }, { "ref": "kalshi:apexnyc", "name": "apexnyc", "venue": "kalshi", "winRate": 0.4142, "return": 0.1026, "profit": 178785.16, "volume": 1742912.01, "trades": 4654, "settled": 4604, "bestKind": "tennis", "lastTradeAt": "2026-10-15T01:18:49.469Z" } ], "nextCursor": "eyJvIjoyfQ" } ``` ## Response fields - `data` (object[]): The traders on this page. - `ref` (trader ref): The trader's ref. - `name` (string or null): The trader's public name. The API never returns a wallet address as a name. - `venue` (string): The venue the trader trades on. - `winRate` (number or null): The share of settled bets the trader won in the chosen markets and window, from 0 to 1. It is `null` until they have 10 settled bets. - `return` (number or null): The trader's profit divided by the money they traded, in the same markets and window. It is `null` until they have 10 settled bets. - `profit` (number or null): The trader's profit in USD, in the same markets and window. - `volume` (number or null): The dollars the trader traded, in the same markets and window. - `trades` (integer): The number of trades in the same markets and window. - `settled` (integer): The number of settled bets in the same markets and window. - `bestKind` (string or null): The kind of market where the trader makes the most, once they have enough settled bets there. - `lastTradeAt` (string or null): When the trader last traded, in ISO 8601 UTC. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. To read a trader's individual trades, call [List Trades](https://rivo.markets/docs/reference/list-trades) with `trader` set to their ref. ## Errors | Code | When | | --- | --- | | `VALIDATION` | A parameter is out of range or unknown, `kind` does not belong to `category`, or the cursor came from another listing. | --- # Get Trader > Reads one trader's record overall, by category and by kind of market. On request it adds their positions, profit curve, habits and similar traders. Source: https://rivo.markets/docs/reference/get-trader `GET /v2/traders/{ref}` Scope: `read` This route returns one trader's record overall, in each category and in each kind of market. It also returns their average entry price and their place on the venue's profit ranking. `include` adds extra sections to the response. Each include counts as a request of its own against your 120 requests a minute, so `?include=positions,curve` uses 3. | include | Adds | | --- | --- | | `positions` | Adds the trader's open and recent positions. Each row folds every buy and sell on one side of one market into a single position. | | `curve` | Adds the trader's profit over time and by month. | | `habits` | Adds the trader's results by bet size and by price paid, how their trades change their positions, when they trade by weekday and hour in US Eastern time, and their streaks. | | `similar` | Adds traders who trade the same kinds of market. | ## Example request curl: ```bash curl "https://api.rivo.markets/v2/traders/kalshi%3Arepanzuliog?include=positions%2Ccurve%2Chabits%2Csimilar" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/traders/kalshi%3Arepanzuliog" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("include", "positions,curve,habits,similar"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["include", "positions,curve,habits,similar"], ]); const res = await fetch(`https://api.rivo.markets/v2/traders/kalshi%3Arepanzuliog?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Path parameters - `ref` (trader ref, required): The trader's `ref`. You can take it from any row that names the trader. ## Query parameters - `include` (string, optional): The extra sections to add: `positions`, `curve`, `habits` or `similar`. Separate them with commas, or repeat the key. ## Example response ```json { "data": { "ref": "kalshi:repanzuliog", "name": "RepanzulioG", "venue": "kalshi", "record": { "trades": 1404, "settled": 1097, "winRate": 0.8423, "return": 0.0646, "profit": 191467.44, "volume": 2965383.35 }, "byCategory": [ { "category": "sports", "kind": null, "trades": 1157, "settled": 1083, "winRate": 0.8532, "return": 0.1097, "profit": 192432.31, "volume": 1754513.37, "avgEntry": 0.795, "lastTradeAt": "2026-09-30T03:14:23.912Z" } ], "byKind": [ { "category": "sports", "kind": "tennis", "trades": 28, "settled": 28, "winRate": 1, "return": 0.5344, "profit": 33026.16, "volume": 61795.96, "avgEntry": 0.6775, "lastTradeAt": "2026-09-28T15:25:09.900Z" } ], "avgEntry": 0.329, "rank": { "position": 2, "outOf": 6335 }, "lastTradeAt": "2026-09-30T03:14:23.912Z", "positions": { "open": [ { "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1WRUNST1NTQ0FURUdPUlktUzIwMjYwNTUyMkIwMTUxQy0xMjI1RDQxMzY5Rg", "contract": "a2Fsc2hpOm1hcmtldDpLWE1WRUNST1NTQ0FURUdPUlktUzIwMjYwNTUyMkIwMTUxQy0xMjI1RDQxMzY5Rg", "title": "no Over 3.5 goals scored,no Over 2.5 goals scored", "venue": "kalshi", "category": "culture", "kind": null }, "outcome": "Yes", "entryPrice": 0.919, "exitPrice": null, "usd": 24845.01, "soldUsd": null, "status": "open", "result": null, "profit": null, "openedAt": "2026-09-20T14:25:15.268Z", "lastTradeAt": "2026-09-20T14:25:15.268Z" } ], "recent": [ { "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1WRUNST1NTQ0FURUdPUlktUzIwMjY5M0Q2MkRCQzVFNy03NzFBODYxQkUwQQ", "contract": "a2Fsc2hpOm1hcmtldDpLWE1WRUNST1NTQ0FURUdPUlktUzIwMjY5M0Q2MkRCQzVFNy03NzFBODYxQkUwQQ", "title": "yes HOU Texans,yes SF 49ers,yes Atlanta wins by over 12.5 points", "venue": "kalshi", "category": "sports", "kind": null }, "outcome": "Yes", "entryPrice": 0.362, "exitPrice": null, "usd": 42597.12, "soldUsd": null, "status": "settled", "result": "win", "profit": 75074.48, "openedAt": "2026-09-30T03:14:23.912Z", "lastTradeAt": "2026-09-30T03:14:23.912Z" } ] }, "curve": { "points": [ { "time": "2026-09-17T15:10:24.005Z", "profit": 86, "trades": 7 } ], "months": [ { "month": "2026-09", "trades": 1097, "profit": 191467.44 } ] }, "habits": { "bySize": [ { "label": "$0 to $14", "trades": 351, "settled": 251, "winRate": 0.9124, "return": 0.0595, "profit": 90.91, "volume": 1527.17 } ], "byPrice": [ { "label": "Under 20¢", "avgEntry": 0.0454, "trades": 90, "settled": 28, "winRate": 0.3571, "return": 1.2214, "profit": 168476.68, "volume": 137938.89 } ], "behaviour": [], "hours": [ { "weekday": 0, "hour": 1, "trades": 76 } ], "streaks": { "current": 1, "currentKind": "win", "longestWin": 13, "longestLoss": 14 } }, "similar": [ { "ref": "kalshi:thebigdon", "name": "thebigdon", "profit": 189605.38 } ] } } ``` ## Response fields - `data` (object): The trader you asked for. - `ref` (trader ref): The trader's ref. - `name` (string or null): The trader's public name. The API never returns a wallet address as a name. - `venue` (string): The venue the trader trades on. - `record` (object): The trader's whole record. - `trades` (integer): The number of trades Rivo has on record. - `settled` (integer): The number of settled bets. - `winRate` (number or null): The share of settled bets won, from 0 to 1. It is `null` until there are 10 settled bets. - `profit` (number or null): The profit in USD. - `volume` (number or null): The dollars traded. - `return` (number or null): The profit divided by the dollars traded. It is `null` until there are 10 settled bets. - `byCategory` (object[]): The trader's record in each category. - `category` (string): The category this row covers. - `kind` (string or null): The kind of market this row covers. It is `null` on a row for a whole category. - `trades` (integer): The number of trades Rivo has on record. - `settled` (integer): The number of settled bets. - `winRate` (number or null): The share of settled bets won, from 0 to 1. It is `null` until there are 10 settled bets. - `profit` (number or null): The profit in USD. - `volume` (number or null): The dollars traded. - `return` (number or null): The profit divided by the dollars traded. It is `null` until there are 10 settled bets. - `avgEntry` (number or null): The average price paid, from 0 to 1. - `lastTradeAt` (string or null): When the trader last traded in this category or kind, in ISO 8601 UTC. - `byKind` (object[]): The trader's record in each kind of market. - `category` (string): The category this row covers. - `kind` (string or null): The kind of market this row covers. It is `null` on a row for a whole category. - `trades` (integer): The number of trades Rivo has on record. - `settled` (integer): The number of settled bets. - `winRate` (number or null): The share of settled bets won, from 0 to 1. It is `null` until there are 10 settled bets. - `profit` (number or null): The profit in USD. - `volume` (number or null): The dollars traded. - `return` (number or null): The profit divided by the dollars traded. It is `null` until there are 10 settled bets. - `avgEntry` (number or null): The average price paid, from 0 to 1. - `lastTradeAt` (string or null): When the trader last traded in this category or kind, in ISO 8601 UTC. - `avgEntry` (number or null): The average price paid, from 0 to 1. - `rank` (object or null): The trader's place on the venue's profit ranking. It is `null` when the trader is not ranked. - `position` (integer): The trader's place, where 1 is the top. - `outOf` (integer): The number of traders in the ranking. - `lastTradeAt` (string or null): When the trader last traded, in ISO 8601 UTC. - `positions` (object): The trader's positions, when you ask for `include=positions`. - `open` (object[]): The positions the trader still holds. - `market` (object or null): The market this position is on. It is `null` on the rare position whose market Rivo cannot name. Here `kind` is always `null`. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the trader holds. - `entryPrice` (number or null): The average price paid, from 0 to 1. - `exitPrice` (number or null): The average price the trader sold at, from 0 to 1. - `usd` (number): The dollars the trader spent buying. - `soldUsd` (number or null): The dollars the trader received from selling. - `status` (string or null): The position's status: `open`, `reduced`, `exited` or `settled`. A position stops counting as open once the trader sells 95% of it. - `result` (string or null): The result once the market settles: `win`, `loss` or `void`. - `profit` (number or null): The profit in USD, once Rivo knows it. - `openedAt` (string): When the trader first bought, in ISO 8601 UTC. - `lastTradeAt` (string): When the trader last bought or sold, in ISO 8601 UTC. - `recent` (object[]): Recent positions the trader has left or that have settled. - `market` (object or null): The market this position is on. It is `null` on the rare position whose market Rivo cannot name. Here `kind` is always `null`. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the trader holds. - `entryPrice` (number or null): The average price paid, from 0 to 1. - `exitPrice` (number or null): The average price the trader sold at, from 0 to 1. - `usd` (number): The dollars the trader spent buying. - `soldUsd` (number or null): The dollars the trader received from selling. - `status` (string or null): The position's status: `open`, `reduced`, `exited` or `settled`. A position stops counting as open once the trader sells 95% of it. - `result` (string or null): The result once the market settles: `win`, `loss` or `void`. - `profit` (number or null): The profit in USD, once Rivo knows it. - `openedAt` (string): When the trader first bought, in ISO 8601 UTC. - `lastTradeAt` (string): When the trader last bought or sold, in ISO 8601 UTC. - `curve` (object): The profit curve, when you ask for `include=curve`. - `points` (object[]): The running total of profit, oldest first. - `time` (string): The time of this point, in ISO 8601 UTC. - `profit` (number): The total profit up to this point, in USD. - `trades` (integer): The number of settled bets counted up to this point. - `months` (object[]): The results for each month. - `month` (string): The month, written as `YYYY-MM`. - `trades` (integer): The number of bets that settled that month. - `profit` (number): The profit that month, in USD. - `habits` (object): The trader's habits, when you ask for `include=habits`. - `bySize` (object[]): The trader's results by bet size, split into four bands with the same number of trades in each. - `label` (string): The band this row covers, such as `$0 to $14` or `Under 20¢`. - `trades` (integer): The number of trades Rivo has on record. - `settled` (integer): The number of settled bets. - `winRate` (number or null): The share of settled bets won, from 0 to 1. It is `null` until there are 10 settled bets. - `profit` (number or null): The profit in USD. - `volume` (number or null): The dollars traded. - `return` (number or null): The profit divided by the dollars traded. It is `null` until there are 10 settled bets. - `byPrice` (object[]): The trader's results by price paid, with `avgEntry` for each band. - `label` (string): The band this row covers, such as `$0 to $14` or `Under 20¢`. - `trades` (integer): The number of trades Rivo has on record. - `settled` (integer): The number of settled bets. - `winRate` (number or null): The share of settled bets won, from 0 to 1. It is `null` until there are 10 settled bets. - `profit` (number or null): The profit in USD. - `volume` (number or null): The dollars traded. - `return` (number or null): The profit divided by the dollars traded. It is `null` until there are 10 settled bets. - `avgEntry` (number or null): The average price paid in this band. - `behaviour` (object[]): How the trader's trades changed their positions, such as opening, adding, trimming or closing, with the most common first. - `label` (string): What this row counts. - `trades` (integer): The number of trades. - `hours` (object[]): The trader's trades by weekday and hour, in US Eastern time. - `weekday` (integer): The day of the week, where 0 is Sunday. - `hour` (integer): The hour of the day, from 0 to 23. - `trades` (integer): The number of trades. - `streaks` (object): The trader's win and loss streaks over settled bets. - `current` (integer): The length of the current streak. - `currentKind` (string or null): Whether the current streak is wins or losses: `win` or `loss`. - `longestWin` (integer): The longest run of wins. - `longestLoss` (integer): The longest run of losses. - `similar` (object[]): Similar traders, when you ask for `include=similar`. - `ref` (trader ref): The trader's ref. - `name` (string or null): The trader's public name. - `profit` (number or null): The trader's profit in USD. Rivo hides traders with a losing record from its lists, but you can still read them by ref. The API answers `NOT_FOUND` for a trader removed from the product. ## Errors | Code | When | | --- | --- | | `VALIDATION` | `ref` is not a trader ref, or a parameter is unknown. | | `NOT_FOUND` | No trader has that ref. | | `QUOTA_EXCEEDED` | The includes would take the key past its per-minute limit. | --- # Backtest > Scores a signal or one trader on settled history at a sizing you choose. You can add a holdout check, a sweep or a breakdown. Source: https://rivo.markets/docs/reference/backtest `POST /v2/backtest` Scope: `read` This route scores an idea on settled whale trades. It works out what you would have made by copying every matching trade and holding each one to settlement at the price paid. It returns scores, never individual trades. Send exactly one of `signal` or `trader`. A signal is a set of conditions that whale buys must meet. A trader scores one trader's own buys. > **Note:** A signal scores buys only. It has no `kind` because live signal bots match on category, not on kind of market. That way a backtest scores exactly what a bot built from the same signal would run. - `holdout` scores the first two thirds of the window and then checks the result on the last third. `holds` is `true` when the last third also made money. - `sweep` runs the signal once for each value of one field, so you can see where a threshold stops paying. Each value counts as one request. - `breakdown` splits the score by category, by month or by price band. Holdout, sweep and breakdown work on signals only. Settled history changes only as markets settle, so Rivo answers a repeated request from its cache for 10 minutes. ## Example request curl: ```bash curl -X POST "https://api.rivo.markets/v2/backtest" \ -H "Authorization: Bearer rivo_live_..." \ -H "Content-Type: application/json" \ -d '{ "signal": { "venue": [ "kalshi" ], "category": [ "sports" ], "priceMin": 0.3, "priceMax": 0.7, "minUsd": 5000 }, "sizing": { "usd": 100 }, "days": 90, "holdout": true }' ``` Python: ```python import requests url = "https://api.rivo.markets/v2/backtest" headers = { "Authorization": "Bearer rivo_live_...", } body = { "signal": { "venue": [ "kalshi", ], "category": [ "sports", ], "priceMin": 0.3, "priceMax": 0.7, "minUsd": 5000, }, "sizing": { "usd": 100, }, "days": 90, "holdout": True, } res = requests.post(url, headers=headers, json=body) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const res = await fetch("https://api.rivo.markets/v2/backtest", { method: "POST", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "signal": { "venue": [ "kalshi" ], "category": [ "sports" ], "priceMin": 0.3, "priceMax": 0.7, "minUsd": 5000 }, "sizing": { "usd": 100 }, "days": 90, "holdout": true }), }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Body - `signal` (object, optional): The conditions a whale buy must meet. Send this or `trader`, not both. The API refuses unknown fields. - `venue` (string[], optional): Matches buys on these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. You can pass up to 50. Leave it out to match every venue. - `category` (string[], optional): Matches buys in these categories: `sports`, `politics`, `crypto`, `finance`, `culture`. You can pass up to 5. It matches the market's stored category, as saved signal bots do. - `minUsd` (number, optional): The smallest trade to match, in USD. - `maxUsd` (number, optional): The largest trade to match, in USD. It must be at least `minUsd`. - `priceMin` (number, optional): The lowest price paid to match, from 0 to 1. - `priceMax` (number, optional): The highest price paid to match, from 0 to 1. It must be at least `priceMin`. - `entry` (string, optional, default `all`): Which buys to match. `open` matches new positions only, `add` matches buys that add to a position, and `all` matches both. - `traders` (trader ref[], optional): Matches only these traders' buys. Pass 1 to 25 trader refs. - `keywords` (string[], optional): Matches markets whose name contains any of these words. Pass 1 to 10 words, each up to 40 characters. - `trader` (trader ref, optional): Scores one trader's buys instead of a signal. Send this or `signal`, not both. - `sizing` (object, optional, default `{ "usd": 100 }`): How much each trade stakes. Send exactly one of the two forms below. - `usd` (number, optional): A fixed stake for each trade in USD, above 0 and up to 1,000,000. - `percent` (number, optional): Stakes this percentage of the source trade's dollars, above 0 and up to 10,000. Send it together with `maxUsd`. - `maxUsd` (number, optional): The most any one trade stakes when you use `percent`, in USD, up to 1,000,000. - `days` (integer, optional, default `90`): How many days back from now to score, from 1 to 365. - `holdout` (boolean, optional): Set to `true` to score the first two thirds of the window and check the result on the last third. It works on signals only. - `sweep` (object, optional): Runs the signal once for each value of one field. It works on signals only. - `field` (string, required): The field to sweep: `priceMin`, `priceMax`, `minUsd` or `maxUsd`. - `values` (number[], required): The values to try, from 2 to 20 of them. Prices run from 0 to 1. - `breakdown` (string, optional): How to split the score: `category`, `month` or `price_band`. It works on signals only. ## Example response ```json { "data": { "days": 90, "sizing": { "usd": 100 }, "trades": 1711, "settled": 1662, "wins": 924, "losses": 738, "open": 48, "winRate": 0.556, "staked": 166200, "profit": 4164.71, "return": 0.0251, "curve": [ { "week": "2026-07-13", "settled": 15, "profit": 1014.77, "cumulative": 1014.77 }, { "week": "2026-07-20", "settled": 0, "profit": 0, "cumulative": 1014.77 }, { "week": "2026-07-27", "settled": 7, "profit": 338.89, "cumulative": 1353.66 } ], "holdout": { "splitAt": "2026-09-15T02:28:05.331Z", "train": { "trades": 733, "settled": 717, "wins": 425, "losses": 292, "open": 16, "winRate": 0.5927, "staked": 71700, "profit": 4527.89, "return": 0.0632 }, "test": { "trades": 978, "settled": 945, "wins": 499, "losses": 446, "open": 32, "winRate": 0.528, "staked": 94500, "profit": -363.18, "return": -0.0038 }, "holds": false } } } ``` ## Response fields - `data` (object): The score for your idea. - `days` (integer): The number of days scored. - `sizing` (object): The sizing this result used: `{ usd }` or `{ percent, maxUsd }`. - `trades` (integer): The number of trades the idea matched in the window, settled or not. - `settled` (integer): The number of matched trades that settled. - `wins` (integer): The number of settled trades that won. - `losses` (integer): The number of settled trades that lost. - `open` (integer): The number of trades still open. Rivo counts them but does not score them. - `winRate` (number or null): The share of settled trades that won, from 0 to 1. It is `null` until a trade settles. - `staked` (number): The dollars staked on settled trades at the sizing you gave. - `profit` (number): The profit in USD, with each trade held to settlement at the price paid. - `return` (number or null): The profit divided by the dollars staked. It is `null` when nothing was staked. - `curve` (object[]): The profit for each week, oldest first. - `week` (string): The Monday that starts the week, written as `YYYY-MM-DD`. - `settled` (integer): The number of trades that settled that week. - `profit` (number): The profit that week, in USD. - `cumulative` (number): The total profit up to the end of that week, in USD. - `holdout` (object): The holdout check, when you send `holdout: true`. - `splitAt` (string): The time where Rivo split the window, in ISO 8601 UTC. - `train` (object): The score for the first two thirds of the window. - `trades` (integer): The number of trades the idea matched in the window, settled or not. - `settled` (integer): The number of matched trades that settled. - `wins` (integer): The number of settled trades that won. - `losses` (integer): The number of settled trades that lost. - `open` (integer): The number of trades still open. Rivo counts them but does not score them. - `winRate` (number or null): The share of settled trades that won, from 0 to 1. It is `null` until a trade settles. - `staked` (number): The dollars staked on settled trades at the sizing you gave. - `profit` (number): The profit in USD, with each trade held to settlement at the price paid. - `return` (number or null): The profit divided by the dollars staked. It is `null` when nothing was staked. - `test` (object): The score for the last third of the window. - `trades` (integer): The number of trades the idea matched in the window, settled or not. - `settled` (integer): The number of matched trades that settled. - `wins` (integer): The number of settled trades that won. - `losses` (integer): The number of settled trades that lost. - `open` (integer): The number of trades still open. Rivo counts them but does not score them. - `winRate` (number or null): The share of settled trades that won, from 0 to 1. It is `null` until a trade settles. - `staked` (number): The dollars staked on settled trades at the sizing you gave. - `profit` (number): The profit in USD, with each trade held to settlement at the price paid. - `return` (number or null): The profit divided by the dollars staked. It is `null` when nothing was staked. - `holds` (boolean): This is `true` when the last third's return is above zero. - `sweep` (object): The sweep results, when you send `sweep`. - `field` (string): The field that was swept. - `results` (object[]): One score for each value. Each row has `value` next to the score fields. - `value` (number): The value Rivo tried. - `trades` (integer): The number of trades the idea matched in the window, settled or not. - `settled` (integer): The number of matched trades that settled. - `wins` (integer): The number of settled trades that won. - `losses` (integer): The number of settled trades that lost. - `open` (integer): The number of trades still open. Rivo counts them but does not score them. - `winRate` (number or null): The share of settled trades that won, from 0 to 1. It is `null` until a trade settles. - `staked` (number): The dollars staked on settled trades at the sizing you gave. - `profit` (number): The profit in USD, with each trade held to settlement at the price paid. - `return` (number or null): The profit divided by the dollars staked. It is `null` when nothing was staked. - `breakdown` (object): The breakdown, when you send `breakdown`. - `by` (string): How the score is split: `category`, `month` or `price_band`. - `rows` (object[]): One score for each bucket. Each row has `bucket` next to the score fields. - `bucket` (string): The bucket: the venue's own category name, a month written as `YYYY-MM`, or a price band such as `0.40-0.60`. - `trades` (integer): The number of trades the idea matched in the window, settled or not. - `settled` (integer): The number of matched trades that settled. - `wins` (integer): The number of settled trades that won. - `losses` (integer): The number of settled trades that lost. - `open` (integer): The number of trades still open. Rivo counts them but does not score them. - `winRate` (number or null): The share of settled trades that won, from 0 to 1. It is `null` until a trade settles. - `staked` (number): The dollars staked on settled trades at the sizing you gave. - `profit` (number): The profit in USD, with each trade held to settlement at the price paid. - `return` (number or null): The profit divided by the dollars staked. It is `null` when nothing was staked. - `byCategory` (object[]): The record by the venue's own category name. Only trader backtests return it. - `category` (string): The venue's name for the category. - `settled` (integer): The number of settled trades. - `wins` (integer): The number of wins. - `profit` (number): The profit in USD. - `return` (number or null): The profit divided by the dollars staked. A signal scored here matches what a signal bot built from the same signal would record. Rivo scores signals on whale trades only. If a signal names `traders`, it counts those traders' buys of any size. ## Errors | Code | When | | --- | --- | | `VALIDATION` | You sent both `signal` and `trader`, or neither. You sent holdout, sweep or breakdown with `trader`. A sweep price is above 1, a minimum is above its maximum, or a field is unknown. | | `QUOTA_EXCEEDED` | The sweep's values would take the key past its per-minute limit. | --- # Place Paper Order > Buys or sells one side of a market on paper. Rivo fills the order against the venue's live order book at the venue's real fee. Source: https://rivo.markets/docs/reference/place-paper-order `POST /v2/paper-orders` Scope: `write` This route places one paper trade by hand. No money moves, and you do not need a venue account. Rivo prices the order against the venue's live order book and folds the venue's taker fee into the price. It records only what fills right now, and nothing rests on the book. To name the side, set `market` to a contract's `ref` and `outcome` to the side's `name` or `label`. You can find all three under `outcomes[]` on [Get Market](https://rivo.markets/docs/reference/get-market). You can also pass `yes` or `no` as `outcome`. The order comes back with the side's `name`. - A buy spends `usd`. A sell gives back `contracts`, or `"all"` to sell everything held by hand on that side. - `maxPrice` on a buy, or `minPrice` on a sell, fills only the part of the book inside that price. If nothing inside that price can fill, the API refuses the order and records nothing. - A sell closes only paper you bought by hand. It never touches a bot's holdings. > **Retries:** Send an `Idempotency-Key` header and reuse it when you retry. The same key always names the same trade. A retry returns the first fill with `Idempotent-Replayed: true` and never places a second trade. If the API refuses an order, you can use its key again. See [Errors](https://rivo.markets/docs/errors#retrying-a-paper-order). ## Example request curl: ```bash curl -X POST "https://api.rivo.markets/v2/paper-orders" \ -H "Authorization: Bearer rivo_live_..." \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-2026-10-15-milwaukee-1" \ -d '{ "market": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "outcome": "Milwaukee", "action": "buy", "usd": 250, "maxPrice": 0.9 }' ``` Python: ```python import requests url = "https://api.rivo.markets/v2/paper-orders" headers = { "Authorization": "Bearer rivo_live_...", "Idempotency-Key": "order-2026-10-15-milwaukee-1", } body = { "market": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "outcome": "Milwaukee", "action": "buy", "usd": 250, "maxPrice": 0.9, } res = requests.post(url, headers=headers, json=body) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const res = await fetch("https://api.rivo.markets/v2/paper-orders", { method: "POST", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "order-2026-10-15-milwaukee-1", }, body: JSON.stringify({ "market": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "outcome": "Milwaukee", "action": "buy", "usd": 250, "maxPrice": 0.9 }), }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Headers - `Idempotency-Key` (string, optional): An optional unique value for each order, up to 200 characters. Reuse it when you retry. Rivo keeps it for 24 hours. ## Body - `market` (market ref, required): One contract's `ref`. The API refuses a market with several contracts, so use a `ref` from its `outcomes[]` instead. - `outcome` (string, required): The side's `outcomes[].name` or `outcomes[].label`, or `yes` or `no`. - `action` (string, required): Whether to buy or sell: `buy` or `sell`. - `usd` (number, optional): The dollars to spend, above 0 and up to 1,000,000. Send it on a buy, where it is required. - `contracts` (number or "all", optional): The number of contracts to sell, up to 10,000,000, or `"all"`. Send it on a sell, where it is required. - `maxPrice` (number, optional): On a buy, the most to pay for each contract, between 0 and 1. - `minPrice` (number, optional): On a sell, the least to accept for each contract, between 0 and 1. ## Example response ```json { "data": { "id": "5b7e0c1a-2f4d-4c8e-9a61-3d2f7b9e0a14", "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "contract": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "title": "Milwaukee wins", "venue": "kalshi", "category": "sports", "kind": "mlb" }, "outcome": "Yes", "action": "buy", "contracts": 284, "avgPrice": 0.8796, "cost": 249.81, "unfilledContracts": 0, "filledAt": "2026-10-15T02:31:07.412Z" } } ``` ## Response fields - `data` (object): The filled order. - `id` (string): The paper order's id. - `market` (object): The market this row is about. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side traded, using the name that [Get Market](https://rivo.markets/docs/reference/get-market) gives it. - `action` (string): Whether the order bought or sold: `buy` or `sell`. - `contracts` (number): The number of contracts that filled. - `avgPrice` (number): The average price for each contract, from 0 to 1, with the venue's fee included. - `cost` (number): What the buy cost, or what the sale brought in, in USD with the fee included. - `unfilledContracts` (number): The contracts the book could not fill right away. Paper orders never rest on the book, so Rivo drops this part. - `filledAt` (string): When the order filled, in ISO 8601 UTC. Paper holdings settle when the market settles. You can see them in [Get Paper Account](https://rivo.markets/docs/reference/get-paper-account). ## Errors | Code | When | | --- | --- | | `SCOPE_REQUIRED` | The key does not have the `write` scope. | | `VALIDATION` | A required field is missing, or you sent a field that does not fit the action. `market` names a market with several contracts, or `outcome` is not one of the market's sides. You hold nothing by hand to sell, or nothing in the book fills inside your price limit. Or you already used the `Idempotency-Key` for a different order. | | `NOT_FOUND` | No market has that ref. | | `IDEMPOTENCY_IN_PROGRESS` | Rivo is still placing an order with the same `Idempotency-Key`. Wait, then retry with the same key. | --- # Get Paper Account > Reads the paper account's totals and positions, from both hand trades and bots. On request it adds the paper orders you placed by hand. Source: https://rivo.markets/docs/reference/get-paper-account `GET /v2/paper` Scope: `read` This route returns the paper account: totals for the whole account, and its positions. It shows open positions by default. It lists positions you placed by hand and positions held by paper bots together, and tags each one with its `source`. `include=orders` adds every paper order you placed by hand, newest first. The orders page with their own cursor, `ordersCursor`. Each sale carries `profit`, measured against the average cost of what you held. Market bots keep their trades on the bot itself. You can find them in [Get Bot](https://rivo.markets/docs/reference/get-bot), not here. ## Example request curl: ```bash curl "https://api.rivo.markets/v2/paper?status=open&include=orders" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/paper" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("status", "open"), ("include", "orders"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["status", "open"], ["include", "orders"], ]); const res = await fetch(`https://api.rivo.markets/v2/paper?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Query parameters - `status` (string, optional, default `open`): Which positions to return: `open`, `settled` or `all`. - `source` (string, optional, default `all`): Which positions to return by where they came from: `hand`, `bot` or `all`. - `bot` (string, optional): Returns only one bot's positions. Pass its id, such as `trader:` or `signal:`. - `venue` (string, optional): Returns rows from these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `market` (market ref, optional): Returns positions on this contract. Pass the contract's `ref`. Repeat the key to pass several, up to 50. - `include` (string, optional): Set to `orders` to add your hand-placed paper orders. - `since` (string, optional): With `include=orders`, returns orders at or after this time, in ISO 8601 with an offset. - `ordersCursor` (string, optional): With `include=orders`, pass the `orders.nextCursor` from the previous page to read the next one. - `limit` (integer, optional, default `50`): The number of rows on each page, from 1 to 200. It applies to both positions and orders. - `cursor` (string, optional): Pass the `positions.nextCursor` from the previous page to read the next one. ## Example response ```json { "data": { "totals": { "openUsd": 1842.6, "unrealized": 63.18, "realized": 412.95, "winRate": 0.5833 }, "positions": { "data": [ { "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "contract": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "title": "Milwaukee wins", "venue": "kalshi", "category": "sports", "kind": "mlb" }, "outcome": "Yes", "contracts": 284, "entryPrice": 0.8796, "price": 0.89, "cost": 249.81, "value": 252.76, "profit": 2.95, "status": "open", "result": null, "source": "hand", "bot": null, "openedAt": "2026-10-15T02:31:07.412Z", "settledAt": null }, { "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE5GTE5GQ05PUlRILTI3LUNISQ", "contract": "a2Fsc2hpOm1hcmtldDpLWE5GTE5GQ05PUlRILTI3LUNISQ", "title": "Will Chicago win the Pro Football NFC North Division?", "venue": "kalshi", "category": "sports", "kind": "nfl" }, "outcome": "Yes", "contracts": 500, "entryPrice": 0.2, "price": 0.2, "cost": 100, "value": 100, "profit": 0, "status": "open", "result": null, "source": "bot", "bot": { "id": "trader:8d3f2a61-0c4e-4b7a-9f15-2e6a1c9b4d70", "name": "avidsmores" }, "openedAt": "2026-10-12T05:13:31.000Z", "settledAt": null } ], "nextCursor": null }, "orders": { "data": [ { "id": "5b7e0c1a-2f4d-4c8e-9a61-3d2f7b9e0a14", "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "contract": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "title": "Milwaukee wins", "venue": "kalshi", "category": "sports", "kind": "mlb" }, "outcome": "Yes", "action": "buy", "contracts": 284, "avgPrice": 0.8796, "cost": 249.81, "unfilledContracts": 0, "filledAt": "2026-10-15T02:31:07.412Z" } ], "nextCursor": null } } } ``` ## Response fields - `data` (object): The paper account. - `totals` (object): Totals for the whole account. Filters do not change them. - `openUsd` (number): What your open holdings are worth now, in USD. - `unrealized` (number): The profit on your open holdings, in USD. - `realized` (number): The profit from settled holdings and sales, in USD. - `winRate` (number or null): The share of settled holdings that won, from 0 to 1. It is `null` until a holding settles. - `positions` (object): One page of positions. - `data` (object[]): The positions on this page. - `market` (object): The market this row is about. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the holding is on. - `contracts` (number): The number of contracts held. - `entryPrice` (number): The average price paid, from 0 to 1. - `price` (number): The side's price now, or its payout once the market settles. - `cost` (number): The contracts multiplied by `entryPrice`, in USD. - `value` (number): What the holding is worth now, in USD. - `profit` (number): The profit in USD, which is `value` minus `cost`. - `status` (string): Whether the holding is `open` or `settled`. - `result` (string or null): The result once the market settles: `win`, `loss` or `void`. - `source` (string): Where the holding came from. `hand` means you placed it with a paper order, and `bot` means a paper bot holds it. - `bot` (object or null): The bot that holds this position. It is `null` for a holding you placed by hand. - `id` (string): The bot's id, as [List Bots](https://rivo.markets/docs/reference/list-bots) returns it. - `name` (string): The bot's name. - `openedAt` (string): When the holding opened, in ISO 8601 UTC. - `settledAt` (string or null): When the holding settled, in ISO 8601 UTC. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. - `orders` (object): One page of the paper orders you placed by hand, when you ask for `include=orders`. - `data` (object[]): The orders on this page, newest first. - `id` (string): The paper order's id. - `market` (object): The market this row is about. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side traded, using the name that [Get Market](https://rivo.markets/docs/reference/get-market) gives it. - `action` (string): Whether the order bought or sold: `buy` or `sell`. - `contracts` (number): The number of contracts that filled. - `avgPrice` (number): The average price for each contract, from 0 to 1, with the venue's fee included. - `cost` (number): What the buy cost, or what the sale brought in, in USD with the fee included. - `unfilledContracts` (number): The contracts the book could not fill right away. Paper orders never rest on the book, so Rivo drops this part. - `filledAt` (string): When the order filled, in ISO 8601 UTC. - `profit` (number or null): On a sale, the profit measured against the average cost of what you held. - `nextCursor` (string or null): Pass this as `ordersCursor` to read the next page. It is `null` on the last page. Rivo prices open holdings from the prices it already holds for each market. A holding settles once Rivo records the market's result. ## Errors | Code | When | | --- | --- | | `VALIDATION` | A parameter is out of range or unknown, or a cursor came from another listing. | --- # List Bots > Lists every bot on the account: trader, signal and market bots, on paper and live. Source: https://rivo.markets/docs/reference/list-bots `GET /v2/bots` Scope: `read` This route returns the account's bots of all three kinds, each with its record. It lists bots you made in the app and bots you made through the API together. - A **trader** bot copies one trader's buys. - A **signal** bot copies every whale buy that matches its signal. - A **market** bot runs a price rule on one market, or on every market of a repeating series. Live bots appear with `mode: "live"`. You can set them up and change them only in the app. ## Example request curl: ```bash curl "https://api.rivo.markets/v2/bots?status=running" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/bots" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("status", "running"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["status", "running"], ]); const res = await fetch(`https://api.rivo.markets/v2/bots?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Query parameters - `kind` (string, optional): Returns bots of these kinds: `trader`, `signal` or `market`. Repeat the key to pass several. - `status` (string, optional): Returns bots with these statuses: `running`, `paused` or `ended`. Repeat the key to pass several. - `venue` (string, optional): Returns rows from these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. To pass several values, repeat the key, up to 50 times. A row matches if it matches any of them. - `limit` (integer, optional, default `50`): The number of rows on each page, from 1 to 200. - `cursor` (string, optional): Pass the `nextCursor` from the previous page to read the next one. A cursor works only on the listing that made it. See [Paging](https://rivo.markets/docs/concepts#paging). ## Example response ```json { "data": [ { "id": "trader:8d3f2a61-0c4e-4b7a-9f15-2e6a1c9b4d70", "name": "avidsmores", "kind": "trader", "mode": "paper", "summary": "Copies avidsmores", "status": "running", "venues": ["kalshi"], "trades": 14, "profit": 186.4, "winRate": 0.6, "lastTradeAt": "2026-10-12T05:13:31.000Z" }, { "id": "signal:2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55", "name": "Kalshi sports buys, 30¢ to 70¢, $5,000 or more", "kind": "signal", "mode": "paper", "summary": "Kalshi sports buys, 30¢ to 70¢, $5,000 or more", "status": "running", "venues": ["kalshi"], "trades": 212, "profit": 523.17, "winRate": 0.5521, "lastTradeAt": "2026-10-15T01:58:12.000Z" } ], "nextCursor": null } ``` ## Response fields - `data` (object[]): The bots on this page. - `id` (string): The bot's id: `trader:`, `signal:` or `market:`. - `name` (string): The bot's name. A trader bot takes its trader's name, and a market bot takes the name of its market or series. - `kind` (string): The kind of bot. A `trader` bot copies one trader, a `signal` bot runs a signal, and a `market` bot runs a price rule on one market. - `mode` (string): Whether the bot trades on `paper` or `live`. A live bot trades real money. You set it up in the app, and the API can only read it. - `summary` (string): What the bot does, in one line. - `status` (string): The bot's status: `running`, `paused` or `ended`. A signal bot is never paused. It runs for as long as it exists. - `venues` (string[]): The venues the bot trades on. - `trades` (integer): The number of trades the bot took. - `profit` (number): The bot's profit in USD, from both closed and open trades. - `winRate` (number or null): The share of the bot's settled trades that won, from 0 to 1. - `lastTradeAt` (string or null): When the bot last traded, in ISO 8601 UTC. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. ## Errors | Code | When | | --- | --- | | `VALIDATION` | A parameter is out of range or unknown, or the cursor came from another listing. | --- # Create Bot > Starts a paper bot that copies a trader, runs a signal, or runs a price rule on one market. Source: https://rivo.markets/docs/reference/create-bot `POST /v2/bots` Scope: `write` This route starts a paper bot. The `trigger` decides what kind of bot it is: | trigger | Kind | What it does | | --- | --- | --- | | `{ "trader": ref }` | trader | The bot follows the trader and copies each of their buys on paper at your sizing. | | `{ "signal": {...} }` | signal | The bot copies every whale buy that matches the signal, at your sizing. It uses the same signal that [Backtest](https://rivo.markets/docs/reference/backtest) scores. | | `{ "market": ref, "outcome": name, "rule": {...} }` | market | The bot watches one side of one contract and buys and sells by its rule. | Every bot you make here trades on paper. Trader bots work only on venues that name their traders. An account can hold up to 20 signal bots and 20 market bots. > **Market bot rules:** Set at most one of `buyBelow`, `buyAbove`, `dropsBy` and `risesBy`. If you set none, the bot buys right away. ## Example request curl: ```bash curl -X POST "https://api.rivo.markets/v2/bots" \ -H "Authorization: Bearer rivo_live_..." \ -H "Content-Type: application/json" \ -d '{ "trigger": { "market": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "outcome": "Milwaukee", "rule": { "buyBelow": 0.8, "takeProfit": 0.95 } }, "sizing": { "usd": 100 } }' ``` Python: ```python import requests url = "https://api.rivo.markets/v2/bots" headers = { "Authorization": "Bearer rivo_live_...", } body = { "trigger": { "market": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "outcome": "Milwaukee", "rule": { "buyBelow": 0.8, "takeProfit": 0.95, }, }, "sizing": { "usd": 100, }, } res = requests.post(url, headers=headers, json=body) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const res = await fetch("https://api.rivo.markets/v2/bots", { method: "POST", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "trigger": { "market": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "outcome": "Milwaukee", "rule": { "buyBelow": 0.8, "takeProfit": 0.95 } }, "sizing": { "usd": 100 } }), }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Body - `trigger` (object, required): One of the three forms above. The API refuses unknown fields. - `trader` (trader ref, optional): For a trader bot, the trader to copy. - `signal` (object, optional): For a signal bot, the signal to run. - `venue` (string[], optional): Matches buys on these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. You can pass up to 50. Leave it out to match every venue. - `category` (string[], optional): Matches buys in these categories: `sports`, `politics`, `crypto`, `finance`, `culture`. You can pass up to 5. It matches the market's stored category, as saved signal bots do. - `minUsd` (number, optional): The smallest trade to match, in USD. - `maxUsd` (number, optional): The largest trade to match, in USD. It must be at least `minUsd`. - `priceMin` (number, optional): The lowest price paid to match, from 0 to 1. - `priceMax` (number, optional): The highest price paid to match, from 0 to 1. It must be at least `priceMin`. - `entry` (string, optional, default `all`): Which buys to match. `open` matches new positions only, `add` matches buys that add to a position, and `all` matches both. - `traders` (trader ref[], optional): Matches only these traders' buys. Pass 1 to 25 trader refs. - `keywords` (string[], optional): Matches markets whose name contains any of these words. Pass 1 to 10 words, each up to 40 characters. - `market` (market ref, optional): For a market bot, one contract's `ref`. Take it from `outcomes[].ref` on [Get Market](https://rivo.markets/docs/reference/get-market). - `outcome` (string, optional): For a market bot, the side to trade. Use `outcomes[].name` or `outcomes[].label`, or `yes` or `no`. - `rule` (object, optional): For a market bot, when to buy and sell. Set at most one of `buyBelow`, `buyAbove`, `dropsBy` and `risesBy`. If you set none, the bot buys right away. - `buyBelow` (number, optional): The bot buys when the side's price falls to this level, between 0 and 1. - `buyAbove` (number, optional): The bot buys when the side's price rises to this level, between 0 and 1. - `dropsBy` (number, optional): The bot buys when the price drops this far below where it stood when the bot started. Write it in dollars of price, so `0.05` is 5 points. - `risesBy` (number, optional): The bot buys when the price rises this far above where it stood when the bot started. Write it in dollars of price. - `takeProfit` (number, optional): The bot sells when the price reaches this level, between 0 and 1. - `stopLoss` (number, optional): The bot sells when the price falls to this level, between 0 and 1. - `takeProfitPoints` (number, optional): The bot sells when the price rises this far above the price paid. Write it in dollars of price. - `stopLossPoints` (number, optional): The bot sells when the price falls this far below the price paid. Write it in dollars of price. - `maxHoldHours` (number, optional): The bot sells after it has held the position this many hours, up to 720. - `repeat` (boolean, optional, default `false`): Set to `true` to buy again after each sale. When it is off, the bot trades once, or once per market when it follows a series. - `name` (string, optional): The bot's name, from 1 to 60 characters. Only signal bots take a name. If you leave it out, Rivo names the bot after its signal. - `sizing` (object, optional, default `{ "usd": 100 }`): How much each trade stakes. Send exactly one of the two forms below. A market bot takes `{ usd }` only. - `usd` (number, optional): A fixed stake for each trade in USD, above 0 and up to 1,000,000. - `percent` (number, optional): Stakes this percentage of the source trade's dollars, above 0 and up to 10,000. Send it together with `maxUsd`. - `maxUsd` (number, optional): The most any one trade stakes when you use `percent`, in USD, up to 1,000,000. - `mirrorExits` (boolean, optional, default `false`): Set to `true` to make a trader bot sell when the trader sells. Only trader bots take it. - `series` (boolean, optional): Set to `true` to move a market bot on to the next market of the repeating series after each one. Only market bots take it. - `alerts` (boolean, optional, default `true`): Whether the bot sends in-app and Telegram messages about its trades. ## Example response ```json { "data": { "id": "market:7a0e4c92-13bd-4e61-8f2a-c5d9b0317e48", "name": "Milwaukee wins", "kind": "market", "mode": "paper", "summary": "Buy Yes when it falls to 80¢, sell at 95¢", "status": "running", "venues": ["kalshi"], "profit": 0, "winRate": null, "lastTradeAt": null, "settings": { "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "contract": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "title": "Milwaukee wins", "outcome": "Yes", "series": false }, "rule": { "buyBelow": 0.8, "takeProfit": 0.95, "repeat": false }, "sizing": { "usd": 100 }, "alerts": true }, "record": { "trades": 0, "settled": 0, "wins": 0, "losses": 0, "open": 0, "winRate": null, "realized": 0, "unrealized": 0, "profit": 0, "staked": 0, "return": null }, "curve": [] } } ``` ## Response fields - `data` (object): The new bot, with the same fields [Get Bot](https://rivo.markets/docs/reference/get-bot) returns. The API answers with status `201 Created`. - `id` (string): The bot's id: `trader:`, `signal:` or `market:`. - `name` (string): The bot's name. A trader bot takes its trader's name, and a market bot takes the name of its market or series. - `kind` (string): The kind of bot. A `trader` bot copies one trader, a `signal` bot runs a signal, and a `market` bot runs a price rule on one market. - `mode` (string): Whether the bot trades on `paper` or `live`. A live bot trades real money. You set it up in the app, and the API can only read it. - `summary` (string): What the bot does, in one line. - `status` (string): The bot's status: `running`, `paused` or `ended`. A signal bot is never paused. It runs for as long as it exists. - `venues` (string[]): The venues the bot trades on. - `profit` (number): The bot's profit in USD, from both closed and open trades. - `winRate` (number or null): The share of the bot's settled trades that won, from 0 to 1. - `lastTradeAt` (string or null): When the bot last traded, in ISO 8601 UTC. - `settings` (object): What the bot runs. The response includes only the fields for the bot's kind. - `trader` (object): On a trader bot, the trader it copies. - `ref` (trader ref): The trader's ref. - `name` (string or null): The trader's public name. - `signal` (object or null): On a signal bot, the signal, with the same fields [Backtest](https://rivo.markets/docs/reference/backtest) takes. It is `null` when the bot holds settings that only the app can edit. - `market` (object): On a market bot, the contract and side it watches. - `ref` (market ref): The contract's ref. - `title` (string): The contract's title. - `outcome` (string): The side the bot trades: `Yes` or `No`. - `series` (boolean): Whether the bot follows the repeating series. - `rule` (object): On a market bot, the rule, with the same fields `POST /v2/bots` takes. - `sizing` (object or null): How much each trade stakes: `{ usd }` or `{ percent, maxUsd }`. - `mirrorExits` (boolean): On a trader bot, whether it sells when the trader sells. - `alerts` (boolean): Whether the bot sends in-app and Telegram messages. - `record` (object): The bot's record. - `trades` (integer): The number of trades the bot took. - `settled` (integer): The number of trades that settled or closed. - `wins` (integer): The number of wins. - `losses` (integer): The number of losses. - `open` (integer): The number of trades still open. - `winRate` (number or null): The share of settled trades that won, from 0 to 1. - `realized` (number): The profit from closed trades, in USD. - `unrealized` (number): The profit on open trades, in USD. - `profit` (number): The closed and open profit added together, in USD. - `staked` (number): The dollars the bot staked. - `return` (number or null): The profit divided by the dollars staked. - `curve` (object[]): The running total of closed profit, one point for each trade. - `time` (string): The time of this point, in ISO 8601 UTC. - `profit` (number): The total profit up to this point, in USD. - `trades` (object): One page of the bot's trades, newest first, when you ask for `include=trades`. - `data` (object[]): The trades on this page. - `market` (object or null): The market the trade is on. Here `category` and `kind` are always `null`. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the bot bought. - `contracts` (number): The number of contracts bought. - `entryPrice` (number): The price paid, from 0 to 1. - `price` (number): The price now while the trade is open. Once it closes, this is the exit price or the payout. - `profit` (number): The profit in USD. - `status` (string): Whether the trade is `open` or `closed`. - `result` (string or null): The result once the trade closes: `win`, `loss` or `void`. - `openedAt` (string): When the trade opened, in ISO 8601 UTC. - `closedAt` (string or null): When the trade closed, in ISO 8601 UTC. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. - `activity` (object or null): How busy the bot's trigger has been, when you ask for `include=activity`. It is `null` for a market bot. - `trades` (integer): The number of trades the trigger matched in the window. - `windowDays` (integer): The length of the window in days, which is 30. - `medianTradeUsd` (number or null): The middle trade size, in USD. - `lastTradeAt` (string or null): When the newest matching trade happened, in ISO 8601 UTC. ## Errors | Code | When | | --- | --- | | `SCOPE_REQUIRED` | The key does not have the `write` scope. | | `VALIDATION` | A field is unknown, or you sent it for the wrong kind of bot: `name` on a trader or market bot, `mirrorExits` on a signal or market bot, or `series` on a trader or signal bot. The trader's venue does not name traders, or you already have a bot on this trader. A market bot's `sizing` is not `{ usd }`, the market has several contracts, or the outcome is not one of its sides. Or the account already holds its limit of bots. | | `NOT_FOUND` | No trader or market has that ref. | --- # Get Bot > Reads one bot's settings, record and profit curve. On request it adds the bot's trades and how busy its trigger has been. Source: https://rivo.markets/docs/reference/get-bot `GET /v2/bots/{id}` Scope: `read` This route returns one bot with its settings, its record and its profit curve. `include` adds extra sections to the response. Each include counts as a request of its own against your 120 requests a minute, so `?include=trades,activity` uses 3. | include | Adds | | --- | --- | | `trades` | Adds the bot's trades, newest first. You can page them with `limit` and `cursor` and filter them with `since` and `result`. | | `activity` | Adds how many trades the bot's trigger matched in the last 30 days, their middle size and the newest one. It is `null` for a market bot. | ## Example request curl: ```bash curl "https://api.rivo.markets/v2/bots/signal%3A2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55?include=trades%2Cactivity&limit=1" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/bots/signal%3A2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55" headers = { "Authorization": "Bearer rivo_live_...", } params = [ ("include", "trades,activity"), ("limit", "1"), ] res = requests.get(url, headers=headers, params=params) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const params = new URLSearchParams([ ["include", "trades,activity"], ["limit", "1"], ]); const res = await fetch(`https://api.rivo.markets/v2/bots/signal%3A2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55?${params}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Path parameters - `id` (string, required): The bot's id, as [List Bots](https://rivo.markets/docs/reference/list-bots) returns it: `trader:`, `signal:` or `market:`. ## Query parameters - `include` (string, optional): The extra sections to add: `trades` or `activity`. Separate them with commas, or repeat the key. - `since` (string, optional): With `include=trades`, returns trades opened at or after this time, in ISO 8601 with an offset. - `result` (string, optional): With `include=trades`, returns trades with this result: `open`, `win` or `loss`. - `limit` (integer, optional, default `50`): Applies with `include=trades`. The number of rows on each page, from 1 to 200. - `cursor` (string, optional): Applies with `include=trades`. Pass the `nextCursor` from the previous page to read the next one. A cursor works only on the listing that made it. See [Paging](https://rivo.markets/docs/concepts#paging). ## Example response ```json { "data": { "id": "signal:2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55", "name": "Kalshi sports buys, 30¢ to 70¢, $5,000 or more", "kind": "signal", "mode": "paper", "summary": "Kalshi sports buys, 30¢ to 70¢, $5,000 or more", "status": "running", "venues": ["kalshi"], "profit": 523.17, "winRate": 0.5521, "lastTradeAt": "2026-10-15T01:58:12.000Z", "settings": { "signal": { "entry": "all", "venue": ["kalshi"], "category": ["sports"], "minUsd": 5000, "priceMin": 0.3, "priceMax": 0.7 }, "sizing": { "usd": 100 }, "alerts": true }, "record": { "trades": 212, "settled": 192, "wins": 106, "losses": 86, "open": 20, "winRate": 0.5521, "realized": 498.6, "unrealized": 24.57, "profit": 523.17, "staked": 19200, "return": 0.026 }, "curve": [ { "time": "2026-09-22T19:04:51.000Z", "profit": -100 }, { "time": "2026-09-23T01:12:08.000Z", "profit": 21.43 } ], "trades": { "data": [ { "market": { "ref": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "contract": "a2Fsc2hpOm1hcmtldDpLWE1MQkdBTUUtMjZPQ1QxNDIwMDBNSUxMQUQtTUlM", "title": "Milwaukee wins", "venue": "kalshi", "category": null, "kind": null }, "outcome": "Yes", "contracts": 256.41, "entryPrice": 0.39, "price": 0.87, "profit": 123.08, "status": "open", "result": null, "openedAt": "2026-10-14T22:49:10.000Z", "closedAt": null } ], "nextCursor": "eyJvIjoxfQ" }, "activity": { "trades": 241, "windowDays": 30, "medianTradeUsd": 8420.5, "lastTradeAt": "2026-10-15T01:58:12.000Z" } } } ``` ## Response fields - `data` (object): The bot you asked for. You can find its trade count in `record.trades`. - `id` (string): The bot's id: `trader:`, `signal:` or `market:`. - `name` (string): The bot's name. A trader bot takes its trader's name, and a market bot takes the name of its market or series. - `kind` (string): The kind of bot. A `trader` bot copies one trader, a `signal` bot runs a signal, and a `market` bot runs a price rule on one market. - `mode` (string): Whether the bot trades on `paper` or `live`. A live bot trades real money. You set it up in the app, and the API can only read it. - `summary` (string): What the bot does, in one line. - `status` (string): The bot's status: `running`, `paused` or `ended`. A signal bot is never paused. It runs for as long as it exists. - `venues` (string[]): The venues the bot trades on. - `profit` (number): The bot's profit in USD, from both closed and open trades. - `winRate` (number or null): The share of the bot's settled trades that won, from 0 to 1. - `lastTradeAt` (string or null): When the bot last traded, in ISO 8601 UTC. - `settings` (object): What the bot runs. The response includes only the fields for the bot's kind. - `trader` (object): On a trader bot, the trader it copies. - `ref` (trader ref): The trader's ref. - `name` (string or null): The trader's public name. - `signal` (object or null): On a signal bot, the signal, with the same fields [Backtest](https://rivo.markets/docs/reference/backtest) takes. It is `null` when the bot holds settings that only the app can edit. - `market` (object): On a market bot, the contract and side it watches. - `ref` (market ref): The contract's ref. - `title` (string): The contract's title. - `outcome` (string): The side the bot trades: `Yes` or `No`. - `series` (boolean): Whether the bot follows the repeating series. - `rule` (object): On a market bot, the rule, with the same fields `POST /v2/bots` takes. - `sizing` (object or null): How much each trade stakes: `{ usd }` or `{ percent, maxUsd }`. - `mirrorExits` (boolean): On a trader bot, whether it sells when the trader sells. - `alerts` (boolean): Whether the bot sends in-app and Telegram messages. - `record` (object): The bot's record. - `trades` (integer): The number of trades the bot took. - `settled` (integer): The number of trades that settled or closed. - `wins` (integer): The number of wins. - `losses` (integer): The number of losses. - `open` (integer): The number of trades still open. - `winRate` (number or null): The share of settled trades that won, from 0 to 1. - `realized` (number): The profit from closed trades, in USD. - `unrealized` (number): The profit on open trades, in USD. - `profit` (number): The closed and open profit added together, in USD. - `staked` (number): The dollars the bot staked. - `return` (number or null): The profit divided by the dollars staked. - `curve` (object[]): The running total of closed profit, one point for each trade. - `time` (string): The time of this point, in ISO 8601 UTC. - `profit` (number): The total profit up to this point, in USD. - `trades` (object): One page of the bot's trades, newest first, when you ask for `include=trades`. - `data` (object[]): The trades on this page. - `market` (object or null): The market the trade is on. Here `category` and `kind` are always `null`. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the bot bought. - `contracts` (number): The number of contracts bought. - `entryPrice` (number): The price paid, from 0 to 1. - `price` (number): The price now while the trade is open. Once it closes, this is the exit price or the payout. - `profit` (number): The profit in USD. - `status` (string): Whether the trade is `open` or `closed`. - `result` (string or null): The result once the trade closes: `win`, `loss` or `void`. - `openedAt` (string): When the trade opened, in ISO 8601 UTC. - `closedAt` (string or null): When the trade closed, in ISO 8601 UTC. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. - `activity` (object or null): How busy the bot's trigger has been, when you ask for `include=activity`. It is `null` for a market bot. - `trades` (integer): The number of trades the trigger matched in the window. - `windowDays` (integer): The length of the window in days, which is 30. - `medianTradeUsd` (number or null): The middle trade size, in USD. - `lastTradeAt` (string or null): When the newest matching trade happened, in ISO 8601 UTC. ## Errors | Code | When | | --- | --- | | `VALIDATION` | A parameter is out of range or unknown, or the cursor came from another listing. | | `NOT_FOUND` | No bot has that id. | | `QUOTA_EXCEEDED` | The includes would take the key past its per-minute limit. | --- # Update Bot > Changes a paper bot's sizing, signal, rule, alerts or status. Source: https://rivo.markets/docs/reference/update-bot `PATCH /v2/bots/{id}` Scope: `write` This route changes a paper bot. Send only the fields you want to change. Each kind of bot takes its own fields: | Kind | Takes | | --- | --- | | trader | `sizing`, `mirrorExits`, `status`, `alerts` | | signal | `name`, `signal`, `sizing`, `alerts` | | market | `rule`, `sizing` (`{ usd }` only), `status`, `alerts` | You cannot pause a signal bot. It records every match for as long as it exists, so delete it to stop it. Some signal bots hold settings that only the app can edit, such as sports leagues. Change those bots' signals in the app. The API refuses changes to a live bot. ## Example request curl: ```bash curl -X PATCH "https://api.rivo.markets/v2/bots/signal%3A2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55" \ -H "Authorization: Bearer rivo_live_..." \ -H "Content-Type: application/json" \ -d '{ "sizing": { "usd": 100 }, "alerts": true }' ``` Python: ```python import requests url = "https://api.rivo.markets/v2/bots/signal%3A2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55" headers = { "Authorization": "Bearer rivo_live_...", } body = { "sizing": { "usd": 100, }, "alerts": True, } res = requests.patch(url, headers=headers, json=body) res.raise_for_status() print(res.json()) ``` TypeScript: ```typescript const res = await fetch("https://api.rivo.markets/v2/bots/signal%3A2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55", { method: "PATCH", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "sizing": { "usd": 100 }, "alerts": true }), }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); const { data } = await res.json(); ``` ## Path parameters - `id` (string, required): The bot's id, as [List Bots](https://rivo.markets/docs/reference/list-bots) returns it: `trader:`, `signal:` or `market:`. ## Body - `name` (string, optional): A signal bot's new name, from 1 to 60 characters. - `sizing` (object, optional): How much each trade stakes. Send exactly one of the two forms below. - `usd` (number, optional): A fixed stake for each trade in USD, above 0 and up to 1,000,000. - `percent` (number, optional): Stakes this percentage of the source trade's dollars, above 0 and up to 10,000. Send it together with `maxUsd`. - `maxUsd` (number, optional): The most any one trade stakes when you use `percent`, in USD, up to 1,000,000. - `signal` (object, optional): A signal bot's new signal. It replaces the old one. - `venue` (string[], optional): Matches buys on these venues: `kalshi`, `polymarket`, `polymarket_us` or `gemini`. You can pass up to 50. Leave it out to match every venue. - `category` (string[], optional): Matches buys in these categories: `sports`, `politics`, `crypto`, `finance`, `culture`. You can pass up to 5. It matches the market's stored category, as saved signal bots do. - `minUsd` (number, optional): The smallest trade to match, in USD. - `maxUsd` (number, optional): The largest trade to match, in USD. It must be at least `minUsd`. - `priceMin` (number, optional): The lowest price paid to match, from 0 to 1. - `priceMax` (number, optional): The highest price paid to match, from 0 to 1. It must be at least `priceMin`. - `entry` (string, optional, default `all`): Which buys to match. `open` matches new positions only, `add` matches buys that add to a position, and `all` matches both. - `traders` (trader ref[], optional): Matches only these traders' buys. Pass 1 to 25 trader refs. - `keywords` (string[], optional): Matches markets whose name contains any of these words. Pass 1 to 10 words, each up to 40 characters. - `rule` (object, optional): A market bot's new rule. It replaces the old one. Set at most one of `buyBelow`, `buyAbove`, `dropsBy` and `risesBy`. If you set none, the bot buys right away. - `buyBelow` (number, optional): The bot buys when the side's price falls to this level, between 0 and 1. - `buyAbove` (number, optional): The bot buys when the side's price rises to this level, between 0 and 1. - `dropsBy` (number, optional): The bot buys when the price drops this far below where it stood when the bot started. Write it in dollars of price, so `0.05` is 5 points. - `risesBy` (number, optional): The bot buys when the price rises this far above where it stood when the bot started. Write it in dollars of price. - `takeProfit` (number, optional): The bot sells when the price reaches this level, between 0 and 1. - `stopLoss` (number, optional): The bot sells when the price falls to this level, between 0 and 1. - `takeProfitPoints` (number, optional): The bot sells when the price rises this far above the price paid. Write it in dollars of price. - `stopLossPoints` (number, optional): The bot sells when the price falls this far below the price paid. Write it in dollars of price. - `maxHoldHours` (number, optional): The bot sells after it has held the position this many hours, up to 720. - `repeat` (boolean, optional, default `false`): Set to `true` to buy again after each sale. When it is off, the bot trades once, or once per market when it follows a series. - `mirrorExits` (boolean, optional): Set to `true` to make a trader bot sell when the trader sells. - `status` (string, optional): A trader or market bot's status: `running` or `paused`. - `alerts` (boolean, optional): Whether the bot sends in-app and Telegram messages about its trades. ## Example response ```json { "data": { "id": "signal:2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55", "name": "Kalshi sports buys, 30¢ to 70¢, $5,000 or more", "kind": "signal", "mode": "paper", "summary": "Kalshi sports buys, 30¢ to 70¢, $5,000 or more", "status": "running", "venues": ["kalshi"], "profit": 523.17, "winRate": 0.5521, "lastTradeAt": "2026-10-15T01:58:12.000Z", "settings": { "signal": { "entry": "all", "venue": ["kalshi"], "category": ["sports"], "minUsd": 5000, "priceMin": 0.3, "priceMax": 0.7 }, "sizing": { "usd": 100 }, "alerts": true }, "record": { "trades": 212, "settled": 192, "wins": 106, "losses": 86, "open": 20, "winRate": 0.5521, "realized": 498.6, "unrealized": 24.57, "profit": 523.17, "staked": 19200, "return": 0.026 }, "curve": [ { "time": "2026-09-22T19:04:51.000Z", "profit": -100 }, { "time": "2026-09-23T01:12:08.000Z", "profit": 21.43 } ] } } ``` ## Response fields - `data` (object): The bot after the change, with the same fields [Get Bot](https://rivo.markets/docs/reference/get-bot) returns. - `id` (string): The bot's id: `trader:`, `signal:` or `market:`. - `name` (string): The bot's name. A trader bot takes its trader's name, and a market bot takes the name of its market or series. - `kind` (string): The kind of bot. A `trader` bot copies one trader, a `signal` bot runs a signal, and a `market` bot runs a price rule on one market. - `mode` (string): Whether the bot trades on `paper` or `live`. A live bot trades real money. You set it up in the app, and the API can only read it. - `summary` (string): What the bot does, in one line. - `status` (string): The bot's status: `running`, `paused` or `ended`. A signal bot is never paused. It runs for as long as it exists. - `venues` (string[]): The venues the bot trades on. - `profit` (number): The bot's profit in USD, from both closed and open trades. - `winRate` (number or null): The share of the bot's settled trades that won, from 0 to 1. - `lastTradeAt` (string or null): When the bot last traded, in ISO 8601 UTC. - `settings` (object): What the bot runs. The response includes only the fields for the bot's kind. - `trader` (object): On a trader bot, the trader it copies. - `ref` (trader ref): The trader's ref. - `name` (string or null): The trader's public name. - `signal` (object or null): On a signal bot, the signal, with the same fields [Backtest](https://rivo.markets/docs/reference/backtest) takes. It is `null` when the bot holds settings that only the app can edit. - `market` (object): On a market bot, the contract and side it watches. - `ref` (market ref): The contract's ref. - `title` (string): The contract's title. - `outcome` (string): The side the bot trades: `Yes` or `No`. - `series` (boolean): Whether the bot follows the repeating series. - `rule` (object): On a market bot, the rule, with the same fields `POST /v2/bots` takes. - `sizing` (object or null): How much each trade stakes: `{ usd }` or `{ percent, maxUsd }`. - `mirrorExits` (boolean): On a trader bot, whether it sells when the trader sells. - `alerts` (boolean): Whether the bot sends in-app and Telegram messages. - `record` (object): The bot's record. - `trades` (integer): The number of trades the bot took. - `settled` (integer): The number of trades that settled or closed. - `wins` (integer): The number of wins. - `losses` (integer): The number of losses. - `open` (integer): The number of trades still open. - `winRate` (number or null): The share of settled trades that won, from 0 to 1. - `realized` (number): The profit from closed trades, in USD. - `unrealized` (number): The profit on open trades, in USD. - `profit` (number): The closed and open profit added together, in USD. - `staked` (number): The dollars the bot staked. - `return` (number or null): The profit divided by the dollars staked. - `curve` (object[]): The running total of closed profit, one point for each trade. - `time` (string): The time of this point, in ISO 8601 UTC. - `profit` (number): The total profit up to this point, in USD. - `trades` (object): One page of the bot's trades, newest first, when you ask for `include=trades`. - `data` (object[]): The trades on this page. - `market` (object or null): The market the trade is on. Here `category` and `kind` are always `null`. - `ref` (market ref): The market's ref. You can pass it to any `market` filter or to [Get Market](https://rivo.markets/docs/reference/get-market). On a market of several outcomes, it names the whole event. - `contract` (market ref or null): The ref of the exact contract. It is the same as `ref` on a market with one contract. On a market of several outcomes, such as a game with a moneyline and a spread, it tells the contracts apart. Pass it to [Place Paper Order](https://rivo.markets/docs/reference/place-paper-order) or a market bot. - `title` (string): The market's title, as the venue writes it. - `venue` (string): The venue that lists the market: one of `kalshi`, `polymarket`, `polymarket_us` or `gemini`. - `category` (string or null): The market's category: one of `sports`, `politics`, `crypto`, `finance`, `culture`. It is `null` when Rivo has not classified the market. - `kind` (string or null): The kind of market, such as `nfl` or `bitcoin`. It is `null` when Rivo has not classified the market yet. - `outcome` (string): The side the bot bought. - `contracts` (number): The number of contracts bought. - `entryPrice` (number): The price paid, from 0 to 1. - `price` (number): The price now while the trade is open. Once it closes, this is the exit price or the payout. - `profit` (number): The profit in USD. - `status` (string): Whether the trade is `open` or `closed`. - `result` (string or null): The result once the trade closes: `win`, `loss` or `void`. - `openedAt` (string): When the trade opened, in ISO 8601 UTC. - `closedAt` (string or null): When the trade closed, in ISO 8601 UTC. - `nextCursor` (string or null): Pass this as `cursor` to read the next page. It is `null` on the last page. - `activity` (object or null): How busy the bot's trigger has been, when you ask for `include=activity`. It is `null` for a market bot. - `trades` (integer): The number of trades the trigger matched in the window. - `windowDays` (integer): The length of the window in days, which is 30. - `medianTradeUsd` (number or null): The middle trade size, in USD. - `lastTradeAt` (string or null): When the newest matching trade happened, in ISO 8601 UTC. ## Errors | Code | When | | --- | --- | | `SCOPE_REQUIRED` | The key does not have the `write` scope. | | `NOT_FOUND` | No bot has that id. | | `VALIDATION` | The body is empty, or a field is unknown or does not fit this kind of bot. You sent `status` to a signal bot, or a market bot's `sizing` is not `{ usd }`. The bot holds a signal that only the app can edit, or the bot is live. | --- # Delete Bot > Stops and removes a paper bot. Its open paper positions stay until they settle. Source: https://rivo.markets/docs/reference/delete-bot `DELETE /v2/bots/{id}` Scope: `write` This route removes a paper bot. Rivo deletes a signal or market bot. A trader bot stops copying, but you keep following the trader, and your alerts stay as they were. Rivo does not sell the bot's open paper positions. They stay in [Get Paper Account](https://rivo.markets/docs/reference/get-paper-account) until they settle. The API refuses to delete a live bot. ## Example request curl: ```bash curl -X DELETE "https://api.rivo.markets/v2/bots/signal%3A2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55" \ -H "Authorization: Bearer rivo_live_..." ``` Python: ```python import requests url = "https://api.rivo.markets/v2/bots/signal%3A2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55" headers = { "Authorization": "Bearer rivo_live_...", } res = requests.delete(url, headers=headers) res.raise_for_status() print(res.status_code) ``` TypeScript: ```typescript const res = await fetch("https://api.rivo.markets/v2/bots/signal%3A2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55", { method: "DELETE", headers: { Authorization: `Bearer ${process.env.RIVO_API_KEY}`, }, }); if (!res.ok) throw new Error(`Rivo API ${res.status}`); ``` ## Path parameters - `id` (string, required): The bot's id, as [List Bots](https://rivo.markets/docs/reference/list-bots) returns it: `trader:`, `signal:` or `market:`. ## Example response ```json { "data": { "id": "signal:2c91e7d4-5a3b-4f08-b6e2-91d04f7a3c55", "deleted": true } } ``` ## Response fields - `data` (object): The bot that the API deleted. - `id` (string): The deleted bot's id. - `deleted` (boolean): This is always `true`. ## Errors | Code | When | | --- | --- | | `SCOPE_REQUIRED` | The key does not have the `write` scope. | | `NOT_FOUND` | No bot has that id. | | `VALIDATION` | The bot is live. |