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

> ## Agent Instructions
> Arcmira has three API capabilities: Search (entities, mentions, appearances, commercial intelligence), Monitors (trackers plus alert delivery), and Transcripts (read, generate, correct). Community Review is the cross-surface correction layer.
> Discover pages at https://docs.arcmira.com/llms.txt, then fetch the matching page's .md export. Each capability has a self-contained '<capability> for coding agents' page; prefer it.
> Auth: Authorization: Bearer arc_sk_... or x-api-key: arc_sk_... (x-api-key wins if both are sent). Never put keys in browser code. Check GET /v1/me for tier, scopes, and remaining rows before metered pulls.
> Search: resolve names with GET /v1/entities/lookup and pin ent_* IDs before pulling metered rows. Only person entities have appearances. Mention and recommendation rows carry canonical numeric timestamps in start_seconds/end_seconds (integer seconds; 0 means full episode); the MM:SS string fields are deprecated. Commercial routes need Pro+ tier AND the recommendations:read scope and bill 10 rows per row.
> Monitors: a tracker watches one entity; a monitor groups trackers with shared delivery (email, Slack, HMAC-signed webhooks). Create trackers with POST /v1/trackers, then attach by id: POST /v1/monitors/{id}/trackers { trackerIds: [...] }. Each fired alert occurrence debits 100 rows. Poll GET /v1/monitors/{id}/alerts?n= for recent deliveries. Send Idempotency-Key on every POST/PATCH.
> Transcripts: switch on the access field (unlocked, locked, premium_pending, not_transcribed, unauthenticated). Honor Retry-After when polling POST /v1/transcriptions jobs. Corrections (POST /v1/videos/{video_id}/corrections; kinds line_edit, speaker_reassign, speaker_identify, add_person, entity_tag, segment_rewrite) cost 0 rows; anchored kinds need revision + anchor.contentHash (djb2 base-36); handle 409 (re-anchor) and 412 (expectedSeq). segment_rewrite replaces an inclusive segment range with new segments (empty replacements array deletes; timestamps repaired server-side).
> Community Review is free (0 rows) and surface-typed: POST /v1/feedback with a type matching the surface you called, the reproducing query, and corrections targeting public IDs (ent_*, men_*, com_*). Nothing auto-applies. Do not send untyped notes.
> Errors: switch on error.code, follow doc_url, quote X-Request-Id to support. Honor Retry-After on 429. Retries of POST/PATCH must reuse the same Idempotency-Key; replays return Idempotency-Replayed: true.

# Monitors for coding agents

> Self-contained reference for Monitors and Trackers: endpoints, request bodies, alert history schema, webhook payloads, and signature verification.

## Overview

Monitors turn entity interests into standing alerts. A **tracker** watches one entity; a **monitor** groups trackers and owns delivery settings (email, Slack, webhook).

* Base URL: `https://api.arcmira.com`, all paths under `/v1`.
* Auth: `Authorization: Bearer arc_sk_...` or `x-api-key: arc_sk_...`.
* Scopes: `read` for all GETs; `monitors:write` for non-GET `/v1/monitors*`; `trackers:write` for non-GET `/v1/trackers*`.
* Send `Idempotency-Key` (a UUID) on **every** POST/PATCH; replays return the cached response with `Idempotency-Replayed: true`.
* Tier gates: Slack delivery from Free; webhook delivery and realtime email on paid tiers.
* Billing: monitor and tracker CRUD is free. **Each alert occurrence (a tracker matching a new appearance) debits 100 rows** from the shared monthly pool, once per occurrence no matter how many channels it fans out to.
* Alerts fire for recently published media (roughly a 14-day publish window) as it is analyzed. This is monitoring, not retroactive search.

## Minimal working example

