> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tako.com/llms.txt
> Use this file to discover all available pages before exploring further.

# For Your Coding Agent

> The build reference for coding agents integrating Tako monitors. Create notification channels and monitors, name a subject, receive and verify webhook deliveries, and debug a monitor that doesn't fire, with copy-paste-ready requests.

export const MonitorsExperimental = () => <Warning>
    <strong>Monitors is experimental.</strong> Request and payload shapes can change without a deprecation window. Monitors isn't recommended for production workflows.
  </Warning>;

This page is a complete reference for building against **Tako monitors**. It's written for coding agents and for developers who want the whole contract on one page. Every request runs after you supply an API key. The parameters and response fields match the live API, and the common mistakes have their own table. For the generated schemas, see the API reference, starting at [Create a monitor](/api-reference/monitors-create). For the request Tako posts when a monitor fires, see [Webhook deliveries](/documentation/integrating-tako/monitors/webhook-deliveries). For an introduction, see the [Overview](/documentation/integrating-tako/monitors/overview).

<MonitorsExperimental />

## What monitors do

A monitor watches Tako data for a condition. When the condition occurs, Tako records a firing and posts a signed request to each notification channel on the monitor.

* **Base URL:** `https://tako.com/api/`
* **Auth:** every endpoint on this page takes your API key in the `X-API-Key` request header. Create a key in the [Tako console](https://tako.com/console/api-keys).
* **Rate limits:** the monitors and notification channels endpoints allow 120 calls per minute, and 20,000 per day, per user. `GET /v1/sports/games` allows 60 calls per minute, and 5,000 per day. If you get a `429`, wait for the time in its `Retry-After` header when it has one. Otherwise, back off with jittered exponential retry.
* **Limits:** by default, you can hold 1 active monitor and 2 notification channels. Tako counts these limits per user, not across an account. A `LIMIT_REACHED` response's `limit` gives your cap.
* **Endpoints:**

| Endpoint                                                | What it does                                                                    |
| ------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `GET /v1/monitor_types`                                 | List every monitor type, with the JSON Schema for its parameters.               |
| `GET`, `POST /v1/notification_channels`                 | List your channels, or register an `https` endpoint and get its signing secret. |
| `GET`, `PATCH`, `DELETE /v1/notification_channels/{id}` | Read, pause or resume, or delete a channel.                                     |
| `GET`, `POST /v1/monitors`                              | List your monitors, or create one.                                              |
| `GET`, `PATCH`, `DELETE /v1/monitors/{id}`              | Read a monitor, change its status or channels, or delete it.                    |
| `GET /v1/monitors/{id}/firings`                         | List a monitor's firings, with each delivery's status.                          |
| `GET /v1/sports/games`                                  | List games, to find the ids that sports monitors take.                          |

## Minimal working example

This script monitors AAPL for a 3% daily rise, as in the [Overview](/documentation/integrating-tako/monitors/overview#examples). It creates a channel and a monitor, then reads the monitor back. It needs `curl` and `jq`, and a `TAKO_API_KEY` environment variable. Replace the URL with an `https` endpoint that you control.

```bash theme={null}
API=https://tako.com/api/v1
AUTH="X-API-Key: $TAKO_API_KEY"

# 1. Create a channel. Store the secret: no later request returns it.
CHANNEL=$(curl -s -X POST "$API/notification_channels" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{"name": "My receiver", "kind": "webhook", "url": "https://api.example.com/tako/webhooks"}')
CHANNEL_ID=$(echo "$CHANNEL" | jq -r .id)
echo "$CHANNEL" | jq -r .secret > tako-webhook-secret.txt

# 2. Read the parameters that stocks.pct_change takes.
curl -s "$API/monitor_types" -H "$AUTH" \
  | jq '.items[] | select(.name == "stocks.pct_change") | .parameters_schema'

# 3. Create the monitor. fire_once: false keeps it firing on every qualifying day.
MONITOR_ID=$(curl -s -X POST "$API/monitors" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "AAPL up 3% in a day",
    "type": "stocks.pct_change",
    "parameters": {"ticker": "AAPL", "window": "1d", "threshold": 3, "direction": "up"},
    "fire_once": false,
    "channel_ids": ["'"$CHANNEL_ID"'"]
  }' | jq -r .id)

# 4. Confirm what it watches, and whether Tako has evaluated it.
curl -s "$API/monitors/$MONITOR_ID" -H "$AUTH" \
  | jq '{subject, status, last_evaluated_at, last_evaluation_outcome}'
```

## Notification channels

A notification channel is an `https` endpoint that Tako posts firings to. Create the channel before the monitor that uses it.

| Field  | Description                                                                                                                                                                                                                                                        |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` | *(Required)* Your label for the channel.                                                                                                                                                                                                                           |
| `url`  | *(Required)* The `https` endpoint. Tako rejects `http`, a port other than 443 or 8443, a URL with embedded credentials, and a host that resolves to a private or loopback address.                                                                                 |
| `kind` | `webhook` (the default) sends nested JSON with the monitor, the firing, and the payload. `slack_workflow` sends a flat body of strings, for a Slack Workflow Builder trigger. See [Send monitor firings to Slack](/documentation/integrating-tako/monitors/slack). |

The `201` response is the only response that holds `secret`. Tako signs every delivery to the channel with it. See [Verify the signature](/documentation/integrating-tako/monitors/webhook-deliveries#verify-the-signature). If you lose the secret, delete the channel and create a new one.

Tako pauses a channel after 2 consecutive failed deliveries, and sets its `status_reason` to `delivery_failures`. Monitors that post to a paused channel keep recording firings, and no one receives them. To resume the channel, fix the endpoint, then send `PATCH /v1/notification_channels/{id}` with `{"status": "active"}`.

## Monitor types

Call `GET /v1/monitor_types` before you create a monitor. The response is the authoritative list, and each item holds these fields:

| Field               | Description                                                               |
| ------------------- | ------------------------------------------------------------------------- |
| `name`              | The type name, namespaced as `domain.subtype`.                            |
| `description`       | What the type watches, when it fires, and how far its source runs behind. |
| `parameters_schema` | The JSON Schema that your `parameters` must satisfy.                      |
| `subject_schema`    | The shape of the subject that the type resolves from your parameters.     |
| `lookback_seconds`  | How far back an occurrence can be and still fire.                         |
| `fire_once_only`    | If `true`, the type accepts only `fire_once: true`.                       |

The response also holds `firing_enabled`. While it's `false`, the environment accepts monitors and evaluates none of them.

| Type                     | Fires when                                                                                               | Subject                               |
| ------------------------ | -------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `stocks.pct_change`      | A stock's move over a `window` of `1d`, `1w`, or `1m` reaches `threshold` percent, in `direction`.       | `ticker`, plus `exchange`             |
| `stocks.crosses`         | A stock's price closes on the other side of your `level` from the prior close.                           | `ticker`, plus `exchange`             |
| `stocks.new_52w_extreme` | A stock passes its highest or lowest daily close of the preceding 365 days.                              | `ticker`, plus `exchange`             |
| `crypto.crosses`         | A crypto asset's price in US dollars reaches your `level`. Fires once.                                   | `ticker` or `coinmarketcap_id`        |
| `forex.crosses`          | A currency pair's rate reaches your `level`, in the quote currency per one unit of the base. Fires once. | `base` and `quote`, as ISO 4217 codes |
| `sports.game_start`      | A game starts.                                                                                           | `game_id`, `team_id`, or both         |
| `sports.game_result`     | A game ends. On a team, it fires only for the `outcome` you set.                                         | `game_id`, `team_id`, or both         |
| `sports.score_event`     | A score rises while a game is live or final. NBA isn't supported.                                        | `game_id`, `team_id`, or both         |
| `sports.odds_crosses`    | A game's betting line at the sportsbook you choose reaches your `level`.                                 | `game_id` and `team_id`               |

<Note>
  `stocks.pct_change` and `stocks.new_52w_extreme` are **state alerts**. If you create one while its condition already holds, the next price update fires it. `stocks.crosses` does the same when the price has already crossed your level. On a delayed feed, that update arrives as late as the feed's delay. `crypto.crosses`, `forex.crosses`, and `sports.odds_crosses` work the other way: if the price or the line has already reached your level, the create fails.
</Note>

## Create a monitor

`POST /v1/monitors` takes these fields:

| Field          | Type            | Default      | Description                                                                                                                            |
| -------------- | --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `type`         | string          | *(required)* | A type name from `GET /v1/monitor_types`.                                                                                              |
| `parameters`   | object          | *(required)* | The subject and the condition. Tako validates it against the type's `parameters_schema`.                                               |
| `name`         | string          | `""`         | Your label. Tako never reads it.                                                                                                       |
| `channel_ids`  | string array    | `[]`         | Up to 20 channels to post each firing to. Every id must be a channel you own. An empty list records firings and delivers them nowhere. |
| `fire_once`    | boolean         | `true`       | If `true`, Tako pauses the monitor after its first firing. To fire on every new occurrence, set it to `false`.                         |
| `active_from`  | ISO 8601 string | now          | Ignore occurrences before this time.                                                                                                   |
| `active_until` | ISO 8601 string | none         | Pause the monitor as `expired` after this time. It must be in the future, and after `active_from`.                                     |

### Check the resolved subject

Tako resolves your parameters to one subject when you create the monitor, and never resolves them again. Every monitor response holds that subject in `subject`. Check it before you rely on the monitor.

```json theme={null}
{
  "id": "0d6f1e77-4b9a-4f0e-8a4d-2c9e7b1f3a55",
  "name": "AAPL up 3% in a day",
  "type": "stocks.pct_change",
  "parameters": { "ticker": "AAPL", "exchange": "XNAS", "window": "1d", "threshold": "3", "direction": "up" },
  "subject": { "ticker": "AAPL", "exchange": "XNAS", "company": "Apple Inc." },
  "fire_once": false,
  "status": "active",
  "status_reason": null,
  "active_from": "2026-09-22T17:00:00+00:00",
  "active_until": null,
  "last_evaluated_at": null,
  "last_evaluation_outcome": null,
  "channel_ids": ["7c1e0b4a-5d2f-4e8b-9a61-3f0c2d7e8b14"],
  "created_at": "2026-09-22T17:00:00+00:00",
  "updated_at": "2026-09-22T17:00:00+00:00"
}
```

`parameters` comes back in the form that you send, with names as Tako resolved them. A ticker that you sent as `aapl` reads back as `AAPL`, with the exchange that Tako picked. A crypto monitor reads back its asset as a `coinmarketcap_id`, whichever name you created it with.

### Handle a rejected create

A `400` response holds an `error_type`:

| `error_type`        | What happened                                                                                                                                                                  | What to do                                                                                                                                   |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `AMBIGUOUS_SUBJECT` | Your parameters match more than one subject. `candidates` lists each one, best first.                                                                                          | Copy the fields of the candidate you want into `parameters`, such as its `exchange`, and retry.                                              |
| `LIMIT_REACHED`     | You're at your limit of active monitors or channels. `limit` is your cap.                                                                                                      | Pause or delete a monitor, or delete a channel, that you don't need. For higher limits, contact [support@tako.com](mailto:support@tako.com). |
| `BAD_REQUEST`       | Anything else, such as parameters that fail the schema, or a `crypto.crosses`, `forex.crosses`, or `sports.odds_crosses` level that the price or the line has already reached. | Read `error_message`, and fix the request.                                                                                                   |

### Name a sports subject

Sports monitors take ids from `GET /v1/sports/games`. Each row's `id` is a `game_id`, and its `home.id` and `away.id` are team ids.

This script monitors every 49ers win, as in the [Overview](/documentation/integrating-tako/monitors/overview#monitor-every-49ers-win). It finds the 49ers' team id, pauses the AAPL monitor to free the active monitor slot, then creates a `sports.game_result` monitor that follows the team. `CHANNEL_ID` is a channel that you created, and `MONITOR_ID` is the AAPL monitor from the [Minimal working example](#minimal-working-example).

```bash theme={null}
API=https://tako.com/api/v1
AUTH="X-API-Key: $TAKO_API_KEY"

# 1. Find the 49ers' team id in the NFL games for the next 7 days.
TEAM_ID=$(curl -s "$API/sports/games?league=NFL" -H "$AUTH" \
  | jq -r '.items[] | .home, .away | select(.name != null and (.name | test("49ers"))) | .id' \
  | head -1)
[ -n "$TEAM_ID" ] || { echo "No 49ers game in the next 7 days"; exit 1; }

# 2. Free the active monitor slot. A paused monitor doesn't count toward the limit.
curl -s -X PATCH "$API/monitors/$MONITOR_ID" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}' | jq '{id, status}'

# 3. Fire on every 49ers win, for as long as the monitor is active.
curl -s -X POST "$API/monitors" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "49ers wins",
    "type": "sports.game_result",
    "parameters": {"team_id": "'"$TEAM_ID"'", "outcome": "wins"},
    "fire_once": false,
    "channel_ids": ["'"$CHANNEL_ID"'"]
  }' | jq '{subject, status}'
