events

Get Event History

Reviewed-or-expired alarms, tenant-scoped, paginated and filterable.

All six filter params are optional and additive: absent means today's unfiltered behaviour, and total is the FILTERED count. The semantics (the half-open window on the effective sort column, the expired clamp on the evaluation-only filters, the q field set) live in EventsService.history_filter_conditions.

get/events/history

Request

  • Base URL
    https://api.verdun.ai (the document declares no server; this is the origin it was published from)
  • URL
    https://api.verdun.ai/events/history
  • Auth
    HTTP bearer

Query parameters

state'dismissed' | 'archived' | 'expired'
limitinteger
offsetinteger
order'desc' | 'asc'
sort'alarm_time' | 'reviewed_at' | 'closed_at'

alarm_time: alarm creation; reviewed_at: disposition time (clamped to alarm_time for expired rows); closed_at: disposition time for dismissed/archived rows or expiry time for expired rows. The date window uses this same timestamp.

fromstring nullable

Inclusive start of the window, an ISO-8601 datetime WITH a UTC offset (a naive value, a bare date, or an epoch number is a 422). Applied to the row's EFFECTIVE sort timestamp: the alarm time under sort=alarm_time, the review time under sort=reviewed_at, or the disposition/expiry time under sort=closed_at. May be sent without to.

tostring nullable

EXCLUSIVE end of the window — [from, to) — an ISO-8601 datetime WITH a UTC offset, same rejections as from. Must be after from when both are given. May be sent without from.

dispositionstring[] nullable

Repeatable. OperatorDisposition MEMBER NAMES (FALSE_ALARM, ESCALATE_DISPATCH, ESCALATE_VOICE_DOWN, SUSPICIOUS_MONITOR, NOT_MONITORED), not the display labels; an unknown name is a 422. Evaluated states only — silently ignored for state=expired, which has no evaluation to filter on.

actor'operator' | 'agent' nullable

Who disposed the alarm: a human operator or the agent. Evaluated states only — silently ignored for state=expired.

qstring nullable

Keyword search, 120 characters or fewer after stripping (longer is a 422; empty or whitespace-only reads as absent). Whitespace splits it into terms and EVERY term must match; a term matches as a case-insensitive substring of the operator/agent notes (evaluated states only), the alarm summary, the zone, the camera label or id, the client name, the site name, or the AI rationale — plus, when the term carries 3 or more digits, the client's contact phone digits. A search with no from covers only the 14 days before to (or now). This search-only limit does not apply to unfiltered History. Clients should display the effective search window; send from to search further back.

site_idstring uuid nullable

Only this site's alarms (a site UUID; anything else is a 422). Narrows inside the caller's tenant scope: a site the caller cannot see returns an empty page.

audit'needs' | 'audited' | 'overturned' | 'disputed' | 'pending_review' nullable

Call-audit lens, for owners, monitoring managers and operators (read-only and customer roles get a 403). A lead sees every audit; an operator's lenses exclude their own calls and only match audits they are party to. needs: sampled or rushed human calls with no audit yet (for a lead also calls whose PEER audit is still open to a lead: a peer uphold or a pending peer overturn), limited to calls inside the 30-day clip window or with a recorded retained clip copy (an age rule, not an S3 check); audited: has an audit; overturned: the audit overturned the call; disputed: a dispute awaits a ruling; pending_review (LEADS ONLY, 403 otherwise): peer overturns awaiting lead confirmation. Evaluated states only — silently ignored for state=expired.

operator_idstring uuid nullable

Only alarms this operator decided (their evaluation's operator). Refused for curated customer roles. Evaluated states only — silently ignored for state=expired.

include_totalboolean

false skips the filtered count and returns total: null. For background refreshes that already have a count from the opening fetch; counting the whole filtered history on every poll was most of the database load in the 2026-10-13 incident. With audit=needs on the dismissed or archived state the count is best effort: past its own 2 s budget total is null and the rows are still returned.

Response

Successful Response

itemsobject[] required

Page of history rows, newest first. Each row is {state, event, camera, site, site_name, parent, operator_evaluation, thread, interpretation_result, poster_url, operator_name, audit, audit_reasons}. site contains the site id, name and the IANA timezone for site-local pipeline timestamps; site_name is retained for existing clients. interpretation_result carries the same to_dict_frontend shape the active queue and GET /events/{id} use (incl. terminal_stage and gate_outcome), and is null for II-off cameras and rows aged into history before any eval landed. The camera dict omits the resolved rtsp_url on this list path. poster_url is a presigned URL to the alert-frame JPEG derived from the event's clip key (null when there is no clip, the clip key doesn't match the expected shape, or presigning failed) — the object may not exist yet for pre-cutover rows, so the URL can 404 client-side by design. event.queued_at is additive and optional: when the row entered the operator queue, server-side (derived, never stored — the early-broadcast stamp, else the AI verdict's time, else creation time for a row queued at ingest), and null for a row that never became queue-visible; it is the same value and shape the queue snapshot and GET /events/{id} carry. operator_name (the deciding operator's First Last, central-staff callers only, human rows only; an audited AI-agent close carries "Verdun Agent" from the audit snapshot), audit (the CallAuditOut call audit: leads see every audit, anyone else only on a call they made) and audit_reasons (sample and/or rushed: why the call is up for audit; for the AI agent's closes only 'sample'; owners, monitoring managers and operators) are additive and null / [] when not applicable.

totalinteger nullable required

Total rows matching the state filter AND every optional filter on the request (from/to, disposition, actor, q, site_id, audit, operator_id) — the filtered count, not the state total, so the client's result line can read it directly. null when the request sent include_total=false (the console's background poll keeps the count from its opening fetch), and for audit=needs on the dismissed or archived state when its count runs past its own 2 s budget (the rows are still returned).

Changes

No recorded changes to this operation across all 2 revisions of this API.