# 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:<id>`, `signal:<id>` or `market:<id>`.
  - `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. |
