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

# Search for coding agents

> Self-contained reference for the Search surface: every endpoint, parameter table, response schema, error, and known mistake on one page.

## Overview

Search resolves entities and pulls evidence from Arcmira's media index (long-form video and podcasts on YouTube).

* Base URL: `https://api.arcmira.com`, all paths under `/v1`.
* Auth: `Authorization: Bearer arc_sk_...` or `x-api-key: arc_sk_...`. If both are sent, `x-api-key` wins.
* Scope: all endpoints on this page need `read` (present on every key). Commercial endpoints additionally need a **Pro+ tier** AND the `recommendations:read` scope; verify with `GET /v1/me` → `recommendations_api_enabled` and `scopes`.
* Billing: lookups and searches are free. Mention/appearance rows cost 1 row each from the shared monthly pool (the first 5 rows of each distinct pull are free). Commercial rows bill at **10 rows each**. `GET /v1/me` costs 0 rows; check `usage.rows_remaining` before large pulls.
* Five entity types: `person`, `organization`, `product`, `topic`, `channel`. Only `person` has appearances.

## Minimal working example

<CodeGroup>
  ```typescript TypeScript theme={null}
  const H = { Authorization: `Bearer ${process.env.ARCMIRA_API_KEY}` };

  const { entity } = await fetch(
    'https://api.arcmira.com/v1/entities/lookup?name=OpenAI&type=organization',
    { headers: H },
  ).then(r => r.json());

  const { data } = await fetch(
    `https://api.arcmira.com/v1/mentions?entity_id=${entity.id}&limit=25`,
    { headers: H },
  ).then(r => r.json());
  ```

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

  H = {'Authorization': f"Bearer {os.environ['ARCMIRA_API_KEY']}"}

  entity = httpx.get(
      'https://api.arcmira.com/v1/entities/lookup',
      params={'name': 'OpenAI', 'type': 'organization'}, headers=H,
  ).json()['entity']

  rows = httpx.get(
      'https://api.arcmira.com/v1/mentions',
      params={'entity_id': entity['id'], 'limit': 25}, headers=H,
  ).json()['data']
  ```

  ```bash cURL theme={null}
  curl 'https://api.arcmira.com/v1/entities/lookup?name=OpenAI&type=organization' \
    -H "Authorization: Bearer $ARCMIRA_API_KEY"
  curl 'https://api.arcmira.com/v1/mentions?entity_id=ent_123881&limit=25' \
    -H "Authorization: Bearer $ARCMIRA_API_KEY"
  ```
</CodeGroup>

## Endpoints

### GET /v1/entities/lookup: resolve to a canonical ID

Exact resolution. Pass `name` (+ optional `type`) or `id`. Follows merge chains; response is `{ "entity": { id, name, type, merged_from_id, route, ... } }`. `merged_from_id` is non-null when the input was an alias. Free. Missing entities: `404 entity_not_found`.

### GET /v1/entities/search: ranked discovery

Fuzzy, ranked, free. `q` (required, min 2 chars), `type`, `has_recommendations_data=true`, `limit` (1-25, default 10). Single page, no cursor. Ordered by `appearance_count` desc. Rows include `recommendations_summary` only for Pro+ callers with `recommendations:read`; other callers get the same row without that field (no error).

### GET /v1/search: resolve a name to a single entity

Single-result name resolution for a free-text `q` (+ optional `type`). Returns the best match as `{ found: true, ... }` or `{ found: false }`. The `entity.id` here is a **raw number**; prefix it as `ent_{id}` for the entities endpoints, or use `lookup`, which returns the public form. Single result, no pagination. Free. Use `entities/search` for ranked lists; use `lookup` when you already know the exact name or ID.

### GET /v1/entities/{id}: read canonical metadata

`{ "entity": { id, canonical_id, name, type, url, image_url, appearance_count, is_canonical, merged_from_id, route } }`. On `organization`/`product` entities, Pro+ + `recommendations:read` callers also get a `recommendations_summary` aggregate block (0 rows). `appearance_count` counts indexed mention/appearance rows for any entity type.

### GET /v1/entities/cards: batched compact cards

`ids` as a comma-separated list of **raw integer entity IDs** (`ids=12,844,1032`, 1-50 of them); the `ent_*` form is not accepted here. Returns compact card objects under the requested IDs; unknown IDs are silently dropped. Free.

### GET /v1/mentions: evidence rows

The primary evidence surface. At least one of `entity_id` or `entity_name` is required; if both are sent, `entity_id` wins.

