Skip to main content
Conventions that hold across the whole API, so you learn them once.

Public IDs

ResourceFormNotes
Entityent_123881Merge-aware: old IDs keep resolving; lookups surface merged_from_id
Mentionmen_2049815What you pass to Community Review for mention and appearance rows
Commercial rowcom_119682What you pass to Community Review for reclassification
Monitormon_abc123Rows minted before the Monitors rename carry a legacy wl_ prefix; both resolve
Trackertrk_9f2ab4c8d1e6
IDs are opaque and stable: rely on the prefix, never parse the suffix. Alert-history rows carry public forms (entity_id as ent_*, mention_id as men_*) alongside numeric media_id/appearance_id, which stay numeric everywhere in the API.

Pagination

List endpoints use cursor pagination. No total counts: fast, stable, infinite-scroll-friendly.
ParameterDescription
limit1-100, default 20
cursorOpaque token from a previous response’s next_cursor; omit on the first request
{
  "data": [ ],
  "has_more": true,
  "next_cursor": "eyJvZmZzZXQiOjI1fQ"
}
When has_more is false, next_cursor is null; stop. Cursors are opaque: pass them back verbatim, never construct or decode them. If the underlying ordering changes (rare), an old cursor may include duplicates or gaps; restart from null for a strict snapshot.
async function* list(url: URL, headers: HeadersInit) {
  let cursor: string | null = null;
  do {
    if (cursor) url.searchParams.set('cursor', cursor);
    const page = await fetch(url, { headers }).then(r => r.json());
    yield* page.data;
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);
}
def list_all(client, url, params, headers):
    cursor = None
    while True:
        if cursor:
            params['cursor'] = cursor
        body = client.get(url, params=params, headers=headers).json()
        yield from body['data']
        if not body['has_more']:
            return
        cursor = body['next_cursor']
Documented exceptions:
  • Alert history (GET /v1/monitors/{id}/alerts, GET /v1/trackers/{id}/alerts) takes ?n= (1-100, default 25) and always returns has_more: false. It is a recent-deliveries window, not a paginated archive.
  • Person appearances (/v1/people/{slug}/appearances) and the related-entity lists (/v1/{type}/{slug}/{topics|people|...}, /v1/channels/{slug}/guests) return { items, total, offset, limit, hasMore } and no cursor, so they serve one page. For paginated appearance evidence use /v1/mentions?entity_id=...&is_appearance=true.
  • Discovery search (/v1/entities/search) is a single ranked page: limit 1-25, default 10, no cursor. Channel sponsors (/v1/channels/{id}/sponsors) is a single rollup: limit 1-200, default 100, no cursor.

Idempotency

Every POST and PATCH accepts an Idempotency-Key header. Send one on every mutation; a UUID v4 per logical operation is the safe default.
  1. Arcmira processes the request and caches the response (body, status, and a hash of the request body) against your key for 24 hours, scoped per API key.
  2. A retry with the same key returns the cached response with Idempotency-Replayed: true.
  3. Reusing a key with a different body returns 409 idempotency_conflict: mint a new key for the new operation.
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" }'
Caveats: GET/DELETE don’t take idempotency keys (already idempotent); the cache may not outlive 24 hours, so long retry windows should mint fresh keys and handle the resulting conflicts; on transcript corrections, only final outcomes are cached and a 412 never replays.

Headers on every response

HeaderDescription
X-Request-IdCorrelation ID; same value as error.request_id on failures. Log it; quote it to support
X-Arcmira-VersionAPI version serving the request
RateLimit-Limit / -Remaining / -ResetPer-key throttle state, on every response past the key check; see Usage, limits & billing
Retry-AfterOn 429, and on in-flight transcription polls: seconds to wait. Always honor it
Idempotency-Replayedtrue when a successful response came from the idempotency cache

Versioning and deprecation

  • The API is versioned in the path (/v1). Additive changes (new fields, new endpoints) ship without notice; removing or renaming anything gets a deprecated marker in the OpenAPI spec with migration prose, a changelog entry, and a deprecation window.
  • Build tolerant readers: ignore unknown fields, and handle unknown enum values gracefully (new event names, statuses, and error codes will appear).
  • The live OpenAPI 3.1 document is at GET /v1/openapi.json and mirrors this site’s API reference.