<CodeGroup>
  ```typescript TypeScript theme={null}
  const BASE = 'https://api.arcmira.com';
  const H = {
    Authorization: `Bearer ${process.env.ARCMIRA_API_KEY}`,
    'Content-Type': 'application/json',
  };

  const { monitor } = await fetch(`${BASE}/v1/monitors`, {
    method: 'POST',
    headers: { ...H, 'Idempotency-Key': crypto.randomUUID() },
    body: JSON.stringify({ name: 'Competitor watch', icon: 'radar', color: 'indigo' }),
  }).then(r => r.json());

  const { tracker } = await fetch(`${BASE}/v1/trackers`, {
    method: 'POST',
    headers: { ...H, 'Idempotency-Key': crypto.randomUUID() },
    body: JSON.stringify({ entityName: 'Ramp', entityType: 'organization' }),
  }).then(r => r.json());

  await fetch(`${BASE}/v1/monitors/${monitor.id}/trackers`, {
    method: 'POST',
    headers: { ...H, 'Idempotency-Key': crypto.randomUUID() },
    body: JSON.stringify({ trackerIds: [tracker.id] }),
  });

  const { data: alerts } = await fetch(
    `${BASE}/v1/monitors/${monitor.id}/alerts?n=25`, { headers: H },
  ).then(r => r.json());
  ```

  ```python Python theme={null}
  import os, uuid, httpx

  BASE = 'https://api.arcmira.com'
  H = {'Authorization': f"Bearer {os.environ['ARCMIRA_API_KEY']}"}

  monitor = httpx.post(
      f'{BASE}/v1/monitors',
      json={'name': 'Competitor watch', 'icon': 'radar', 'color': 'indigo'},
      headers={**H, 'Idempotency-Key': str(uuid.uuid4())},
  ).json()['monitor']

  tracker = httpx.post(
      f'{BASE}/v1/trackers',
      json={'entityName': 'Ramp', 'entityType': 'organization'},
      headers={**H, 'Idempotency-Key': str(uuid.uuid4())},
  ).json()['tracker']

  httpx.post(
      f"{BASE}/v1/monitors/{monitor['id']}/trackers",
      json={'trackerIds': [tracker['id']]},
      headers={**H, 'Idempotency-Key': str(uuid.uuid4())},
  )

  alerts = httpx.get(f"{BASE}/v1/monitors/{monitor['id']}/alerts", params={'n': 25}, headers=H).json()['data']
  ```

  ```bash cURL theme={null}
  curl -X POST 'https://api.arcmira.com/v1/monitors' \
    -H "Authorization: Bearer $ARCMIRA_API_KEY" \
    -H "Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{ "name": "Competitor watch", "icon": "radar", "color": "indigo" }'
  ```
</CodeGroup>

## Endpoints

### Monitors

| Method | Path                                      | Scope            | Purpose                                                            |
| ------ | ----------------------------------------- | ---------------- | ------------------------------------------------------------------ |
| GET    | `/v1/monitors`                            | `read`           | List monitors with tracker counts                                  |
| POST   | `/v1/monitors`                            | `monitors:write` | Create                                                             |
| PATCH  | `/v1/monitors/{id}`                       | `monitors:write` | Partial update (send only changed fields)                          |
| DELETE | `/v1/monitors/{id}`                       | `monitors:write` | Delete (cannot be undone)                                          |
| GET    | `/v1/monitors/{id}/trackers`              | `read`           | List trackers in the monitor                                       |
| POST   | `/v1/monitors/{id}/trackers`              | `monitors:write` | Attach existing trackers by ID: `{ "trackerIds": ["trk_..."] }`    |
| GET    | `/v1/monitors/{id}/alerts?n=`             | `read`           | Recent alert deliveries                                            |
| POST   | `/v1/monitors/{id}/webhook-secret/rotate` | `monitors:write` | Rotate the signing secret (returned once; 24h dual-signed overlap) |

### Trackers

| Method | Path                          | Scope            | Purpose                 |
| ------ | ----------------------------- | ---------------- | ----------------------- |
| GET    | `/v1/trackers`                | `read`           | List trackers           |
| POST   | `/v1/trackers`                | `trackers:write` | Create                  |
| PATCH  | `/v1/trackers/{id}`           | `trackers:write` | Update                  |
| DELETE | `/v1/trackers/{id}`           | `trackers:write` | Delete                  |
| GET    | `/v1/trackers/{id}/alerts?n=` | `read`           | Recent alert deliveries |

## Request parameters

Monitor create/update body (all fields optional except `name` on create):