| Parameter               | Type     | Default | Description                                                                                                             |
| ----------------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `entity_id`             | string   | —       | Canonical `ent_*`. Mutually exclusive with `entity_name`. Prefer this.                                                  |
| `entity_name`           | string   | —       | Human-readable name; pair with `entity_type` to disambiguate.                                                           |
| `entity_type`           | enum     | —       | `person`, `organization`, `product`, `topic`, `channel`.                                                                |
| `channel_id`            | string   | —       | Restrict to one YouTube channel ID (`UC...`).                                                                           |
| `channel_name`          | string   | —       | Substring match on source channel name.                                                                                 |
| `q`                     | string   | —       | Free text over description, media title, entity name, channel name.                                                     |
| `sentiment`             | enum     | —       | `positive`, `neutral`, `negative`.                                                                                      |
| `is_appearance`         | boolean  | —       | **Person entities only**; `true` on a non-person returns `400 appearances_person_only`.                                 |
| `date_from` / `date_to` | ISO date | —       | Inclusive bounds on `published_at`.                                                                                     |
| `details`               | enum     | —       | `full` attaches commercial rows per mention. **Pro+ + `recommendations:read`**; charges premium rate per attached item. |
| `limit`                 | int      | 20      | 1-100.                                                                                                                  |
| `cursor`                | string   | —       | Opaque, from prior `next_cursor`.                                                                                       |

### GET /v1/entities/{id}/mentions: entity-scoped variant

Identical shape and filters (minus `entity_*`); the entity rides in the path. Preferred for production: explicit, no URL-encoded names.

### GET /v1/people/{slug}/appearances: person appearances

The appearance subset for one person. Person-only: the equivalent route for any other type returns `400 appearances_person_only`.

**Documented exception**: this route and the related-entity lists below return `{ items, total, offset, limit, hasMore }` (camelCase, offset-based, with a total) instead of the standard `{ data, has_more, next_cursor }` envelope. No cursor is returned, so pages past the first are not reachable here today. For appearance rows with standard pagination, date filters, and the mention-row shape, prefer `GET /v1/mentions?entity_id=...&is_appearance=true`.

### Related-entity lists

```text theme={null}
GET /v1/{people|organizations|products|topics|channels}/{slug}/{topics|people|organizations|products|channels}
GET /v1/channels/{slug}/guests
```

Filters: `q` (substring), `field`, `sort`, `order`. One row each. Same `{ items, total, ... }` exception shape as appearances above; raise `limit` (max 100) and narrow with `q` instead of paging.

### Commercial endpoints (Pro+ + `recommendations:read`)

```text theme={null}
GET /v1/recommendations                       ad reads, endorsements; entity_id|entity_name required
GET /v1/entities/{id}/recommendations         same, entity in path
GET /v1/channels/{channel_id}/sponsors        recurring sponsor rollup; channel_id is the YouTube UC... ID
```

Key params: `mention_class` (`ad_read`|`endorsement`|`mention`|`all`, default `all`), `min_confidence` (default 0.7), `include_disputed` (default false), `min_ad_reads` (sponsors, default 3), `status` (sponsors: `active`|`lapsed`|`ended`|`uncertain`). Full narrative: [Commercial intelligence](/commercial-intelligence).

## Response schema

Mention row (the shape you will parse most):

```json theme={null}
{
  "data": [
    {
      "id": "men_2049815",
      "appearance_id": 2049815,
      "entity": { "id": "ent_123881", "name": "OpenAI", "type": "organization" },
      "media": {
        "id": 9912345,
        "video_id": "abcd_1234",
        "title": "...",
        "url": "https://www.youtube.com/watch?v=abcd_1234",
        "published_at": "2026-04-12T14:00:00Z",
        "channel_id": "UCabc...",
        "view_count": 412034,
        "source_channel": { "id": "ent_5012", "name": "TBPN", "url": "https://www.youtube.com/@tbpn" }
      },
      "start_timestamp": "06:52",
      "end_timestamp": "07:58",
      "start_seconds": 412,
      "end_seconds": 478,
      "is_appearance": false,
      "description": "OpenAI's new model release...",
      "confidence": 0.93,
      "sentiment_score": 0.42,
      "sentiment": "positive",
      "referenced_url": null,
      "referenced_platform": null,
      "extracted_content": "..."
    }
  ],
  "has_more": true,
  "next_cursor": "eyJvZmZzZXQiOjI1fQ",
  "entity": { "id": "ent_123881", "name": "OpenAI", "type": "organization", "appearance_count": 0 }
}
```