```

The endpoint covers one day back to seven days ahead. A team's id doesn't change, so you can store it and reuse it when the team has no game in the window. Filter with `league`, `team`, `status`, `from`, and `to`. A row whose `source` is `schedule_table` comes from a schedule without a live feed, and a monitor rejects its ids.

For `sports.game_start`, `sports.game_result`, and `sports.score_event`, the ids combine this way:

* `game_id` alone watches the whole game, and each firing reports both sides.
* `team_id` alone follows that team's games as they come.
* `game_id` and `team_id` together watch one team's side of one game.

## Receive a delivery

Each delivery is a `POST` with a JSON body and these headers: `webhook-id`, `webhook-timestamp`, and `webhook-signature`. Tako follows the [Standard Webhooks](https://www.standardwebhooks.com/) specification. For the full body, the verification code, and the payload fields for each type, see [Webhook deliveries](/documentation/integrating-tako/monitors/webhook-deliveries).

A receiver needs to do these things, in order:

1. Verify `webhook-signature` against the raw request bytes, with the channel's secret. Reject a `webhook-timestamp` more than 5 minutes from the current time.
2. Skip a `webhook-id` that you've already stored. Every retry of one delivery sends the same id.
3. Store the body, and respond with a `2xx` within 10 seconds.
4. Do the slow work after you respond.

If a delivery gets a `408`, a `429`, a `5xx`, or no response, Tako retries it 5 times over about 4 hours. Tako doesn't retry any other status, and doesn't follow redirects. For the retry schedule, see [How Tako retries](/documentation/integrating-tako/monitors/webhook-deliveries#how-tako-retries).

## Check a monitor that hasn't fired

Read the monitor with `GET /v1/monitors/{id}`. `last_evaluated_at` says when Tako last evaluated it, and `last_evaluation_outcome` says what that evaluation found:

| Outcome                                            | What it means                                                                                                                                                                                                           |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `null`                                             | Tako hasn't evaluated the monitor since you created it or resumed it. The type's source hasn't sent new data since then.                                                                                                |
| `no_occurrence`                                    | Tako read the data, and the condition wasn't met.                                                                                                                                                                       |
| `predates_monitor`, `before_window`                | Tako found an occurrence from before you created the monitor, or before `active_from`. `before_window` also means that `active_from` hasn't arrived yet. The monitor is running, and it's waiting for a new occurrence. |
| `outside_lookback`                                 | Tako found an occurrence older than the type's `lookback_seconds`.                                                                                                                                                      |
| `fired`                                            | Tako recorded a firing.                                                                                                                                                                                                 |
| `already_fired`                                    | Tako found an occurrence it had already recorded.                                                                                                                                                                       |
| `completed`                                        | The subject reached an end state, such as a finished game, so the condition can't occur again.                                                                                                                          |
| `expired`, `unloadable`, `unknown_type`, `crashed` | Tako stopped before it read the data, or the type failed while it read the data (`crashed`).                                                                                                                            |

For every outcome, see the `last_evaluation_outcome` schema in [Get a monitor](/api-reference/monitors-get).

If the monitor is `paused`, `status_reason` says why: `fired_once`, `expired`, `completed`, or `unloadable`. It's `null` when you paused the monitor yourself.

### Check deliveries

`GET /v1/monitors/{id}/firings` lists the monitor's firings, newest first. Each firing holds `deliveries`, with one entry for each channel:

| `state`               | What it means                                                                                       |
| --------------------- | --------------------------------------------------------------------------------------------------- |
| `pending`, `retrying` | Tako is still trying to deliver.                                                                    |
| `delivered`           | The channel returned a `2xx`.                                                                       |
| `failed`              | The delivery ended without a `2xx`. Read `attempts` and `last_status_code` to see why.              |
| `channel_paused`      | The channel was paused, so Tako didn't send a request. Resume the channel to receive later firings. |

The `deliveries` list covers the last 30 days. Tako removes a firing that's older than its type's lookback window.

## Change or remove a monitor

`PATCH /v1/monitors/{id}` changes two fields, and nothing else:

* `status`: set `paused` to stop evaluating the monitor, or `active` to resume it. If `active_until` has passed, resuming returns `400`.
* `channel_ids`: replace the monitor's channels with this list.

To change a monitor's type, parameters, or active window, delete the monitor and create a new one. Deleting a monitor also deletes its firings. To keep the history, pause the monitor instead.

## Common mistakes

<Warning>
  Avoid these. They're the mistakes that coding agents make most often with monitors.
</Warning>

| Wrong                                                               | Correct                                                                                                                                                                          |
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Creating a monitor to find out whether a condition already happened | A monitor fires only for occurrences after you create it. To read history, use [Search](/documentation/integrating-tako/search/overview).                                        |
| Losing the channel `secret`                                         | Only the `201` create response returns it. Store it at create time.                                                                                                              |
| Verifying the signature against re-serialized JSON                  | Sign the raw request bytes. Any reformatting breaks the signature. See [Verify the signature](/documentation/integrating-tako/monitors/webhook-deliveries#verify-the-signature). |
| Doing slow work before responding                                   | Respond `2xx` within 10 seconds, then do the work. A slow receiver gets the same delivery again.                                                                                 |
| Treating each request as a new firing                               | Deduplicate on `webhook-id`.                                                                                                                                                     |
| Reading `payload` from a `slack_workflow` delivery                  | A `slack_workflow` body holds 7 strings, and `summary` carries the values. To read the values as fields, use a `webhook` channel.                                                |
| Guessing a `game_id` or `team_id`                                   | Read ids from `GET /v1/sports/games`.                                                                                                                                            |
| Setting `fire_once: false` on `crypto.crosses` or `forex.crosses`   | These types fire once. Read `fire_once_only` on the type.                                                                                                                        |
| Setting a level that the price has already reached                  | `crypto.crosses`, `forex.crosses`, and `sports.odds_crosses` reject it, and `stocks.crosses` fires on the next price update. Set a level on the far side of the current value.   |
| Expecting a paused channel to resume itself                         | Fix the endpoint, then resume the channel with `PATCH`.                                                                                                                          |
| Sending `PATCH` to change a threshold                               | Only `status` and `channel_ids` change. Delete the monitor and create a new one.                                                                                                 |
| Reading an empty firings list as a broken monitor                   | Read `last_evaluated_at` and `last_evaluation_outcome` first.                                                                                                                    |

## Errors

A `400`, `401`, or `404` returns `{ "error_message": "...", "error_type": "..." }`. A `429` returns `{ "detail": "..." }`.

| Status | Meaning                                                                                   | Fix                                                                                                                           |
| ------ | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The parameters fail the type's schema, match more than one subject, or exceed your limit. | Read `error_type`. See [Handle a rejected create](#handle-a-rejected-create).                                                 |
| `401`  | The API key is missing or invalid.                                                        | Send a valid key in the `X-API-Key` header.                                                                                   |
| `404`  | The monitor or channel doesn't exist, or you don't own it.                                | Check the id. Every `channel_ids` entry must be a channel you own.                                                            |
| `429`  | You've exceeded a rate limit.                                                             | Wait for the time in the `Retry-After` header when the response has one. Otherwise, back off with jittered exponential retry. |