| Field                                   | Type          | Default    | Description                                                                                                                                        |
| --------------------------------------- | ------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                                  | string        | —          | Display name. Required on create.                                                                                                                  |
| `icon`                                  | string        | `eye`      | Kebab-case Lucide icon name.                                                                                                                       |
| `color`                                 | string        | `blue`     | One of the dashboard palette names (`gray`, `red`, ..., `rose`).                                                                                   |
| `notifyEmails`                          | string\[]     | `[]`       | Email recipients.                                                                                                                                  |
| `notifyFrequency`                       | enum          | `realtime` | `realtime` (as analysis completes), `hourly`, `daily` (batched digests). On Free, email delivery is coerced to `daily` regardless of what you set. |
| `digestDay`                             | string        | `monday`   | Digest day. Consulted only by weekly digests, which are dashboard-configured today; inert for API-set frequencies.                                 |
| `digestTime`                            | string        | `09:00`    | Send hour for `daily` digests (account timezone).                                                                                                  |
| `notifyWebhook`                         | boolean       | `false`    | Enable webhook delivery. Paid tiers only.                                                                                                          |
| `webhookUrl`                            | string        | —          | HTTPS endpoint to receive deliveries.                                                                                                              |
| `notifySlack`                           | boolean       | `false`    | Enable Slack delivery.                                                                                                                             |
| `slackIntegrationId` / `slackChannelId` | string        | —          | From the dashboard Slack OAuth flow.                                                                                                               |
| `isPaused`                              | boolean       | `false`    | Dashboard pause state. Delivery-time pause is per tracker (`PATCH /v1/trackers/{id}` `{ "paused": true }`).                                        |
| `isCollapsed` / `sortOrder`             | boolean / int | —          | Dashboard display state.                                                                                                                           |

Tracker create/update body (`POST /v1/trackers`; creating the same entity twice returns `409` with the `existingId`):

| Field                                                  | Type    | Description                                                                  |
| ------------------------------------------------------ | ------- | ---------------------------------------------------------------------------- |
| `entityName`                                           | string  | Required on create. The name to resolve and watch.                           |
| `entityType`                                           | enum    | Required on create. `person`, `organization`, `product`, `topic`, `channel`. |
| `displayName`                                          | string  | Optional label shown in alerts and the dashboard.                            |
| `notifyEmail` / `notifyWebhook` / `notifySlack`        | boolean | Per-tracker delivery overrides.                                              |
| `webhookUrl` / `slackChannelId` / `slackIntegrationId` | string  | Per-tracker destination overrides.                                           |
| `filters`                                              | object  | Optional matching filters.                                                   |

## Response schema

Monitor object (create/read):

```json theme={null}
{
  "monitor": {
    "id": "mon_abc123",
    "name": "Competitor watch",
    "icon": "radar",
    "color": "indigo",
    "isCollapsed": false,
    "isPaused": false,
    "sortOrder": 3,
    "notifyEmails": ["ops@example.com"],
    "notifyFrequency": "realtime",
    "digestDay": "monday",
    "digestTime": "09:00",
    "notifyWebhook": true,
    "webhookUrl": "https://example.com/hooks/arcmira",
    "webhookSecretSet": true,
    "webhookSecretHint": "f3a1",
    "webhookFailures": 0,
    "webhookDisabledAt": null,
    "webhookDisabledReason": null,
    "notifySlack": false,
    "slackIntegrationId": null,
    "slackChannelId": null,
    "createdAt": "2026-07-01T18:00:00.000Z",
    "updatedAt": "2026-07-09T02:10:00.000Z",
    "trackerCount": 4
  }
}
```

| Field                                         | Type                     | Notes                                                                                                                                                                                                                           |
| --------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                          | string                   | `mon_*` (rows minted before the Monitors rename carry a legacy `wl_*` prefix; both resolve).                                                                                                                                    |
| `webhookSecretSet` / `webhookSecretHint`      | boolean / string \| null | Whether a signing secret exists, plus its last 4 characters. The secret itself is never returned on reads.                                                                                                                      |
| `webhookFailures`                             | number                   | Consecutive delivery failures recorded for this monitor. Reset by rotation or `PATCH { "notifyWebhook": true }`. Today the delivery pipeline accrues failures on the tracker that fired, so this counter can lag (fix pending). |
| `webhookDisabledAt` / `webhookDisabledReason` | string \| null           | Set when repeated failures auto-disable the webhook (10 consecutive). Null while healthy. Fix your endpoint, then re-enable with `PATCH { "notifyWebhook": true }`.                                                             |
| `isPaused`                                    | boolean                  | Dashboard pause state. **Delivery-time pause is enforced per tracker today** (`PATCH /v1/trackers/{id}` with `{ "paused": true }`); monitor-level pause alone does not stop tracker deliveries (fix pending).                   |