| Field                               | Type           | Notes                                                                                                                                         |
| ----------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                | string         | Public `men_*` form. Use it in Community Review corrections.                                                                                  |
| `start_seconds` / `end_seconds`     | number \| null | **Integer seconds; read these.** `0` means the full episode, no specific moment. Null when the analyzer could not locate the mention in time. |
| `start_timestamp` / `end_timestamp` | string \| null | Deprecated `MM:SS` (or `HH:MM:SS`) strings, kept until a changelog-announced removal. Same information as the seconds fields.                 |
| `sentiment_score`                   | number \| null | Raw `[-1, 1]`, null when not computed. `sentiment` is the bucketed label (positive > 0.2, negative \< -0.2, else neutral).                    |
| `is_appearance`                     | boolean        | Always `false` for non-person entities.                                                                                                       |
| `confidence`                        | number         | Extraction confidence `[0, 1]`.                                                                                                               |
| `source_channel`                    | object \| null | Null when the video has not been linked to a channel entity (including some YouTube rows).                                                    |
| `next_cursor`                       | string \| null | Null when `has_more` is `false`. Opaque; pass back verbatim.                                                                                  |

With `details=full`, each row gains `recommendations: { items: [...] }` where items carry `com_*` IDs, `mention_class`, `verbatim_quote`, `promo_code`, and timestamps (`start_seconds`/`end_seconds` plus the deprecated `MM:SS` strings); `items` is `[]` when nothing matches.

## Errors

Envelope on every error:

```json theme={null}
{ "error": { "type": "...", "code": "...", "message": "...", "doc_url": "https://docs.arcmira.com/errors#...", "request_id": "req_..." } }
```

| Code                          | HTTP | When                                                                              |
| ----------------------------- | ---- | --------------------------------------------------------------------------------- |
| `invalid_query`               | 400  | Param validation failed; `error.message` has the validator output.                |
| `appearances_person_only`     | 400  | Appearance surface or `is_appearance=true` on a non-person. Use mentions instead. |
| `invalid_api_key`             | 401  | Missing, malformed, disabled, or internal (`arc_int_*`) key.                      |
| `quota_exceeded`              | 402  | Monthly rows exhausted and on-demand disabled.                                    |
| `insufficient_scope`          | 403  | Key lacks the needed scope (message names it).                                    |
| `recommendations_not_enabled` | 403  | Commercial route on a tier below Pro+, regardless of scope.                       |
| `entity_not_found`            | 404  | ID or name did not resolve.                                                       |
| `channel_not_found`           | 404  | Sponsors called with a channel ID that has no indexed media.                      |
| `rate_limit_exceeded`         | 429  | Honor `Retry-After`.                                                              |

Switch on `error.code`, never on `error.message`. Quote `X-Request-Id` to support.

## Rate limits & idempotency

* Per-key fixed 60-second window: free 60/min, paid (incl. Ultra) 240, teams/enterprise 600. Headers on every authenticated response: `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`; 429s add `Retry-After`.
* Every data endpoint on this page is `GET`: no `Idempotency-Key` needed, retries are safe. The one write is `POST /v1/feedback` below; send `Idempotency-Key` on it.

## Community Review

Every result on this page is disputable, free (0 rows), via `POST /v1/feedback`. Pick the `type` matching the surface you called, echo the reproducing `query`, and target rows by public ID:

| You called                                 | `type`             | Correction targets                                                                                                          |
| ------------------------------------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `/v1/search`                               | `search`           | The resolved `ent_*` (or a `missing_result`); for `bad_ranking`, `suggested_change` carries `observed_rank`/`expected_rank` |
| `/v1/entities/search`                      | `entities_search`  | `ent_*` hits; `issue_type`: `bad_ranking`, `wrong_entity_type`, `duplicate_entity`, `merge_suggestion`, `missing_result`    |
| `/v1/entities/lookup`, `/v1/entities/{id}` | `entities`         | The `ent_*`; `wrong_entity_type`, `stale_metadata`, `merge_suggestion`                                                      |
| `/v1/mentions`                             | `mentions`         | `men_*` rows; `wrong_entity`, `missing_result`                                                                              |
| `/v1/people/{slug}/appearances`            | `appearances`      | Appearance rows; `person_not_present`, `wrong_person`, `wrong_appearance_role`                                              |
| `/v1/recommendations`                      | `recommendations`  | `com_*` rows; `reason`: `false_positive_ad_read`, `missed_ad_read`, `wrong_classification`, ...                             |
| `/v1/channels/{id}/sponsors`               | `channel_sponsors` | Sponsor `ent_*` with `query.channel_id`                                                                                     |

Check any submission's status later with `GET /v1/feedback/{feedback_id}`.

