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.
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):
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.
Webhook delivery POSTs each alert to your webhookUrl as JSON. Paid tiers only. Endpoint requirements: HTTPS, publicly reachable, responds 2xx within 10 seconds.
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).
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.
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.
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.
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.
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.
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):
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.
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.