Alert history row (`GET /v1/monitors/{id}/alerts?n=25`; also per tracker):

```json theme={null}
{
  "data": [
    {
      "id": "dlv_8c41f2a9b7e64d1a90cc55aa1b2c3d4e",
      "tracker_id": "trk_9f2ab4c8d1e6",
      "monitor_id": "mon_abc123",
      "entity_id": null,
      "mention_id": "men_2049815",
      "media_id": 9912345,
      "appearance_id": 2049815,
      "channel": "webhook",
      "status": "sent",
      "final_status": "sent",
      "error_message": null,
      "scheduled_at": "2026-07-09T01:58:00.000Z",
      "sent_at": "2026-07-09T01:58:04.000Z",
      "created_at": "2026-07-09T01:58:00.000Z",
      "tracker": { "id": "trk_9f2ab4c8d1e6", "entity_name": "Ramp", "entity_type": "organization", "display_name": "Ramp" },
      "monitor": { "id": "mon_abc123", "name": "Competitor watch" }
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

| Field                        | Type           | Notes                                                                                                                                                                                                                                                                                                     |
| ---------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel`                    | enum           | `email`, `slack`, `webhook`. **One matched appearance produces one row per enabled channel, and one per recipient for email**; do not count delivery rows as distinct detections. Group by `appearance_id` for occurrence counts. Digest-frequency email lands in these rows only after the digest sends. |
| `status` / `final_status`    | string         | Delivery state machine; `error_message` set on failures.                                                                                                                                                                                                                                                  |
| `entity_id`                  | string \| null | Public `ent_*` form; joins directly against entity reads. Null on older rows; the embedded `tracker` always carries the entity.                                                                                                                                                                           |
| `mention_id`                 | string \| null | Public `men_*` form; joins directly against mention rows and Community Review.                                                                                                                                                                                                                            |
| `media_id` / `appearance_id` | number         | Numeric, matching their forms elsewhere in the API.                                                                                                                                                                                                                                                       |
| `n` param                    | int            | 1-100, default 25. **This endpoint does not cursor-paginate**: `has_more` is always `false`. A documented exception to the standard list contract.                                                                                                                                                        |

## Webhooks

Webhook delivery POSTs each alert to your `webhookUrl` as JSON. Paid tiers only. Endpoint requirements: HTTPS, publicly reachable, responds 2xx within 10 seconds.

### Delivery payload

```json theme={null}
{
  "event": "new_appearance",
  "tracker": { "id": "trk_9f2ab4c8d1e6", "entityName": "Ramp", "entityType": "organization", "entitySlug": "ramp" },
  "monitor": { "id": "mon_abc123", "name": "Competitor watch", "icon": "radar", "color": "indigo" },
  "appearance": {
    "id": 2049815,
    "videoId": "abcd_1234",
    "videoTitle": "…",
    "channelName": "TBPN",
    "timestamp": "08:12",
    "timestampSeconds": 492,
    "duration": "1:02:00",
    "sentiment": 0.42,
    "context": "Every startup I know is moving their spend management over…",
    "videoUrl": "https://www.youtube.com/watch?v=abcd_1234&t=492"
  },
  "media": { "title": "…", "channel": "TBPN", "publishedAt": "2026-07-08T16:00:00Z", "duration": "1:02:00", "thumbnailUrl": "…" },
  "people": [{ "name": "John Coogan" }],
  "topics": [{ "name": "fintech", "sentiment": "positive" }],
  "arcmira": { "momentUrl": "https://arcmira.com/watch?...", "channelUrl": "…", "shareId": "…" },
  "timestamp": "2026-07-09T01:58:04.000Z"
}
```