```json theme={null}
{
  "type": "mentions",
  "query": { "entity_id": "ent_123881", "sentiment": "positive", "limit": 25 },
  "request_id": "req_...",
  "corrections": [
    { "id": "men_2049815", "issue_type": "wrong_entity", "notes": "Row is about a different OpenAI merge candidate." }
  ]
}
```

Nothing auto-applies. Supports `Idempotency-Key`. Up to 100 corrections per submission. Full catalog: [Community Review](/feedback).

## Common mistakes

Wrong parameters and patterns frequently generated for this API. Do not use the left column.

| Wrong                                                | Correct                                                                                                   |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `Authorization: arc_sk_...` (bare key)               | `Authorization: Bearer arc_sk_...` or `x-api-key: arc_sk_...`                                             |
| Using an `arc_int_*` session key                     | v1 rejects internal keys; use an `arc_sk_*` API key                                                       |
| Pulling mentions by `entity_name` in production      | Resolve once with `/v1/entities/lookup`, cache `ent_*`, pass `entity_id`                                  |
| `GET /v1/organizations/{slug}/appearances`           | Appearances are person-only; use `/v1/mentions?entity_id=...`                                             |
| `is_appearance=true` with a non-person entity        | Returns `400 appearances_person_only`; omit it                                                            |
| Parsing `start_timestamp` as a number                | Read `start_seconds`/`end_seconds` (integer seconds); the legacy `start_timestamp` strings are deprecated |
| Calling `/v1/recommendations` with only `read` scope | Needs Pro+ tier AND `recommendations:read`; check `/v1/me` first                                          |
| Passing `ent_*` IDs to `/v1/entities/cards`          | That endpoint takes raw integers only (`ids=12,844`); strip the prefix                                    |
| Decoding or constructing `cursor` values             | Cursors are opaque; pass `next_cursor` back verbatim                                                      |
| Branching on `error.message`                         | Switch on `error.code`; follow `doc_url`                                                                  |

## Patterns and gotchas

* Resolve-then-pull: lookups are free, metered rows are not; never burn rows on an ambiguous name.
* `recommendations_summary` blocks are aggregates and cost 0 rows; row-level pulls are what bill.
* `details=full` multiplies cost (mention rows + 10 rows per attached commercial item); use only when you need the overlay.
* Merge-aware queries: querying a canonical `ent_*` includes rows from entities merged into it. If results look split across variants, that is a `merge_suggestion` for Community Review, not a client-side join.
* `has_more: false` means `next_cursor: null`; stop paging.
* Watch `RateLimit-Remaining` while iterating; back off before hitting 429.
* Every response carries `X-Request-Id`; log it.

## Complete examples

Full evidence pull with pagination and budget check:

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

  // 0. Budget check (free)
  const me = await fetch(`${BASE}/v1/me`, { headers: H }).then(r => r.json());
  if (me.usage.rows_remaining < 500) throw new Error('row budget too low');

  // 1. Resolve (free)
  const { entity } = await fetch(
    `${BASE}/v1/entities/lookup?name=Ramp&type=organization`, { headers: H },
  ).then(r => r.json());

  // 2. Page through positive mentions since April (1 row per row)
  const rows = [];
  let cursor: string | null = null;
  do {
    const url = new URL(`${BASE}/v1/entities/${entity.id}/mentions`);
    url.searchParams.set('sentiment', 'positive');
    url.searchParams.set('date_from', '2026-04-01');
    url.searchParams.set('limit', '100');
    if (cursor) url.searchParams.set('cursor', cursor);
    const page = await fetch(url, { headers: H }).then(r => r.json());
    rows.push(...page.data);
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);

  console.log(`${rows.length} rows`, rows[0]?.media.title);
  ```

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

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

  me = httpx.get(f'{BASE}/v1/me', headers=H).json()
  assert me['usage']['rows_remaining'] >= 500, 'row budget too low'

  entity = httpx.get(
      f'{BASE}/v1/entities/lookup',
      params={'name': 'Ramp', 'type': 'organization'}, headers=H,
  ).json()['entity']

  rows, cursor = [], None
  while True:
      params = {'sentiment': 'positive', 'date_from': '2026-04-01', 'limit': 100}
      if cursor:
          params['cursor'] = cursor
      page = httpx.get(
          f"{BASE}/v1/entities/{entity['id']}/mentions", params=params, headers=H,
      ).json()
      rows.extend(page['data'])
      if not page['has_more']:
          break
      cursor = page['next_cursor']

  print(len(rows), 'rows')
  ```
</CodeGroup>
