# 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.