| Field                         | Type           | Notes                                                                                                                                                                    |
| ----------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `event`                       | enum           | `new_appearance` (a tracked entity was matched) or `test` (dashboard test delivery). Handle unknown values gracefully; new event names will be dot-delimited past-tense. |
| `appearance.timestampSeconds` | number \| null | **Integer seconds where the moment starts; read this.** `0` means the full episode, no specific moment. Null only if the stored string is unparseable.                   |
| `appearance.timestamp`        | string         | Deprecated `MM:SS` (or `HH:MM:SS`) string, same information as `timestampSeconds`. `"0:00"` means the full episode. Kept until a changelog-announced removal.            |
| `appearance.sentiment`        | number \| null | Raw sentiment score in `[-1, 1]`; null when not computed. `context` is the quote. (Per-topic `topics[].sentiment` values are bucketed labels.)                           |
| `arcmira`                     | object         | Deep links (watch-the-moment URL, channel URL, share ID).                                                                                                                |
| `timestamp`                   | string         | ISO delivery time.                                                                                                                                                       |

### Request headers

Every delivery carries:

```text theme={null}
Content-Type: application/json
User-Agent: Arcmira-Webhook/1.0
X-Arcmira-Signature: sha256=<base64 HMAC>
X-Arcmira-Timestamp: 1751940000
X-Arcmira-Delivery-Id: <delivery id>
X-Arcmira-Event: new_appearance
```

### Signature verification

The signature is HMAC-SHA256 over the string `{X-Arcmira-Timestamp}.{raw request body}`, keyed by your webhook secret (`whsec_...`, with the `whsec_` prefix stripped before use), base64-encoded, and sent as `sha256=<base64>`. Reject deliveries whose timestamp is more than 300 seconds from now (replay protection), and always compare in constant time.

<Warning>
  Verify against the **raw request body bytes**, before any JSON parsing or re-serialization. Framework body parsers that re-encode JSON will break the signature.
</Warning>

<CodeGroup>
  ```typescript TypeScript (Node) theme={null}
  import { createHmac, timingSafeEqual } from 'node:crypto';
  import express from 'express';

  const app = express();

  app.post(
    '/hooks/arcmira',
    express.raw({ type: 'application/json' }), // raw body is required
    (req, res) => {
      const secret = process.env.ARCMIRA_WEBHOOK_SECRET!.replace(/^whsec_/, '');
      const timestamp = req.header('X-Arcmira-Timestamp') ?? '';
      const signature = req.header('X-Arcmira-Signature') ?? '';

      if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
        return res.status(400).send('stale timestamp');
      }

      const expected = 'sha256=' + createHmac('sha256', secret)
        .update(`${timestamp}.${req.body.toString('utf8')}`)
        .digest('base64');

      const a = Buffer.from(signature);
      const b = Buffer.from(expected);
      if (a.length !== b.length || !timingSafeEqual(a, b)) {
        return res.status(401).send('bad signature');
      }

      const alert = JSON.parse(req.body.toString('utf8'));
      // handle alert.event === 'new_appearance' ...
      res.sendStatus(200);
    },
  );
  ```

  ```python Python (FastAPI) theme={null}
  import base64, hashlib, hmac, os, time
  from fastapi import FastAPI, Request, HTTPException

  app = FastAPI()
  SECRET = os.environ['ARCMIRA_WEBHOOK_SECRET'].removeprefix('whsec_').encode()

  @app.post('/hooks/arcmira')
  async def arcmira_hook(request: Request):
      raw = await request.body()  # raw bytes, before parsing
      timestamp = request.headers.get('x-arcmira-timestamp', '')
      signature = request.headers.get('x-arcmira-signature', '')

      if abs(time.time() - int(timestamp or 0)) > 300:
          raise HTTPException(400, 'stale timestamp')

      digest = hmac.new(SECRET, f'{timestamp}.'.encode() + raw, hashlib.sha256).digest()
      expected = 'sha256=' + base64.b64encode(digest).decode()
      if not hmac.compare_digest(signature, expected):
          raise HTTPException(401, 'bad signature')

      alert = await request.json()
      # handle alert['event'] == 'new_appearance' ...
      return {'ok': True}
  ```
</CodeGroup>

### Secret lifecycle

