---
title: "Get Event By Id"
method: GET
path: "/events/{event_id}"
tags: ["events"]
---

# Get Event By Id

`GET /events/{event_id}`

## 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/{event_id}`
- Auth: HTTP bearer

## Path parameters

- `event_id` string, required

## Response `200`

Successful Response

- GetEventByIdResponse
  - `event` EventsModel, required
    - `id` string, uuid, required: UUID of event
    - `kvs_stream_name` string, required: KVS stream name
    - `zone` string, required: Zone code
    - `clip_s3_url` string, required: S3 URL
    - `summary` string, nullable: Mapped summary column
    - `motion_score` number, nullable: Mapped motion score column
    - `source` string, nullable: Mapped source column
    - `is_demo` boolean, nullable: Owner-uploaded showcase clip (Demo tab only)
    - `starred_at` string, date-time, nullable: Set when an owner starred the event
    - `thread_id` string, uuid, nullable: Threat watch this event belongs to after subject correlation
    - `correlation_status` string, nullable: threat_trigger, related_sighting, or standalone possible_match
    - `candidate_thread_id` string, uuid, nullable: Candidate threat watch for a standalone possible_match
    - `occurred_at` string, date-time, nullable: Ingestor-reported motion time (display/audit; ordering keys on created_at)
    - `created_at` string, date-time, nullable: Event creation time
    - `monitoring_state` string, nullable: The camera's effective monitoring state when this event was ingested (SchedulesService.effective_monitoring_state), stamped once at ingest time — never recomputed live. Same value the queue snapshot and History rows already carry; null for a row from before this column existed.
    - `queued_at` string, date-time, nullable: When this event entered the operator queue, server-side (derived, never stored): the early-broadcast stamp if it has one, else the AI verdict's time, else creation time for a row queued at ingest. Null while the row is not queue-visible. The console's time-in-queue clock anchors on this instead of created_at, so every operator and every reload agree; same value and same shape the queue snapshot and the SSE payloads carry.
    - `has_continued_clip` boolean: True once the ingestor has committed a continued clip for this alarm (extra footage cut because motion was still firing after the clip's post-roll). Fetch it as continued_clip_url from GET /events/{event_id}/clip-url.
    - `continued_clip_seconds` integer, nullable: Length of the continued clip in seconds; null when has_continued_clip is false.
    - `raised_by_operator_id` string, uuid, nullable: The operator who raised this alarm from the Live View wall. Null for every other event, and after that operator's account is deleted (raised_by_name survives).
    - `raised_by_name` string, nullable: Name snapshot of the operator who raised this alarm from the Live View wall. NON-NULL IS THE DEFINITION of an operator-raised alarm (never test `source` or the id). Null for every other event.
    - `raised_incident_type` string, nullable: The incident type the operator chose when raising the alarm (an IncidentType name). Null for every other event.
    - `raised_note` string, nullable: The operator's free-text note (at most 500 characters). Deliberately NOT in `summary`, which the UI renders as the AI rationale. Null when none was given.
    - `operator_alarm_capture` OperatorAlarmCaptureModel
      - `status` string, required: pending | claimed | captured | unavailable | failed
      - `reason` string, nullable: Why no clip, set only when status is unavailable or failed
  - `camera` CamerasModel
    - `id` string, uuid, required: The camera ID.
    - `camera_label` string, required: The label of the camera.
    - `camera_id` string, required: The camera ID.
    - `kvs_stream_name` string, required: The KVS stream name.
    - `ip_address` string, required: The IP address of the camera.
    - `port` string, required: The port of the camera.
    - `username` string, nullable: Camera account username (the password is never returned). Absent for customer roles.
    - `integration` 'RTSP' | 'Network Optix' | 'Axis' | 'Avigilon' | 'Milestone' | 'Verkada' | 'Turing' | 'Hikvision' | 'AvertX', required
    - `ii_enabled` boolean: Incident intelligence on/off; when false motion events still record and alert, but skip AI classification.
    - `live_view_only` boolean: Live View only: the camera is connected for Live View with no AI and no alarms (its monitoring state is always off). The site-level flag on the camera's site has the same effect.
    - `agent_mode` boolean: Agent mode on/off for this camera.
    - `agent_observation_minutes` integer: Post-voice-down observation window in minutes.
    - `agent_incident_types` string[], nullable: Incident types the agent may process at all on this camera.
    - `incident_routing` object, nullable: Per-camera incident routing: incident type -> 'ignore' | 'humans' | 'agent'. Absent type routes to 'humans'; UNKNOWN is never present. Typed as plain strings on the RESPONSE side, deliberately: the read path fails closed (a mode outside the enum reads as 'humans'), and response validation must not undo that by 500-ing on a stored value this build does not know - a hand edit, or a newer task's mode during a rolling deploy. The enum is enforced on the REQUEST model only.
    - `rag_context` string, nullable: Per-camera scene context injected into the LLM classification prompt.
    - `map_x` number, nullable: Normalized floor-plan x position (0..1) on the site map.
    - `map_y` number, nullable: Normalized floor-plan y position (0..1) on the site map.
    - `map_lat` number, nullable: Geographic latitude on the site map.
    - `map_lng` number, nullable: Geographic longitude on the site map.
    - `map_location_label` string, nullable: Operator-facing location prose shown on queue rows and mini-maps.
    - `map_heading` number, nullable: Direction the device faces, degrees clockwise from map-up; null = not aimed.
  - `site` SitesModel
    - `id` string, uuid, required: Unique identifier of the site
    - `account_id` string, uuid, nullable: Account (the client's customer) this site is grouped under; null = directly under the client. This is the ONLY expression of account membership -- group sites by it client-side.
    - `name` string, required: Name of the site
    - `address` string, required: Address of the site
    - `city` string, required: City of the site
    - `state` string, required: State of the site
    - `zip_code` string, required: Zip code of the site
    - `type` 'Commercial' | 'Car Dealership' | 'Construction' | 'Residential' | 'Logistics' | 'Education' | 'Healthcare' | 'Utilities' | 'Other', required
    - `priority_tier` 'critical' | 'high' | 'medium', required
    - `monitoring_hours` 'Nights' | 'All Day' | 'Weekends' | 'Custom', required
    - `timezone` string, required: IANA timezone the site's monitoring schedule is evaluated in
    - `police_nearest_precinct_name` string, required: Police nearest precinct name
    - `police_non_emergency_line` string, required: Police non-emergency line
    - `police_direct_dispatch_line` string, nullable: Police direct dispatch line
    - `police_jurisdiction_notes` string, nullable: Police jurisdiction notes
    - `alarm_license_number` string, nullable: Dealer alarm license number
    - `alarm_license_issuer` string, nullable: Who issued the alarm license
    - `mobile_guard_contract` boolean, required: Mobile guard contract available?
    - `guard_company` string, nullable: Mobile guard company name.
    - `guard_unit` string, nullable: Mobile guard unit or contact identifier.
    - `guard_contact` string, nullable: Mobile guard dispatch phone.
    - `agent_context` string, nullable: Site-wide scene context composed into the LLM prompt for every camera at this site.
    - `agent_thread_window_minutes` integer, nullable: Incident-thread join window for this site's agent cameras, in minutes; null = global default.
    - `heightened_exceptions` object[], nullable: Owner-curated heightened-monitoring exception categories; null = none.
    - `holidays` object[], nullable: Per-site closure calendar: on each listed site-local day every schedule cell that would read 'off' reads 'monitored' instead. Entries are {'rule': <catalog key>} or {'date': 'YYYY-MM-DD', 'label': str}. Managed through GET/PUT /sites/{id}/holidays; null on a payload built before the column existed.
    - `stage` 'live' | 'test', nullable: 'test' or 'live'. A display label only: the console badges test sites; monitoring, reporting and billing are identical for both. New sites start as 'test'. Null only on a site not yet saved.
    - `closed` boolean, nullable: Closed site: any human activity warrants an immediate police call. A display flag only (the console badges it); nothing else branches on it. Set via PUT /sites/{id}/closed.
    - `onboarding_marks` object: Onboarding checklist marks: mark key -> when it was set (ISO-8601 UTC). Keys: setup_call, first_night, streaming_test, skip:setup_call, skip:instructions, skip:passcode, skip:speakers, skip:holidays, completed. Who set a mark is never exposed. Empty when none.
    - `map_mode` string, nullable: Map-tab mode: 'geo' (geographic pin) or 'floorplan' (uploaded plan); null = not configured.
    - `map_lat` number, nullable: Geographic latitude of the site pin (geo mode).
    - `map_lng` number, nullable: Geographic longitude of the site pin (geo mode).
    - `floorplan_width` integer, nullable: Natural pixel width of the uploaded floor plan.
    - `floorplan_height` integer, nullable: Natural pixel height of the uploaded floor plan.
    - `has_floorplan` boolean: Whether a floor-plan image is uploaded (fetch it via GET /sites/{id}/floorplan-url).
    - `disarm_until` string, date-time, nullable: End of the current or most recent disarm window. NOT a null-means-armed flag: an early re-arm sets this to the re-arm instant, so it is non-null for every site that has ever been disarmed; read disarm_active for the live state. Expiry is implicit -- nothing sweeps it.
    - `disarm_set_at` string, date-time, nullable: When the current disarm window began. The resolver's predicate is the window between this and disarm_until, so a historical read is never repainted by a disarm set later.
    - `disarm_by` string, nullable: Who turned the cameras off: 'sms:<E.164>' for a customer text, 'voice:<E.164>' for a customer call, 'operator:<uuid>' from the console.
    - `disarm_active` boolean: Whether this site's cameras are off RIGHT NOW because of a text-to-disarm. Resolved server-side from the same window predicate the ingest gate uses -- do NOT recompute it from disarm_until, which stays set at the moment an early re-arm ended the window and so is non-null for every site that has ever been disarmed.
    - `arm_until` string, date-time, nullable: End of the current or most recent TEST ARM window, during which an operator held the cameras on outside the schedule. NOT a null-means-off flag: ending a test early sets this to that instant, so it is non-null for every site ever tested; read arm_active for the live state.
    - `arm_set_at` string, date-time, nullable: When the current test-arm window began. The resolver's predicate is the window between this and arm_until, so a historical read is never repainted by a test started later.
    - `arm_by` string, nullable: Who started the test: always 'operator:<uuid>'. The console is the only channel into a test arm, so unlike disarm_by this never carries a customer's phone number.
    - `arm_active` boolean: Whether this site's cameras are on FOR A TEST right now. Resolved server-side from the same window predicate the ingest gate uses -- do NOT recompute it from arm_until. A live disarm beats a live test arm, and starting a disarm ends the test outright.
    - `disarm_max_minutes` integer, nullable: The longest a single disarm may last at this site (1-120).
    - `live_view_only` boolean: Live View only: every camera at this site is connected for Live View with no AI and no alarms (monitoring state always off), whatever the schedule, disarm or test arm say.
    - `has_disarm_passcode` boolean: Whether this site has a disarm passcode. Read the code through the dedicated disarm-passcode endpoint.
    - `disarm_passcode_hint` string, nullable: Last two characters of the stored passcode, for rendering it as '****92'.
    - `disarm_passcode_revision` string, nullable: Opaque revision that changes whenever the passcode is minted or reset, including when its hint is unchanged.
    - `monitoring_now` string, nullable: Live View only (site flag or every live camera individually flagged) reads 'live_view_only' and overrides both arm and disarm windows. Otherwise: Whether this site's cameras are being monitored RIGHT NOW, resolved server-side: 'disarmed' (a live text/call/console disarm window - it beats every schedule), 'monitored' (at least one live camera resolves monitored or heightened now), 'off_schedule' (no camera is monitored now, but the schedule turns one on within 7 days - see monitoring_next_change_at), or 'unscheduled' (no monitored hours anywhere in the week). Derived, never stored. Null on payloads that embed a site incidentally (an event); ask a site endpoint. Do NOT derive this from the schedule grid client-side.
    - `monitoring_next_change_at` string, date-time, nullable: The next instant this site's monitored/off state flips BY SCHEDULE ALONE - the disarm window is deliberately ignored, because the console already has disarm_until. Null when nothing changes within 7 days and for an 'unscheduled' site. Aligned to a 15-minute boundary in the site's own timezone.
    - `contacts` SiteContactResponseModel[], required: List of site contacts
      - `first_name` string, required: The first name of the site contact.
      - `last_name` string, required: The last name of the site contact.
      - `role` string, required: The role of the site contact.
      - `primary_phone_number` string, required: The primary phone number of the site contact.
      - `afterhours_phone_number` string, required: The afterhours phone number of the site contact.
      - `email` string, required: The email of the site contact.
      - `notify_method` 'call' | 'text' | 'email', required
      - `availability` ContactAvailabilityWindow[], nullable: Weekly windows, in the site's timezone, when this contact is the one to reach. The review panel pre-selects the first contact on the list whose window covers the current site-local time. Null or empty means no schedule: the contact is eligible at any time and is the fallback when no scheduled contact is on. On edit, omitting the key keeps the stored schedule; null or [] clears it.
        - `days` integer[], required: Days the window starts on, Monday-first: 0 = Monday ... 6 = Sunday.
        - `start` string, required: Start time, HH:MM on a 24-hour clock, site-local.
        - `end` string, required: End time, HH:MM on a 24-hour clock, site-local; '24:00' means midnight. An end earlier than start wraps past midnight into the next day.
      - `id` string, uuid, required: Unique identifier of the site contact.
      - `daily_summary_opt_out` boolean: True when this contact has opted out of the daily site summary e-mail (set by the unsubscribe link, not by the console). Does not affect alerts about the property.
  - `parent` ParentModel
    - `id` string, uuid, required: Unique client id
    - `company_name` string, required: Name of the client
    - `type` 'Monitoring Dealer' | 'End User' | 'Internal', required
    - `primary_contact_name` string, required: Name of the primary contact
    - `primary_contact_phone_number` string, nullable: Phone number of the primary contact. Absent for customer roles.
    - `primary_contact_email` string, required: Email of the primary contact
    - `notes` string, nullable: Notes about the client
  - `operator_evaluation` object, nullable: The disposition on this event, if any (actor='agent' marks an agent auto-dismissal). Central-staff callers also get an optional operator_name (the closing operator's First Last) on a human row whose operator still exists and has a name; absent otherwise, and never present for a customer-side role.
  - `interpretation_result` object, nullable: The AI verdict in the queue-payload shape (to_dict_frontend), incl. matched_exception, terminal_stage, gate_outcome and failsafe_sentinel (true on the fail-safe sweep's placeholder row, whose UNKNOWN is a stand-in, not a VLM verdict); null when no eval has landed. gate_outcome is {reason, detector_provider, detector_confidence} on an object_gate row and null otherwise — it is NOT coalesced, because null means 'no gate stop happened' rather than a missing value. reason is an open string: incident-intelligence owns the closed set and may add values, so never switch on it exhaustively. Also pre_floor_recommended_action (the VLM's own suggestion before policy: key absent when its provenance is unknown, null when the VLM made none) and assessment_context ({withheld_reason, withheld_provenance, detection_check}, written once at ingest; null on older rows). Both are stripped for every customer role (the curated roles and SITE_MANAGER); on a VLM fallback (backup) row only, the stripped assessment_context is replaced by the content-free marker {backup_row: true} so a customer-side composer can refuse to draft from that row's envelope text. For staff, a VLM fallback (backup) row's assessment_context.backup_assessment carries the backup model's own read in raw, except when that read names NORMAL, MAINTENANCE, DELIVERY or UNKNOWN: then raw is null, raw_withheld is true and raw_withheld_reason is backup_benign or backup_unknown (the stored row keeps the full read for audit). detection_stages and debug are deliberately not exposed.
  - `agent_action` object, nullable: Newest agent action for this event (voice-down lifecycle states), if any
  - `thread` object, nullable: Incident-thread summary when this event is part of a cross-camera thread; GET /threads/{id} returns the full timeline
  - `extended_clip_url` string, nullable: Presigned URL for the event's extended pre-roll ("Rewind") clip, top-level on the bundle rather than inside `event` because it is derived per request and has no column behind it. Null when the clip key does not derive one (demo/training/pre-cutover rows) or the presign failed. Presigned BLIND: a non-null URL does NOT mean the object exists — probe it (a 403/404 means the extended clip was never uploaded or has expired by the clips-extended/ 7-day lifecycle). Valid for the same 3600s as GET /events/{event_id}/clip-url, which returns a fresh pair.
  - `poster_url` string, nullable: Presigned URL for the event's alert-frame JPEG (the poster), top-level on the bundle rather than inside `event` because it is derived per request and has no column behind it. Derived from the clip key exactly as the History row's poster_url is. Null when the clip key does not derive one (demo/training/pre-cutover rows) or the presign failed. Presigned BLIND: a non-null URL does NOT mean the object exists — probe it. Valid 3600s. A null here does NOT predict POST /events/{event_id}/notify-owner with attach_snapshot=true: that path HEADs the object and answers 409 SNAPSHOT_UNAVAILABLE, which stays authoritative.
  - `continued_poster_url` string, nullable: Presigned URL for the continued clip's poster frame (JPEG), valid 3600s. Null unless the alarm has a committed continued clip (event.has_continued_clip) still inside the clip retention window, or when the presign failed. Presigned BLIND: the ingestor writes the poster best-effort, so the object may be absent; fall back on a load error.
  - `operator_alarm_capture` OperatorAlarmCaptureModel
    - `status` string, required: pending | claimed | captured | unavailable | failed
    - `reason` string, nullable: Why no clip, set only when status is unavailable or failed
  - `talk_down_available` boolean, nullable: Whether an operator's Talk Down would resolve a mapped, usable speaker for this event's camera, as of this read. A snapshot, not a delivery guarantee: the POST's 409 SPEAKER_NOT_MAPPED stays authoritative, and an absent value means 'unknown — treat as available'.
  - `talk_down_available_as_of` string, date-time, nullable: Database statement time of the query that read talk_down_available (UTC). Compare two snapshots of the same event by this value and keep the newer one. Null or absent means no database read time is available, including camera-less events.
  - `ai_announcement_available` boolean, nullable: Whether an operator-started AI announcement (POST /events/{event_id}/voice-agent-call) would resolve a mapped, usable, SIP-enabled speaker for this event's camera, as of this read. A snapshot, not a delivery guarantee: the POST's 409 SPEAKER_NOT_MAPPED stays authoritative, and an absent value means 'unknown - treat as available'.
  - `ai_announcement_available_as_of` string, date-time, nullable: Database statement time of the query that read ai_announcement_available (UTC); the same read as talk_down_available_as_of. Null or absent means no database read time is available, including camera-less events.
  - `voice_agent_call` object, nullable: The AI voice announcement placed for this event, if any: {id, created_at, status, speaker_label, connected_at, announced_at, outcome}. status is placing | connected | ended | failed; outcome is subject_left | subject_stayed | no_response | human_requested | unknown, or null while the call is live. Null when no announcement has been placed.
  - `ai_feedback_requested` boolean, nullable: Legacy, read-only: true when THIS event id falls in the deterministic 1-in-10 hash sample (app.services.ai_feedback_sampling.is_ai_feedback_sampled). NOT the ask rule: since v2 (2026-10-01) the AI-feedback question is shown, and ai_feedback_asked recorded, on SUSPICIOUS_MONITOR close-outs only, never on FALSE_ALARM or ESCALATE_*; no client reads this field for the ask. Kept for compatibility; removal is a follow-up. Absent/null on an older backend or a demo/pre-feature event.

## Other responses

- `422`: Validation Error

## Changes

- **2026-11-02** `35a2a6ebedda`: 2 info
  - added the optional property `site/anyOf[subschema #1: SitesModel]/alarm_license_issuer` to the response with the `200` status
  - added the optional property `site/anyOf[subschema #1: SitesModel]/alarm_license_number` to the response with the `200` status

[Operation history](https://skmtc.dev/verdun/apis/verdun-api/changes/events/:event_id/get.md)

---

[API](https://skmtc.dev/verdun/apis/verdun-api.md) - [All operations](https://skmtc.dev/verdun/apis/verdun-api/llms.txt) - [OpenAPI document](https://skmtc.dev/verdun/apis/verdun-api/revisions/00252d8227c1?raw)