* The signing secret (`whsec_...`) is generated when webhook delivery is first enabled on a monitor.
* **The secret is returned exactly once**, in the response that enabled the webhook. Store it securely; reads return `webhookSecretSet: true` and a hint only.
* Lost it? Rotate: `POST /v1/monitors/{id}/webhook-secret/rotate` (scope `monitors:write`) returns a fresh secret once. During a 24h overlap window, deliveries are signed with both secrets (`X-Arcmira-Signature` new, `X-Arcmira-Signature-Previous` old) so you can roll with zero downtime.
* Rotation does not re-enable a disabled webhook; re-enable explicitly with `PATCH { "notifyWebhook": true }` after fixing your endpoint.

### Retries and auto-disable

The delivery queue retries failed deliveries (platform-default schedule; rate-limited endpoints retry after 60 seconds). **10 consecutive failures auto-disable the webhook**: `notifyWebhook` stays configured but `webhookDisabledAt`/`webhookDisabledReason` are set and deliveries stop. Auto-disable is enforced per tracker today; a monitor-level webhook's failure counter accrues on the tracker that fired (fix pending), so also watch your endpoint's own logs.

| State         | Detection                                       | Action                                                                                  |
| ------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------- |
| Healthy       | `webhookFailures: 0`, `webhookDisabledAt: null` | Nothing.                                                                                |
| Failing       | `webhookFailures > 0`                           | Check your endpoint's 2xx rate and latency (respond in under 10s; do heavy work async). |
| Auto-disabled | `webhookDisabledAt` set                         | Fix the endpoint, then `PATCH { "notifyWebhook": true }` to re-enable.                  |

## Errors

| Code                                      | HTTP | When                                                                          |
| ----------------------------------------- | ---- | ----------------------------------------------------------------------------- |
| `invalid_api_key`                         | 401  | Bad or internal key.                                                          |
| `insufficient_scope`                      | 403  | Missing `monitors:write` / `trackers:write` on a mutation.                    |
| `monitor_not_found` / `tracker_not_found` | 404  | ID not found or not yours.                                                    |
| `webhook_not_configured`                  | 409  | Rotate called on a monitor with no webhook.                                   |
| `idempotency_conflict`                    | 409  | Same `Idempotency-Key`, different body. Mint a new key per logical operation. |
| `rate_limit_exceeded`                     | 429  | Honor `Retry-After`.                                                          |

Envelope: `{ "error": { "type", "code", "message", "doc_url", "request_id" } }`. Switch on `error.code`.

**Documented exception**: tier gates on delivery features (webhook or Slack requested on a plan without them) return a legacy bare `403` body, `{ "error": "...", "upgradeRequired": true, "feature": "webhooks" }`, without the envelope. Detect it by the `upgradeRequired` field.

## Rate limits & idempotency

* Per-key sliding 60s window (free 60/min, paid 240, teams/enterprise 600); `RateLimit-*` headers on every response.
* `Idempotency-Key` on every POST/PATCH; 24h cache per key; replays return `Idempotency-Replayed: true`; reuse with a different body returns 409.

## Community Review

Any fired alert is disputable, free (0 rows), via `type: "monitor_alert"` on `POST /v1/feedback`. Corrections key off the alert row `id`; referenced rows must belong to your account (`404 alert_not_found` otherwise):

```json theme={null}
{
  "type": "monitor_alert",
  "query": { "monitor_id": "mon_abc123", "tracker_id": "trk_9f2ab4c8d1e6", "alert_id": "<alert row id>" },
  "endpoint": "/v1/monitors/mon_abc123/alerts",
  "corrections": [
    {
      "id": "<alert row id>",
      "issue_type": "wrong_entity",
      "suggested_change": { "entity_id": "ent_999" },
      "notes": "Fired for the wrong OpenAI merge candidate."
    }
  ]
}
```

Issue set: `false_positive_alert`, `wrong_entity`, `wrong_media`, `wrong_timestamp`, `duplicate_alert`, `missed_alert` (expectation shape: no row to target; `suggested_change` carries `source_url` + `approximate_timestamp_seconds`), `delivery_issue` (targets the delivery row; `suggested_change: { "channel": ... }`), `other`. Content disputes target the occurrence, not each delivery row; file once per `appearance_id`. Check status later with `GET /v1/feedback/{feedback_id}`. Nothing auto-applies. Catalog: [Community Review](/feedback#monitor-alerts).

## Common mistakes

| Wrong                                                          | Correct                                                                                                                   |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| Counting alert rows as detections                              | One matched appearance writes one row **per enabled channel**; group by `appearance_id`                                   |
| Paginating alert history with `cursor`                         | It takes `?n=` (1-100, default 25) and never cursor-paginates; documented exception                                       |
| Joining alerts to mentions via `appearance_id` string tricks   | Alert rows carry `mention_id` (`men_*`) and `entity_id` (`ent_*`) directly; `media_id`/`appearance_id` stay numeric       |
| Expecting `GET /v1/monitors/{id}` to return the webhook secret | The secret is returned once when the webhook is enabled; recover via the rotate endpoint                                  |
| Verifying the signature against re-serialized JSON             | Verify against the raw body bytes; use a raw body parser                                                                  |
| Skipping `Idempotency-Key` on POST/PATCH                       | Send it on every mutation; retries are unsafe without it                                                                  |
| `POST /v1/monitors/{id}/trackers` with `entityName`            | That route attaches existing trackers: `{ "trackerIds": ["trk_..."] }`. Create the tracker first with `POST /v1/trackers` |
| One monitor per entity                                         | Trackers watch entities; monitors group them with shared delivery                                                         |
| Expecting alerts for years-old videos                          | Alerts cover recently published media (\~14-day window); backfill with Search                                             |
| `entityType: "company"`                                        | The enum is `person`, `organization`, `product`, `topic`, `channel`                                                       |

## Patterns and gotchas

* Webhook for speed, polling for reconciliation: consume webhooks in realtime and sweep `?n=100` alert history periodically to catch anything missed during downtime.
* Respond 2xx fast (under 10s) and process async; slow endpoints accumulate failures and auto-disable after 10 consecutive.
* To stop deliveries, pause the tracker: `PATCH /v1/trackers/{id}` with `{ "paused": true }`. There is no backfill for the paused window. Monitor-level `isPaused` is dashboard state and does not stop tracker deliveries today.
* Alert occurrences debit 100 rows each; `/v1/me` → `usage.rows_remaining` is the live balance.
* Deliveries from usage-cap pauses appear as missed alerts in the dashboard and can be replayed there.
* Dashboard-created monitors and API-created monitors are the same objects; you can mix freely.

## Complete examples

Create, deliver, verify, and reconcile:

```typescript TypeScript theme={null}
const BASE = 'https://api.arcmira.com';
const H = {
  Authorization: `Bearer ${process.env.ARCMIRA_API_KEY}`,
  'Content-Type': 'application/json',
};

// 1. Monitor with webhook delivery; capture the one-time secret
const createRes = await fetch(`${BASE}/v1/monitors`, {
  method: 'POST',
  headers: { ...H, 'Idempotency-Key': crypto.randomUUID() },
  body: JSON.stringify({
    name: 'Portfolio watch',
    notifyWebhook: true,
    webhookUrl: 'https://example.com/hooks/arcmira',
  }),
}).then(r => r.json());
// -> persist createRes.monitor.webhookSecret (whsec_...) NOW; it is not shown again

// 2. Trackers from your own list, attached to the monitor
const trackerIds: string[] = [];
for (const org of ['Ramp', 'Vanta', 'Figma']) {
  const { tracker } = await fetch(`${BASE}/v1/trackers`, {
    method: 'POST',
    headers: { ...H, 'Idempotency-Key': crypto.randomUUID() },
    body: JSON.stringify({ entityName: org, entityType: 'organization' }),
  }).then(r => r.json());
  trackerIds.push(tracker.id);
}
await fetch(`${BASE}/v1/monitors/${createRes.monitor.id}/trackers`, {
  method: 'POST',
  headers: { ...H, 'Idempotency-Key': crypto.randomUUID() },
  body: JSON.stringify({ trackerIds }),
});

// 3. Reconciliation sweep (run on a schedule)
const { data } = await fetch(
  `${BASE}/v1/monitors/${createRes.monitor.id}/alerts?n=100`, { headers: H },
).then(r => r.json());
const occurrences = new Set(data.map((a: any) => a.appearance_id));
console.log(`${occurrences.size} occurrences across ${data.length} deliveries`);
```
