{"openapi":"3.1.0","info":{"title":"Verdun API","version":"1.0.0"},"paths":{"/health":{"get":{"summary":"Health Check","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/sites/{site_id}":{"get":{"tags":["sites"],"summary":"Get Site By Id","operationId":"get_site_by_id_sites__site_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteByIdResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["sites"],"summary":"Edit Site","operationId":"edit_site_sites__site_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["sites"],"summary":"Delete Site","operationId":"delete_site_sites__site_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/story":{"get":{"tags":["sites"],"summary":"One site's active alarms, oldest first, as the queue serves them","description":"The site story (site-queue plan, Phase 2): every queued alarm at this site in motion order, each row in exactly the shape of `GET /operator/events/all` (event, camera, site, parent, interpretation, thread, claim, first_viewed, presigned clip URL). Tenant-scoped through the site: 404 unknown or retired, 403 outside the caller's scope. `X-Total-Count` carries the site's full active count; the body is capped and keeps the OLDEST rows.","operationId":"get_site_story_sites__site_id__story_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":500,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get Site Story Sites  Site Id  Story Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/claim":{"post":{"tags":["sites"],"summary":"Mark every queued alarm at a site as being worked (soft claim) - RETIRED by default","description":"RETIRED (claim-lock step 3a, `SITE_CLAIM_ENABLED` off, the default): for every client, including an old tab, this takes, renews and clears NOTHING and answers `claims` with the live claims at the site as they are (read-only); claims already held drain by their 120 s TTL, an actively reviewed alarm renews through `POST /events/{id}/claim/renew`, and ingest no longer inherits a claim. With `SITE_CLAIM_ENABLED=true` it behaves as follows. Called when an operator opens a site's story, and as a heartbeat while they review it. Every queued alarm at the site that nobody holds takes the caller's claim in one statement, unless another operator already holds a live claim at the site (one holder per site); the caller's own claims are renewed. Claims another operator holds are never stolen and are returned as theirs. Same two-minute TTL, triage rule and owner exemption as `POST /events/{id}/claim`. Requires `review_label_alarms` and tenant scope (out of scope is 403, unknown is 404).","operationId":"claim_site_sites__site_id__claim_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/":{"post":{"tags":["sites"],"summary":"Create Site","operationId":"create_site_sites__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSiteRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/sites/{site_id}/contact":{"post":{"tags":["sites"],"summary":"Create Site Contact","operationId":"create_site_contact_sites__site_id__contact_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSiteContactRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSiteContactResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/contacts/{contact_id}":{"put":{"tags":["sites"],"summary":"Edit Site Contact","operationId":"edit_site_contact_sites_contacts__contact_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteContactRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteContactResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["sites"],"summary":"Delete Site Contact","operationId":"delete_site_contact_sites_contacts__contact_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSiteContactResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/contacts/order":{"put":{"tags":["sites"],"summary":"Reorder Site Contacts","operationId":"reorder_site_contacts_sites__site_id__contacts_order_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorderSiteContactsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorderSiteContactsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/stage":{"put":{"tags":["sites"],"summary":"Update Site Stage","operationId":"update_site_stage_sites__site_id__stage_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteStageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/onboarding-marks/{mark_key}":{"put":{"tags":["sites"],"summary":"Set one onboarding mark (staff tick or customer skip)","description":"Sets `mark_key` to now (UTC), stamped with the caller; setting it again refreshes it. `mark_key` is one of `setup_call`, `first_night`, `streaming_test` (staff ticks: owner, monitoring manager, operator, support) or `skip:setup_call`, `skip:instructions`, `skip:passcode`, `skip:speakers`, `skip:holidays` (customer skips: owner, monitoring manager, site manager within scope) or `completed` (the console's onboarding-complete latch: either set of roles). Any other key is 422. Returns the site's full projected map; who set a mark is never returned. Unknown or retired site is 404, and so is a site outside the caller's scope (a wrong role is 403). For streaming_test or first_night, send camera_ids with the exact tested or reviewed camera row IDs. A legacy write without coverage is accepted but cannot complete the streaming checklist.","operationId":"set_site_onboarding_mark_sites__site_id__onboarding_marks__mark_key__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"mark_key","in":"path","required":true,"schema":{"enum":["setup_call","first_night","streaming_test","skip:setup_call","skip:instructions","skip:passcode","skip:speakers","skip:holidays","completed"],"type":"string","title":"Mark Key"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SetOnboardingMarkRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingMarksResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["sites"],"summary":"Clear one onboarding mark","description":"Removes `mark_key`; removing an absent key succeeds (200). Same keys, roles, scope and statuses as the PUT. Returns the site's full projected map.","operationId":"clear_site_onboarding_mark_sites__site_id__onboarding_marks__mark_key__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"mark_key","in":"path","required":true,"schema":{"enum":["setup_call","first_night","streaming_test","skip:setup_call","skip:instructions","skip:passcode","skip:speakers","skip:holidays","completed"],"type":"string","title":"Mark Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingMarksResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/closed":{"put":{"tags":["sites"],"summary":"Update Site Closed","operationId":"update_site_closed_sites__site_id__closed_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteClosedRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/agent-config":{"put":{"tags":["sites"],"summary":"Update Site Agent Config","operationId":"update_site_agent_config_sites__site_id__agent_config_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteAgentConfigRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/incident-routing":{"put":{"tags":["sites"],"summary":"Update Site Incident Routing","operationId":"update_site_incident_routing_sites__site_id__incident_routing_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateIncidentRoutingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/operator-instructions":{"put":{"tags":["sites"],"summary":"Set or clear this site's operator instructions","description":"Free-text instructions shown to the HUMAN monitoring operator on every alarm at this site, and again at the escalation step. NEVER enters an AI prompt — that is what `agent_context` on the agent-config route is for. Requires the owner, monitoring_manager or site_manager role (a site_manager only within its own client and granted sites). A site id outside the caller's scope, or a retired one, answers 404 rather than 403: the status code must not tell a caller which of another client's site ids exist.","operationId":"update_site_operator_instructions_sites__site_id__operator_instructions_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteCameraOperatorInstructionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteCameraOperatorInstructionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/heightened-exceptions":{"put":{"tags":["sites"],"summary":"Update Site Heightened Exceptions","operationId":"update_site_heightened_exceptions_sites__site_id__heightened_exceptions_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateHeightenedExceptionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/agent-mode":{"put":{"tags":["sites"],"summary":"Set Site Agent Mode","operationId":"set_site_agent_mode_sites__site_id__agent_mode_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSiteAgentModeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSiteAgentModeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/live-view-only":{"put":{"tags":["sites"],"summary":"Set Site Live View Only","operationId":"set_site_live_view_only_sites__site_id__live_view_only_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteLiveViewOnlyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSiteLiveViewOnlyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/heightened-grounded-clear":{"put":{"tags":["sites"],"summary":"Set Site Heightened Grounded Clear","operationId":"set_site_heightened_grounded_clear_sites__site_id__heightened_grounded_clear_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteHeightenedGroundedClearRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSiteHeightenedGroundedClearResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/map":{"put":{"tags":["sites"],"summary":"Update Site Map","operationId":"update_site_map_sites__site_id__map_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteMapRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/floorplan":{"post":{"tags":["sites"],"summary":"Upload Site Floorplan","operationId":"upload_site_floorplan_sites__site_id__floorplan_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_site_floorplan_sites__site_id__floorplan_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/floorplan-url":{"get":{"tags":["sites"],"summary":"Get Site Floorplan Url","operationId":"get_site_floorplan_url_sites__site_id__floorplan_url_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteFloorplanUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/geocode":{"get":{"tags":["sites"],"summary":"Geocode Site","operationId":"geocode_site_sites__site_id__geocode_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/map-search":{"get":{"tags":["sites"],"summary":"Map Search","operationId":"map_search_sites__site_id__map_search_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"query","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":256,"description":"Free-text address to locate.","title":"Query"},"description":"Free-text address to locate."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MapSearchResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/schedule":{"get":{"tags":["sites"],"summary":"Get Site Schedule","operationId":"get_site_schedule_sites__site_id__schedule_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["sites"],"summary":"Update Site Schedule","operationId":"update_site_schedule_sites__site_id__schedule_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScheduleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/monitoring-schedule":{"get":{"tags":["sites"],"summary":"Get Site Monitoring Schedule","operationId":"get_site_monitoring_schedule_sites__site_id__monitoring_schedule_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetMonitoringScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["sites"],"summary":"Update Site Monitoring Schedule","operationId":"update_site_monitoring_schedule_sites__site_id__monitoring_schedule_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMonitoringScheduleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMonitoringScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/timezone":{"put":{"tags":["sites"],"summary":"Update Site Timezone","operationId":"update_site_timezone_sites__site_id__timezone_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteTimezoneRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteTimezoneResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/holidays":{"get":{"tags":["sites"],"summary":"Get Site Holidays","operationId":"get_site_holidays_sites__site_id__holidays_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteHolidaysResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["sites"],"summary":"Update Site Holidays","operationId":"update_site_holidays_sites__site_id__holidays_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteHolidaysRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteHolidaysResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/disarm-passcode":{"get":{"tags":["sites"],"summary":"Get Site Disarm Passcode","operationId":"get_site_disarm_passcode_sites__site_id__disarm_passcode_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteDisarmPasscodeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["sites"],"summary":"Generate Site Disarm Passcode","operationId":"generate_site_disarm_passcode_sites__site_id__disarm_passcode_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SetDisarmPasscodeRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmPasscodeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/disarm-settings":{"put":{"tags":["sites"],"summary":"Update Site Disarm Settings","operationId":"update_site_disarm_settings_sites__site_id__disarm_settings_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDisarmSettingsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmSettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/disarm":{"post":{"tags":["sites"],"summary":"Disarm Site","operationId":"disarm_site_sites__site_id__disarm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmSiteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/rearm":{"post":{"tags":["sites"],"summary":"Rearm Site","operationId":"rearm_site_sites__site_id__rearm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/early-disarm":{"get":{"tags":["sites"],"summary":"Get Site Early Disarm","description":"Each live camera's scheduled disarm and whether it can be disarmed early\nnow (0174). Any login the site is in scope for may read it.","operationId":"get_site_early_disarm_sites__site_id__early_disarm_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteEarlyDisarmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["sites"],"summary":"Early Disarm Cameras","description":"Turn cameras off ahead of their scheduled disarm (owner ask 2026-10-17).\n\n`review_label_alarms`, NOT `configure_schedules`: every operator who works\nthe queue may do this, and only this -- no more than 60 minutes ahead and\nending exactly at the schedule's own disarm, which the service enforces.\nWidening `configure_schedules` instead would have handed operators every\nschedule edit and a 120-minute disarm at any hour.","operationId":"early_disarm_cameras_sites__site_id__early_disarm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EarlyDisarmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteEarlyDisarmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/early-disarm/rearm":{"post":{"tags":["sites"],"summary":"Early Rearm Cameras","description":"Turn early-disarmed cameras back on now. Idempotent, never a 409.","operationId":"early_rearm_cameras_sites__site_id__early_disarm_rearm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EarlyRearmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteEarlyDisarmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/camera-pauses":{"get":{"tags":["sites"],"summary":"Get Site Camera Pauses","description":"Which cameras are paused now, when each ends, and where the site stands\nagainst the pause limits (operator ask 2026-10-28). Any login the site is in\nscope for may read it; staff names and notes are redacted for customer-side\nlogins.","operationId":"get_site_camera_pauses_sites__site_id__camera_pauses_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteCameraPausesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["sites"],"summary":"Pause Site Cameras","description":"Hold back NEW alarms on cameras for 5, 10 or 15 minutes (operator ask 2026-10-28).\n\n`review_label_alarms` AND the site in the caller's scope (the service checks\nit), the same keys as the early disarm: every operator who works the queue\nand a Parent Manager, not a customer account or location manager nor a\nread-only login. The limits, the whole-site rule and the pre-registration\nblock are enforced in the service under the site lock; going over them\nneeds an ops lead with `limit_override` and a note.","operationId":"pause_site_cameras_sites__site_id__camera_pauses_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraPauseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteCameraPausesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/camera-pauses/resume":{"post":{"tags":["sites"],"summary":"Resume Site Cameras","description":"End live pauses now. Idempotent, never a 409. The response says what each\nresumed camera is doing now (its schedule may have it off).","operationId":"resume_site_cameras_sites__site_id__camera_pauses_resume_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraResumeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteCameraPausesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/arm":{"post":{"tags":["sites"],"summary":"Arm Site For Testing","description":"Hold this site's cameras ON for a bounded test (0104).\n\nRefused with a 409 while a customer's disarm is live — their decision is\nnot cancelled by our install test.","operationId":"arm_site_for_testing_sites__site_id__arm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmSiteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/end-arm":{"post":{"tags":["sites"],"summary":"End Site Test Arm","operationId":"end_site_test_arm_sites__site_id__end_arm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArmStateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/disarms":{"get":{"tags":["sites"],"summary":"Get Site Disarms","operationId":"get_site_disarms_sites__site_id__disarms_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Page size, newest first.","default":50,"title":"Limit"},"description":"Page size, newest first."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Rows to skip.","default":0,"title":"Offset"},"description":"Rows to skip."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSiteDisarmsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/disarm-trusted":{"get":{"tags":["sites"],"summary":"Get Site Disarm Trusted","operationId":"get_site_disarm_trusted_sites__site_id__disarm_trusted_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmTrustedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["sites"],"summary":"Update Site Disarm Trusted","operationId":"update_site_disarm_trusted_sites__site_id__disarm_trusted_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDisarmTrustedRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmTrustedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/disarm-trusted/numbers":{"post":{"tags":["sites"],"summary":"Add Site Disarm Trusted Number","operationId":"add_site_disarm_trusted_number_sites__site_id__disarm_trusted_numbers_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddDisarmTrustedNumberRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmTrustedNumberModel"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/disarm-trusted/numbers/{number_id}":{"delete":{"tags":["sites"],"summary":"Delete Site Disarm Trusted Number","operationId":"delete_site_disarm_trusted_number_sites__site_id__disarm_trusted_numbers__number_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"number_id","in":"path","required":true,"schema":{"type":"string","title":"Number Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/stream":{"get":{"tags":["events"],"summary":"Event Stream","operationId":"event_stream_events_stream_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/events/operator-eval":{"get":{"tags":["events"],"summary":"Get All Operator Evaluated","operationId":"get_all_operator_evaluated_events_operator_eval_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Get All Operator Evaluated Events Operator Eval Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/events/history":{"get":{"tags":["events"],"summary":"Get Event History","description":"Reviewed-or-expired alarms, tenant-scoped, paginated and filterable.\n\nAll six filter params are optional and additive: absent means today's\nunfiltered behaviour, and `total` is the FILTERED count. The semantics\n(the half-open window on the effective sort column, the expired clamp on\nthe evaluation-only filters, the q field set) live in\nEventsService.history_filter_conditions.","operationId":"get_event_history_events_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"enum":["dismissed","archived","expired"],"type":"string","default":"dismissed","title":"State"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"order","in":"query","required":false,"schema":{"enum":["desc","asc"],"type":"string","default":"desc","title":"Order"}},{"name":"sort","in":"query","required":false,"schema":{"enum":["alarm_time","reviewed_at","closed_at"],"type":"string","description":"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.","default":"alarm_time","title":"Sort"},"description":"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."},{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"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`.","title":"From"},"description":"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`."},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"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`.","title":"To"},"description":"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`."},{"name":"disposition","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"},"maxItems":10},{"type":"null"}],"description":"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.","title":"Disposition"},"description":"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."},{"name":"actor","in":"query","required":false,"schema":{"anyOf":[{"enum":["operator","agent"],"type":"string"},{"type":"null"}],"description":"Who disposed the alarm: a human operator or the agent. Evaluated states only — silently ignored for state=expired.","title":"Actor"},"description":"Who disposed the alarm: a human operator or the agent. Evaluated states only — silently ignored for state=expired."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"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.","title":"Q"},"description":"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."},{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"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.","title":"Site Id"},"description":"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."},{"name":"audit","in":"query","required":false,"schema":{"anyOf":[{"enum":["needs","audited","overturned","disputed","pending_review"],"type":"string"},{"type":"null"}],"description":"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.","title":"Audit"},"description":"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."},{"name":"operator_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only alarms this operator decided (their evaluation's operator). Refused for curated customer roles. Evaluated states only — silently ignored for state=expired.","title":"Operator Id"},"description":"Only alarms this operator decided (their evaluation's operator). Refused for curated customer roles. Evaluated states only — silently ignored for state=expired."},{"name":"include_total","in":"query","required":false,"schema":{"type":"boolean","description":"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.","default":true,"title":"Include Total"},"description":"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."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetEventHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/starred":{"get":{"tags":["events"],"summary":"Get Starred Events","operationId":"get_starred_events_events_starred_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Get Starred Events Events Starred Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/events/{event_id}/star":{"put":{"tags":["events"],"summary":"Star Event","operationId":"star_event_events__event_id__star_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Star Event Events  Event Id  Star Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["events"],"summary":"Unstar Event","operationId":"unstar_event_events__event_id__star_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Unstar Event Events  Event Id  Star Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/demo/collections":{"get":{"tags":["events"],"summary":"Get Demo Collections","operationId":"get_demo_collections_events_demo_collections_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Get Demo Collections Events Demo Collections Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["events"],"summary":"Create Demo Collection","operationId":"create_demo_collection_events_demo_collections_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDemoCollectionRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Demo Collection Events Demo Collections Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/events/demo/collections/{collection_id}":{"patch":{"tags":["events"],"summary":"Rename Demo Collection","operationId":"rename_demo_collection_events_demo_collections__collection_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","title":"Collection Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameDemoCollectionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameDemoCollectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["events"],"summary":"Delete Demo Collection","operationId":"delete_demo_collection_events_demo_collections__collection_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","title":"Collection Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Demo Collection Events Demo Collections  Collection Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/demo/collections/{collection_id}/events/{event_id}":{"put":{"tags":["events"],"summary":"Add Demo Event To Collection","operationId":"add_demo_event_to_collection_events_demo_collections__collection_id__events__event_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","title":"Collection Id"}},{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Add Demo Event To Collection Events Demo Collections  Collection Id  Events  Event Id  Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["events"],"summary":"Remove Demo Event From Collection","operationId":"remove_demo_event_from_collection_events_demo_collections__collection_id__events__event_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"collection_id","in":"path","required":true,"schema":{"type":"string","title":"Collection Id"}},{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Remove Demo Event From Collection Events Demo Collections  Collection Id  Events  Event Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/demo/library":{"get":{"tags":["events"],"summary":"Get Demo Library","operationId":"get_demo_library_events_demo_library_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keyset cursor from the previous page; omit for the first page","title":"Cursor"},"description":"Keyset cursor from the previous page; omit for the first page"},{"name":"collection_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional named Demo collection UUID to browse directly","title":"Collection Id"},"description":"Optional named Demo collection UUID to browse directly"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetDemoLibraryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/demo":{"get":{"tags":["events"],"summary":"Get Demo Events","operationId":"get_demo_events_events_demo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Get Demo Events Events Demo Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["events"],"summary":"Create Demo Event Upload","operationId":"create_demo_event_upload_events_demo_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_demo_event_upload_events_demo_post"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Demo Event Upload Events Demo Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/events/demo/prompt-meta":{"get":{"tags":["events"],"summary":"Get Demo Prompt Meta","description":"Prompt-profile versions (demo + production) for the Demo tab indicator.\n\nProxies incident-intelligence GET /prompts/meta — ops-view cannot reach\nthe internal ALB directly, and that endpoint carries no auth of its own.","operationId":"get_demo_prompt_meta_events_demo_prompt_meta_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Demo Prompt Meta Events Demo Prompt Meta Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/events/demo/policy-runs":{"get":{"tags":["events"],"summary":"Get Sandbox Policy Runs","description":"Append-only Sandbox results scoped to one Demo clip.","operationId":"get_sandbox_policy_runs_events_demo_policy_runs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"query","required":true,"schema":{"type":"string","description":"Demo clip UUID whose Sandbox runs should be listed","title":"Event Id"},"description":"Demo clip UUID whose Sandbox runs should be listed"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get Sandbox Policy Runs Events Demo Policy Runs Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/demo/{event_id}/policy-test":{"post":{"tags":["events"],"summary":"Create Sandbox Policy Run","description":"Side-effect-free AI replay with snapshotted camera/policy inputs.","operationId":"create_sandbox_policy_run_events_demo__event_id__policy_test_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSandboxPolicyRunRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Sandbox Policy Run Events Demo  Event Id  Policy Test Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/demo/policy-runs/{run_id}":{"delete":{"tags":["events"],"summary":"Delete Sandbox Policy Run","description":"Remove one Sandbox policy-test result; the Demo clip is untouched.","operationId":"delete_sandbox_policy_run_events_demo_policy_runs__run_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/demo/{event_id}/policy-runs":{"delete":{"tags":["events"],"summary":"Clear Sandbox Policy Runs","description":"Clear every Sandbox policy-test result for a Demo clip (fresh slate for\nthe next customer demo); the clip and its AI history are left intact.","operationId":"clear_sandbox_policy_runs_events_demo__event_id__policy_runs_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClearPolicyRunsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/demo/{event_id}":{"delete":{"tags":["events"],"summary":"Delete Demo Event","operationId":"delete_demo_event_events_demo__event_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/claims":{"get":{"tags":["events"],"summary":"Live alarm claims in the caller's scope (claims-only read)","description":"What a console refetches on the `claims_changed` stream message instead of the whole queue. The live claims (`operator_id`, `operator_name`, `claimed_at`, `expires_at`, `expires_in_seconds`) on queued alarms in the caller's tenant scope, keyed by event id, with the queue's own membership predicate. An alarm with no live claim is absent, so for the alarms the request covers absence means free. Optional `site_id` and repeated `event_ids` (at most 500) narrow what is covered. Central staff only: customer-side callers get an empty map and never staff identity. `activity` (claim-lock R2) is a sibling map of escalation activity per event id (a valid intent and/or live calls, derived from the session records); it is NOT inside `claims` because activity outlives the claim. A keyed read (`event_ids`) covers every queued alarm named, an unfiltered or `site_id`-only read the alarms with a live claim; empty while `CLAIM_ESCALATION_ENABLED` is off. Requires claim participation (`review_label_alarms`, or, for central staff, any of `escalate_voice_down`, `escalate_dispatch`, `notify_property_owner`: whoever the claim refusal can refuse). Malformed ids are 404; also 404 while `CLAIM_CLEANUP_ENABLED` is off (read `claim_cleanup_enabled` on `GET /auth/me` first).","operationId":"list_claims_events_claims_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only alarms at this site.","title":"Site Id"},"description":"Only alarms at this site."},{"name":"event_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"},"maxItems":500},{"type":"null"}],"description":"Only these alarms (repeat the parameter); at most 500 IDs to bound a single explicit filter. This cap does not limit an unfiltered response's size.","title":"Event Ids"},"description":"Only these alarms (repeat the parameter); at most 500 IDs to bound a single explicit filter. This cap does not limit an unfiltered response's size."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListClaimsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/claims/next":{"post":{"tags":["events"],"summary":"Next alarm: claim the oldest eligible unheld alarm in one call","description":"One call hands the caller an unheld, unreserved alarm, ALREADY CLAIMED, in one transaction, chosen best-effort among the alarms the statement can see and lock. Order: any eligible alarm queued at least 45 s first (oldest of those first); below that the caller's own Focus alarms; then the oldest in the queue (`queued_at`, then id). Severity does not sort the hand-out. Never a live-held alarm (including the caller's own), an alarm validly reserved for another operator's Focus, or (with `CLAIM_ESCALATION_ENABLED` on) an alarm with a live call or announcement in progress (a snapshot read, not a commit-time guarantee). Two operators pressing at once get distinct alarms. The claim is an ordinary claim (120 s TTL, renewed by the review page, a fresh ownership generation). Manual clicks and Take over are unchanged.\n\n`request_id` identifies ONE press: retrying the same id (a lost response, a timeout) returns the RECORDED result with the alarm's current state (`replay: true`; `current` is `yours`, `taken_over`, `closed`, `lapsed`, `reserved`, `out_of_scope` or `unavailable`) and never selects a second alarm; the record is kept 24 hours. `status: none` carries a `reason` and grouped `counts` inside the caller's scope: `empty`, `all_held`, `all_reserved`, `all_in_session`, `contended` (others are picking right now), `scan_budget` (the 200-row scan stopped early; pick from the list) or `not_a_claimant` (owners never take claims). 503 `outcome: not_applied` (this call established that the `request_id` has no record AND its allocation was rolled back: nothing claimed, the id may be abandoned) or `unknown` (anything else: a failure before the record was looked up, while replaying it, or after the COMMIT may have been sent; keep the id and retry the SAME `request_id`, the server's record decides) with `Retry-After`. Requires `review_label_alarms` and central staff (403 otherwise). 404 as if the route did not exist while `CLAIM_NEXT_ENABLED` is off (read `claim_next_enabled` on `GET /auth/me`).","operationId":"claim_next_events_claims_next_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimNextRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimNextResponse"}}}},"503":{"description":"Deadline or database failure; see `outcome`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimNextUnavailableBody"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/events/open-alarms":{"get":{"tags":["events"],"summary":"Every open alarm in a scope, oldest first, stably paged (the rail's open list)","description":"Pages through EVERY open alarm (the queue's own membership) at one site, optionally narrowed to `camera_id`s, oldest first on `(created_at, id)` with an opaque `cursor`. Unlike `GET /operator/events/all` (newest first, offset-paged, 1,000 by default) an old urgent alarm can never sit beyond the first page, and closes or arrivals between pages cannot duplicate or skip rows. Page 1 pins `snapshot.as_of`; every page of the pass bounds membership by CREATION time (`created_at <= as_of`, immutable per alarm) and by current membership at that page's own read (still open, unexpired, queue-visible). An alarm that was open at `as_of` and still is when its page runs is therefore always returned, even if a late AI verdict re-stamps its `queued_at`. Alarms created after `as_of` are the push stream's and the next pass's. Late visibility: an alarm created at or before `as_of` that only becomes visible to the queue after it (a late verdict or failsafe flip) is returned if its key is still ahead of the cursor, otherwise it is the push stream's and the next pass's. `status` is `more` (continue), `complete` (last page; complete as of `as_of` under these rules) or `truncated` (the pass ceiling was hit; `omitted` is the count of eligible alarms after the last returned one at that read, at least that many when `omitted_capped`). `counts` are PAGE-ONE observations: the scalar totals (`scope_total`, `site_total`), the exact overdue counts by `age_marks` and `oldest_queued_at` repeat unchanged from page one on every later page, while `by_camera` is returned on page one only (null afterwards). The rows returned over a pass can exceed the totals (late-visible alarms) or fall short of them (closes). Each row carries `queued_at` (age = `snapshot.server_time - queued_at`) and its live `claim`. A 200 is always a true statement; anything else (404 unknown or out-of-scope site or any invisible camera, one answer; 403 customer roles; 422 `CURSOR_INVALID` for an invalid cursor, 422 `CURSOR_EXPIRED` for an expired one (restart from page 1); 503 deadline or database failure) is UNKNOWN, never an empty list. Central staff only, tenant-scoped in SQL on every page. Read-only: it stamps no first view and takes no claim. Capability: `open_alarms_paged_read` on `GET /auth/me`.","operationId":"get_open_alarms_events_open_alarms_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","description":"The site","title":"Site Id"},"description":"The site"},{"name":"camera_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":50},{"type":"null"}],"description":"Narrow to these cameras (repeat the parameter; at most 50). Absent = the whole site.","title":"Camera Id"},"description":"Narrow to these cameras (repeat the parameter; at most 50). Absent = the whole site."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Rows per page","default":200,"title":"Limit"},"description":"Rows per page"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"description":"`next_cursor` from the previous page; absent = a new pass. An invalid cursor is 422 `CURSOR_INVALID`, an expired one 422 `CURSOR_EXPIRED`","title":"Cursor"},"description":"`next_cursor` from the previous page; absent = a new pass. An invalid cursor is 422 `CURSOR_INVALID`, an expired one 422 `CURSOR_EXPIRED`"},{"name":"age_marks","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"description":"Page 1: comma list of 1 to 3 overdue marks in seconds for `counts.older_than` (default `30,60`). Later pages retain page one's counters; this parameter may be omitted and does not change them.","title":"Age Marks"},"description":"Page 1: comma list of 1 to 3 overdue marks in seconds for `counts.older_than` (default `30,60`). Later pages retain page one's counters; this parameter may be omitted and does not change them."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAlarmsResponse"}}}},"503":{"description":"Deadline or database failure (code PAGED_READ_UNAVAILABLE). Retry; never an empty list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedReadErrorBody"}}}},"422":{"description":"`CURSOR_INVALID` for an invalid cursor (malformed, forged, or not belonging to this request), `CURSOR_EXPIRED` for an expired one (restart from page 1), or `INVALID_PARAMETER`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedReadErrorBody"}}}}}}},"/events/{event_id}":{"get":{"tags":["events"],"summary":"Get Event By Id","operationId":"get_event_by_id_events__event_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetEventByIdResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["events"],"summary":"Delete Event","operationId":"delete_event_events__event_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/ai-eval":{"get":{"tags":["events"],"summary":"Get Event Ai Eval","operationId":"get_event_ai_eval_events__event_id__ai_eval_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Event Ai Eval Events  Event Id  Ai Eval Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/clip-url":{"get":{"tags":["events"],"summary":"Get Event Clip Url","description":"Freshly presign the event's clip (1h). The clip object is HEAD-checked first: 404 when the event has no clip key, 409 `CLIP_UNAVAILABLE` when the key exists but the object is gone from storage (the `clips/` lifecycle). A starred event plays from its retained `starred-clips/` copy when it has one. `continued_clip_url` (same expiry) is set only once the ingestor has committed a continued clip for the alarm; `extended_clip_url` is presigned blind.","operationId":"get_event_clip_url_events__event_id__clip_url_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetEventClipUrlResponse"}}}},"409":{"description":"The clip object is gone from storage (code CLIP_UNAVAILABLE)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/captures":{"get":{"tags":["events"],"summary":"After-voice-down recordings on this alarm","description":"Every 30 s recording of the camera started when a voice down played on this alarm (AI announcement, Live Talk, Verkada talk-down, clip talk-down), with its status and, when unavailable, the reason. `clip_url` is presigned for 1h and present only while the object exists (`available`); an object past the `clips/` lifecycle answers `available:false, reason:'expired'`. A starred alarm plays from its retained copy. Tenant scope and visibility exactly like `clip-url`; the ingestor's diagnostics are never returned.","operationId":"get_event_captures_events__event_id__captures_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetEventCapturesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/snapshot":{"get":{"tags":["events"],"summary":"The alarm's picture: the poster, else a frame cut from the clip","description":"Returns a presigned URL for the event's alert picture: one frame cut from the clip where the AI boxed a person or vehicle; else the ingestor's poster when it exists; else a frame cut at the alarm's trigger moment (server-chosen offsets), stored under `snapshots/` so later calls are a HEAD and a presign. The first call for an event can take a few seconds. Tenant scope and visibility exactly like `clip-url`. 409 SNAPSHOT_UNAVAILABLE when there is no poster and no frame can be cut (no clip, clip expired, demo/training rows).","operationId":"get_event_snapshot_events__event_id__snapshot_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventSnapshotResponse"}}}},"409":{"description":"No picture for this event (code SNAPSHOT_UNAVAILABLE)"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/snapshot-frame":{"post":{"tags":["events"],"summary":"Cut a snapshot frame from the event's clip at an operator-picked offset","description":"Extracts one JPEG from the event's clip at `offset_ms` (server-side, ffmpeg), stores it beside the clip under a key derived from (clip key, offset), and returns a presigned preview. Idempotent: the same offset is a cache hit. The operator then sends the text with `attach_snapshot: true` and `snapshot_offset_ms` set to the returned `offset_ms`; the send path re-derives the key and proves the object exists before anything is written or sent. Requires `notify_property_owner` (the only consumer of a picked frame is the text that carries it) and tenant scope exactly like `clip-url` (out of scope is 403). 409 SNAPSHOT_UNAVAILABLE when the event has no clip, its clip key derives no snapshot key (demo/training rows), the clip object is missing, or there is no frame at that offset (past the end of the clip).","operationId":"create_snapshot_frame_events__event_id__snapshot_frame_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotFrameRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotFrameResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/claim":{"get":{"tags":["events"],"summary":"Read who is working a queued alarm (no claim is taken)","description":"Read-only twin of `POST /events/{id}/claim`, for a console opening an alarm's deep link before its queue snapshot has loaded: it never takes, renews or clears a claim and sends no nudge. Same body as the POST: the live claim (`operator_id`, `operator_name`, `claimed_at`, `expires_at`, `expires_in_seconds`) whoever holds it, or `claim: null` when nobody does (free, or the alarm has left the active queue). Customer-side callers always get null and never staff identity. Requires claim participation (`review_label_alarms`, or, for central staff, any of `escalate_voice_down`, `escalate_dispatch`, `notify_property_owner`: whoever the claim refusal can refuse) and tenant scope (out of scope is 403, unknown or malformed id is 404). Also 404 while `CLAIM_CLEANUP_ENABLED` is off; consoles read `claim_cleanup_enabled` on `GET /auth/me` first, and treat any error here as unknown, never as free. `activity` (claim-lock R2) is the escalation activity on this alarm (a valid intent and/or live calls), present even when `claim` is null because activity outlives the claim; the `activity` key is omitted while `CLAIM_ESCALATION_ENABLED` is off.","operationId":"get_claim_status_events__event_id__claim_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["events"],"summary":"Mark a queued alarm as being worked (soft claim)","description":"Called when an operator opens an alarm's review panel. The first operator to open it holds the claim for two minutes or until the alarm is triaged, whichever is shorter; other consoles show an \"Operator working\" badge on it and grey it out for other operators and monitoring managers (owners see the badge but are not greyed out). Reopening does not extend a claim. Monitoring managers take claims like operators; owners never do. Customer-side callers neither take claims nor receive claimant identity. Always 200 with the live claim for central staff, whoever holds it, or null; opening never blocks anyone (with `CLAIM_REFUSAL_ENABLED` on, what a claim does is refuse ANOTHER operator's close and outbound actions, see `POST /events/{id}/claim/take-over`). With claim cleanup enabled, alarms outside the active queue return null and are not claimed. This event-level acquire endpoint is separate from the renew-only endpoint `POST /events/{id}/claim/renew`; the whole-site claim (`POST /sites/{id}/claim`) is retired by default (`SITE_CLAIM_ENABLED`). Requires `review_label_alarms` and tenant scope (out of scope is 403, unknown is 404). Unlike renew, take-over and the two claim reads, acquire is NOT widened to the outbound-action permissions: it is the review-panel open, and a refused operator without `review_label_alarms` becomes the holder through take-over.","operationId":"claim_event_events__event_id__claim_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"entry","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"description":"Logged only: how this acquire was reached (`manual`, `advance`, `review_mount`, `deep_link`); any other value is logged as `other`. It never changes behaviour. Absent from an older console.","title":"Entry"},"description":"Logged only: how this acquire was reached (`manual`, `advance`, `review_mount`, `deep_link`); any other value is logged as `other`. It never changes behaviour. Absent from an older console."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/claim/renew":{"post":{"tags":["events"],"summary":"Renew your own claim on a queued alarm (renew-only heartbeat)","description":"The active-review heartbeat. RENEW-ONLY by construction: it restarts the TTL if and only if the caller still holds a live claim on a queued alarm, and it never acquires a free, lapsed or foreign claim, whatever the configuration. Always answers `renewed` (true: `claim` is the renewed claim; false: `claim` is the live claim of whoever holds it now, or null when the alarm is free, lapsed, closed or not on the active queue). A 404 may mean this route is unavailable on an older or disabled task (`CLAIM_CLEANUP_ENABLED` off), or that the event ID is unknown or malformed. Treat every error as unknown, never as renewed, and never fall back to acquire. Read `claim_cleanup_enabled` on `GET /auth/me` first. Customer-side callers get null and never staff identity. Requires claim participation (`review_label_alarms`, or, for central staff, any of `escalate_voice_down`, `escalate_dispatch`, `notify_property_owner`: whoever the claim refusal can refuse) and tenant scope (out of scope is 403, unknown or malformed id is 404).","operationId":"renew_claim_events__event_id__claim_renew_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/claim/take-over":{"post":{"tags":["events"],"summary":"Take over an alarm another operator is working","description":"Claim-lock step 3a, ONE HOLDER PER ALARM. The holder works the alarm: a close (`POST /eval/operator/{id}` 409 `claim_held`, a per-item 423 in the bulk close) and the operator-started Voice down, AI voice announcement, call to the owner, dispatch call and event-scoped Live Talk are refused 409 `claim_held` while ANOTHER operator holds a live claim; anyone else uses this first. Open to every central-staff operator (owners included); not a priority tier. Body `{expected_holder_id, expected_claimed_at}` is the claim the console showed (echo `operator_id` and `claimed_at` from it). Under the event row lock, if that is still the live claim the claim moves to the caller (200 with the new claim, consoles are nudged so the displaced holder learns at once); otherwise nothing moves: 409 `claim_changed` with the CURRENT claim (or null when nobody holds it now), and the console reconfirms. Never retry a `claim_changed` automatically. Two operators taking over at once: exactly one wins. A closed, expired or non-queued alarm is a plain 409. Taking over a claim the caller already holds is 200 and writes nothing. Take over transfers no draft: the displaced operator's notes stay on their own screen. Reserved shape (Focus, `expected_focus_id` with no `expected_claimed_at`): a live claim appearing meanwhile is 409 `claim_changed` with that claim, a reservation that is gone or changed is 409 `claim_changed` with `claim` null (and the current `reservation`, if any), and Focus being off is 404 (after the scope and staff checks below); a body that names neither `expected_claimed_at` nor `expected_focus_id` is 422 (as is any schema-invalid body), except that with Focus on an alarm reserved for another operator's focus with no live claim answers 409 `claim_changed` with the current `reservation`. Requires claim participation (`review_label_alarms`, or, for central staff, any of `escalate_voice_down`, `escalate_dispatch`, `notify_property_owner`: whoever the claim refusal can refuse) and tenant scope (out of scope is 403, unknown or malformed id is 404); non-staff callers get 403. 404 while `CLAIM_REFUSAL_ENABLED` is off (or the api predates this): read `claim_refusal_enabled` on `GET /auth/me` first.","operationId":"take_over_claim_events__event_id__claim_take_over_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TakeOverClaimRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/claim/acting":{"post":{"tags":["events"],"summary":"Record that the holder opened Voice Down or picked Escalate - dispatch","description":"Claim-lock R2, the escalation record. The operator HOLDING this alarm tells the other consoles they are about to escalate, so they show \"Escalation in progress\". Body `{action: voice_down|dispatch, generation, action_token}`: `generation` is `claim.generation` (the ownership generation, which changes when the holder changes and never on a renewal), `action_token` a client nonce minted once per panel open. There is no expiry: the intent lasts while the alarm is this operator's and is cleared by `.../acting/cancel`, a close, a claim lapse or a take-over. Under the event row lock: a closed, expired or unqueued alarm is a plain 409; no live claim is 409 `claim_changed` (claim null); another operator's live claim is 409 `claim_held`; a stale `generation` is 409 `claim_changed` with the current claim; the same token and generation again is a 200 replay that writes nothing, and so is the token of an action that already finished (cancelled, completed, closed, lapsed or taken over) or was replaced by a newer pick (the row remembers the newest 32 per generation): `recorded: false, replay: true`, it is never revived and never overwrites the newer pick; a new pick needs a new token. Requires claim participation, central staff, and the action's own permission (`escalate_voice_down` / `escalate_dispatch`). 404 while `CLAIM_ESCALATION_ENABLED` is off (or site claims are on, or claim cleanup is off): read `claim_escalation_enabled` on `GET /auth/me` first.","operationId":"set_claim_acting_events__event_id__claim_acting_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetClaimActingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimActingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/claim/acting/cancel":{"post":{"tags":["events"],"summary":"Clear the escalation record (compare-and-clear)","description":"Ends the caller's own escalation intent when, and only when, the ownership `generation` AND the `action_token` still match: a stale generation (another holder, a re-acquire) is 409 `claim_changed` / `claim_held`; a token this alarm has not seen that is not the current one is 409 `activity_changed` with the current `claim` and `activity`, and nothing is cleared. A token that already finished or was replaced by a newer pick (the row remembers the newest 32 of its generation) is `already_clear`, nothing written. 200 outcomes: `cleared`, `already_clear` (a retried or late cancel) and `alarm_closed` (the close already cleared it). `reason` is `cancelled`, `panel_closed` or `completed`. 404 while `CLAIM_ESCALATION_ENABLED` is off.","operationId":"cancel_claim_acting_events__event_id__claim_acting_cancel_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelClaimActingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimActingCancelResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/missed-person-boxes":{"get":{"tags":["events"],"summary":"List an event's operator-drawn missed-person boxes","description":"Every active (non-deleted) box on this event, oldest first -- every staff caller's boxes, not just the requesting operator's. Staff-only (`require_central_staff` + `review_label_alarms`) and tenant-scoped (`ensure_event_in_scope`: out of scope is 403, unknown is 404).","operationId":"list_operator_miss_boxes_events__event_id__missed_person_boxes_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListOperatorMissBoxesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["events"],"summary":"Draw a box around a person the AI missed","description":"Saves one box, pinned to one frame time, with full provenance snapshotted from the event at save time. Staff-only and tenant-scoped, same gates as the list route. class_label is 'person' only in phase 1.","operationId":"create_operator_miss_box_events__event_id__missed_person_boxes_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOperatorMissBoxRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorMissBoxModel"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/missed-person-boxes/{box_id}":{"patch":{"tags":["events"],"summary":"Move or resize an operator-drawn missed-person box","description":"Bbox only -- the frame a box is pinned to is not editable; delete and redraw for a different frame. Only the box's own author, or an ops lead (owner/monitoring_manager), may edit it.","operationId":"update_operator_miss_box_events__event_id__missed_person_boxes__box_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"box_id","in":"path","required":true,"schema":{"type":"string","title":"Box Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOperatorMissBoxRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorMissBoxModel"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["events"],"summary":"Delete an operator-drawn missed-person box","description":"Soft delete -- excluded from lists and exported annotations from this moment on, but retained in exported camera history (it is the historical fact a later fine-tune export run's fold resolution needs; see the design). Deleting a box never affects any camera's fold -- there is no fold state in this database at all. Only the box's own author, or an ops lead, may delete it.","operationId":"delete_operator_miss_box_events__event_id__missed_person_boxes__box_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"box_id","in":"path","required":true,"schema":{"type":"string","title":"Box Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteOperatorMissBoxResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/viewed":{"post":{"tags":["events"],"summary":"Record that a central-staff viewer opened this queued alarm","description":"Called when an alarm's review page opens. Stamps the FIRST open only and never overwrites it; every later call answers with the original viewer. Feeds the \"By site\" queue's \"+N NEW\" badge and its wait clock, which count from the oldest clip nobody has opened. Owners and monitoring managers count as viewers (unlike claims). Advisory: nothing here gates a disposition. Customer-side callers stamp nothing and receive null. Requires `review_label_alarms` and tenant scope (out of scope is 403, unknown is 404).","operationId":"mark_event_viewed_events__event_id__viewed_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarkViewedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/operator-instructions":{"get":{"tags":["events"],"summary":"Operator instructions that apply to this event","description":"Resolves the account, site and camera operator instructions for one event, server-side. Read-only context for the human reviewing the alarm: visible to every confirmed viewer of the event (operators, read_only, a scoped account_manager or location_manager) and gated by tenant scope rather than by role, the same way `ai-eval` and `clip-url` are. `account` is null for a site with no account and for a retired one; `site` and `camera` are always present. DEMO events answer 404 — they are camera-less by construction and the review panel never shows one, so there is no null-camera shape to handle. An out-of-scope event id answers 404, not 403, so this route is not an event-id oracle.","operationId":"get_event_operator_instructions_events__event_id__operator_instructions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventOperatorInstructionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/operator-instructions/ack":{"post":{"tags":["events"],"summary":"Acknowledge (\"Got it\") changed operator instructions on this event","description":"Acks each level at the `updated_at` the GET returned. An item that is not the level's CURRENT id and version (the text changed again) is ignored silently. Returns the GET's payload; same gate and 404 collapse as the GET.","operationId":"ack_event_operator_instructions_events__event_id__operator_instructions_ack_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AckOperatorInstructionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventOperatorInstructionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/around":{"get":{"tags":["events"],"summary":"Around this alarm: the closed alarms nearby and the cameras that fire with this one","description":"The context rail for one alarm. `anchor` is the alarm; `graph.neighbours` are the cameras that co-fire with its camera over 30 days (cached per site per hour; `graph.status` says ready / stale / unavailable and the rail still works without it); `items` are the CLOSED alarms in the window `before_minutes` before to `after_minutes` after the anchor, newest first, as three kinds: `reviewed` (an operator closed it), `filtered` (stopped before AI review by a pre-processing gate, DeepAlert and its confirmers) and `agent_closed`. `scope=related` (default) draws from the anchor's camera and its neighbours; `scope=site` from every camera at the site. `counts` always covers the whole window for the related cameras and the rest of the site, so the console can say how much is not shown. Open alarms are not returned (they are in the queue, and new ones arrive over the push stream). Open an item with `GET /events/{event_id}` and `/clip-url`. Central staff only (`require_central_staff`: owner, monitoring manager, operator, support, read-only), and tenant-scoped like every per-event read: an alarm outside the caller's scope and an unknown id are both 404, and only cameras the caller may see are read. `ALARM_CONTEXT_ENABLED` off answers 200 `status: \"disabled\"` with nothing else, before any alarm-context database work (authentication and the caller's scope load still run): show no rail. A feed or counts query that fails or hits its 3 s deadline answers 200 `status: \"degraded\"` with no items: say the rail is unavailable and retry later. This is an optional-rail fallback, not an end-to-end latency guarantee.","operationId":"get_alarm_context_events__event_id__around_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}},{"name":"before_minutes","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Minutes before the alarm to read","default":15,"title":"Before Minutes"},"description":"Minutes before the alarm to read"},{"name":"after_minutes","in":"query","required":false,"schema":{"type":"integer","maximum":15,"minimum":0,"description":"Minutes after the alarm to read","default":5,"title":"After Minutes"},"description":"Minutes after the alarm to read"},{"name":"scope","in":"query","required":false,"schema":{"enum":["related","site"],"type":"string","description":"`related`: this camera and its neighbours. `site`: every camera at the site","default":"related","title":"Scope"},"description":"`related`: this camera and its neighbours. `site`: every camera at the site"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Most items to return","default":100,"title":"Limit"},"description":"Most items to return"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlarmContextResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/show":{"get":{"tags":["events"],"summary":"Show: every clip around this alarm across chosen cameras, stably paged","description":"The Show scene for one alarm (`event_id` is the anchor): every clip at the anchor's site in a window, open queue alarms and closed context (`reviewed`, `filtered`, `agent_closed`, classified exactly like `GET /events/{id}/around`) merged in ONE oldest-first pass on `(created_at, id)`, so an alarm that closes between pages is returned once. `until=anchor` (default): window = anchor time minus `before_seconds` to plus `after_seconds`; `until=now`: the last `before_seconds` up to the snapshot (`after_seconds` ignored). `camera_id` (repeatable, at most 50, at the anchor's site) narrows the scene; the anchor's camera is always included; absent = every camera at the site. Membership, paging and the error rules mirror `GET /events/open-alarms`: every page bounds clips by creation time (`created_at <= snapshot.as_of`) and by current membership at that page's read (open, or closed context); a clip created at or before `as_of` that only becomes visible or closed after it is returned if its key is still ahead of the cursor; `counts` (`total`, `by_state`, `by_camera`) are page-one observations (`total` and `by_state` repeat unchanged from page one on later pages; `by_camera` is returned on page one only) and the rows returned can differ from them; on `truncated`, `omitted` is the eligible clips after the last returned one at that read (a lower bound when `omitted_capped`). A 200 is always a true statement, anything else is UNKNOWN, never an empty scene. Items carry media REFERENCES only (`media.clip_url_path`, `media.snapshot_path`): no presign, no S3 call, no video. Central staff only; the anchor goes through the same scope check as `/around` (out of scope, demo and unknown are one 404) and every row is tenant-scoped in SQL on every page. Read-only: Show never acquires, claims or marks viewed what it displays. Capability: `alarm_show_read` on `GET /auth/me`.","operationId":"get_alarm_show_events__event_id__show_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}},{"name":"camera_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":50},{"type":"null"}],"description":"Narrow to these cameras at the anchor's site (repeatable)","title":"Camera Id"},"description":"Narrow to these cameras at the anchor's site (repeatable)"},{"name":"before_seconds","in":"query","required":false,"schema":{"type":"integer","maximum":900,"minimum":1,"description":"Seconds before the anchor (or before the snapshot when until=now)","default":300,"title":"Before Seconds"},"description":"Seconds before the anchor (or before the snapshot when until=now)"},{"name":"after_seconds","in":"query","required":false,"schema":{"type":"integer","maximum":900,"minimum":0,"description":"Seconds after the anchor (ignored when until=now)","default":0,"title":"After Seconds"},"description":"Seconds after the anchor (ignored when until=now)"},{"name":"until","in":"query","required":false,"schema":{"enum":["anchor","now"],"type":"string","description":"`anchor`: window around the alarm. `now`: the last `before_seconds` up to the snapshot","default":"anchor","title":"Until"},"description":"`anchor`: window around the alarm. `now`: the last `before_seconds` up to the snapshot"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"description":"Rows per page","default":100,"title":"Limit"},"description":"Rows per page"},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"description":"`next_cursor` from the previous page; absent = a new pass. An invalid cursor is 422 `CURSOR_INVALID`, an expired one 422 `CURSOR_EXPIRED`","title":"Cursor"},"description":"`next_cursor` from the previous page; absent = a new pass. An invalid cursor is 422 `CURSOR_INVALID`, an expired one 422 `CURSOR_EXPIRED`"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlarmShowResponse"}}}},"503":{"description":"Deadline or database failure (code PAGED_READ_UNAVAILABLE). Retry; never an empty list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedReadErrorBody"}}}},"422":{"description":"`CURSOR_INVALID` for an invalid cursor (malformed, forged, or not belonging to this request), `CURSOR_EXPIRED` for an expired one (restart from page 1), or `INVALID_PARAMETER`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedReadErrorBody"}}}}}}},"/events/{stream_id}":{"post":{"tags":["events"],"summary":"Create Event","operationId":"create_event_events__stream_id__post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"stream_id","in":"path","required":true,"schema":{"type":"string","title":"Stream Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/continued-clip/offer":{"post":{"tags":["events"],"summary":"Offer Continued Clip","description":"Ingestor only (X-API-Key). Whether to cut and upload a continued clip for this alarm. Declines a demo event, an event whose clip derives no continued key, an alarm already continued, and one the AI agent has closed; accepts while the verdict is pending. 404 for an unknown event.","operationId":"offer_continued_clip_events__event_id__continued_clip_offer_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContinuedClipOfferResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/continued-clip":{"post":{"tags":["events"],"summary":"Commit Continued Clip","description":"Ingestor only (X-API-Key). Record the uploaded continued clip on the alarm and refresh its card on open consoles. `s3_key` must equal the event's derived clips-continued/ key (422 otherwise). Idempotent: a repeat commit is 200 and changes nothing. 404 for an unknown event.","operationId":"commit_continued_clip_events__event_id__continued_clip_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitContinuedClipRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommitContinuedClipResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/app-branding":{"get":{"tags":["parents"],"summary":"Get App Branding","operationId":"get_app_branding_parents__parent_id__app_branding_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppBrandingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["parents"],"summary":"Save App Branding","operationId":"save_app_branding_parents__parent_id__app_branding_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Parent Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_save_app_branding_parents__parent_id__app_branding_put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppBrandingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/":{"get":{"tags":["parents"],"summary":"Get All Parents","operationId":"get_all_parents_parents__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get All Parents Parents  Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["parents"],"summary":"Create Parent","operationId":"create_parent_parents__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateParentRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateParentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/parents/{parent_id}":{"get":{"tags":["parents"],"summary":"Get Parent By Id","operationId":"get_parent_by_id_parents__parent_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetParentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["parents"],"summary":"Edit Parent","operationId":"edit_parent_parents__parent_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditParentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditParentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["parents"],"summary":"Delete Parent","operationId":"delete_parent_parents__parent_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteClientResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/sites":{"get":{"tags":["parents"],"summary":"Get All Sites By Parent","operationId":"get_all_sites_by_parent_parents__parent_id__sites_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetParentSitesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/accounts":{"get":{"tags":["parents"],"summary":"Get All Accounts By Parent","operationId":"get_all_accounts_by_parent_parents__parent_id__accounts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetParentAccountsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["parents"],"summary":"Create Account","operationId":"create_account_parents__parent_id__accounts_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Parent Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccountRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAccountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/disarm-number":{"post":{"tags":["parents"],"summary":"Purchase Disarm Number","operationId":"purchase_disarm_number_parents__parent_id__disarm_number_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PurchaseDisarmNumberRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmNumberResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["parents"],"summary":"Release Disarm Number","operationId":"release_disarm_number_parents__parent_id__disarm_number_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReleaseDisarmNumberResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/disarm-number/voice":{"get":{"tags":["parents"],"summary":"Get Disarm Voice Status","operationId":"get_disarm_voice_status_parents__parent_id__disarm_number_voice_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmVoiceStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/disarm-number/voice/connect":{"post":{"tags":["parents"],"summary":"Connect Disarm Voice","operationId":"connect_disarm_voice_parents__parent_id__disarm_number_voice_connect_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmVoiceStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/disarms":{"get":{"tags":["parents"],"summary":"Get Parent Disarms","operationId":"get_parent_disarms_parents__parent_id__disarms_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}},{"name":"active","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Active"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListParentDisarmsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/cameras":{"get":{"tags":["parents"],"summary":"Get All Cameras For Parent","operationId":"get_all_cameras_for_parent_parents__parent_id__cameras_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GetParentCamerasIngestorResponse"},"title":"Response Get All Cameras For Parent Parents  Parent Id  Cameras Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/live-intents":{"get":{"tags":["parents"],"summary":"Get Live Intents For Parent","operationId":"get_live_intents_for_parent_parents__parent_id__live_intents_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GetLiveIntentsResponse"},"title":"Response Get Live Intents For Parent Parents  Parent Id  Live Intents Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/post-action-captures":{"get":{"tags":["parents"],"summary":"Get Pending Captures For Parent","operationId":"get_pending_captures_for_parent_parents__parent_id__post_action_captures_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}},{"name":"shard_index","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Serving ingestor shard index.","default":0,"title":"Shard Index"},"description":"Serving ingestor shard index."},{"name":"shard_count","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Total ingestor shards; SHA256 camera-id assignment.","default":1,"title":"Shard Count"},"description":"Total ingestor shards; SHA256 camera-id assignment."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PendingCaptureResponse"},"title":"Response Get Pending Captures For Parent Parents  Parent Id  Post Action Captures Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/events/all":{"get":{"tags":["operator"],"summary":"Get All Events For Operator","operationId":"get_all_events_for_operator_operator_events_all_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":1000,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get All Events For Operator Operator Events All Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/":{"get":{"tags":["operator"],"summary":"Get All Operators","operationId":"get_all_operators_operator__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAllOperatorsResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["operator"],"summary":"Create Operator","operationId":"create_operator_operator__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOperatorRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOperatorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/operator/{operator_id}":{"get":{"tags":["operator"],"summary":"Get Operator By Id","operationId":"get_operator_by_id_operator__operator_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOperatorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["operator"],"summary":"Edit Operator","operationId":"edit_operator_operator__operator_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditOperatorRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditOperatorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["operator"],"summary":"Delete Operator","operationId":"delete_operator_operator__operator_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteOperatorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/online":{"post":{"tags":["operator"],"summary":"Signal Online Heartbeat","operationId":"signal_online_heartbeat_operator_online_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Signal Online Heartbeat Operator Online Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/operator/{operator_id}/permissions":{"patch":{"tags":["operator"],"summary":"Patch Operator Permissions","operationId":"patch_operator_permissions_operator__operator_id__permissions_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchOperatorPermissionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditOperatorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/{operator_id}/coverage-groups":{"put":{"tags":["operator"],"summary":"Set Operator Coverage Groups","description":"Replace this operator's standing coverage-group membership.\n\nCOVERAGE GROUPS CONFER SCOPE, NEVER PERMISSION. Membership changes which\nSITES the operator can see -- every site the group's grants cover, off-shift\nincluded -- and never what they may do; their capabilities remain exactly\ntheir role's. Their own parent/site grants are untouched and keep working as\nan additive exception list.\n\nReplace-all: the list sent IS the membership, and `[]` clears it. Refusals:\n409 for a structurally scoped target (site_manager / account_manager /\nlocation_manager, whose\nscope is derived) or an unconfined one (owner / monitoring_manager -- the row would be inert);\n403 for a confined caller; 404 for an unknown operator or group.\n\nChanges reach an open SSE stream within the subscriber scope refresh window\n(30 s), in both directions.","operationId":"set_operator_coverage_groups_operator__operator_id__coverage_groups_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetOperatorCoverageGroupsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditOperatorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/{operator_id}/site-access":{"put":{"tags":["operator"],"summary":"Set Operator Site Access","description":"Switch a user between ALL SITES and RESTRICTED (groups + exceptions).\n\nOwner / monitoring manager only (403 otherwise, including a site manager or\na manage_users operator). 409 for a role that cannot hold it (owner and\nmonitoring manager are already unrestricted; site / account / location\nmanagers are structurally scoped). Lossless both ways: groups and\nexceptions are kept and re-apply when switched back. Reaches an open SSE\nstream within the 30 s scope refresh.","operationId":"set_operator_site_access_operator__operator_id__site_access_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetOperatorSiteAccessRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditOperatorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/{operator_id}/deactivate":{"post":{"tags":["operator"],"summary":"Deactivate Operator","operationId":"deactivate_operator_operator__operator_id__deactivate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditOperatorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/{operator_id}/reactivate":{"post":{"tags":["operator"],"summary":"Reactivate Operator","operationId":"reactivate_operator_operator__operator_id__reactivate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditOperatorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/operator/{operator_id}/reset-password":{"post":{"tags":["operator"],"summary":"Reset Operator Password","operationId":"reset_operator_password_operator__operator_id__reset_password_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/health":{"get":{"tags":["cameras"],"summary":"Get Cameras Health","operationId":"get_cameras_health_cameras_health_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only cameras at this site","title":"Site Id"},"description":"Only cameras at this site"},{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only cameras of this client (parent)","title":"Parent Id"},"description":"Only cameras of this client (parent)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCamerasHealthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}":{"get":{"tags":["cameras"],"summary":"Get Camera By Id","operationId":"get_camera_by_id_cameras__camera_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraByIdResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["cameras"],"summary":"Edit Camera","operationId":"edit_camera_cameras__camera_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["cameras"],"summary":"Delete Camera","operationId":"delete_camera_cameras__camera_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/stream":{"get":{"tags":["cameras"],"summary":"Get Camera Livestream","operationId":"get_camera_livestream_cameras__camera_id__stream_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCamerasLivestreamResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/health":{"get":{"tags":["cameras"],"summary":"Get Camera Health","operationId":"get_camera_health_cameras__camera_id__health_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraHealthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["cameras"],"summary":"Update Camera Health","operationId":"update_camera_health_cameras__camera_id__health_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCameraHealthRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCameraHealthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/health-samples":{"post":{"tags":["cameras"],"summary":"Post Camera Health Sample","operationId":"post_camera_health_sample_cameras__camera_id__health_samples_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCameraHealthSampleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCameraHealthSampleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/health-config":{"put":{"tags":["cameras"],"summary":"Update Camera Health Config","operationId":"update_camera_health_config_cameras__camera_id__health_config_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCameraHealthConfigRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCameraHealthConfigResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/health-reference":{"post":{"tags":["cameras"],"summary":"Set Camera Health Reference","operationId":"set_camera_health_reference_cameras__camera_id__health_reference_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SetCameraHealthReferenceRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCameraHealthReferenceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["cameras"],"summary":"Clear Camera Health Reference","operationId":"clear_camera_health_reference_cameras__camera_id__health_reference_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteCameraHealthReferenceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/live":{"post":{"tags":["cameras"],"summary":"Start Live View","operationId":"start_live_view_cameras__camera_id__live_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartLiveViewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/operator-alarm":{"post":{"tags":["cameras"],"summary":"Raise Operator Alarm","operationId":"raise_operator_alarm_cameras__camera_id__operator_alarm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RaiseOperatorAlarmRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RaiseOperatorAlarmResponse"}}}},"200":{"description":"The camera already had an open operator-raised alarm (or this replays an idempotency key): that alarm, with existing=true.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RaiseOperatorAlarmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{site_id}":{"post":{"tags":["cameras"],"summary":"Create Camera","operationId":"create_camera_cameras__site_id__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCameraRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/operator-instructions":{"put":{"tags":["cameras"],"summary":"Set or clear this camera's operator instructions","description":"Free-text instructions shown to the HUMAN monitoring operator on every alarm from this camera, and again at the escalation step. NEVER enters an AI prompt — that is what `rag_context` on the agent-config route is for, and it is never sent to the ingestor either. Requires the owner, monitoring_manager or site_manager role (a site_manager only within its own client and granted sites). A camera id outside the caller's scope, or a retired one, answers 404 rather than 403: the status code must not tell a caller which of another client's camera ids exist.","operationId":"update_camera_operator_instructions_cameras__camera_id__operator_instructions_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteCameraOperatorInstructionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteCameraOperatorInstructionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/agent-config":{"put":{"tags":["cameras"],"summary":"Update Agent Config","operationId":"update_agent_config_cameras__camera_id__agent_config_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentConfigRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/live-view-only":{"put":{"tags":["cameras"],"summary":"Update Camera Live View Only","operationId":"update_camera_live_view_only_cameras__camera_id__live_view_only_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLiveViewOnlyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/display-rotation":{"put":{"tags":["cameras"],"summary":"Update Camera Display Rotation","operationId":"update_camera_display_rotation_cameras__camera_id__display_rotation_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDisplayRotationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/incident-routing":{"put":{"tags":["cameras"],"summary":"Update Camera Incident Routing","operationId":"update_camera_incident_routing_cameras__camera_id__incident_routing_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateIncidentRoutingRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/detection-mode":{"put":{"tags":["cameras"],"summary":"Update Detection Mode","operationId":"update_detection_mode_cameras__camera_id__detection_mode_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDetectionModeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDetectionModeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/deepalert-threshold":{"get":{"tags":["cameras"],"summary":"Get Deepalert Threshold","operationId":"get_deepalert_threshold_cameras__camera_id__deepalert_threshold_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["cameras"],"summary":"Update Deepalert Threshold","operationId":"update_deepalert_threshold_cameras__camera_id__deepalert_threshold_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeepalertThresholdRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/map-position":{"put":{"tags":["cameras"],"summary":"Update Camera Map Position","operationId":"update_camera_map_position_cameras__camera_id__map_position_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCameraMapPositionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/snapshot":{"get":{"tags":["cameras"],"summary":"Get Camera Snapshot","operationId":"get_camera_snapshot_cameras__camera_id__snapshot_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraSnapshotResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/alarm-heat":{"get":{"tags":["cameras"],"summary":"Get Camera Alarm Heat","description":"Where this camera's alarms came from over the last 7 days, as small\nheat grids for the Masking zones editor. Same audience as editing masks\n(configure_ignore_zones), within the caller's scope.","operationId":"get_camera_alarm_heat_cameras__camera_id__alarm_heat_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraAlarmHeatResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/zones":{"get":{"tags":["cameras"],"summary":"Get Camera Zones","operationId":"get_camera_zones_cameras__camera_id__zones_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraZonesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["cameras"],"summary":"Update Camera Zones","operationId":"update_camera_zones_cameras__camera_id__zones_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCameraZonesRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCameraZonesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/schedule":{"get":{"tags":["cameras"],"summary":"Get Camera Schedule","operationId":"get_camera_schedule_cameras__camera_id__schedule_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["cameras"],"summary":"Update Camera Schedule","operationId":"update_camera_schedule_cameras__camera_id__schedule_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScheduleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["cameras"],"summary":"Delete Camera Schedule","operationId":"delete_camera_schedule_cameras__camera_id__schedule_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/monitoring-schedule":{"get":{"tags":["cameras"],"summary":"Get Camera Monitoring Schedule","operationId":"get_camera_monitoring_schedule_cameras__camera_id__monitoring_schedule_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetMonitoringScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["cameras"],"summary":"Update Camera Monitoring Schedule","operationId":"update_camera_monitoring_schedule_cameras__camera_id__monitoring_schedule_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMonitoringScheduleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMonitoringScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/me":{"get":{"tags":["auth"],"summary":"Get Me","operationId":"get_me_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/auth/":{"post":{"tags":["auth"],"summary":"Login","operationId":"login_auth__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/refresh":{"post":{"tags":["auth"],"summary":"Refresh Token","operationId":"refresh_token_auth_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/complete-new-password":{"post":{"tags":["auth"],"summary":"Complete New Password","operationId":"complete_new_password_auth_complete_new_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteNewPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/forgot-password":{"post":{"tags":["auth"],"summary":"Forgot Password","operationId":"forgot_password_auth_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/confirm-forgot-password":{"post":{"tags":["auth"],"summary":"Confirm Forgot Password","operationId":"confirm_forgot_password_auth_confirm_forgot_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmForgotPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/auth/change-password":{"post":{"tags":["auth"],"summary":"Change Password","operationId":"change_password_auth_change_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/eval/ai":{"post":{"tags":["eval"],"summary":"Create Ai Eval","operationId":"create_ai_eval_eval_ai_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAIEvalRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Ai Eval Eval Ai Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/eval/operator/bulk":{"post":{"tags":["eval"],"summary":"Close out several queued alarms with one disposition","description":"The site story's \"Close all N\". Every event is disposed under the single-event route's rules (tenant scope, disposition permission, one evaluation per event) in its own transaction; ids that could not be disposed come back in `failed` with the status the single route would have answered, and the rest still land. An alarm another operator holds a live claim on is refused per item with status 423 and code `claim_held` (never the 409 \"already closed\"): it stays queued and the others in the batch still close; the caller uses Take over first. Only the false-alarm and non-threatening dispositions are accepted (422 otherwise): a talk-down or a dispatch is decided against one clip on the single route.","operationId":"create_operator_eval_bulk_eval_operator_bulk_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOperatorEvalRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOperatorEvalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/eval/operator/bulk/undo":{"post":{"tags":["eval"],"summary":"Undo your own site-story Close all","description":"Takes back the caller's own bulk false-alarm / non-threatening closes (the `disposed` ids of `POST /eval/operator/bulk`), up to 1,000 ids per request. The window is 10 s + a 2 s latency allowance from the caller's NEWEST own bulk false-alarm / non-threatening close anywhere (not just among the requested ids), measured on the database clock and judged once at request start. Each requested row must also be the caller's own bulk false-alarm / non-threatening close and no more than 90 s old; an older row is never undone. Each event is reopened in its own transaction: the evaluation and any training sample captured from it are deleted and the alarm returns to the queue UNCLAIMED. Ids that stay closed come back in `failed` with a `code` (`not_found`, `not_closed` = already open, `forbidden`, `not_yours`, `not_undoable` = not a bulk false-alarm/non-threatening close, `audited`, `too_late`, `error`); the rest still reopen. 403 for the whole request when the caller lacks the review_label_alarms permission. A single-alarm close is undone on `POST /eval/operator/{event_id}/undo`, which still refuses bulk rows.","operationId":"undo_operator_eval_bulk_eval_operator_bulk_undo_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUndoOperatorEvalRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUndoOperatorEvalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/eval/operator/{event_id}":{"post":{"tags":["eval"],"summary":"Create Operator Eval","operationId":"create_operator_eval_eval_operator__event_id__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOperatorEvalRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Operator Eval Eval Operator  Event Id  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eval/operator/{event_id}/undo":{"post":{"tags":["eval"],"summary":"Undo your own false-alarm or non-threatening close","description":"Takes back the caller's own single-event FALSE_ALARM or SUSPICIOUS_MONITOR close within 10 seconds of it (database clock, plus a 2 s latency allowance): the evaluation and any training sample captured from it are deleted, the alarm returns to the queue, and the caller takes its claim. 404 when the event is unknown or has no close to undo (including a second undo), 403 out of scope, missing the review_label_alarms permission, or the close was made by another operator or the agent; 409 \"Too late to undo this close\" after the window, 409 \"This close can't be undone\" for an escalation, a bulk close, or an audited close.","operationId":"undo_operator_eval_eval_operator__event_id__undo_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UndoOperatorEvalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eval/ai/{interpretation_result_id}":{"delete":{"tags":["eval"],"summary":"Delete Ai Eval","operationId":"delete_ai_eval_eval_ai__interpretation_result_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"interpretation_result_id","in":"path","required":true,"schema":{"type":"string","title":"Interpretation Result Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Ai Eval Eval Ai  Interpretation Result Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/eval/operator/{operator_evaluations_id}":{"delete":{"tags":["eval"],"summary":"Delete Operator Eval","operationId":"delete_operator_eval_eval_operator__operator_evaluations_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"operator_evaluations_id","in":"path","required":true,"schema":{"type":"string","title":"Operator Evaluations Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Operator Eval Eval Operator  Operator Evaluations Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/analytics/summary":{"get":{"tags":["analytics"],"summary":"Get Analytics Summary","operationId":"get_analytics_summary_analytics_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Report start; window must be positive and at most 366 days.","title":"Start"},"description":"Report start; window must be positive and at most 366 days."},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Report end; exclusive for hourly alarm volume.","title":"End"},"description":"Report end; exclusive for hourly alarm volume."},{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":200},{"type":"null"}],"title":"Parent Id"}},{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":500},{"type":"null"}],"title":"Site Id"}},{"name":"camera_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id"}},{"name":"operator_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":200},{"type":"null"}],"title":"Operator Id"}},{"name":"include_agent","in":"query","required":false,"schema":{"type":"boolean","description":"Include agent-mode evaluations (actor='agent') in every figure. Default false: the dashboard measures operator decisions only, and metrics.agent_evaluations reports how many agent rows were left out.","default":false,"title":"Include Agent"},"description":"Include agent-mode evaluations (actor='agent') in every figure. Default false: the dashboard measures operator decisions only, and metrics.agent_evaluations reports how many agent rows were left out."},{"name":"tz","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"IANA time zone for the hour-of-day chart (e.g. America/Los_Angeles). Defaults to UTC.","title":"Tz"},"description":"IANA time zone for the hour-of-day chart (e.g. America/Los_Angeles). Defaults to UTC."},{"name":"slow_threshold_seconds","in":"query","required":false,"schema":{"type":"integer","maximum":600,"minimum":30,"default":90,"title":"Slow Threshold Seconds"}},{"name":"stale_after_seconds","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":86400,"minimum":60},{"type":"null"}],"description":"When set, dispositions of alarms that were already older than this many seconds (event-created -> disposition) when cleared are excluded from every metric in the response. Omit to include everything (the default).","title":"Stale After Seconds"},"description":"When set, dispositions of alarms that were already older than this many seconds (event-created -> disposition) when cleared are excluded from every metric in the response. Omit to include everything (the default)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAnalyticsSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/analytics/camera-health":{"get":{"tags":["analytics"],"summary":"Get Camera Health Analytics","description":"Per-camera connectivity, image quality and stream health for the whole\nin-scope fleet — the Analytics -> Camera Health tab.\n\nScoped exactly like /summary above: an explicit parent_id outside the\noperator's grants is rejected rather than silently emptied, the per-site\ngap check closes ?site_id= for a narrowed manager, and the per-site\nTenantScope goes to the service, where it becomes the ONLY tenant filter\non the fleet query (every other statement keys off the ids it returns).","operationId":"get_camera_health_analytics_analytics_camera_health_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id"}},{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraHealthAnalyticsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/analytics/camera-alarm-volume":{"get":{"tags":["analytics"],"summary":"Get Camera Alarm Volume","description":"Per camera, how many alarms reached the operator queue in the last 24h\nand 7d: the Masking zones tab sorts sites and cameras by it so the\nnoisiest cameras get masked first.\n\nReadable by whoever may edit masks (the decision it serves), over that\ncaller's tenant scope only; the scope is the service's sole tenant filter.\nThe fleet-wide equivalent is GET /logs/summary, which is diagnostics-only.","operationId":"get_camera_alarm_volume_analytics_camera_alarm_volume_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraAlarmVolumeResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/analytics/camera-health/recheck":{"post":{"tags":["analytics"],"summary":"Post Camera Health Recheck","description":"The Camera Health tab's \"Re-check cameras\" button: ask every in-scope\nmotion_clip camera for a fresh health sample now instead of at the next\nhourly tick.\n\nExactly the tab's own scope, by construction: the same filter checks as\nthe GET above (one shared helper), and the same TenantScope handed to the\nservice, which bounds the write with the very predicate list the read\nuses. Whoever can see a camera on the tab can re-check it, and nobody can\nre-check a camera they cannot see.\n\nWho may press it is NARROWER than who may read the tab: the\nrequire_camera_health_recheck role allowlist above (ops-view's canMutate)\nrefuses the end-customer logins and read_only, which the GET admits. What\nan admitted caller may reach is NOT narrower: once past the role gate the\nscope is the tab's own, so a site manager re-checks only its granted\nsites. Not activity-logged: a re-check changes no configuration, it only\nasks for one more sample, and the service's per-camera cooldown bounds\nhow many.","operationId":"post_camera_health_recheck_analytics_camera_health_recheck_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/PostCameraHealthRecheckRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCameraHealthRecheckResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/analytics/camera-health/connection-test":{"post":{"tags":["analytics"],"summary":"Post Camera Connection Test","description":"\"Reconnect & notify\", step 1: try to open each named camera's stream\nnow, and ask its ingestor for a fresh health sample.\n\nOne RTSP DESCRIBE per camera from the API (rtsp_probe, the NVR Test &\nSave probe), channels of one recorder account tried serially and stopped\nat the first credential that went out without being proven accepted,\nwith secret reads, DNS and probes sharing an ~11 s budget. The answer separates \"the customer's camera is\nunreachable\" from \"reachable, so the outage is ours\" - the second must\nnot turn into an e-mail to the customer.\n\nVERDUN STAFF ONLY (require_central_staff; read_only is then refused by\nthe global write guardrail). NARROWER than Re-check on purpose: the\ncustomer-side logins can edit a camera's RTSP address, and a probe that\nreports reachable vs refused for an address they chose is a port scanner\npointed at our own network. Scope is the Camera Health tab's own; an id\noutside it is a 404. Not activity-logged: it changes no configuration.","operationId":"post_camera_connection_test_analytics_camera_health_connection_test_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCameraConnectionTestRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCameraConnectionTestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/analytics/camera-health/notify-down":{"post":{"tags":["analytics"],"summary":"Post Camera Down Notify","description":"\"Reconnect & notify\", step 2: e-mail each site's chosen recipients\nabout its cameras that are still down.\n\nOne message per recipient (no CC), sent as the owner-e-mail identity\n(OWNER_EMAIL_FROM_* / Reply-To support) and behind the same switch:\n503 EMAIL_NOT_CONFIGURED while OWNER_EMAIL_ENABLED is off. Every camera\nmust be one the caller can see and must belong to the site it is listed\nunder. Each site's send is recorded in the Activity Log (counts only).\nPer-recipient outcomes come back in the body; an SES failure on one\nrecipient does not fail the request.","operationId":"post_camera_down_notify_analytics_camera_health_notify_down_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCameraDownNotifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostCameraDownNotifyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/analytics/system-health":{"get":{"tags":["analytics"],"summary":"Get System Health","description":"Fleet-wide detection-pipeline health — the Analytics → System Health tab.\n\nOWNER + SUPPORT (require_diagnostics_reader; support since the owner ask\nof 2026-10-14) and NOT the monitoring manager — note that /summary beside\nit was relaxed to require_ops_lead by owner decision 2026-09-15 while this\nroute deliberately was not (the decision's \"Out of scope\" list names\nGET /analytics/system-health alongside Training and logs/*). The two no\nlonger share a dependency, and that divergence is the point: every block\nhere is a cross-tenant fleet\naggregate (fail-open share across the whole fleet, per-camera accept-rate\noutliers naming cameras under every parent). There is deliberately no\nscoped variant — one would leak fleet facts by construction — so there are\nno filter parameters and nothing for ensure_analytics_filter_in_scope to\ncheck. Adding a filter later is therefore a deliberate act, not a default.\n\nNo query parameters in v1: the windows are fixed (1h / 24h / 7-day band per\nblock, each block self-describing), and the service serves the whole\npayload from a ~5-minute in-process cache, so polling it is cheap by\nconstruction.","operationId":"get_system_health_analytics_system_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSystemHealthResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/analytics/delivery-latency":{"get":{"tags":["analytics"],"summary":"Get Delivery Latency","description":"How long alarms take to reach an operator — the Analytics → System\nHealth delivery-latency panel.\n\nOwner + support through the SAME dependency /system-health uses, and for the\nsame reason: every number here is a cross-tenant fleet aggregate, and the\ncamera rows name cameras, sites and parents under every customer. There is\ndeliberately no scoped variant — a narrowed one would leak fleet facts by\nconstruction — so there is no parent_id/site_id parameter and nothing for\nensure_analytics_filter_in_scope to check.\n\n`window` IS A TWO-VALUE ENUM, NOT AN `hours` INT. The cost profile is tuned\nfor exactly these two windows and the ~5-minute cache is keyed on them, so\na free int would make the cache's key space unbounded and would invite\narbitrary scans of the highest-insert table in the system. Anything else is\na 422 rather than an expensive 200.","operationId":"get_delivery_latency_analytics_delivery_latency_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"window","in":"query","required":false,"schema":{"enum":["24h","7d"],"type":"string","description":"Which window: the latest 24 clock-hour buckets, or the latest 168 (7 days). The last bucket is the hour IN PROGRESS and is partial, so the span is 23 (or 167) whole hours plus the elapsed part of the current hour — the response publishes the exact bounds as window_start/window_end.","default":"24h","title":"Window"},"description":"Which window: the latest 24 clock-hour buckets, or the latest 168 (7 days). The last bucket is the hour IN PROGRESS and is partial, so the span is 23 (or 167) whole hours plus the elapsed part of the current hour — the response publishes the exact bounds as window_start/window_end."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetDeliveryLatencyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/analytics/incidents":{"get":{"tags":["analytics"],"summary":"Get Incident Counts","description":"AI detections grouped by incident_type over a window (default rolling\n24h; start/end win when both given), rolled up per site and per camera —\nthe Analytics → Incident Data tab. site_id/camera_id repeat for\nmulti-select: UUID-typed so malformed ids 422 at the edge instead of\n500ing in Postgres, and length-capped to bound the authorization and\nanalytics IN clauses. Same tenant scoping as /analytics/summary.","operationId":"get_incident_counts_analytics_incidents_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hours","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":168,"minimum":1},{"type":"null"}],"title":"Hours"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}},{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id"}},{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":200},{"type":"null"}],"title":"Site Id"}},{"name":"camera_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":500},{"type":"null"}],"title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetIncidentCountsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/analytics/incidents/events":{"get":{"tags":["analytics"],"summary":"Get Incident Events","description":"The individual events behind one incident-type count (the Incident\nData drill-down; rows open in the review panel). Same window, filters,\nclock, and scoping as /analytics/incidents so lists reconcile with bars.","operationId":"get_incident_events_analytics_incidents_events_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"incident_type","in":"query","required":true,"schema":{"$ref":"#/components/schemas/IncidentType"}},{"name":"hours","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":168,"minimum":1},{"type":"null"}],"title":"Hours"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}},{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id"}},{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":200},{"type":"null"}],"title":"Site Id"}},{"name":"camera_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":500},{"type":"null"}],"title":"Camera Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetIncidentEventsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/analytics/agent-agreement":{"get":{"tags":["analytics"],"summary":"Get Agent Agreement","description":"AI-vs-operator agreement: precision/recall of the agent's recommended\naction (and of the actual auto-dismiss gate) against human dispositions as\nground truth. Same tenant scoping as /analytics/summary.\n\n403 for a curated role rather than curated: there is no customer-\nmeaningful reading of \"AI vs operator agreement precision\" — it is a\nstaff accuracy-monitoring metric, and every input to it (gate-stops,\nauto-dismissals, what the agent recommended doing) is exactly the internal\npipeline detail this feature hides. Unlike the incident-counts/events\nendpoints, curating this one down to only customer_visible rows would\nnot produce a meaningful \"agreement rate\" — it would produce a rate over\na handful of already-escalated events, which is not what this metric\nmeans.","operationId":"get_agent_agreement_analytics_agent_agreement_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}},{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":200},{"type":"null"}],"title":"Parent Id"}},{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":500},{"type":"null"}],"title":"Site Id"}},{"name":"camera_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAgentAgreementResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/analytics/escalations":{"get":{"tags":["analytics"],"summary":"Get Escalations","description":"Reporting -> Escalations: one row per escalated alarm (Escalate - Voice\nDown or Escalate - Dispatch, Site Summary's own definition) with its full\naction timeline on one clock, and KPI medians over the FULL filtered set.\n\nThe window keys on the ESCALATION INSTANT (the operator close-out, else the\nAI announcement), like Site Summary, so an alarm created before the window\nstart but escalated inside it appears; rows are ordered by when the alarm\nlanded. Tenant scoping is /analytics/incidents': a forged parent/site/camera\nfilter is 403, rows outside the caller's grants never appear. Operator names\nare returned only to owner / monitoring manager; everyone else sees\n\"Operator\". Customer-side logins also lose internal-only detail.","operationId":"get_escalations_analytics_escalations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hours","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":720,"minimum":1},{"type":"null"}],"description":"Rolling window ending now (default 168 = 7 days). start/end win when both are given.","title":"Hours"},"description":"Rolling window ending now (default 168 = 7 days). start/end win when both are given."},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}},{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":200},{"type":"null"}],"title":"Parent Id"}},{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":200},{"type":"null"}],"title":"Site Id"}},{"name":"camera_id","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":500},{"type":"null"}],"title":"Camera Id"}},{"name":"kind","in":"query","required":false,"schema":{"anyOf":[{"enum":["voice_down","dispatch"],"type":"string"},{"type":"null"}],"title":"Kind"}},{"name":"escalated_by","in":"query","required":false,"schema":{"anyOf":[{"enum":["operator","agent"],"type":"string"},{"type":"null"}],"title":"Escalated By"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetEscalationsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/analytics/escalations/{event_id}":{"get":{"tags":["analytics"],"summary":"Get Escalation","description":"One escalation by alarm id, for deep links (Site Summary cards, Incident\nData rows) that may point outside the current window or filters. Same scope\nand visibility as the list: 403 out of scope, 404 unknown, hidden or not an\nescalation.","operationId":"get_escalation_analytics_escalations__event_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EscalationRow"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/nvrs":{"post":{"tags":["nvrs"],"summary":"Create Nvr","operationId":"create_nvr_sites__site_id__nvrs_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateNvrRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NvrResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["nvrs"],"summary":"Get Nvrs For Site","operationId":"get_nvrs_for_site_sites__site_id__nvrs_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetNvrsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/nvrs/{nvr_id}":{"put":{"tags":["nvrs"],"summary":"Edit Nvr","operationId":"edit_nvr_nvrs__nvr_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"nvr_id","in":"path","required":true,"schema":{"type":"string","title":"Nvr Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditNvrRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NvrResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["nvrs"],"summary":"Delete Nvr","operationId":"delete_nvr_nvrs__nvr_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"nvr_id","in":"path","required":true,"schema":{"type":"string","title":"Nvr Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteNvrResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/nvrs/{nvr_id}/devices":{"get":{"tags":["nvrs"],"summary":"Get Nvr Devices","operationId":"get_nvr_devices_nvrs__nvr_id__devices_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"nvr_id","in":"path","required":true,"schema":{"type":"string","title":"Nvr Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetNvrDevicesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/nvrs/{nvr_id}/cameras/bulk":{"post":{"tags":["nvrs"],"summary":"Bulk Onboard Cameras","operationId":"bulk_onboard_cameras_nvrs__nvr_id__cameras_bulk_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"nvr_id","in":"path","required":true,"schema":{"type":"string","title":"Nvr Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOnboardRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOnboardResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/careers/applications":{"post":{"tags":["careers"],"summary":"Submit Application","description":"Accept a job application (multipart form + résumé) and email a summary\nwith the résumé attached to the careers inbox.","operationId":"submit_application_careers_applications_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_submit_application_careers_applications_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sms-opt-in/consent":{"post":{"tags":["sms-opt-in"],"summary":"Submit Consent","description":"Record an individual's affirmative SMS opt-in consent.\n\nTHE ROW IS THE RECORD NOW, not the email. This used to only email a\nproof-of-consent artifact to support@ — consent existed solely in an inbox,\nqueryable by nobody. It now persists an `sms_consent` row as\n`pending_review` (a web submission is NOT bound to a site contact and\nauthorizes nothing until an owner reviews and links it), commits, answers,\nand only then emails.\n\nNO-FAIL-AFTER-PERSIST, AND IT IS A DELIBERATE BEHAVIOR CHANGE: the email is\nbest-effort AFTER the commit, so an SES outage no longer 502s a submission\nwhose consent we have already durably recorded. Telling someone their\nopt-in failed when it did not is worse than a missing notification email.\n\nALSO A BEHAVIOR CHANGE: numbers are canonicalized with the shared US-only\n`to_e164`, so a non-NANP number is now a 422 instead of a stored record we\ncould never text. Both changes affect the live verdun.ai form.\n\n`consent_version` is optional for exactly as long as verdun.ai does not\nsend it: missing means `legacy_web_v1` (the live copy), while a version we\ndo not recognize is a 422 — a key we cannot resolve to words looks like\nevidence and isn't.\n\nThe response body is ALWAYS the same generic `{\"status\": \"ok\"}`. It never\nechoes the row: an unauthenticated endpoint that reflected stored consent\ndetails back would be an oracle for whether a number has opted in.","operationId":"submit_consent_sms_opt_in_consent_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_submit_consent_sms_opt_in_consent_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/contacts/{contact_id}/sms-enroll":{"post":{"tags":["sms-consent"],"summary":"Enroll Contact For Sms","description":"Enroll one site contact for SMS and send the ONE confirmation text.\n\nTwo steps, deliberately not one transaction: the enrollment commits first,\nthen the confirmation is sent against it. A provider failure therefore\nleaves an open enrollment (re-sendable, and the send is idempotent per row)\nrather than rolling back the record the idempotency depends on.\n\nThe contact must reply YES before this authorizes anything.","operationId":"enroll_contact_for_sms_sites_contacts__contact_id__sms_enroll_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","title":"Contact Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrollContactRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsConsentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sms-consent/optout-events":{"get":{"tags":["sms-consent"],"summary":"List Optout Events","description":"Durable STOP events. A non-empty unresolved list is why SMS may be\nrefusing every send.","operationId":"list_optout_events_sms_consent_optout_events_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"unresolved_only","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Unresolved Only"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OptOutEventResponse"},"title":"Response List Optout Events Sms Consent Optout Events Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sms-consent/optout-events/{event_id}/reconcile":{"post":{"tags":["sms-consent"],"summary":"Reconcile Optout Event","description":"Offer a CANDIDATE number for an unattributed opt-out.\n\nTHE NUMBER IS NOT ACCEPTED, IT IS PROVEN. The finalizer runs a live lookup\nand resolves only if the candidate's single matching Surge contact IS this\nevent's contact. A typo returns `unverified`, suppresses nothing, resolves\nnothing, and leaves SMS paused — which is the entire point: accepting an\nowner's word would suppress the wrong number and lift the account-wide\nrefusal for the real subscriber.","operationId":"reconcile_optout_event_sms_consent_optout_events__event_id__reconcile_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReconcileOptOutRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReconcileOptOutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sms-consent":{"get":{"tags":["sms-consent"],"summary":"List Consent Queue","description":"Consents awaiting a human: `pending_review`, orphans, and (non-linkable)\n`unverifiable` rows. Keyset-paginated.","operationId":"list_consent_queue_sms_consent_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsConsentQueueResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sms-consent/{consent_id}/link":{"post":{"tags":["sms-consent"],"summary":"Link Consent","description":"Bind a reviewed consent to a contact slot.\n\nLinking records consent; it does NOT lift a suppression. Surge has no\nserver-side re-subscribe, so a number that said STOP stays blocked until\nthe subscriber texts START — the response's status will say `linked` while\nthe number remains unsendable, and that is correct.","operationId":"link_consent_sms_consent__consent_id__link_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"consent_id","in":"path","required":true,"schema":{"type":"string","title":"Consent Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkConsentRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsConsentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sms-consent/{consent_id}/dismiss":{"post":{"tags":["sms-consent"],"summary":"Dismiss Consent","description":"Close a reviewed consent without linking it.\n\nOnly `pending_review` rows and orphans can be dismissed. Removing a LIVE\nbinding is a different, explicit action (it writes `revoked`), so a queue\ntidy-up can never silently stop a customer's alerts.","operationId":"dismiss_consent_sms_consent__consent_id__dismiss_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"consent_id","in":"path","required":true,"schema":{"type":"string","title":"Consent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmsConsentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/activity/":{"get":{"tags":["activity"],"summary":"Get Activity","operationId":"get_activity_activity__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetActivityResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/logs/telemetry":{"post":{"tags":["logs"],"summary":"Post Pipeline Telemetry","description":"Internal, batched ingest-worker telemetry. Never accepts operator auth.","operationId":"post_pipeline_telemetry_logs_telemetry_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostPipelineTelemetryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Post Pipeline Telemetry Logs Telemetry Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/logs/infra-alarms":{"post":{"tags":["logs"],"summary":"Post Infra Alarm","description":"SNS webhook: CloudWatch alarm state changes land in the system log.\n\nAuthentication is the SNS envelope's own cryptographic signature plus a\nTopicArn pin (ALARM_SNS_TOPIC_ARN) — no secret in the URL. SNS posts as\ntext/plain, so the body is parsed from the raw payload rather than a\nPydantic model. Journal writes are strict: a failed write returns 5xx so\nSNS redelivers instead of the alarm being lost.","operationId":"post_infra_alarm_logs_infra_alarms_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Post Infra Alarm Logs Infra Alarms Post"}}}}}}},"/logs/":{"get":{"tags":["logs"],"summary":"Get Logs","operationId":"get_logs_logs__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"level","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SystemLogLevel"},{"type":"null"}],"title":"Level"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"$ref":"#/components/schemas/SystemLogCategory"},{"type":"null"}],"title":"Category"}},{"name":"camera_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id"}},{"name":"hours","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":168,"minimum":1},{"type":"null"}],"title":"Hours"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetLogsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/logs/summary":{"get":{"tags":["logs"],"summary":"Get Logs Summary","operationId":"get_logs_summary_logs_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hours","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"default":24,"title":"Hours"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetLogsSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/logs/timeseries":{"get":{"tags":["logs"],"summary":"Get Logs Timeseries","operationId":"get_logs_timeseries_logs_timeseries_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hours","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"default":24,"title":"Hours"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Start"}},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End"}},{"name":"group_by","in":"query","required":false,"schema":{"enum":["none","camera","site"],"type":"string","default":"none","title":"Group By"}},{"name":"camera_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":200},{"type":"null"}],"title":"Camera Ids"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetLogsTimeseriesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/logs/status":{"get":{"tags":["logs"],"summary":"Get Logs Status","description":"Cheap owner + support count used by the global System Logs nav badge.","operationId":"get_logs_status_logs_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Logs Status Logs Status Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/threads/{thread_id}":{"get":{"tags":["threads"],"summary":"Get Thread By Id","operationId":"get_thread_by_id_threads__thread_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetThreadByIdResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/threads/{thread_id}/star":{"put":{"tags":["threads"],"summary":"Star Thread","operationId":"star_thread_threads__thread_id__star_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Star Thread Threads  Thread Id  Star Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["threads"],"summary":"Unstar Thread","operationId":"unstar_thread_threads__thread_id__star_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"thread_id","in":"path","required":true,"schema":{"type":"string","title":"Thread Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Unstar Thread Threads  Thread Id  Star Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/notify-owner":{"post":{"tags":["telephony"],"summary":"Notify Owner","description":"Text the site's property contact about this event.\n\nLINKS ARE PERMITTED in the body (the 10DLC campaign declares them and\nlink shortening is disabled per message), and `attach_snapshot` sends the\nevent's alert-frame JPEG as MMS — 409 SNAPSHOT_UNAVAILABLE when the event\nhas none, with nothing written and nothing sent.\n\nRESPONSE SHAPE (this route is `response_model=dict`, a legacy escape\nhatch, so the shape lives here rather than in a typed model): the\n`notification_log` row as `to_dict_frontend()` renders it, PLUS\n`attachments` — `null`, or a list of `{kind, url, content_type}` where\n`url` is a presigned GET valid 3600s and minted for this read. `kind` is\nan open string (\"snapshot\" today). `null`, never `[]`, when nothing was\nattached, and the S3 key behind each record is never exposed.","operationId":"notify_owner_events__event_id__notify_owner_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotifyOwnerRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Notify Owner Events  Event Id  Notify Owner Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/call-owner":{"post":{"tags":["telephony"],"summary":"Call Owner","description":"Call the site's property contact. SAME PERMISSION AS TEXTING THEM\n(owner decision, 2026-09-10): an account manager who may notify the owner\nmay reach them by phone as well as by text.\n\nONE EXCEPTION, channel='phone_bridge', which ALSO requires\nescalate_dispatch: the bridge's first leg is the caller-supplied\noperator_number, so it can ring any number at all, and there is no verified\noperator callback number on file to constrain it. Notify-only operators get\na 403 there and use channel='browser_call', whose operator leg is the\nsoftphone client itself.\n\nThe row this writes is a dispatch_call with target_type='owner' — it reuses\nthe dispatch transport but is NOT dispatch: it never appears in\nGET /events/{id}/dispatch-calls, and it surfaces on the event's\ncommunications timeline instead.","operationId":"call_owner_events__event_id__call_owner_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallOwnerRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Call Owner Events  Event Id  Call Owner Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/email-owner":{"post":{"tags":["telephony"],"summary":"Email Owner","description":"E-mail the site's property contact about this event. The destination is\nresolved from the named contact's row — no address is ever accepted from\nthe client. 503 until OWNER_EMAIL_ENABLED is set.","operationId":"email_owner_events__event_id__email_owner_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailOwnerRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Email Owner Events  Event Id  Email Owner Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/log-attempt":{"post":{"tags":["telephony"],"summary":"Log Attempt","description":"Record an owner-contact attempt the operator made OFF-SYSTEM.\n\nSAME DEPENDENCY OBJECT as `/notify-owner`, `/call-owner` and `/email-owner`\n— not a re-derived `require_permission(...)` call — so the four\nowner-contact actions on this event cannot drift apart in what they allow.\n\nNOTHING IS SENT. This writes one `notification_log` row with\n`provider_sid NULL` and `entered_manually=true`, and it is deliberately\nNOT gated on consent or on telephony configuration; `log_attempt`'s\ndocstring carries the argument. It is a WRITE, so it gets no\n`READ_ONLY_WRITE_ALLOWLIST` entry — read-only operators must not reach it.","operationId":"log_attempt_events__event_id__log_attempt_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogAttemptRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Log Attempt Events  Event Id  Log Attempt Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/notifications":{"get":{"tags":["telephony"],"summary":"List Event Notifications","description":"The event's whole communications timeline: SMS, voice and e-mail rows,\noldest first.\n\nRESPONSE SHAPE (this route is `response_model=list[dict]`, a legacy escape\nhatch): each row is `NotificationLog.to_dict_frontend()` PLUS\n`attachments`. THAT KEY IS PRESENT ON EVERY ROW OF THIS RESPONSE and is\n`null` on the voice, e-mail and manually-logged ones — uniform within a\nresponse, absent from unrelated responses. Each entry is\n`{kind, url, content_type}`; `url` is a presigned GET valid 3600s, minted\nfor this read and null if presigning failed (the row is still returned).\n`null`, never `[]`, when a row carried nothing.","operationId":"list_event_notifications_events__event_id__notifications_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Event Notifications Events  Event Id  Notifications Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/share-links":{"get":{"tags":["telephony"],"summary":"List Event Share Links","description":"Every login-free clip link minted on this event, oldest first.\n\nTHE SAME DEPENDENCY OBJECT as `/notify-owner`, `/call-owner`,\n`/email-owner` and `/log-attempt` — not a re-derived\n`require_permission(...)` — so the owner-contact actions on this event\ncannot drift apart in what they allow. Whoever may text a property owner\na clip link may see, and kill, the links they sent.\n\nContainment is the service's, through the same\n`NotificationsService._get_event_in_scope` the notifications read uses:\nan out-of-tenant event id is a 403 (this repo's established answer for a\nper-event route), not a 404 -- see `ShareLinkService.list_for_event`'s\ndocstring. A foreign LINK id on the revoke route below is the one that\nmust never be a 403.\n\nNO TOKEN AND NO HASH in the response. The plaintext URL is already in the\nmessage body on the timeline; this list is the link's STATE.","operationId":"list_event_share_links_events__event_id__share_links_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ShareLinkRecord"},"title":"Response List Event Share Links Events  Event Id  Share Links Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/share-links/{link_id}/revoke":{"post":{"tags":["telephony"],"summary":"Revoke Event Share Link","description":"Kill one clip link. The recipient's next load is the same 404 an\nunknown token gets.\n\nIDEMPOTENT: an already-revoked link answers 200 with the UNCHANGED first\nrevoker and first timestamp. A second revoke must not rewrite the audit,\nand a client retrying a click must not see an error for a link that is\nalready dead.\n\nA link id belonging to ANOTHER event is the same 404 as one that does not\nexist — this route must not confirm the existence of link ids outside the\nevent the caller can already reach.\n\nA FAILED SEND DOES NOT AUTO-REVOKE ITS LINK, and this route is why.\nA provider failure is often AMBIGUOUS (the network died; Surge may well\nhave sent it), so the customer may already hold the URL and revoking would\nbreak a message that did arrive. The operator sees the failed row with its\nlink and decides.","operationId":"revoke_event_share_link_events__event_id__share_links__link_id__revoke_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"link_id","in":"path","required":true,"schema":{"type":"string","title":"Link Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLinkRecord"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/telephony/config":{"get":{"tags":["telephony"],"summary":"Telephony Config","description":"Whether telephony is connected — ops-view renders the notify-owner and\ndispatch-call cards in a 'not connected yet' state (matching the voice-down\npattern) until the provider credentials land.\n\nTHE TWO FLAGS HAVE DIFFERENT PROVIDERS AND DIFFERENT GATES: sms_enabled\ntracks Surge (SURGE_* env), voice_enabled tracks Twilio (TWILIO_* env)\nplus the DISPATCH_CALLS_ENABLED launch gate, which stays off until operator\ncallback numbers are verified with the customer. The response shape is\nunchanged by the SMS provider swap, so ops-view needs no update.\n\nTHE CONSENT FIELDS ARE ROLE-SPLIT, AND THE SPLIT IS A TENANCY BOUNDARY,\nnot tidiness. Every operator needs to know that SMS is currently refusing\nsends — otherwise the notify-owner card shows a 503 with no explanation —\nbut the DETAIL is account-wide consent state:\n\n- `sms_paused` (EVERY operator): a plain boolean, the minimum the card\n  needs. True when an unattributed opt-out is holding the gate closed.\n- `sms_unresolved_optout_count` (OWNER ONLY): the exact number of\n  outstanding opt-outs. `sms_suppressions`/`sms_optout_events` have no\n  tenant column by design — a STOP is a fact about a phone number against\n  our single shared Surge sending number — so this count spans EVERY\n  tenant on the account. A site-scoped or read-only operator polling it\n  could watch STOP activity at other customers, which is exactly what the\n  owner-only consent routes exist to prevent; leaking the same state\n  through a config probe would have made those guards decorative.\n- `sms_unresolved_stop_gate_enabled` / `sms_positive_consent_gate_enabled`\n  (OWNER ONLY): deployment posture. Not secret, but not a tenant\n  operator's business, and grouped with the count so the split is one rule\n  rather than three judgements.\n\n`sms_paused` is derived from the SAME predicate the gate uses, so it can\nnever disagree with the behavior an operator is seeing.","operationId":"telephony_config_telephony_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Telephony Config Telephony Config Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/telephony/voice-token":{"post":{"tags":["telephony"],"summary":"Voice Token","description":"Mint a short-lived Twilio Voice access token for the browser softphone.\n\nEITHER PERMISSION that can start a browser leg: escalate_dispatch (police)\nor notify_property_owner (the property contact). read_only holds neither\nand is refused, which is pinned by test.\n\nWidening WHO may hold a token does not widen what can be dialled with one:\nthe grant is outgoing-only and bound to our TwiML App, and\nbuild_softphone_twiml still enforces the per-row identity, one-shot and\nfreshness checks before any number is dialled, and re-reads the operator's\npermissions against the reservation's target_type — and the reservation's\nevent against their current tenant scope — at that moment, so a token held\non one permission cannot consume a reservation that needs the other, and a\nrevoked tenant grant cannot be dialled through. 503 until browser calling\nis configured AND the dispatch launch gate is on.","operationId":"voice_token_telephony_voice_token_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceTokenResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/events/{event_id}/dispatch-call":{"post":{"tags":["telephony"],"summary":"Dispatch Call","operationId":"dispatch_call_events__event_id__dispatch_call_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DispatchCallRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Dispatch Call Events  Event Id  Dispatch Call Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/dispatch-calls":{"get":{"tags":["telephony"],"summary":"List Event Dispatch Calls","description":"Same permission as placing a dispatch call: rows carry operators'\npersonal callback numbers, and the UI only surfaces this audit trail to\nescalate_dispatch holders anyway.","operationId":"list_event_dispatch_calls_events__event_id__dispatch_calls_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List Event Dispatch Calls Events  Event Id  Dispatch Calls Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/site-police-calls":{"get":{"tags":["telephony"],"summary":"List Site Police Calls","description":"Police calls already placed (or in progress) about OTHER alarms at this\nevent's site, so an operator is warned before a possible duplicate call.\nA warning read only; it never blocks placing a call. Same permission and\nevent scope as /dispatch-calls; carries no phone numbers.","operationId":"list_site_police_calls_events__event_id__site_police_calls_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SitePoliceCallsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/telephony/webhooks/surge":{"post":{"tags":["telephony"],"summary":"Surge Webhook","description":"Every Surge SMS event: message.sent/delivered/failed, message.received,\ncontact.opted_out/opted_in. ONE endpoint for all of them — Surge registers\na single project-level webhook URL in its dashboard (there is no per-message\ncallback URL and no API to register one), so the event type inside the\nenvelope is the router.\n\nPUBLIC, authenticated solely by the Standard Webhooks signature, mirroring\nthe Twilio voice webhooks. Note the signature covers\n`webhook-id.webhook-timestamp.<raw body>` and NOT the URL, so unlike the\nTwilio validator this needs no TELEPHONY_WEBHOOK_BASE_URL.\n\nTHE BODY IS READ AS RAW BYTES BEFORE ANYTHING PARSES IT. Re-serializing\nparsed JSON changes whitespace and key order and would invalidate every\ngenuine signature.\n\nTHREE RESPONSE CLASSES, AND THE DISTINCTION IS THE CONTRACT:\n\n- **403** — bad, missing, or stale signature. Authentication failure only.\n- **200 `{\"status\": \"ok\"}`** — accepted. Surge's webhook spec documents\n  200 explicitly (\"Return a 200 status to indicate that the data was\n  received successfully\"), so this returns 200 rather than a bare 204.\n  This covers PERMANENT failures too: a body we cannot parse, an event we\n  cannot correlate, a type we do not know. None of those improve on a\n  retry, and non-2xx would buy an exponential-backoff retry storm.\n- **500** — a TRANSIENT upstream failure (see SurgeTransientError) is\n  allowed to propagate so Surge redelivers. Acknowledging a blip would\n  turn a recoverable failure into a permanently lost opt-out.","operationId":"surge_webhook_telephony_webhooks_surge_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/telephony/webhooks/dispatch-twiml":{"post":{"tags":["telephony"],"summary":"Twilio Dispatch Twiml","description":"Fetched when the operator's bridge leg is answered; returns TwiML that\ndials the server-stored dispatch target for ?dcid=. The target is read\nfrom the DB row, never from the request, so the answered call can only be\nconnected to the number we resolved server-side. Unknown/absent row ⇒\nhang up rather than dial anything.","operationId":"twilio_dispatch_twiml_telephony_webhooks_dispatch_twiml_post","responses":{"200":{"description":"TwiML","content":{"application/xml":{}}}}}},"/telephony/webhooks/softphone-twiml":{"post":{"tags":["telephony"],"summary":"Twilio Softphone Twiml","description":"The TwiML App's Voice URL: fired when an operator's browser (WebRTC)\nleg connects. The dcid rides the SDK's connect() params (form body, so it\nis inside Twilio's signature), and the service consumes the matching\nbrowser-call reservation — one-shot, identity-matched, freshness-bounded —\nbefore returning TwiML that dials the server-stored target. Any failed\ncheck ⇒ hang up, never dial.","operationId":"twilio_softphone_twiml_telephony_webhooks_softphone_twiml_post","responses":{"200":{"description":"TwiML","content":{"application/xml":{}}}}}},"/telephony/webhooks/dispatch-status":{"post":{"tags":["telephony"],"summary":"Twilio Dispatch Status","operationId":"twilio_dispatch_status_telephony_webhooks_dispatch_status_post","responses":{"204":{"description":"Successful Response"}}}},"/telephony/webhooks/dispatch-target-status":{"post":{"tags":["telephony"],"summary":"Twilio Dispatch Target Status","description":"Child-leg completion, including when the operator hangs up first.","operationId":"twilio_dispatch_target_status_telephony_webhooks_dispatch_target_status_post","responses":{"204":{"description":"Successful Response"}}}},"/telephony/webhooks/dispatch-dial-result":{"post":{"tags":["telephony"],"summary":"Twilio Dispatch Dial Result","description":"<Dial action> callback: fires when the bridged dial to the TARGET ends,\ncarrying DialCallStatus/DialCallSid/DialCallDuration — the authoritative\ndispatch outcome (the parent call's status callback only tracks the\noperator leg). The returned TwiML ends the call.","operationId":"twilio_dispatch_dial_result_telephony_webhooks_dispatch_dial_result_post","responses":{"200":{"description":"TwiML","content":{"application/xml":{}}}}}},"/telephony/webhooks/inbound-voice":{"post":{"tags":["telephony"],"summary":"Twilio Inbound Voice","description":"The Verdun number's Voice URL: rings the on-shift central-staff consoles, or takes a\nvoicemail. Flag off => a polite hang-up. Twilio-signature authenticated.","operationId":"twilio_inbound_voice_telephony_webhooks_inbound_voice_post","responses":{"200":{"description":"TwiML","content":{"application/xml":{}}}}}},"/telephony/webhooks/inbound-dial-result":{"post":{"tags":["telephony"],"summary":"Twilio Inbound Dial Result","description":"<Dial action> for the ring: answered => hang up; otherwise take a voicemail.","operationId":"twilio_inbound_dial_result_telephony_webhooks_inbound_dial_result_post","responses":{"200":{"description":"TwiML","content":{"application/xml":{}}}}}},"/telephony/webhooks/inbound-voicemail-done":{"post":{"tags":["telephony"],"summary":"Twilio Inbound Voicemail Done","description":"<Record action>: thank the caller and hang up.","operationId":"twilio_inbound_voicemail_done_telephony_webhooks_inbound_voicemail_done_post","responses":{"200":{"description":"TwiML","content":{"application/xml":{}}}}}},"/telephony/webhooks/inbound-status":{"post":{"tags":["telephony"],"summary":"Twilio Inbound Status","description":"The Verdun number's StatusCallback: stamps the final status, end time and duration.","operationId":"twilio_inbound_status_telephony_webhooks_inbound_status_post","responses":{"204":{"description":"Successful Response"}}}},"/telephony/webhooks/inbound-recording":{"post":{"tags":["telephony"],"summary":"Twilio Inbound Recording","description":"Voicemail recordingStatusCallback: stores the recording sid; the pipeline loop does the rest.","operationId":"twilio_inbound_recording_telephony_webhooks_inbound_recording_post","responses":{"204":{"description":"Successful Response"}}}},"/telephony/webhooks/dispatch-recording":{"post":{"tags":["telephony"],"summary":"Twilio Dispatch Recording","description":"Dial recordingStatusCallback for a police/owner call (CALL_RECORDING_ENABLED).","operationId":"twilio_dispatch_recording_telephony_webhooks_dispatch_recording_post","responses":{"204":{"description":"Successful Response"}}}},"/telephony/webhooks/call-whisper":{"post":{"tags":["telephony"],"summary":"Twilio Call Whisper","description":"<Number url>: played to the police / owner leg after it answers and before the\noperator is bridged (the recorded-call notice). Static.","operationId":"twilio_call_whisper_telephony_webhooks_call_whisper_post","responses":{"200":{"description":"TwiML","content":{"application/xml":{}}}}}},"/training/samples":{"get":{"tags":["training"],"summary":"List Training Samples","operationId":"list_training_samples_training_samples_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Keyset cursor from the previous page's next_cursor; omit for the first page","title":"Cursor"},"description":"Keyset cursor from the previous page's next_cursor; omit for the first page"},{"name":"disagreement","in":"query","required":false,"schema":{"type":"string","pattern":"^(true|false|unknown|all)$","default":"true","title":"Disagreement"}},{"name":"actor","in":"query","required":false,"schema":{"type":"string","pattern":"^(operator|agent|all)$","default":"operator","title":"Actor"}},{"name":"incident_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incident Type"}},{"name":"operator_disposition","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Enum member name, e.g. FALSE_ALARM","title":"Operator Disposition"},"description":"Enum member name, e.g. FALSE_ALARM"},{"name":"prompt_version","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Version"}},{"name":"date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date From"}},{"name":"date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date To"}},{"name":"include_demo","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Demo"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTrainingSamplesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/training/summary":{"get":{"tags":["training"],"summary":"Get Training Summary","operationId":"get_training_summary_training_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date From"}},{"name":"date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date To"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTrainingSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/training/export":{"get":{"tags":["training"],"summary":"Export Training Samples","description":"Filtered corpus as NDJSON (one sample per line, reruns embedded) —\nthe notebook-side entry point. Streamed in keyset-paginated batches\n(cursor on created_at+id); the corpus is never materialized in memory.","operationId":"export_training_samples_training_export_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"disagreement","in":"query","required":false,"schema":{"type":"string","pattern":"^(true|false|unknown|all)$","default":"true","title":"Disagreement"}},{"name":"actor","in":"query","required":false,"schema":{"type":"string","pattern":"^(operator|agent|all)$","default":"operator","title":"Actor"}},{"name":"incident_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incident Type"}},{"name":"operator_disposition","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Enum member name, e.g. FALSE_ALARM","title":"Operator Disposition"},"description":"Enum member name, e.g. FALSE_ALARM"},{"name":"prompt_version","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Version"}},{"name":"date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date From"}},{"name":"date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Date To"}},{"name":"include_demo","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Demo"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/training/operator-miss-boxes/export":{"get":{"tags":["training"],"summary":"Export Operator Miss Boxes","description":"Stage 1 of the operator miss-annotation export (design round-0 Q4,\nround-2 R1.1): NDJSON with two record types, no fold decided here.\n`record_type=\"box\"` carries each active operator-drawn annotation.\nTrailing `record_type=\"camera_history\"` records carry `source_camera_id`,\nthe box-time `site_id`/`parent_id` snapshot and `first_boxed_at` for every\never-boxed camera (one record per site it was boxed under), including\nthose with only deleted boxes or no surviving camera/event. All\ntimestamps are UTC ISO-8601. Read the COMPLETE\nexport before assigning folds; history preserves permanent camera\nrestrictions, and only box records are training labels.\nOwner-only via the router-level dependency above -- the\noffline fine-tune tooling (stage 2, not in this repo) reads this,\nresolves train vs. eval-nomination per camera against\n`camera_folds.json` on S3, and extracts frames from each box's\n`clip_s3_key` at `frame_timestamp_ms`.","operationId":"export_operator_miss_boxes_training_operator_miss_boxes_export_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/training/samples/{sample_id}":{"get":{"tags":["training"],"summary":"Get Training Sample","operationId":"get_training_sample_training_samples__sample_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sample_id","in":"path","required":true,"schema":{"type":"string","title":"Sample Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingSampleDetailModel"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/training/samples/{sample_id}/clip-url":{"get":{"tags":["training"],"summary":"Get Training Sample Clip Url","operationId":"get_training_sample_clip_url_training_samples__sample_id__clip_url_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sample_id","in":"path","required":true,"schema":{"type":"string","title":"Sample Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTrainingClipUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/training/samples/{sample_id}/rerun":{"post":{"tags":["training"],"summary":"Rerun Training Sample","description":"Synchronous replay through ii (10-60s). The response is the persisted\nrerun row — status 'failed' rows come back 200 with the error inline so\nthe network prerequisite (api-backend -> ii security group) degrades\nvisibly, not silently.","operationId":"rerun_training_sample_training_samples__sample_id__rerun_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sample_id","in":"path","required":true,"schema":{"type":"string","title":"Sample Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTrainingRerunRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingRerunModel"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/settings/":{"get":{"tags":["settings"],"summary":"Get Org Settings","operationId":"get_org_settings_settings__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOrgSettingsResponse"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["settings"],"summary":"Update Org Settings","operationId":"update_org_settings_settings__put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOrgSettingsRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOrgSettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/settings/slow-triage-target":{"put":{"tags":["settings"],"summary":"Update Slow Triage Target","description":"Dedicated, field-specific write (P1-2, 2026-09-02 owner\nchange-request) — and now the SOLE write path for\nslow_triage_target_seconds. The ops-view threshold control calls this\ninstead of the whole-record PUT /settings/ above, so a concurrent\nmonitoring_center_timezone change on another connection can never be\nlost — this endpoint doesn't read or write that column at all, closing\nthe lost-update window a client-side fresh-GET-then-whole-record-PUT\nmitigation could only narrow. Originally additive alongside the\nwhole-record PUT (which also accepted the target, omit-means-unchanged);\nthat turned out to be the SAME hazard in the mirror direction — a stale\ntimezone-editor payload carrying the target could revert a target set\nconcurrently via this endpoint — so UpdateOrgSettingsRequest dropped the\nfield entirely (Copilot re-review on PR #175) rather than being hardened\nfurther. The timezone editor keeps using the now-timezone-only PUT\n/settings/.\n\nLiteral path, no catch-all conflict with the `/` routes above.","operationId":"update_slow_triage_target_settings_slow_triage_target_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSlowTriageTargetRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOrgSettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/settings/stale-alarm-alert":{"get":{"tags":["settings"],"summary":"Get Stale Alarm Alert","description":"The stale-queue Slack alert threshold (Admin -> Alerts). Ops-lead\nonly, like the write: it reports whether a Slack webhook is configured,\nwhich is not every operator's business.","operationId":"get_stale_alarm_alert_settings_stale_alarm_alert_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetStaleAlarmAlertResponse"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["settings"],"summary":"Update Stale Alarm Alert","description":"Field-specific write, same reasoning as update_slow_triage_target.\n0 turns the alert off; null reverts to the server default.","operationId":"update_stale_alarm_alert_settings_stale_alarm_alert_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateStaleAlarmAlertRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetStaleAlarmAlertResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/settings/agent-audit-sampling":{"get":{"tags":["settings"],"summary":"Get Agent Audit Sampling","description":"How much of the AI agent's closes go into the call-audit queue, per\ndisposition (Reporting -> Overturned Calls). Ops-lead only, like the write.","operationId":"get_agent_audit_sampling_settings_agent_audit_sampling_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAgentAuditSamplingResponse"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["settings"],"summary":"Update Agent Audit Sampling","description":"Field-specific write, same reasoning as update_slow_triage_target.\nApplies to the next audit-queue read; calls already audited keep their\naudits, and calls that leave the sample simply stop showing as Needs audit.","operationId":"update_agent_audit_sampling_settings_agent_audit_sampling_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAgentAuditSamplingRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAgentAuditSamplingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/settings/ai-feedback-capture-enabled":{"put":{"tags":["settings"],"summary":"Update Ai Feedback Capture Enabled","description":"Dedicated, field-specific write for the operator AI-feedback panel's\ndark-by-default gate — OWNER-ONLY (unlike the ops-lead-gated writes\nabove), same as the feature's other owner-decided rollout gate would be.\nMirrors update_slow_triage_target's reasoning exactly: this never reads\nor writes monitoring_center_timezone or slow_triage_target_seconds, so a\nconcurrent change to either on another connection can never be lost\nhere, and the general whole-record PUT /settings/ stays timezone-only.\n\nLiteral path, no catch-all conflict with the `/` routes above.","operationId":"update_ai_feedback_capture_enabled_settings_ai_feedback_capture_enabled_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAiFeedbackCaptureEnabledRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOrgSettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/settings/deepalert-default-confidence-threshold/impact":{"get":{"tags":["settings"],"summary":"Get Deepalert Default Threshold Impact","description":"What changing the DeepAlert fleet default would move, counted by the\nserver with the same resolver the camera reads and create-event use (never\nfrom a client-side tree). OWNER-ONLY, like the write. Carries the revision\nit counted against; the confirm dialog sends it as expected_revision.\nLiteral path, no catch-all conflict with the `/` routes above.","operationId":"get_deepalert_default_threshold_impact_settings_deepalert_default_confidence_threshold_impact_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeepAlertThresholdImpactResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/settings/deepalert-default-confidence-threshold":{"put":{"tags":["settings"],"summary":"Update Deepalert Default Threshold","description":"The DeepAlert fleet default confidence floor: OWNER-ONLY, the sole\nwriter. One transaction writes the value, the revision and the durable\ndeepalert_threshold_changes row; a stale expected_revision is a 409 carrying\nthe current value and revision. This is an ACTIVATION capability: shipping\nit authorizes nothing, and it is used only per the recorded flip\nprocedure. The audit-log line below is best-effort and written after the\ncommit; the change table is the record.","operationId":"update_deepalert_default_threshold_settings_deepalert_default_confidence_threshold_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeepAlertDefaultThresholdRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeepAlertDefaultThresholdResponse"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeepAlertThresholdConflictResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/scheduling/coverage-groups":{"get":{"tags":["scheduling"],"summary":"Get Coverage Groups","operationId":"get_coverage_groups_scheduling_coverage_groups_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCoverageGroupsResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["scheduling"],"summary":"Create Coverage Group","operationId":"create_coverage_group_scheduling_coverage_groups_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCoverageGroupRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCoverageGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/scheduling/coverage-groups/{group_id}":{"patch":{"tags":["scheduling"],"summary":"Update Coverage Group","operationId":"update_coverage_group_scheduling_coverage_groups__group_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCoverageGroupRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCoverageGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["scheduling"],"summary":"Delete Coverage Group","operationId":"delete_coverage_group_scheduling_coverage_groups__group_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteCoverageGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/coverage-groups/{group_id}/grants":{"put":{"tags":["scheduling"],"summary":"Update Coverage Grants","operationId":"update_coverage_grants_scheduling_coverage_groups__group_id__grants_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCoverageGrantsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCoverageGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/coverage-groups/{group_id}/members":{"put":{"tags":["scheduling"],"summary":"Update Coverage Members","description":"Replace this coverage group's standing members.\n\nCOVERAGE GROUPS CONFER SCOPE, NEVER PERMISSION. A member sees every site\nthis group's grants cover -- off-shift included, because a manager\nreviewing last night must see the same sites they cover -- and gains no\ncapability whatsoever; theirs remain exactly their role's. Shift seating is\na separate thing: it says who is on the desk when, and confers nothing.\n\nThe user-side mirror is `PUT /operator/{id}/coverage-groups`; both write\nthrough the two ends of one relationship and share their rails.\n\nReplace-all: the list sent IS the membership, and `[]` clears it (which is\nalso how a group is prepared for deletion -- deleting one with members is\nrefused with a 409). 409 for a structurally scoped operator (site_manager /\naccount_manager / location_manager) or an unconfined one (owner / monitoring_manager)\nanywhere in the list; 404 for\nan unknown group or operator id.","operationId":"update_coverage_members_scheduling_coverage_groups__group_id__members_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCoverageMembersRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCoverageGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/shift-blocks":{"get":{"tags":["scheduling"],"summary":"Get Shift Blocks","operationId":"get_shift_blocks_scheduling_shift_blocks_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Start"}},{"name":"end","in":"query","required":true,"schema":{"type":"string","format":"date","title":"End"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetShiftBlocksResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["scheduling"],"summary":"Create Shift Block","operationId":"create_shift_block_scheduling_shift_blocks_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateShiftBlockRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShiftBlockResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/shift-blocks/{shift_id}":{"patch":{"tags":["scheduling"],"summary":"Update Shift Block","operationId":"update_shift_block_scheduling_shift_blocks__shift_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"shift_id","in":"path","required":true,"schema":{"type":"string","title":"Shift Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateShiftBlockRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShiftBlockResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["scheduling"],"summary":"Delete Shift Block","operationId":"delete_shift_block_scheduling_shift_blocks__shift_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"shift_id","in":"path","required":true,"schema":{"type":"string","title":"Shift Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteShiftBlockResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/support-shifts":{"get":{"tags":["scheduling"],"summary":"Get Support Shifts","description":"Support shifts overlapping [start, end] (at most 62 days), each with its\nordered on-call list and every seated person's display name.","operationId":"get_support_shifts_scheduling_support_shifts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Start"}},{"name":"end","in":"query","required":true,"schema":{"type":"string","format":"date","title":"End"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSupportShiftsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["scheduling"],"summary":"Create Support Shift","description":"404 for an unknown operator id; 409 `{detail: {code:\n\"ineligible_operator\"}}` when one is not a support or owner user; 422 for\nduplicates or a bad range.","operationId":"create_support_shift_scheduling_support_shifts_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSupportShiftRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportShiftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/support-roster":{"get":{"tags":["scheduling"],"summary":"Get Support Roster","description":"Everyone who may be put on the support rotation: support and owner\nusers, sorted by name.","operationId":"get_support_roster_scheduling_support_roster_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSupportRosterResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/scheduling/support-on-call":{"get":{"tags":["scheduling"],"summary":"Get Support On Call","description":"Who is on call for support right now, in the monitoring center's\ntimezone. Among overlapping running shifts the most-recently-started one\nwins; its seat 0 is the primary and the rest are the backups.","operationId":"get_support_on_call_scheduling_support_on_call_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSupportOnCallResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/scheduling/support-shifts/{shift_id}":{"patch":{"tags":["scheduling"],"summary":"Update Support Shift","description":"All fields optional; `operator_ids` replaces the on-call list in order.\nSame 404 / 409 ineligible_operator / 422 contract as create.","operationId":"update_support_shift_scheduling_support_shifts__shift_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"shift_id","in":"path","required":true,"schema":{"type":"string","title":"Shift Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSupportShiftRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportShiftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["scheduling"],"summary":"Delete Support Shift","operationId":"delete_support_shift_scheduling_support_shifts__shift_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"shift_id","in":"path","required":true,"schema":{"type":"string","title":"Shift Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSupportShiftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/shifts":{"get":{"tags":["scheduling"],"summary":"Get Shifts","description":"DEPRECATED hourly view of shift blocks. Use GET /scheduling/shift-blocks.\n\nLossy by construction: a shift paints every hour it overlaps by any amount,\nand overlapping shifts have their seats merged into one list ordered\nmost-recently-started first, so index 0 is the incoming operator during a\nhandoff. Retires when ops-view moves to the calendar.","operationId":"get_shifts_scheduling_shifts_get","deprecated":true,"security":[{"HTTPBearer":[]}],"parameters":[{"name":"start","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Start"}},{"name":"end","in":"query","required":true,"schema":{"type":"string","format":"date","title":"End"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetShiftsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/shifts/cell":{"put":{"tags":["scheduling"],"summary":"Update Shift Cell","description":"DEPRECATED hourly write. Use POST/PATCH /scheduling/shift-blocks.\n\nReplaces one hour's seats by CARVING the hour out of every shift that\ncovers it, which can split a long block in two - the honest reading of\n\"these are the seats for this hour\", and unreachable from the deployed\nconsole, which has no way to author long blocks. Retires with the grid.","operationId":"update_shift_cell_scheduling_shifts_cell_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateShiftCellRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateShiftCellResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"deprecated":true,"security":[{"HTTPBearer":[]}]}},"/scheduling/shifts/copy-week":{"post":{"tags":["scheduling"],"summary":"Copy Shift Week","description":"Copy one week's shift blocks onto another (the grid's \"copy to next week\").\n\nWrites only into an empty target week unless `replace` is set; otherwise\nanswers `target_not_empty` with the count and writes nothing, so the\nconsole can confirm before overwriting. Not deprecated with the hourly\npair below: it operates on blocks and survives the calendar migration.","operationId":"copy_shift_week_scheduling_shifts_copy_week_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyShiftWeekRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CopyShiftWeekResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/scheduling/fleet-bands":{"get":{"tags":["scheduling"],"summary":"Get Fleet Bands","operationId":"get_fleet_bands_scheduling_fleet_bands_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"start","in":"query","required":true,"schema":{"type":"string","format":"date","title":"Start"}},{"name":"end","in":"query","required":true,"schema":{"type":"string","format":"date","title":"End"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetFleetBandsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/fleet-bands/detail":{"get":{"tags":["scheduling"],"summary":"Get Fleet Band Detail","description":"The site list behind the band covering `at`.\n\n404s when no band covers that instant - nothing monitored is not an empty\nband, it is no band, and the week view never drew one there to click.","operationId":"get_fleet_band_detail_scheduling_fleet_bands_detail_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"at","in":"query","required":true,"schema":{"type":"string","format":"date-time","title":"At"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetFleetBandDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/on-shift":{"get":{"tags":["scheduling"],"summary":"Get Site On Shift","operationId":"get_site_on_shift_scheduling_on_shift_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteOnShiftResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/scheduling/on-shift/groups":{"get":{"tags":["scheduling"],"summary":"Get On Shift Groups","description":"Who is on shift now in every coverage group the caller may see.\n\nThe queue page's \"On shift\" chip. Same rules as `/on-shift` per group; an\nunconfined caller sees every group plus the floor-wide `also_clocked_in`,\na confined caller sees only groups its tenant scope allows and an empty\n`also_clocked_in`.","operationId":"get_on_shift_groups_scheduling_on_shift_groups_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetOnShiftGroupsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/scheduling/timeclock/clock-in":{"post":{"tags":["scheduling"],"summary":"Clock In","operationId":"clock_in_scheduling_timeclock_clock_in_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClockInResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/scheduling/timeclock/clock-out":{"post":{"tags":["scheduling"],"summary":"Clock Out","operationId":"clock_out_scheduling_timeclock_clock_out_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClockOutResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/scheduling/timeclock/status":{"get":{"tags":["scheduling"],"summary":"Timeclock Status","operationId":"timeclock_status_scheduling_timeclock_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimeclockStatusResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/scheduling/timeclock/active":{"get":{"tags":["scheduling"],"summary":"Timeclock Active","operationId":"timeclock_active_scheduling_timeclock_active_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetActivePunchesResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/sites/{site_id}/speakers":{"post":{"tags":["speakers"],"summary":"Create Speaker","operationId":"create_speaker_sites__site_id__speakers_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSpeakerRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["speakers"],"summary":"Get Speakers For Site","operationId":"get_speakers_for_site_sites__site_id__speakers_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSpeakersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/speakers/discover":{"post":{"tags":["speakers"],"summary":"Discover Speakers","description":"Probe one host for Axis speakers: which device answers on each\ncandidate port (serial, model, firmware - read without credentials), and,\nwhen credentials are available, whether they work there and which clip ids\nthe unit holds. Built for the 2026-10-03 install where two horns sat behind\none IP and the record pointed at the wrong one.\n\nWRITES NOTHING, but it is a POST that makes outbound calls to a customer\naddress with device credentials, so it takes exactly the guard of creating\na speaker on the site (`require_manage_sites_cameras` + the service's site\nscope check), and read_only is refused by the global write guardrail like\nany other POST - deliberately no allowlist entry. Not activity-logged:\nnothing changed, and the request carries a password.","operationId":"discover_speakers_sites__site_id__speakers_discover_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverSpeakersRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerDiscoveryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/talk-speakers":{"get":{"tags":["speakers"],"summary":"Get Talk Speakers For Site","description":"The speaker picker for live voice down - the ONE operator-readable\nspeaker route.\n\nWHY IT EXISTS. Everything else in this router that lists speakers is\n`require_site_admin` (owner or monitoring manager; it was `require_owner`\nuntil the 2026-09-15 decision opened the speaker surface to the monitoring\ncentre's managers), so before this there was no way for an operator to learn\nthe id of a speaker they are already permitted to talk through: the three\ntalk-session routes all take a `speaker_id` the client must already know,\nand the event payload carries only a boolean. The picker had no source.\n\nWHY THIS GUARD IS NOT A WIDENING. `ESCALATE_VOICE_DOWN` already lets its\nholder MAKE SOUND at this site through `POST /speakers/{id}/talk-sessions`.\nReading the label of the horn they are about to speak through is strictly\nless than what they can already do, and the response is a minimal\nprojection that deliberately excludes every field describing how we reach\nthe device - see `TalkSpeakerResponse`, which is a disclosure boundary with\nthe reasoning attached.","operationId":"get_talk_speakers_for_site_sites__site_id__talk_speakers_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTalkSpeakersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-health/speakers":{"get":{"tags":["speakers","camera-health"],"summary":"Get Camera Health Speakers","description":"The Camera health tab's Speakers section: in-scope speakers whose last\nhealth check failed (longest down first), plus counts of healthy / failed /\nnever-checked speakers.\n\nStaff only, the same gate as `GET /camera-health/flags` (owner, monitoring\nmanager, operator, support). The caller's per-site TenantScope is the only\ntenant filter. Retired and disabled speakers are excluded; a never-checked\nspeaker is unknown, counted but not listed. Read-only, not activity-logged.","operationId":"get_camera_health_speakers_camera_health_speakers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraHealthSpeakersResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/speakers/{speaker_id}":{"put":{"tags":["speakers"],"summary":"Edit Speaker","operationId":"edit_speaker_speakers__speaker_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditSpeakerRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["speakers"],"summary":"Delete Speaker","operationId":"delete_speaker_speakers__speaker_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSpeakerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speakers/{speaker_id}/map-position":{"put":{"tags":["speakers"],"summary":"Update Speaker Map Position","operationId":"update_speaker_map_position_speakers__speaker_id__map_position_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSpeakerMapPositionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speakers/{speaker_id}/cameras":{"get":{"tags":["speakers"],"summary":"Get Speaker Cameras","operationId":"get_speaker_cameras_speakers__speaker_id__cameras_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerMappingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["speakers"],"summary":"Set Speaker Cameras","operationId":"set_speaker_cameras_speakers__speaker_id__cameras_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSpeakerCamerasRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerMappingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speakers/{speaker_id}/test":{"post":{"tags":["speakers"],"summary":"Test Speaker","description":"Play the deterrent clip on demand — install verification with the\ncustomer on the phone. This makes real sound at a real site, so it is\naudited like a mutation even though it changes no row.","operationId":"test_speaker_speakers__speaker_id__test_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerTestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speakers/{speaker_id}/sip-test":{"post":{"tags":["speakers"],"summary":"Run Speaker Sip Test","description":"Place a REAL AI call on this speaker to prove the SIP path works.\n\nSITE-ADMIN ONLY, like the rest of this router and for the same reason: it\nhands out real-world sound at a customer site. During an install it is often\nan ESCALATE_VOICE_DOWN operator on the phone with the technician — widening\nit to them was considered and declined (round-0 review, OQ-5), and that\nrefusal is unchanged: the 2026-09-15 widening admitted the monitoring\nmanager, who runs the floor, not the operator on the call.\n\nIT ANNOUNCES NOTHING AND CANNOT. The call row has no reservation, so the\nonly writer of `announced_at` has nothing to write on; the provider webhook\ndrops the call as not-a-talk-down; and the test agent is published with no\ncustom functions, so it cannot escalate, dismiss, or record an outcome.\n\n503 until `RETELL_SIPTEST_AGENT_ID` is set (or when it equals the talk-down\nagent's id). 409 for a speaker tested inside the last minute, for a site\nthat has used its daily allowance, and for a horn somebody else is already\ntalking on. **A DEVICE REFUSAL IS A 200, NOT A 502**: the device's own error\nbody (\"Invalid SIPAccountId 'Gate horn'\") is the answer the owner pressed the\nbutton for, so it comes back in `device_error` with `passed: false` rather\nthan as an error carrying nothing. A 502 here means the device could not be\nreached at all.\n\n**A 200 MEANS THE HORN IS ON A CALL RIGHT NOW**, not that the test is over:\nthe route returns at connect so the owner hears it, and the watchdog hangs\nit up at the cap. Poll the GET below to watch it end.","operationId":"run_speaker_sip_test_speakers__speaker_id__sip_test_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerSipTestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["speakers"],"summary":"Get Speaker Sip Test","description":"The newest SIP test on this speaker, or null if it has never been tested.\n\nPolled by the result panel while the call is open, which is how the owner\nsees `Terminated` and the platform's own disconnection reason land rather\nthan being told the test \"succeeded\" and left to wonder.\n\nTHIS GET WRITES, AND DELIBERATELY IS NOT AUDITED: `refresh_platform_view`\ncaches what the voice platform itself says about this tenant's own call —\nits status, disconnection reason and duration — onto the test row,\nbest-effort, because there is no webhook for a test call and this poll is\none of only two things that ever re-ask. It records no operator decision and\nchanges nothing an operator did, so an `ActivityLogService` entry for it\nwould be an audit trail of the provider's opinion rather than of anybody's\naction (round-2 review, accepted and recorded).","operationId":"get_speaker_sip_test_speakers__speaker_id__sip_test_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SpeakerSipTestResponse"},{"type":"null"}],"title":"Response Get Speaker Sip Test Speakers  Speaker Id  Sip Test Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["speakers"],"summary":"Cancel Speaker Sip Test","description":"Hang the test call up NOW.\n\nTHE RELEASE FOR A HORN NOBODY CAN GET OFF. A test whose dial went\nindeterminate holds its speaker until the watchdog gives up, and while it\ndoes, Talk Down, the clip Test and live Talk all refuse — with somebody\nstanding at the device. `DELETE /events/{id}/voice-agent-call` cannot help:\nit is addressed by EVENT and joins `agent_actions`, which a test row has\nnone of.\n\nOps-lead only and tenant-scoped exactly like the POST, through the same\n`_load_speaker`. Idempotent: a test that has already ended is returned\nunchanged, and 404 means this speaker has never been tested.\n\nIt asks the DEVICE to hang up and closes the row only if that is allowed —\nan unconfirmed hang-up leaves the row open with its reason recorded and the\nspeaker held, which is the shared rule for releasing a speaker and not a\nsecond opinion about it.","operationId":"cancel_speaker_sip_test_speakers__speaker_id__sip_test_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SpeakerSipTestResponse"},{"type":"null"}],"title":"Response Cancel Speaker Sip Test Speakers  Speaker Id  Sip Test Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speakers/{speaker_id}/sip-accounts":{"get":{"tags":["speakers"],"summary":"Get Speaker Sip Accounts","description":"The device's own SIP accounts, read live, so the console offers a\ndropdown instead of a text field somebody transcribes into.\n\n502 when the device cannot be reached — the console then degrades to its\nfree-text field, so a horn behind an unfinished port forward does not block\nthe rest of the install.","operationId":"get_speaker_sip_accounts_speakers__speaker_id__sip_accounts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerSipAccountsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speakers/{speaker_id}/onboarding-checklist":{"put":{"tags":["speakers"],"summary":"Set Speaker Onboarding Checklist","description":"Merge the owner's device-setup attestations. PARTIAL — send only what\nchanged.\n\nITS OWN ROUTE, NEVER `PUT /speakers/{id}`: that body is a full replace that\nre-defaults every omitted field, so checklist state living there would be\none forgotten key away from an unrelated edit wiping the record of a device\nsomeone drove to a site to configure.","operationId":"set_speaker_onboarding_checklist_speakers__speaker_id__onboarding_checklist_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSpeakerChecklistRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeakerChecklistResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cameras/{camera_id}/speakers":{"get":{"tags":["speakers"],"summary":"Get Camera Speakers","operationId":"get_camera_speakers_cameras__camera_id__speakers_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","title":"Camera Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraSpeakersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/voice-down":{"post":{"tags":["speakers"],"summary":"Manual Voice Down","description":"Operator Talk Down: play the deterrent clip on this event's camera-mapped\nspeakers, now.\n\nSame resolution and delivery path as the agent, audited as an agent_actions\nrow with actor=operator. No observation window and no disposition — the\nevent stays exactly where it is in the queue and the operator keeps\nwatching. A failed attempt is audited too, then surfaces as a 502.","operationId":"manual_voice_down_events__event_id__voice_down_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceDownResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/voice-agent-call":{"post":{"tags":["speakers"],"summary":"Start Voice Agent","description":"Start the AI voice announcement on this event's camera, now.\n\nThe review panel's voice modes are this and Live Talk (owner decision\n2026-10-03). Same permission and tenant scoping as the hang-up below and\nthe manual Talk Down. Runs the agent's own call machinery under an\noperator-authored reservation that never auto-dismisses the event - see\n`VoiceAgentService.start_for_operator`.","operationId":"start_voice_agent_events__event_id__voice_agent_call_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceAgentStartResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["speakers"],"summary":"Hang Up Voice Agent","description":"Hang the AI voice agent up and take the incident back.\n\nSame permission and same tenant scoping as the operator's Talk Down, and\nfor the same reason: this is an operations power over real-world sound at a\ncustomer site, not a configuration one.\n\nThe event id is the ONLY address. There is no call id in the request, so an\noperator can only ever reach the call belonging to an event their grants\nalready let them open.\n\n404 when no announcement was ever placed; 409 when one was and has already\nended; 200 otherwise — including when the device refuses to hang up, which\nis logged rather than raised (see the response model).","operationId":"hang_up_voice_agent_events__event_id__voice_agent_call_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceAgentHangupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speakers/{speaker_id}/talk-sessions":{"post":{"tags":["voice-sessions"],"summary":"Start Talk Session","description":"Claim a speaker for live voice and hand back the socket credentials.\n\nTalking to a speaker with NO camera mapping is allowed (owner decision\n2026-08-25): an operator addresses a place, not a camera. Tenant scoping is\nwhat contains it - `SpeakersService.get_speaker` runs\n`ensure_parent_in_scope`, so a site-scoped operator cannot reach another\ncustomer's speaker.","operationId":"start_talk_session_speakers__speaker_id__talk_sessions_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"speaker_id","in":"path","required":true,"schema":{"type":"string","title":"Speaker Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/StartVoiceSessionRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartVoiceSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/speakers/talk-sessions/{session_id}":{"delete":{"tags":["voice-sessions"],"summary":"Stop Talk Session","description":"Stop a session. Idempotent: a client that both closes the socket and\nsends this is doing the right thing twice, not making an error.","operationId":"stop_talk_session_speakers_talk_sessions__session_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StopVoiceSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/voice-sessions/{session_id}/audio-url":{"get":{"tags":["voice-sessions"],"summary":"Get Talk Session Audio Url","description":"Presigned (1 h) link to the recorded audio of an alarm-scoped Live Talk\nsession, for the internal escalations log. OPS LEADS ONLY (owner |\nmonitoring_manager); customers, site managers and operators get 403. The\nsession's alarm must be in the caller's scope. 404 when nothing was\nrecorded or the 30-day original has expired. The link is never logged.","operationId":"get_talk_session_audio_url_voice_sessions__session_id__audio_url_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceSessionAudioUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}/operator-instructions":{"put":{"tags":["accounts"],"summary":"Set or clear this account's operator instructions","description":"Free-text instructions shown to the HUMAN monitoring operator on every alarm at any of this account's sites, and again at the escalation step. NEVER enters an AI prompt — the agent-context fields are what steer the model. Requires the owner, monitoring_manager or site_manager role; a site_manager needs the account's client granted in FULL (a grant narrowed to some of its sites is refused). An account id outside the caller's scope, or a retired one, answers 404 rather than 403: the status code must not tell a caller which of another client's account ids exist.","operationId":"update_account_operator_instructions_accounts__account_id__operator_instructions_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateOperatorInstructionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperatorInstructionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/accounts/{account_id}":{"put":{"tags":["accounts"],"summary":"Edit Account","operationId":"edit_account_accounts__account_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditAccountRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditAccountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["accounts"],"summary":"Delete Account","operationId":"delete_account_accounts__account_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Account Id"}},{"name":"delete_sites","in":"query","required":false,"schema":{"type":"boolean","description":"When true, also RETIRE every live site assigned to this account, together with its cameras, NVRs and speakers. The database retirement is atomic: account, sites and subtree are soft-deleted in one transaction, or none of them are. Destruction of EXTERNAL state -- credential secrets in Secrets Manager, DeepAlert sensors, stored floor plans -- happens AFTER that commit on a best-effort basis and is not covered by the transaction, so a success response guarantees the retirement, not that every external resource was removed. Default false releases the sites instead, leaving them live under the client.","default":false,"title":"Delete Sites"},"description":"When true, also RETIRE every live site assigned to this account, together with its cameras, NVRs and speakers. The database retirement is atomic: account, sites and subtree are soft-deleted in one transaction, or none of them are. Destruction of EXTERNAL state -- credential secrets in Secrets Manager, DeepAlert sensors, stored floor plans -- happens AFTER that commit on a best-effort basis and is not covered by the transaction, so a success response guarantees the retirement, not that every external resource was removed. Default false releases the sites instead, leaving them live under the client."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAccountResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/integrations":{"get":{"tags":["integrations"],"summary":"Get Integrations For Parent","operationId":"get_integrations_for_parent_parents__parent_id__integrations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteIntegrationsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["integrations"],"summary":"Create Integration","operationId":"create_integration_parents__parent_id__integrations_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateIntegrationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteIntegrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/integrations":{"get":{"tags":["integrations"],"summary":"Get Integrations For Site","description":"The connections assigned to this site, at most one per provider. The full\nowner projection — never a credential.","operationId":"get_integrations_for_site_sites__site_id__integrations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssignedSiteIntegrationsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/integrations/{integration_id}":{"put":{"tags":["integrations"],"summary":"Assign Site Integration","description":"Assign an existing connection of the site's client to the site.\n\nIdempotent (200, `already_assigned: true`) for the same pair. 404 for a\nconnection of another client (out-of-tenant is not found). 409 when the site\nalready uses a DIFFERENT connection of that provider, or has live cameras\nstill streaming through one.","operationId":"assign_site_integration_sites__site_id__integrations__integration_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"integration_id","in":"path","required":true,"schema":{"type":"string","title":"Integration Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssignSiteIntegrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["integrations"],"summary":"Unassign Site Integration","description":"Unassign a connection from a site. 404 when it is not assigned; 409 while\nlive cameras on THIS site are onboarded from it. Cameras are never touched.","operationId":"unassign_site_integration_sites__site_id__integrations__integration_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}},{"name":"integration_id","in":"path","required":true,"schema":{"type":"string","title":"Integration Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteIntegrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/integrations/{integration_id}":{"patch":{"tags":["integrations"],"summary":"Update Integration","description":"Edit the connection's ONE display field: the vendor console's org short\nname. No provider call, no secret read or write, no status change.\n\nIt is a separate route rather than part of `credentials` because it cannot\ntouch the credential at all — see SiteIntegrationsService.update_integration\nfor why that separation is the point, and why a create-only field would have\nbeen unreachable for every connection that already exists in production.","operationId":"update_integration_parents__parent_id__integrations__integration_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}},{"name":"integration_id","in":"path","required":true,"schema":{"type":"string","title":"Integration Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateIntegrationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteIntegrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["integrations"],"summary":"Delete Integration","operationId":"delete_integration_parents__parent_id__integrations__integration_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}},{"name":"integration_id","in":"path","required":true,"schema":{"type":"string","title":"Integration Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteIntegrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/integrations/{integration_id}/test":{"post":{"tags":["integrations"],"summary":"Test Integration","operationId":"test_integration_parents__parent_id__integrations__integration_id__test_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}},{"name":"integration_id","in":"path","required":true,"schema":{"type":"string","title":"Integration Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteIntegrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/integrations/{integration_id}/credentials":{"put":{"tags":["integrations"],"summary":"Rotate Integration Credentials","operationId":"rotate_integration_credentials_parents__parent_id__integrations__integration_id__credentials_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}},{"name":"integration_id","in":"path","required":true,"schema":{"type":"string","title":"Integration Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateIntegrationCredentialsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteIntegrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/integrations/{integration_id}/cameras":{"get":{"tags":["integrations"],"summary":"Get Integration Cameras","operationId":"get_integration_cameras_parents__parent_id__integrations__integration_id__cameras_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}},{"name":"integration_id","in":"path","required":true,"schema":{"type":"string","title":"Integration Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetIntegrationCamerasResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/parents/{parent_id}/integrations/{integration_id}/onboard":{"post":{"tags":["integrations"],"summary":"Onboard Integration Cameras","operationId":"onboard_integration_cameras_parents__parent_id__integrations__integration_id__onboard_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","title":"Parent Id"}},{"name":"integration_id","in":"path","required":true,"schema":{"type":"string","title":"Integration Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardIntegrationCamerasRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardIntegrationCamerasResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/internal/integrations/{integration_id}/cameras/{device_id}/stream":{"get":{"tags":["integrations"],"summary":"Get Internal Integration Camera Stream","operationId":"get_internal_integration_camera_stream_internal_integrations__integration_id__cameras__device_id__stream_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"integration_id","in":"path","required":true,"schema":{"type":"string","title":"Integration Id"}},{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StreamGrantResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/internal/bridge/targets":{"get":{"tags":["integrations"],"summary":"Get Bridge Targets","operationId":"get_bridge_targets_internal_bridge_targets_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"parent_id","in":"query","required":true,"schema":{"type":"string","title":"Parent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BridgeTargetResponse"},"title":"Response Get Bridge Targets Internal Bridge Targets Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/camera-groups":{"get":{"tags":["camera-groups"],"summary":"Get Camera Groups For Site","operationId":"get_camera_groups_for_site_sites__site_id__camera_groups_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraGroupsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["camera-groups"],"summary":"Create Camera Group","operationId":"create_camera_group_sites__site_id__camera_groups_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCameraGroupRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraGroupModel"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-groups/{group_id}":{"get":{"tags":["camera-groups"],"summary":"Get Camera Group","operationId":"get_camera_group_camera_groups__group_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraGroupModel"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["camera-groups"],"summary":"Edit Camera Group","operationId":"edit_camera_group_camera_groups__group_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCameraGroupRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraGroupModel"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["camera-groups"],"summary":"Delete Camera Group","operationId":"delete_camera_group_camera_groups__group_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteCameraGroupResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-groups/{group_id}/members":{"get":{"tags":["camera-groups"],"summary":"Get Camera Group Members","operationId":"get_camera_group_members_camera_groups__group_id__members_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraGroupMembersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["camera-groups"],"summary":"Set Camera Group Members","operationId":"set_camera_group_members_camera_groups__group_id__members_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCameraGroupMembersRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraGroupMembersResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-groups/{group_id}/monitoring-schedule":{"get":{"tags":["camera-groups"],"summary":"Get Camera Group Monitoring Schedule","operationId":"get_camera_group_monitoring_schedule_camera_groups__group_id__monitoring_schedule_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetMonitoringScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["camera-groups"],"summary":"Update Camera Group Monitoring Schedule","description":"Give this camera group a week. THIS is the write that changes monitoring.\n\nEvery member camera without its own override now follows THIS week instead\nof the site schedule (overlapping scheduled groups combine under \"off\nwins\") — so an off cell here silences a camera the site schedule watches,\nand the ingest gate starts 409-ing its events; a monitored cell here keeps\nthe camera at monitored even while the site week says heightened.","operationId":"update_camera_group_monitoring_schedule_camera_groups__group_id__monitoring_schedule_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMonitoringScheduleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMonitoringScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["camera-groups"],"summary":"Delete Camera Group Monitoring Schedule","description":"Remove the camera group's schedule, keeping the group and its members.\n\nThe un-silence path: the group stops contributing immediately, so every\nmember camera reverts to its OTHER scheduled groups, or to the site\nschedule when it has none. 404 when there is no schedule — \"there was none\" and \"I removed\nit\" must not look the same.","operationId":"delete_camera_group_monitoring_schedule_camera_groups__group_id__monitoring_schedule_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteScheduleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/site-summaries/unsubscribe":{"get":{"tags":["site summaries"],"summary":"Get Site Summary Unsubscribe","description":"Show the confirm form. CHANGES NOTHING, AND VALIDATES NOTHING (R6.7).\n\nBrief §5 had the GET flip the flag; it must not. Mail scanners and link\nprefetchers follow GET links, so that shape unsubscribes people who never\nclicked — the customer then reports \"you stopped sending my report\" and\nnothing in the system disagrees with them. The state change is the POST.\n\nTHE PAGE IS BYTE-IDENTICAL FOR EVERY INPUT, apart from the token echoed\ninto the hidden field it arrived in. The previous shape rendered a \"this\nlink is no longer valid\" page without a form for a bad token and a confirm\npage with one for a good token, which is an oracle: anyone holding a\nharvested link learns from a single unauthenticated GET whether it is still\nlive — i.e. whether that contact is still on this site's summary. Nothing\nis lost by dropping the distinction, because a forged token then gets a\nbutton whose POST already answers uniformly.\n\nThe only branch left is provisioning: with no usable secret the route 503s,\nexactly as the POST does, because no link we ever minted can be honoured.","operationId":"get_site_summary_unsubscribe_site_summaries_unsubscribe_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","maxLength":1024,"description":"Unsubscribe token from the e-mail link.","default":"","title":"Token"},"description":"Unsubscribe token from the e-mail link."}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["site summaries"],"summary":"Post Site Summary Unsubscribe","description":"Flip `daily_summary_opt_out`. Idempotent.\n\nTHE TOKEN MAY ARRIVE TWO WAYS (amendment A6). RFC 8058 one-click POSTs to\nthe `List-Unsubscribe` URL itself with the body `List-Unsubscribe=One-Click`\n— so the token is in the QUERY STRING and the body carries no token at all.\nThe confirm form in the GET page posts it as a field. The query wins when\nboth are present, because that is the URL we signed into the header.\n\nTHE RESPONSE IS THE SAME PAGE FOR EVERY OUTCOME — valid, expired, forged,\na contact row that no longer exists, and (R6.5) a contact row whose e-mail\nhas since been changed to somebody else's. Anything else makes this an\noracle for \"was this token ever real\", which is a statement about a real\ncustomer's address, answerable by anyone who can guess.","operationId":"post_site_summary_unsubscribe_site_summaries_unsubscribe_post","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","maxLength":1024,"description":"Unsubscribe token; RFC 8058 one-click posts to the List-Unsubscribe URL, so it arrives here.","default":"","title":"Token"},"description":"Unsubscribe token; RFC 8058 one-click posts to the List-Unsubscribe URL, so it arrives here."}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_post_site_summary_unsubscribe_site_summaries_unsubscribe_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/site-summaries":{"get":{"tags":["site summaries"],"summary":"List Site Summaries","description":"One date's summaries across the caller's in-scope sites.\n\n`date=today` returns the computed \"today so far\" view (counts and\nescalations only, capped — see the service). Any other value is a\nsite-local calendar date and returns stored rows plus a stub for any date a\nstretched window swallowed.","operationId":"list_site_summaries_site_summaries_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"date","in":"query","required":false,"schema":{"type":"string","description":"Site-local YYYY-MM-DD, or 'today' for the live view.","default":"today","title":"Date"},"description":"Site-local YYYY-MM-DD, or 'today' for the live view."},{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id"}},{"name":"account_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id"}},{"name":"site_ids","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":200},{"type":"null"}],"title":"Site Ids"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteSummariesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/site-summaries/{summary_id}":{"get":{"tags":["site summaries"],"summary":"Get Site Summary","description":"One summary in full: facts, inclusions, current settings, and who a send\nwould reach. A customer-side login gets the customer projection and no\ngrounding block; `view` says which.","operationId":"get_site_summary_site_summaries__summary_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"summary_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Summary Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/site-summaries/{summary_id}/email":{"get":{"tags":["site summaries"],"summary":"Get Site Summary Email","description":"The customer e-mail's HTML body, server-rendered.\n\nOne of the handful of routes with no `response_model`: the body is a\ndocument, not JSON. `Content-Security-Policy: default-src 'none'` is the\nload-bearing header — the body interpolates operator-typed camera labels\nand LLM prose, every value HTML-escaped, and the CSP means that even a\nfuture escaping slip cannot load a script or a font from this response.\nThe one thing it admits is an image from S3: the preview shows each\ncard's snapshot through a short presigned URL (the mailed copy\nembeds the bytes instead), so `img-src` names S3 and nothing else.\n\nRendered from the CUSTOMER PROJECTION for every role — the e-mail is a\ncustomer artefact whoever is previewing it.","operationId":"get_site_summary_email_site_summaries__summary_id__email_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"summary_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Summary Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/site-summaries/{summary_id}/sends":{"get":{"tags":["site summaries"],"summary":"Get Site Summary Sends","description":"Delivery history for one summary, newest first, including the attempts\nthat deliberately sent nothing.\n\nA customer-side login (ACCOUNT_MANAGER / LOCATION_MANAGER) gets the customer\nprojection: no test sends, no SES\nmessage ids, no raw provider error (R6.3).","operationId":"get_site_summary_sends_site_summaries__summary_id__sends_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"summary_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Summary Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteSummarySendsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/summary-settings":{"get":{"tags":["site summaries"],"summary":"Get Site Summary Settings","description":"This site's summary settings; the defaults with `is_default: true` when\nno row exists. Readable by a customer-side login — it is their own report's\nschedule.","operationId":"get_site_summary_settings_sites__site_id__summary_settings_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteSummarySettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["site summaries"],"summary":"Update Site Summary Settings","description":"Upsert this site's summary settings (owner or site manager only).\n\nThe activity log is recorded AFTER the commit and records field names\nonly — the same rule every other write in this repo follows.","operationId":"update_site_summary_settings_sites__site_id__summary_settings_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteSummarySettingsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteSummarySettingsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/site-summaries/{summary_id}/inclusions":{"patch":{"tags":["site summaries"],"summary":"Update Site Summary Inclusions","description":"Tick one event into (or out of) this summary's customer e-mail.\n\n`require_summary_write` (owner, monitoring manager, parent manager) for the\nsame reason the settings write carries it: this decides what a customer\nreads, and a permission would also be held by any ordinary operator an owner\nhad granted it. A customer-side login is refused BY ROLE — they read their own\nreport, they do not edit it.\n\nAn event that is in neither of the sheet's lists is a 404 raised inside the\nscoped resolution, so there is no id a caller can probe to learn that an\nevent exists in another tenant.","operationId":"update_site_summary_inclusions_site_summaries__summary_id__inclusions_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"summary_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Summary Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteSummaryInclusionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSiteSummaryInclusionsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/site-summaries/{summary_id}/send":{"post":{"tags":["site summaries"],"summary":"Send Site Summary","description":"Send this summary now — `test` to the caller, `resend` to the contacts.\n\nTHREE REFUSALS BEFORE ANY MAIL:\n\n* 503 when `SITE_SUMMARY_EMAILS_ENABLED` is not exactly `true`. That\n  INCLUDES `mode: test`: the flag means \"this deployment may put mail in\n  somebody's inbox\", and an operator's own inbox is somebody's.\n* 429 when this summary has already spent its five test/resend sends in the\n  last hour. Counted in the DATABASE, because two ECS tasks would give a\n  per-process counter double the budget.\n* 404 for a summary outside the caller's scope, from the resolving query.\n\nThe response is the audit row, so the drawer can render the outcome per\nrecipient without a second call.","operationId":"send_site_summary_site_summaries__summary_id__send_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"summary_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Summary Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendSiteSummaryRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteSummarySendItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/site-summaries/{summary_id}/regenerate":{"post":{"tags":["site summaries"],"summary":"Regenerate Site Summary","description":"Re-run the narrative for one stored summary. NEVER re-sends.\n\nSite managers may regenerate summaries within their assigned sites.\nThe service applies the same tenant scope as the other summary writes.","operationId":"regenerate_site_summary_site_summaries__summary_id__regenerate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"summary_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Summary Id"}},{"name":"rebuild_facts","in":"query","required":false,"schema":{"type":"boolean","description":"Also rebuild the fact sheet from the database. Refused with 409 past 30 days, when camera telemetry has been pruned and the rebuild would silently report no outages.","default":false,"title":"Rebuild Facts"},"description":"Also rebuild the fact sheet from the database. Refused with 409 past 30 days, when camera telemetry has been pruned and the rebuild would silently report no outages."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sites/{site_id}/summary-events":{"get":{"tags":["site summaries"],"summary":"Get Site Summary Events","description":"Read the records behind a stored summary, or this site's live window.","operationId":"get_site_summary_events_sites__site_id__summary_events_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}},{"name":"metric","in":"query","required":true,"schema":{"enum":["motion","screened_pre_review","agent_cleared","operator_reviewed","false_alarm","monitor","voice_down","dispatch"],"type":"string","title":"Metric"}},{"name":"summary_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Summary Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSiteSummaryEventsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/customer-app/push/config":{"get":{"tags":["Customer app"],"summary":"Config","operationId":"config_customer_app_push_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushConfigResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/customer-app/push/subscriptions":{"post":{"tags":["Customer app"],"summary":"Subscribe","operationId":"subscribe_customer_app_push_subscriptions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushSubscriptionInput"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushSubscriptionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]},"delete":{"tags":["Customer app"],"summary":"Unsubscribe","operationId":"unsubscribe_customer_app_push_subscriptions_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemovePushInput"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PushSubscriptionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/customer-app/events/{event_id}/recipients":{"get":{"tags":["Customer app"],"summary":"Recipients","operationId":"recipients_customer_app_events__event_id__recipients_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ActivityRecipientResponse"},"title":"Response Recipients Customer App Events  Event Id  Recipients Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/customer-app/events/{event_id}/requests":{"get":{"tags":["Customer app"],"summary":"Event Requests","operationId":"event_requests_customer_app_events__event_id__requests_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ActivityRequestResponse"},"title":"Response Event Requests Customer App Events  Event Id  Requests Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Customer app"],"summary":"Create","operationId":"create_customer_app_events__event_id__requests_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateActivityRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/customer-app/requests/{request_id}/notify":{"post":{"tags":["Customer app"],"summary":"Notify","operationId":"notify_customer_app_requests__request_id__notify_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/customer-app/requests":{"get":{"tags":["Customer app"],"summary":"Inbox","operationId":"inbox_customer_app_requests_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CustomerActivityRequestResponse"},"type":"array","title":"Response Inbox Customer App Requests Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/customer-app/requests/{request_id}":{"get":{"tags":["Customer app"],"summary":"Detail","operationId":"detail_customer_app_requests__request_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"request_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Request Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerActivityRequestResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/customer-app/sites/{site_id}/disarm":{"get":{"tags":["Customer app"],"summary":"Disarm Status","operationId":"disarm_status_customer_app_sites__site_id__disarm_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Customer app"],"summary":"Disarm","operationId":"disarm_customer_app_sites__site_id__disarm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmInput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/customer-app/sites/{site_id}/rearm":{"post":{"tags":["Customer app"],"summary":"Rearm","operationId":"rearm_customer_app_sites__site_id__rearm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RearmInput"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisarmResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/telephony/webhooks/retell/inbound":{"post":{"tags":["telephony"],"summary":"Retell Inbound Webhook","description":"`call_inbound`: Retell asks, before answering, who this caller is talking\nto. We answer with the dealer's persona and the call's identity.\n\nTEN SECONDS, THREE RETRIES, THEN FALLBACK. Retell gives this hook a 10s\ntimeout and, after three failures, connects the number's default agent with\nno dynamic variables and no metadata. So it does ONE indexed read, takes no\nlock and writes nothing.\n\nACCEPT ⇒ `{\"call_inbound\": {\"dynamic_variables\": …, \"metadata\": …}}`;\nreject ⇒ `{\"call_inbound\": {\"reject\": true}}`. An event that is not\n`call_inbound` — the same URL also receives `chat_inbound` — is\nacknowledged with `{}` and changes nothing. Never a 4xx for a body we\nsimply do not act on: that buys three retries and then the fallback agent.","operationId":"retell_inbound_webhook_telephony_webhooks_retell_inbound_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/telephony/webhooks/retell/tools/{tool}":{"post":{"tags":["telephony"],"summary":"Retell Tool Webhook","description":"One custom-function call.\n\n`verify_passcode` / `disarm` / `rearm` / `status` belong to voice disarm;\n`report_outcome` / `request_human` to the AI voice talk-down. The name\npicks the service (see `_TOOL_SERVICES`), and each service additionally\nchecks the `metadata.kind` it minted.\n\nIDENTITY COMES FROM `call.metadata` — which this API minted — and never\nfrom `args`, which an LLM fills in from what the caller said. The service\nenforces that; the route simply keeps the two fields apart.\n\nTHE OPERATION COMES FROM THE SIGNED BODY, NOT FROM THE URL. The signature\ncovers the body and the timestamp only, so the path is unauthenticated\nrouting: `handle_tool` refuses unless `call.name` equals the path's tool\n(missing counts as a mismatch), which is what stops a captured signed\n`status` body from being replayed to `/tools/rearm`.\n\nEVERY ANSWER IS 200 WITH A JSON OBJECT, so Retell can bind response\nvariables and the agent always has a sentence to read.","operationId":"retell_tool_webhook_telephony_webhooks_retell_tools__tool__post","parameters":[{"name":"tool","in":"path","required":true,"schema":{"type":"string","title":"Tool"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/telephony/webhooks/retell/call-events":{"post":{"tags":["telephony"],"summary":"Retell Call Events Webhook","description":"The call-lifecycle webhook: `call_started`, `call_ended`, `call_analyzed`.\n\nONE ACCOUNT-LEVEL URL FOR EVERY CALL, so both voice features land here and\nthe discriminator is `metadata.kind` — which each service checks for\nitself. A talk-down call advances the call row and, when a second signal\nfrom the device agrees, sets `announced_at` on the reservation.\n\nTEN SECONDS, THREE RETRIES. Retell retries a non-2xx up to three times on a\nten-second timeout, so EVERY body the signature admits is answered `200 {}`\n— an unknown event type, another feature's call, an unknown call id. Three\nredeliveries of a body we would ignore anyway buy nothing, and three\nduplicate handlings of one we would not are worse.\n\nTHE BODY IS BOUNDED BEFORE IT IS PARSED. A transcript and a call analysis\narrive in here and are written into a row; a runaway body must not become a\nmulti-megabyte insert, and a failed insert is a 500 that buys two more\ndeliveries of the same body. The adapter caps the two fields; this is the\nouter bound on the whole payload.","operationId":"retell_call_events_webhook_telephony_webhooks_retell_call_events_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/events/{event_id}/external-talkdowns":{"post":{"tags":["external-talkdowns"],"summary":"Create External Talkdown","description":"Open the audit row for one talk-down attempt.\n\nIdempotent on `(operator, client_ref)`: a retry returns the existing row\nwith a 200, indistinguishable from the first write. NO ACTIVITY-LOG ENTRY\nhere — `requested` only records that a button was pressed, and the row\nitself already says so.","operationId":"create_external_talkdown_events__event_id__external_talkdowns_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateExternalTalkdownRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalTalkdownResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["external-talkdowns"],"summary":"Get External Talkdowns","description":"Every vendor-console attempt on this event, oldest first.\n\nEVENT-scoped, not operator-scoped: a colleague's earlier attempt is part of\nthe same incident's trail.","operationId":"get_external_talkdowns_events__event_id__external_talkdowns_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetExternalTalkdownsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/external-talkdowns/{talkdown_id}":{"patch":{"tags":["external-talkdowns"],"summary":"Update External Talkdown","description":"Report what the extension observed: `connected`, `ended` or `failed`.\n\n`requested -> connected -> ended`, `requested -> failed`,\n`connected -> failed`. Anything else is a 409, EXCEPT an exact repeat of the\nrow's current terminal status with the same `fail_reason`, which is an\nidempotent 200 no-op so a lost response does not make the extension's retry\nlook like an error. Timestamps and `duration_ms` are server-side.","operationId":"update_external_talkdown_events__event_id__external_talkdowns__talkdown_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"talkdown_id","in":"path","required":true,"schema":{"type":"string","title":"Talkdown Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateExternalTalkdownRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalTalkdownResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/conversations/unread-count":{"get":{"tags":["Conversations"],"summary":"Unread Count","description":"Total unread across OPEN threads in the caller's scope.","operationId":"unread_count_conversations_unread_count_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnreadCountResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/conversations":{"get":{"tags":["Conversations"],"summary":"List Conversations","description":"Keyset page of the caller's threads, newest activity first.\n\n`status` defaults to `open` and is an ADDITION to the brief's route list:\nwithout it an archived thread with no further inbound would be stranded out\nof reach.\n\n`order`, `parent_id`, `account_id`, `site_id` and `q` are the inbox's\nsort / scope / search controls (owner ask 2026-09-22, \"like the History\ntab\"). All optional; omitted, the list is byte-identical to before.","operationId":"list_conversations_conversations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"filter","in":"query","required":false,"schema":{"type":"string","pattern":"^(all|unread|mine)$","default":"all","title":"Filter"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","pattern":"^(open|archived|all)$","default":"open","title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"order","in":"query","required":false,"schema":{"enum":["desc","asc"],"type":"string","description":"Sort by last activity: `desc` newest first (the default), `asc` oldest first. Same vocabulary as GET /events/history. A cursor is only valid under the order it was minted with.","default":"desc","title":"Order"},"description":"Sort by last activity: `desc` newest first (the default), `asc` oldest first. Same vocabulary as GET /events/history. A cursor is only valid under the order it was minted with."},{"name":"parent_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only threads of this client. Out of scope matches nothing (never 403).","title":"Parent Id"},"description":"Only threads of this client. Out of scope matches nothing (never 403)."},{"name":"account_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only threads whose SITE is under this account. A client-wide thread has no site and so never matches an account.","title":"Account Id"},"description":"Only threads whose SITE is under this account. A client-wide thread has no site and so never matches an account."},{"name":"site_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only threads on this site.","title":"Site Id"},"description":"Only threads on this site."},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"description":"Keyword search, History's rules: whitespace-split AND-ed terms, each matched case-insensitively against the contact name, client / account / site names, the customer's number (3+ digits) and every message body in the thread.","title":"Q"},"description":"Keyword search, History's rules: whitespace-split AND-ed terms, each matched case-insensitively against the contact name, client / account / site names, the customer's number (3+ digits) and every message body in the thread."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Conversations"],"summary":"Open Conversation","description":"Find-or-create the thread for a named contact.\n\n200, NOT 201: it is idempotent find-or-create, so a 201 would be a lie on\nthe common path. Tenancy comes from the RESOLVED CONTACT'S SITE, never from\nthe request body, and an out-of-scope contact answers 404 rather than 403.","operationId":"open_conversation_conversations_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenConversationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/conversations/{conversation_id}":{"get":{"tags":["Conversations"],"summary":"Get Conversation","description":"One thread and a keyset page of its messages, newest first.","operationId":"get_conversation_conversations__conversation_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}},{"name":"before","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Before"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationThreadResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/conversations/{conversation_id}/messages":{"post":{"tags":["Conversations"],"summary":"Send Message","description":"Reply in a thread.\n\nRuns the SAME three consent gates as notify-owner, with the same arguments.\nA thread with no resolved contact (a parent-level thread) answers 409:\nconsent is per (number, contact) and cannot be checked without one.\n\n`attach_snapshot` sends the event's alert-frame JPEG as MMS. It requires\n`event_id` (422 at the body level without one) and answers 409\nSNAPSHOT_UNAVAILABLE when that event has no snapshot — nothing is written\nand nothing is sent in that case.","operationId":"send_message_conversations__conversation_id__messages_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationMessage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/conversations/{conversation_id}/read":{"post":{"tags":["Conversations"],"summary":"Mark Read","description":"Zero the thread's unread badge. A WRITE — see `require_thread_writer`.","operationId":"mark_read_conversations__conversation_id__read_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/conversations/{conversation_id}/claim":{"post":{"tags":["Conversations"],"summary":"Claim","description":"Take ownership of answering this thread. 409 if another operator holds\nit, unless `force`.","operationId":"claim_conversations__conversation_id__claim_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ClaimRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/conversations/{conversation_id}/unclaim":{"post":{"tags":["Conversations"],"summary":"Unclaim","description":"Release the thread. 409 if another operator holds it, unless `force`.","operationId":"unclaim_conversations__conversation_id__unclaim_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ClaimRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/conversations/{conversation_id}/archive":{"post":{"tags":["Conversations"],"summary":"Archive","description":"Archive or reopen. An inbound (or an outbound) reopens it anyway.","operationId":"archive_conversations__conversation_id__archive_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ArchiveRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}/conversations":{"get":{"tags":["Conversations"],"summary":"Conversations For Event","description":"Threads carrying at least one message ABOUT THIS EVENT.\n\nMEMBERSHIP IS PER MESSAGE, not per site: a thread appears here only when one\nof its `sms_messages` rows has this event's id. Said here so a client does\nnot read it as \"every thread at this event's site\".","operationId":"conversations_for_event_events__event_id__conversations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationsForEventResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/live-view/activity":{"get":{"tags":["live view"],"summary":"Get Live View Activity","operationId":"get_live_view_activity_live_view_activity_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"minutes","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"description":"Window length ending now. The console asks for 5.","default":5,"title":"Minutes"},"description":"Window length ending now. The console asks for 5."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetLiveViewActivityResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/public/share/{token}":{"get":{"tags":["public-share"],"summary":"Get Shared Clip","description":"Serve one event's clip to whoever holds a live share link.\n\nUNAUTHENTICATED BY DESIGN. The recipient is a property contact with no\nconsole login; the 43-character token IS the capability, and it was minted\ninside an audited send by a named operator. Nothing here reads a bearer,\nand `enforce_read_only_guardrail` does not answer it either -- that guard\nreturns immediately for safe methods, so the view-counter write the\nservice makes inside this GET is invisible to it and needs no allowlist\nentry.\n\nEVERY NEGATIVE OUTCOME IS THE SAME 404, WITH THE SAME BODY: a malformed\ntoken, an unknown hash, an expired link, a revoked link, a retired site,\nand a missing camera or site row. Distinguishing any two of them hands a\nscanner an oracle -- which is why `serve_public_clip` answers one `None`\nfor all of them rather than an exception per cause.\n\nNOTHING IS LOGGED ON A MISS, beyond whatever the limiter does. A scanner\nmust not be able to fill CloudWatch. A served hit logs the LINK ID and the\nEVENT ID -- never the token, never a presigned URL. Both live in the\nservice, with the read they describe.\n\nTHE TWO PRE-CHECKS BELOW STAY IN THE ROUTER because they are PURE: they\ntouch no DB and no AWS, and their whole value is being cheap enough that a\nscanner's guess costs nothing. Everything with a side effect is in\n`app/services/share_link_read_service.py` (Greptile G4).\n\nTHE FEATURE-DARK CHECK RUNS FIRST, before the token-syntax check and\nbefore any DB work (round-2 review C2). `SHARE_LINK_BASE_URL` can be\nunset AFTER links already exist -- an ops rollback, a task-definition\nedit -- and without this check a still-valid link would keep serving\neven though `mint()` now refuses every new one and `GET /telephony/\nconfig` reports the feature dark. Same 404, same body: this must not be\na fourth distinguishable outcome.","operationId":"get_shared_clip_public_share__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicSharedClip"}}}},"404":{"description":"Unknown, expired, revoked or malformed link"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/inquiries":{"post":{"tags":["inquiries"],"summary":"Submit Inquiry","description":"After input validation, discard honeypots, apply submission limits, then wait\nfor SES acceptance.","operationId":"submit_inquiry_inquiries_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInquiryRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInquiryResponse"}}}},"413":{"description":"Request body exceeds the 32 KiB limit."},"429":{"description":"Too many inquiries from this IP, or from all sources this hour; retry later."},"502":{"description":"Email provider did not confirm acceptance; delivery failed or is uncertain."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/essay-engagement/comments/{comment_id}/moderate":{"get":{"tags":["essay engagement"],"summary":"View Comment For Moderation","description":"Show the comment with Approve and Delete buttons. CHANGES NOTHING: mail\nscanners and link prefetchers follow GET links, so the state change is the POST.","operationId":"view_comment_for_moderation_essay_engagement_comments__comment_id__moderate_get","parameters":[{"name":"comment_id","in":"path","required":true,"schema":{"type":"string","title":"Comment Id"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","description":"Moderation token from the emailed link.","default":"","title":"Token"},"description":"Moderation token from the emailed link."}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Unknown comment or wrong token.","content":{"text/html":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["essay engagement"],"summary":"Moderate Comment","description":"Approve (idempotent) or delete the comment. The same token still deletes\nan already-approved comment later.","operationId":"moderate_comment_essay_engagement_comments__comment_id__moderate_post","parameters":[{"name":"comment_id","in":"path","required":true,"schema":{"type":"string","title":"Comment Id"}}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_moderate_comment_essay_engagement_comments__comment_id__moderate_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"400":{"description":"Unknown action.","content":{"text/html":{}}},"404":{"description":"Unknown comment or wrong token.","content":{"text/html":{}}},"413":{"description":"Request body exceeds the 8 KiB limit."},"429":{"description":"Too many moderation requests from this IP."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/essay-engagement/{slug}/comments":{"post":{"tags":["essay engagement"],"summary":"Submit Comment","description":"Store the comment as pending and email the owner a moderation link.\n\nA filled honeypot gets the same 202 and stores and sends nothing.","operationId":"submit_comment_essay_engagement__slug__comments_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"Essay slug.","title":"Slug"},"description":"Essay slug."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEssayCommentRequest"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEssayCommentResponse"}}}},"404":{"description":"Malformed slug."},"413":{"description":"Request body exceeds the 32 KiB limit."},"429":{"description":"Too many comments from this IP, or from all sources this hour."},"502":{"description":"The moderation email was not accepted; the comment was discarded."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/essay-engagement/{slug}":{"get":{"tags":["essay engagement"],"summary":"Get Essay Engagement","description":"Like count and approved comments (oldest first, at most 500).","operationId":"get_essay_engagement_essay_engagement__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"Essay slug.","title":"Slug"},"description":"Essay slug."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetEssayEngagementResponse"}}}},"404":{"description":"Malformed slug."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/essay-engagement/{slug}/likes":{"post":{"tags":["essay engagement"],"summary":"Like Essay","description":"Idempotent per (slug, visitor_id).","operationId":"like_essay_essay_engagement__slug__likes_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"Essay slug.","title":"Slug"},"description":"Essay slug."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LikeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EssayLikeResponse"}}}},"404":{"description":"Malformed slug."},"413":{"description":"Request body exceeds the 8 KiB limit."},"429":{"description":"Too many like changes from this IP."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/essay-engagement/{slug}/likes/{visitor_id}":{"delete":{"tags":["essay engagement"],"summary":"Unlike Essay","description":"Idempotent: removing a like that is not there is a no-op.","operationId":"unlike_essay_essay_engagement__slug__likes__visitor_id__delete","parameters":[{"name":"visitor_id","in":"path","required":true,"schema":{"type":"string","format":"uuid4","title":"Visitor Id"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"Essay slug.","title":"Slug"},"description":"Essay slug."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EssayLikeResponse"}}}},"404":{"description":"Malformed slug."},"413":{"description":"Request body exceeds the 8 KiB limit."},"429":{"description":"Too many like changes from this IP."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/site-intake":{"post":{"tags":["site-intake"],"summary":"Submit Site Intake","description":"Store passwords, then email the fixed inbox; both must succeed. The receipt\nto the submitter is best-effort.","operationId":"submit_site_intake_site_intake_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSiteIntakeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSiteIntakeResponse"}}}},"413":{"description":"Request body exceeds the 1 MiB limit."},"429":{"description":"Too many submissions from this IP, or from all sources this hour."},"502":{"description":"Passwords could not be stored or the Verdun inbox did not accept the email."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cake-claims":{"post":{"tags":["cake-claims"],"summary":"Submit Cake Claim","operationId":"submit_cake_claim_cake_claims_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateCakeClaimRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCakeClaimResponse"}}}},"413":{"description":"Photo or request too large."},"429":{"description":"Too many attempts or claims; retry later."},"502":{"description":"Email provider did not confirm acceptance."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cake-claims/referrals":{"post":{"tags":["cake-claims"],"summary":"Submit Cake Referral","operationId":"submit_cake_referral_cake_claims_referrals_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCakeReferralRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCakeClaimResponse"}}}},"413":{"description":"Request too large."},"429":{"description":"Too many submissions; retry later."},"502":{"description":"Email provider did not confirm acceptance."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ingestor/pool/cameras":{"get":{"tags":["ingestor pool"],"summary":"Get Pool Cameras","operationId":"get_pool_cameras_ingestor_pool_cameras_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/GetParentCamerasIngestorResponse"},"type":"array","title":"Response Get Pool Cameras Ingestor Pool Cameras Get"}}}}},"security":[{"APIKeyHeader":[]}]}},"/ingestor/pool/live-intents":{"get":{"tags":["ingestor pool"],"summary":"Get Pool Live Intents","operationId":"get_pool_live_intents_ingestor_pool_live_intents_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/GetLiveIntentsResponse"},"type":"array","title":"Response Get Pool Live Intents Ingestor Pool Live Intents Get"}}}}},"security":[{"APIKeyHeader":[]}]}},"/ingestor/pool/post-action-captures":{"get":{"tags":["ingestor pool"],"summary":"Get Pool Pending Captures","operationId":"get_pool_pending_captures_ingestor_pool_post_action_captures_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"shard_index","in":"query","required":false,"schema":{"type":"integer","minimum":0,"description":"Serving ingestor shard index.","default":0,"title":"Shard Index"},"description":"Serving ingestor shard index."},{"name":"shard_count","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Total ingestor shards; SHA256 camera-id assignment.","default":1,"title":"Shard Count"},"description":"Total ingestor shards; SHA256 camera-id assignment."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PendingCaptureResponse"},"title":"Response Get Pool Pending Captures Ingestor Pool Post Action Captures Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/internal/bridge/pool/targets":{"get":{"tags":["bridge pool"],"summary":"Get Bridge Pool Targets","operationId":"get_bridge_pool_targets_internal_bridge_pool_targets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/BridgePoolTargetResponse"},"type":"array","title":"Response Get Bridge Pool Targets Internal Bridge Pool Targets Get"}}}}},"security":[{"APIKeyHeader":[]}]}},"/internal/bridge/pool/live-intents":{"get":{"tags":["bridge pool"],"summary":"Get Bridge Pool Live Intents","operationId":"get_bridge_pool_live_intents_internal_bridge_pool_live_intents_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/GetLiveIntentsResponse"},"type":"array","title":"Response Get Bridge Pool Live Intents Internal Bridge Pool Live Intents Get"}}}}},"security":[{"APIKeyHeader":[]}]}},"/audits/summary":{"get":{"tags":["audits"],"summary":"Get Audit Summary","description":"Lead dashboard: calls still needing an audit, disputes awaiting a ruling,\nper-operator audit counts and the 50 most recent audits.","operationId":"get_audit_summary_audits_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Look-back window in days, 1 to 90.","default":30,"title":"Days"},"description":"Look-back window in days, 1 to 90."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditSummaryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audits/overturns":{"get":{"tags":["audits"],"summary":"Get Audit Overturns","description":"Every overturned call in one list for a lead to review, with per-status counts.","operationId":"get_audit_overturns_audits_overturns_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"enum":["pending","confirmed","rejected","disputed","reversed","all"],"type":"string","description":"Lens over overturned calls. pending = awaiting lead review; confirmed = confirmed and not disputed/reversed; disputed / reversed = dispute state.","default":"all","title":"Status"},"description":"Lens over overturned calls. pending = awaiting lead review; confirmed = confirmed and not disputed/reversed; disputed / reversed = dispute state."},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Audits created in the past N days, 1 to 365.","default":30,"title":"Days"},"description":"Audits created in the past N days, 1 to 365."},{"name":"operator_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only calls this operator decided.","title":"Operator Id"},"description":"Only calls this operator decided."},{"name":"auditor_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Only audits this person made.","title":"Auditor Id"},"description":"Only audits this person made."},{"name":"decider","in":"query","required":false,"schema":{"enum":["operator","agent","all"],"type":"string","description":"operator = overturns of a human operator's call; agent = overturns of the AI agent's close.","default":"all","title":"Decider"},"description":"operator = overturns of a human operator's call; agent = overturns of the AI agent's close."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditOverturnsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audits/mine":{"get":{"tags":["audits"],"summary":"Get My Audits","description":"The caller's in-scope audits created within the window, newest activity first (cap 100).","operationId":"get_my_audits_audits_mine_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":90,"minimum":1,"description":"Audits created in the past N days, 1 to 90.","default":7,"title":"Days"},"description":"Audits created in the past N days, 1 to 90."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditMineResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audits/events/{event_id}":{"get":{"tags":["audits"],"summary":"Get Event Audit","description":"The audit (if the caller may see it), why the call is up for audit, who\nmade it, and what the caller may do about it.","operationId":"get_event_audit_audits_events__event_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetEventAuditResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["audits"],"summary":"Create Event Audit","description":"Audit one human decision (owner, monitoring manager or operator; read-only\nis refused). A lead's overturn is effective at once; a peer's waits for a\nlead's review. A lead may replace a peer uphold or a still-pending peer\noverturn. 404 no event / no evaluation, 409 not a human decision or already\naudited, 403 on your own call.","operationId":"create_event_audit_audits_events__event_id__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCallAuditRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallAuditOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audits/events/{event_id}/undo":{"post":{"tags":["audits"],"summary":"Undo Event Audit","description":"Take back your own ruling within 10 s (the console's Undo toast): the\ncall goes back to Needs audit. 404 no audit, 403 someone else's ruling,\n409 too late, or a ruling someone has since acted on (a lead's review, a\ndispute) or one that replaced a peer's.","operationId":"undo_event_audit_audits_events__event_id__undo_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UndoCallAuditResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audits/events/{event_id}/review":{"post":{"tags":["audits"],"summary":"Review Event Audit","description":"A lead confirms or rejects a peer's pending overturn. 403 on an overturn\nof your own call; 409 unless the audit is an overturn awaiting review.","operationId":"review_event_audit_audits_events__event_id__review_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewCallAuditRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallAuditOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audits/events/{event_id}/dispute":{"post":{"tags":["audits"],"summary":"Dispute Event Audit","description":"A writable staff decider disputes an overturn, once. Read-only accounts\nare refused. 403 unless the caller made the call; 409 unless the audit is overturned and not yet disputed.","operationId":"dispute_event_audit_audits_events__event_id__dispute_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisputeCallAuditRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallAuditOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/audits/events/{event_id}/dispute/ruling":{"post":{"tags":["audits"],"summary":"Rule Event Audit Dispute","description":"A lead other than the original auditor rules on a pending dispute.","operationId":"rule_event_audit_dispute_audits_events__event_id__dispute_ruling_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Event Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleCallAuditDisputeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallAuditOut"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/leaderboard":{"get":{"tags":["leaderboard"],"summary":"Get Leaderboard","description":"Operator leaderboard (BETA). Everyone sees rank, score, alarms and median\npickup; the points breakdown (and overturns) is the caller's own, or any\noperator's for a lead.","operationId":"get_leaderboard_leaderboard_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"window","in":"query","required":false,"schema":{"enum":["today","week","month","last_month"],"type":"string","description":"today = since local midnight; week = since Monday 00:00; month = since the 1st 00:00; last_month = the previous calendar month, closed (monitoring-center time zone). Ranked by score except week (points per hour).","default":"today","title":"Window"},"description":"today = since local midnight; week = since Monday 00:00; month = since the 1st 00:00; last_month = the previous calendar month, closed (monitoring-center time zone). Ranked by score except week (points per hour)."},{"name":"operator_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"description":"Leads only: whose points breakdown to return. A non-lead naming anyone but themself gets a 403.","title":"Operator Id"},"description":"Leads only: whose points breakdown to return. A non-lead naming anyone but themself gets a 403."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetLeaderboardResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ingestor/post-action-captures/{capture_id}/claim":{"post":{"tags":["post-action captures (ingestor)"],"summary":"Claim Capture","description":"Atomically claim a pending capture (180 s lease). 409 when someone else\nholds it or it is finished/expired; a repeat by the current holder is an\nidempotent 200.","operationId":"claim_capture_ingestor_post_action_captures__capture_id__claim_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"capture_id","in":"path","required":true,"schema":{"type":"string","title":"Capture Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimCaptureRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimCaptureResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/ingestor/post-action-captures/{capture_id}/complete":{"post":{"tags":["post-action captures (ingestor)"],"summary":"Complete Capture","description":"Report captured / unavailable / failed. An identical replay by the\nholder is an idempotent 200; a non-holder or a different outcome is 409.","operationId":"complete_capture_ingestor_post_action_captures__capture_id__complete_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"capture_id","in":"path","required":true,"schema":{"type":"string","title":"Capture Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteCaptureRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompleteCaptureResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-health/flags":{"get":{"tags":["camera-health"],"summary":"Get Camera View Flags","description":"Open and confirmed view flags, plus those cleared or resolved in the last 7 days,\nin the caller's Camera Health scope.","operationId":"get_camera_view_flags_camera_health_flags_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCameraViewFlagsResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/camera-health/flags/sites/{site_id}/notify-log":{"get":{"tags":["camera-health"],"summary":"Get Camera View Flags Notify Log","description":"Recorded customer e-mails about this site's camera views (both the\nsite's Notify customer and the per-camera notify), newest first, one entry\nper send: when, who, which cameras, recipient counts. Never addresses. A\nsite outside the caller's scope has an empty history. Older Activity Log\nrows without verified site snapshots cannot safely be attributed here.","operationId":"get_camera_view_flags_notify_log_camera_health_flags_sites__site_id__notify_log_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraViewFlagsNotifyLogResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-health/flags/{flag_id}/viewed":{"post":{"tags":["camera-health"],"summary":"Mark Camera View Flag Viewed","description":"Record the first actual item open. Repeated opens preserve the timestamp.","operationId":"mark_camera_view_flag_viewed_camera_health_flags__flag_id__viewed_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"flag_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Flag Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraViewFlagViewed"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-health/flags/{flag_id}/confirm":{"post":{"tags":["camera-health"],"summary":"Confirm Camera View Flag","description":"An operator agrees the view is blocked: open -> confirmed (409 from any\nother state). Activity-logged.","operationId":"confirm_camera_view_flag_camera_health_flags__flag_id__confirm_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"flag_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Flag Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraViewFlag"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-health/flags/{flag_id}/clear":{"post":{"tags":["camera-health"],"summary":"Clear Camera View Flag","description":"An operator judges the view usable: open/confirmed -> cleared, with a\nreason, and the camera stays quiet for view_flag_quiet_days unless it gets\nworse (409 from any other state). Activity-logged (the reason is a field\nname only there; it is on the item).","operationId":"clear_camera_view_flag_camera_health_flags__flag_id__clear_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"flag_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Flag Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClearCameraViewFlagRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CameraViewFlag"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-health/flags/{flag_id}/notify":{"post":{"tags":["camera-health"],"summary":"Notify Camera View Flag","description":"E-mail the customer about a CONFIRMED flag (409 otherwise), one message\nper recipient, through the owner-e-mail path: 503 EMAIL_NOT_CONFIGURED\nwhile OWNER_EMAIL_ENABLED is off, 502 when no recipient was accepted.\nRecords notified_at / notified_by_name / notify_summary only when at least\none message went. Activity-logged with counts, never addresses.","operationId":"notify_camera_view_flag_camera_health_flags__flag_id__notify_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"flag_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Flag Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotifyCameraViewFlagRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotifyCameraViewFlagResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-health/flags/{flag_id}/check-now":{"post":{"tags":["camera-health"],"summary":"Check Camera View Flag Now","description":"\"Check now\": ask the item's camera for a fresh frame and run the AI view\ncheck on it as soon as it lands (about a minute), instead of waiting for\nthe next sweep. 202: the answer arrives as a new entry in the item's\n`checks` with `received_at` >= `requested_at`; poll GET /camera-health/flags.\n409 when the item is no longer open or confirmed; 422 UNSUPPORTED_CAMERA\nfor a camera with no health sampler. Not activity-logged: like\n\"Re-check cameras\", it changes no configuration.","operationId":"check_camera_view_flag_now_camera_health_flags__flag_id__check_now_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"flag_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Flag Id"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckCameraViewFlagNowResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/camera-health/flags/notify-site":{"post":{"tags":["camera-health"],"summary":"Notify Camera View Flags Site","description":"ONE e-mail about several cameras at one site, one message per recipient,\neach camera's newest checked frame attached. Items must be in scope (404),\nat one site (400) and open or confirmed (409); open items are confirmed by\nthe send, and only once at least one message was accepted. Same e-mail\nrules as /{flag_id}/notify (503 EMAIL_NOT_CONFIGURED, 502 when nothing was\naccepted). Activity-logged per item, with counts, never addresses.","operationId":"notify_camera_view_flags_site_camera_health_flags_notify_site_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotifyCameraViewFlagsSiteRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotifyCameraViewFlagsSiteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/billing/overview":{"get":{"tags":["billing"],"summary":"Rates, billable sites, MRR, ARR and contracted ARR for every client","operationId":"get_billing_overview_billing_overview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBillingOverviewResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/billing/history":{"get":{"tags":["billing"],"summary":"Every billing change, newest first","operationId":"get_billing_history_billing_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBillingHistoryResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/parents/{parent_id}/rate":{"put":{"tags":["billing"],"summary":"Set or clear a client's base per-camera rate","operationId":"set_parent_rate_billing_parents__parent_id__rate_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Parent Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetBillingRateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBillingOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/parents/{parent_id}/contracted-arr":{"put":{"tags":["billing"],"summary":"Set or clear the ARR a client contracted for","operationId":"set_parent_contracted_arr_billing_parents__parent_id__contracted_arr_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"parent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Parent Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetContractedArrRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBillingOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/accounts/{account_id}/rate":{"put":{"tags":["billing"],"summary":"Set or clear an account's per-camera rate override","operationId":"set_account_rate_billing_accounts__account_id__rate_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetBillingRateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBillingOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/sites/{site_id}/rate":{"put":{"tags":["billing"],"summary":"Set or clear a site's per-camera rate override","operationId":"set_site_rate_billing_sites__site_id__rate_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetBillingRateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBillingOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/sites/{site_id}/billable":{"put":{"tags":["billing"],"summary":"Mark a site billable or not","operationId":"set_site_billable_billing_sites__site_id__billable_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"site_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Site Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSiteBillableRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBillingOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/cameras/{camera_id}/rate":{"put":{"tags":["billing"],"summary":"Set or clear a camera's rate override","operationId":"set_camera_rate_billing_cameras__camera_id__rate_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"camera_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Camera Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetBillingRateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBillingOverviewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/concessions":{"get":{"tags":["billing"],"summary":"Every client concession","operationId":"list_billing_concessions_billing_concessions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBillingConcessionsResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["billing"],"summary":"Record a concession given to a client","operationId":"create_billing_concession_billing_concessions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveConcessionRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingConcessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/billing/concessions/{concession_id}":{"put":{"tags":["billing"],"summary":"Edit a concession","operationId":"update_billing_concession_billing_concessions__concession_id__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"concession_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Concession Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveConcessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingConcessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["billing"],"summary":"Delete a concession","operationId":"delete_billing_concession_billing_concessions__concession_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"concession_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Concession Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/focus":{"get":{"tags":["focus"],"summary":"Who is focusing (the active focus records in your scope)","description":"The active focus records in the caller's tenant scope: what makes \"everyone sees who is focusing\". The claims reads (`GET /events/claims`, `GET /events/{id}/claim`) carry the same records beside `reservations`. Central staff only. 404 while `FOCUS_ENABLED` is off.","operationId":"list_focus_focus_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FocusListResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["focus"],"summary":"Focus on a site or a camera, or hold a site","description":"From now on every NEW alarm in scope that enters the operator queue while nobody holds it is reserved for the caller until they release (or the Keep / Release check runs out). It never takes alarms already waiting; anyone can Take over a reserved alarm. Body `{site_id, scope: {kind: site} | {kind: camera, camera_id}}`. 201 for a new focus, 200 (the existing record) when the caller already has this exact one. 409 `focus_exists` (the caller has a focus with another scope; `focus` is it), 409 `focus_conflict` (another operator's focus overlaps; `focus` names them), 409 `focus_draining` (a previous generation is still being cleaned up: retry), 409 CONFLICT (retired site), 403 (not central staff, an owner, no `review_label_alarms`, or the site is out of scope), 404 (unknown site, or a camera not at the site). 404 while `FOCUS_ENABLED` is off. With `origin: \"story\"` (site scope only) this starts a SITE HOLD: the caller owns the site while its story is open, every alarm there (waiting ones included, with no age limit) is reserved for them, the page keeps it with `POST /focus/{id}/heartbeat`. 201 / 200 as above; 409 `focus_conflict` names whoever already holds or focuses the site (an operator may hold several sites); 404 while `SITE_HOLD_ENABLED` is off.","operationId":"start_focus_focus_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartFocusRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/FocusRecord"},{"$ref":"#/components/schemas/StoryHoldRecord"}],"title":"Response Start Focus Focus Post"}}}},"200":{"description":"The caller already has this exact focus or hold (idempotent).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/FocusRecord"},{"$ref":"#/components/schemas/StoryHoldRecord"}],"title":"Response 200 Start Focus Focus Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/focus/{focus_id}/input":{"post":{"tags":["focus"],"summary":"Meaningful input from the focuser (resets the idle check)","description":"The console sends this on meaningful input, throttled to about one per 10 s (what counts is the console's call; a live call or open voice session should keep pinging). Accepted only BEFORE the check is due; once the check is showing it is ignored (200 with the current record), because only an explicit Keep answers it. At or after the check deadline the focus is dead: 409 `focus_changed` with the released record. A `focus_id` that is not the caller's is 409 `focus_changed` with the caller's own current focus or null.","operationId":"focus_input_focus__focus_id__input_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"focus_id","in":"path","required":true,"schema":{"type":"string","title":"Focus Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FocusRecord"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/focus/{focus_id}/keep":{"post":{"tags":["focus"],"summary":"Keep focusing (the answer to \"Still focusing?\")","description":"The only thing that resets the check once it is due. At or after the check deadline the focus is dead: the release is materialized and this answers 409 `focus_changed` with the released record (a late Keep never revives it). A `focus_id` that is not the caller's, already released, or no longer inside the caller's current scope (it is then released as `scope_lost`) is 409 `focus_changed`; the body never describes an out-of-scope record.","operationId":"keep_focus_focus__focus_id__keep_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"focus_id","in":"path","required":true,"schema":{"type":"string","title":"Focus Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FocusRecord"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/focus/{focus_id}/release":{"post":{"tags":["focus"],"summary":"Release the focus or the site hold","description":"Idempotent: releasing the caller's own released focus is 200 with the record. From the commit every reservation of this focus is inert; the alarm markers are then cleared in chunks. A claim the focuser holds on one of those alarms is not touched. A `focus_id` that is not the caller's is 409 `focus_changed`. A generation of the caller's that is no longer inside their CURRENT scope (a revoked grant, a camera that moved) is still released, but the answer is 409 `focus_changed` with their in-scope current focus or null: its scope is never returned. For a site hold id the body may be `{\"reason\": \"submit\" | \"flag_off\"}` (default `submit`); the hold's markers are cleared, its alarms return to the shared queue with their original `queued_at`, and a hold that is not the caller's is 409 `hold_changed`.","operationId":"release_focus_focus__focus_id__release_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"focus_id","in":"path","required":true,"schema":{"type":"string","title":"Focus Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ReleaseHoldRequest"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/FocusRecord"},{"$ref":"#/components/schemas/StoryHoldRecord"}],"title":"Response Release Focus Focus  Focus Id  Release Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/focus/{hold_id}/heartbeat":{"post":{"tags":["focus"],"summary":"The holder's page is open (site hold)","description":"Sent by the console about every 10 s while a site hold's story page is open (`heartbeat_s` on `GET /auth/me`). One single-row write: it sets `last_seen_at`, clears `left_at` and the new-alert deadline. Without it the hold stops being open after `open_s` and, if no clean leave was seen, returns its alarms after `idle_s` (the lost-heartbeat backstop). After a clean leave, a heartbeat while the hold is still valid (normally within 30 s, sooner if its idle or new-alert deadline comes first, later while a live call defers it) clears it and the hold carries on. A heartbeat after the hold's clocks ran out never revives it: 409 `hold_changed` with the released record. 409 `hold_changed` for a hold that is not the caller's. 404 while `SITE_HOLD_ENABLED` is off.","operationId":"hold_heartbeat_focus__hold_id__heartbeat_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hold_id","in":"path","required":true,"schema":{"type":"string","title":"Hold Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryHoldRecord"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/focus/{hold_id}/leave":{"post":{"tags":["focus"],"summary":"The holder's page was left (site hold, best effort)","description":"Sent on `pagehide` / a route change. Sets `left_at`, which ends \"open\" at once; the hold then returns its alarms about 30 s later (a fixed server constant, not a field of the hold), or sooner if its idle or new-alert deadline comes first, unless a live call defers it. A heartbeat while the hold is still valid clears `left_at` and the hold carries on. A repeat leave keeps the first instant. Idempotent, and a hold that already ended answers 200 with its record so a beacon never errors. 409 `hold_changed` for a hold that is not the caller's. 404 while `SITE_HOLD_ENABLED` is off.","operationId":"hold_leave_focus__hold_id__leave_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hold_id","in":"path","required":true,"schema":{"type":"string","title":"Hold Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryHoldRecord"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/focus/{hold_id}/take-over":{"post":{"tags":["focus"],"summary":"Take over a site another operator holds","description":"Site-level Take over, one confirm. Body `{expected_operator_id, expected_focus_id?}` names the hold the console showed. If it is still that holder's valid hold, in one transaction it is released (`taken_over`) and the caller's hold is created IN THE SAME LINEAGE, so every held alarm stays held, now for the caller, with no gap; the displaced operator's live claims at the site then move to the caller. 200 with the caller's new hold record (also when the caller already holds it, which writes nothing). Otherwise nothing moves: 409 `claim_changed` with `hold` = the valid hold at the site now (or null). Open to every central-staff claim participant, owners included; a taker without `review_label_alarms` can act on the alarms but not label them. 403 out of scope or not central staff. 404 while `SITE_HOLD_ENABLED` is off.","operationId":"hold_take_over_focus__hold_id__take_over_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hold_id","in":"path","required":true,"schema":{"type":"string","title":"Hold Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TakeOverHoldRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoryHoldRecord"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/inbound-calls":{"get":{"tags":["inbound-calls"],"summary":"List Inbound Calls","description":"Recent calls to the Verdun line, newest first (at most 200). Central staff holding\nescalate_dispatch or notify_property_owner only. `event_id` is set only for a caller\nwe phoned in the last 24 h.","operationId":"list_inbound_calls_inbound_calls_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":30,"minimum":1,"description":"How far back to look.","default":7,"title":"Days"},"description":"How far back to look."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InboundCallRecord"},"title":"Response List Inbound Calls Inbound Calls Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/inbound-calls/{call_id}/answered":{"post":{"tags":["inbound-calls"],"summary":"Answered","description":"The console that picked up reports it. First writer wins; the loser gets 200 with\n`recorded: false`. 404 for an unknown call.","operationId":"answered_inbound_calls__call_id__answered_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"call_id","in":"path","required":true,"schema":{"type":"string","title":"Call Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnsweredResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dispatch-calls/{call_id}/report-line":{"post":{"tags":["inbound-calls"],"summary":"Set Report Line","description":"Confirm, change or clear (outcome null) the daily-report line for a POLICE call.\nVerdun staff only; 409 for a non-police call; the alarm must be in the caller's scope.","operationId":"set_report_line_dispatch_calls__call_id__report_line_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"call_id","in":"path","required":true,"schema":{"type":"string","title":"Call Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportLineRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportLineResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dispatch-calls/{call_id}/transcript":{"get":{"tags":["inbound-calls"],"summary":"Get Transcript","description":"The call's transcript, summary and AI suggestion. Ops leads (owner, monitoring\nmanager) only.","operationId":"get_transcript_dispatch_calls__call_id__transcript_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"call_id","in":"path","required":true,"schema":{"type":"string","title":"Call Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DispatchTranscriptResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AccountsModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the account."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client (parent) this account belongs to."},"name":{"type":"string","title":"Name","description":"Name of the account (the client's customer).","examples":["Northgate Logistics"]},"primary_contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Name","description":"Primary contact's name at the account.","examples":["Dana Reyes"]},"primary_contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Phone","description":"Primary contact's phone number at the account.","examples":["8015551234"]},"primary_contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Email","description":"Primary contact's email address at the account.","examples":["dana@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes about the account.","examples":["Four dealership lots; night monitoring only."]}},"type":"object","required":["id","parent_id","name"],"title":"AccountsModel","description":"One account: the optional grouping level between a client and its sites.\n\nCarries NO site membership. The grouping key lives on the site\n(`sites[].account_id`) and only there, so the two can never disagree;\nconsumers group client-side."},"AckOperatorInstructionsRequest":{"properties":{"versions":{"items":{"$ref":"#/components/schemas/OperatorInstructionVersion"},"type":"array","maxItems":3,"minItems":1,"title":"Versions","description":"Versions read."}},"type":"object","required":["versions"],"title":"AckOperatorInstructionsRequest"},"ActivityAction":{"type":"string","enum":["create","update","delete","login"],"title":"ActivityAction"},"ActivityActor":{"properties":{"operator_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Id","description":"The operator acting. Null for an automated agent call."},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Their display name; null if the operator no longer exists or kind is agent."},"kind":{"type":"string","enum":["operator","agent"],"title":"Kind","description":"`agent` is an automated AI call: it never counts as an operator acting."}},"type":"object","required":["kind"],"title":"ActivityActor"},"ActivityEntity":{"type":"string","enum":["camera","camera_zones","camera_schedule","account","site","site_contact","site_schedule","nvr","operator","client","demo_event","demo_collection","event","notification","org_settings","dispatch_call","coverage_group","shift_assignment","shift_block","speaker","camera_outreach","integration","sms_consent","site_disarm","dealer_line","camera_group","camera_view_flag","site_summary_settings","site_summary","sms_conversation","call_audit","support_shift"],"title":"ActivityEntity"},"ActivityEntryModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of the activity log entry"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the change was made"},"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"UUID of the operator who made the change; null once that operator is deleted"},"operator_name":{"type":"string","title":"Operator Name","description":"Name of the operator at the time of the change (snapshot)","examples":["Joe Johnson"]},"operator_email":{"type":"string","title":"Operator Email","description":"Email of the operator at the time of the change (snapshot)","examples":["JJ@example.com"]},"action":{"$ref":"#/components/schemas/ActivityAction","description":"What kind of change was made","examples":["create","delete"]},"entity_type":{"$ref":"#/components/schemas/ActivityEntity","description":"What kind of entity was changed","examples":["camera","site"]},"entity_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Id","description":"ID of the changed entity, when one exists"},"summary":{"type":"string","title":"Summary","description":"Human-readable description of the change","examples":["Updated camera 'Front Gate'"]},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details","description":"Names of the request fields the change touched (never values)","examples":[{"fields":["camera_label","ip_address"]}]}},"type":"object","required":["id","created_at","operator_name","operator_email","action","entity_type","summary"],"title":"ActivityEntryModel"},"ActivityIntent":{"properties":{"action":{"type":"string","enum":["voice_down","dispatch"],"title":"Action","description":"What the holder opened: the Voice Down panel, or Escalate - dispatch."},"set_at":{"type":"string","title":"Set At","description":"When the holder opened Voice Down / picked dispatch (ISO 8601, UTC)"},"operator_id":{"type":"string","title":"Operator Id","description":"The operator who recorded the intent (the claim holder when it was set)"},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Their display name; null if the operator no longer exists"},"generation":{"type":"string","title":"Generation","description":"The ownership generation the intent is bound to"}},"type":"object","required":["action","set_at","operator_id","generation"],"title":"ActivityIntent"},"ActivityPayload":{"properties":{"state":{"type":"string","enum":["preparing","active"],"title":"State","description":"`active` iff at least one live session record exists (operator or automated). Otherwise `preparing`: a valid intent and no session yet."},"operator_acting":{"type":"boolean","title":"Operator Acting","description":"True iff a valid intent exists or a live session is attributed to an operator. The console shows no red cue while this is true; the age stays visible either way. False means only an automated agent call is live."},"since":{"type":"string","title":"Since","description":"`preparing`: the intent's set_at. `active`: the earliest live session start (ISO 8601, UTC)"},"elapsed_seconds":{"type":"integer","title":"Elapsed Seconds","description":"Server-computed now - since, for the age display; time it from this against the local clock at receipt"},"action":{"type":"string","enum":["voice_down","dispatch"],"title":"Action","description":"Of the primary source"},"actor":{"$ref":"#/components/schemas/ActivityActor","description":"Who is ACTING (primary source: operator sessions, then the intent, then automated sessions). After a take-over this is the caller, not the claim holder."},"intent":{"anyOf":[{"$ref":"#/components/schemas/ActivityIntent"},{"type":"null"}],"description":"The valid escalation intent, if any. The client's action token is never returned."},"sessions":{"items":{"$ref":"#/components/schemas/ActivitySession"},"type":"array","title":"Sessions","description":"Live records, oldest first, at most five. Empty while `preparing`."}},"type":"object","required":["state","operator_acting","since","elapsed_seconds","action","actor"],"title":"ActivityPayload"},"ActivityRecipientResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Site contact identity"},"name":{"type":"string","title":"Name","description":"Customer display name"},"enabled":{"type":"boolean","title":"Enabled","description":"A unique scoped customer with an enrolled device and configured push"},"devices":{"type":"integer","title":"Devices","description":"Number of registered app devices; not a delivery guarantee"}},"type":"object","required":["id","name","enabled","devices"],"title":"ActivityRecipientResponse"},"ActivityRequestResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Request identity"},"recipient_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Recipient Id","description":"Intended customer identity"},"contact_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Contact Id","description":"Selected site contact"},"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"Associated incident"},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"Currently authorized property"},"site_name":{"type":"string","title":"Site Name","description":"Property name"},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera identity"},"camera_name":{"type":"string","title":"Camera Name","description":"Camera label"},"recipient_name":{"type":"string","title":"Recipient Name","description":"Customer name at the time of request"},"message":{"type":"string","title":"Message","description":"Customer-visible context supplied by the operator"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the notification was created"},"push_status":{"type":"string","title":"Push Status","description":"Provider acceptance, never proof of delivery"},"push_accepted":{"type":"integer","title":"Push Accepted","description":"Devices whose provider accepted the notification"},"push_attempted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Push Attempted At","description":"When the last provider attempt began. A 'sending' status older than ten minutes may be retried; younger ones are still uncertain"},"requester_name":{"type":"string","title":"Requester Name","description":"Requesting operator name at the time of request"}},"type":"object","required":["id","recipient_id","contact_id","event_id","site_id","site_name","camera_id","camera_name","recipient_name","message","created_at","push_status","push_accepted","push_attempted_at","requester_name"],"title":"ActivityRequestResponse","description":"Staff responses retain attribution for the requesting operator."},"ActivitySession":{"properties":{"source":{"type":"string","enum":["voice_session","voice_agent_call","external_talkdown","dispatch_call"],"title":"Source","description":"Which session table the live record comes from."},"session_id":{"type":"string","title":"Session Id","description":"The id of that session record"},"action":{"type":"string","enum":["voice_down","dispatch"],"title":"Action","description":"`dispatch` is the grouping for every dispatch_call row; `target` says which."},"target":{"anyOf":[{"type":"string","enum":["police","guard","owner"]},{"type":"null"}],"title":"Target","description":"Dispatch rows only: who was dialled. An owner call is activity but is not police dispatch."},"status":{"type":"string","title":"Status","description":"The source's own status word (opening/live, placing/connected, requested/connected, the dispatch status)"},"started_at":{"type":"string","title":"Started At","description":"When the record started being live (ISO 8601, UTC); the source's own start time"},"actor":{"$ref":"#/components/schemas/ActivityActor","description":"Who is on this call. `agent` for an automated AI announcement."}},"type":"object","required":["source","session_id","action","status","started_at","actor"],"title":"ActivitySession"},"AddDisarmTrustedNumberRequest":{"properties":{"phone_number":{"type":"string","title":"Phone Number","description":"A US phone number in any common format. Stored as E.164 (+18015550142); anything that is not a full 10-digit NANP number is refused with a 422.","examples":["(801) 555-0142"]},"label":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Label","description":"Who this is, for the list. Optional; not used in any message.","examples":["Jerry - GM"]},"site_contact_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Contact Id","description":"Provenance only: the site contact this number was picked from. It must belong to the SAME site. The number is COPIED, so a later contact edit never changes who is trusted, and being a contact grants nothing by itself."}},"type":"object","required":["phone_number"],"title":"AddDisarmTrustedNumberRequest","description":"POST /sites/{id}/disarm-trusted/numbers: trust one more phone number."},"AgentAuditSamplingRate":{"properties":{"configured_bp":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Configured Bp","description":"Rate set in the console, in basis points; null when not set.","examples":[100]},"default_bp":{"type":"integer","title":"Default Bp","description":"What applies when configured_bp is null.","examples":[100]},"effective_bp":{"type":"integer","title":"Effective Bp","description":"The rate selection uses right now (100 = 1%).","examples":[100]},"closes_last_7_days":{"type":"integer","title":"Closes Last 7 Days","description":"The agent's closes with this disposition in the last 7 days.","examples":[8800]},"expected_per_week":{"type":"integer","title":"Expected Per Week","description":"closes_last_7_days x effective rate, rounded: about how many reach the audit queue a week.","examples":[88]}},"type":"object","required":["default_bp","effective_bp","closes_last_7_days","expected_per_week"],"title":"AgentAuditSamplingRate"},"AgreementTotals":{"properties":{"reviewed":{"type":"integer","title":"Reviewed","description":"Human dispositions in range (actor='operator', non-demo).","examples":[124]},"with_recommendation":{"type":"integer","title":"With Recommendation","description":"Of those, evals that carried a recommended_action (prompt v2+; failsafe sentinels excluded).","examples":[110]},"agreement":{"type":"integer","title":"Agreement","description":"Exact 4-class matches between recommendation and human disposition.","examples":[96]},"agreement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Agreement Rate","description":"agreement / with_recommendation as a percentage; null when no covered reviews.","examples":[87.3]}},"type":"object","required":["reviewed","with_recommendation","agreement"],"title":"AgreementTotals"},"AiFeedbackBlock":{"properties":{"agree":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Agree","description":"Operator's tap on whether the AI's call was correct. None = no opinion (skipped or never asked); never defaulted to True. Strict bool (G3, round-5 review, the #314 lesson again): a lax bool coerces \"true\"/1/\"false\"/0, which would let a client bug silently turn a string or an int into a real tap."},"reason":{"anyOf":[{"type":"string","enum":["wrong_class","benign_routine","camera_fault","environmental","person_shaped_static","detection_phantom","context_needed","missed_person","other"]},{"type":"null"}],"title":"Reason","description":"Present if and only if agree is False.","examples":["benign_routine"]},"should_have_been":{"anyOf":[{"type":"string","enum":["INTRUSION","TAILGATING","FIGHTING","ARMED_ROBBERY","VEHICLE_THEFT","LOITERING","MAINTENANCE","DELIVERY","NORMAL","HEIGHTENED_ACTIVITY"]},{"type":"null"}],"title":"Should Have Been","description":"Optional even when agree is False; forbidden unless agree is False. Restricted to the security profile's incident types plus NORMAL and HEIGHTENED_ACTIVITY — see AI_FEEDBACK_SHOULD_HAVE_BEEN_TYPES.","examples":["LOITERING"]},"frame_ms":{"anyOf":[{"type":"integer","maximum":3600000.0,"minimum":0.0},{"type":"null"}],"title":"Frame Ms","description":"Optional clip offset in milliseconds the operator tapped ('the frame that shows it'). Strict int, no float coercion (the #314 lesson: a stub once accepted a float where the API requires a strict int). Bounded to a generous 1-hour sanity ceiling here; the real clip-length bound is enforced by the client's video element, not this schema.","examples":[4200]}},"additionalProperties":false,"type":"object","title":"AiFeedbackBlock","description":"Internal operator judgment on the AI's own call (operator-feedback-\nloop round-0/1 design; owner GO, dispatch D-20260929-27). NEVER\ncustomer-visible — see customer_projection_service.\nSTRIP_WHEN_NESTED_UNDER[\"closeout\"] and the SITE_MANAGER-specific strip\nbeside it. Optional everywhere: the API never requires this block, on\nany disposition, sampled or not — EvalService._validate_closeout's\nrequired-ness matrix never references it, and that absence IS the\nenforcement (see that function's docstring).\n\nAccepted from EVERY role that can submit a close-out — no role gate here\nor in EvalService (research/spec-owner call, round-1-review.md \"Carried\nto the owner\" #2): provenance is handled by the sibling\noperator_evaluations.submitter_role snapshot, not by restricting who may\nanswer.\n\nNested inside CloseoutEntry, itself additive and optional — adding this\nfield needed no schema_version bump (see CloseoutEntry's own docstring\non why schema_version stays a hard Literal[1] wall): a v1 row without\nthis key is unaffected, and `ai_feedback` simply reads as None/absent."},"AlarmClaim":{"properties":{"operator_id":{"type":"string","title":"Operator Id","description":"The operator holding the alarm"},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Their display name; null if the operator no longer exists"},"claimed_at":{"type":"string","title":"Claimed At","description":"When the claim was taken (ISO 8601, UTC)"},"expires_at":{"type":"string","title":"Expires At","description":"When the claim lapses (ISO 8601, UTC)"},"expires_in_seconds":{"type":"integer","title":"Expires In Seconds","description":"Seconds left on the claim, computed server-side. Time the claim from this against the local clock at receipt, not from `expires_at`, so a skewed workstation clock does not move it."},"generation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generation","description":"Ownership generation (a UUID): it changes whenever the claim's HOLDER changes (acquire, take-over) and never on a renewal. Echo it on `POST /events/{id}/claim/acting` and its cancel; a write carrying an old one is rejected. Null while CLAIM_ESCALATION_ENABLED is off, or on a claim stamped before generations existed (the holder's next renew fills it in)."}},"type":"object","required":["operator_id","claimed_at","expires_at","expires_in_seconds"],"title":"AlarmClaim"},"AlarmContextAnchor":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"The alarm the rail is built around"},"camera_id":{"type":"string","title":"Camera Id","description":"The anchor alarm's camera"},"camera_name":{"type":"string","title":"Camera Name","description":"That camera's label"},"site_id":{"type":"string","title":"Site Id","description":"The anchor alarm's site"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the anchor alarm fired"}},"type":"object","required":["event_id","camera_id","camera_name","site_id","created_at"],"title":"AlarmContextAnchor"},"AlarmContextCounts":{"properties":{"reviewed":{"type":"integer","title":"Reviewed","description":"Alarms an operator closed","default":0},"filtered":{"type":"integer","title":"Filtered","description":"Alarms stopped before AI review by a pre-processing gate (DeepAlert and its confirmers)","default":0},"agent_closed":{"type":"integer","title":"Agent Closed","description":"Alarms the agent closed","default":0}},"type":"object","title":"AlarmContextCounts"},"AlarmContextCountsByRelation":{"properties":{"related":{"$ref":"#/components/schemas/AlarmContextCounts","description":"This camera plus its neighbours (excluding the anchor alarm itself)"},"rest_of_site":{"$ref":"#/components/schemas/AlarmContextCounts","description":"Every other camera at the site"}},"type":"object","required":["related","rest_of_site"],"title":"AlarmContextCountsByRelation"},"AlarmContextGraph":{"properties":{"status":{"type":"string","enum":["ready","stale","unavailable"],"title":"Status","description":"`ready`: this hour's graph. `stale`: an earlier graph, served because this hour's could not be built right now. `unavailable`: no graph yet (first request for the site after a restart, or builds are failing); `neighbours` is then empty and the rail has only the anchor camera."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why the status is not `ready`: `building` (another request or task is building it), `busy` (this process is already building its maximum number of graphs), `backoff` (a recent build failed), `timeout`, `over_budget`, `error`"},"built_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Built At","description":"When the graph served here was built"},"window_days":{"type":"integer","title":"Window Days","description":"Length of the history the graph is learned from"},"truncated":{"type":"boolean","title":"Truncated","description":"True when the graph was built within its work budget: the site had more events than the event cap (only the newest were used) or more cameras than the camera cap (only the first were candidates). The feed, counts and anchor are never limited by this.","default":false},"neighbours":{"items":{"$ref":"#/components/schemas/AlarmContextNeighbour"},"type":"array","title":"Neighbours","description":"The anchor camera's neighbours, strongest first; never includes the anchor camera"}},"type":"object","required":["status","window_days","neighbours"],"title":"AlarmContextGraph"},"AlarmContextItem":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"Open it with GET /events/{event_id} and /clip-url"},"camera_id":{"type":"string","title":"Camera Id","description":"The camera this alarm fired on"},"camera_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Name","description":"Null for a camera that is no longer at the site"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the alarm fired"},"offset_seconds":{"type":"number","title":"Offset Seconds","description":"created_at minus the anchor alarm's, in seconds; negative = before"},"relation":{"type":"string","enum":["self","neighbour","site"],"title":"Relation","description":"`self`: the anchor alarm's camera. `neighbour`: a camera in the graph's neighbour list. `site`: any other camera at the site (only returned with scope=site)"},"kind":{"type":"string","enum":["reviewed","filtered","agent_closed"],"title":"Kind","description":"`reviewed`: closed by an operator. `filtered`: stopped before AI review by a pre-processing gate (DeepAlert, or its confirmers). `agent_closed`: closed by the agent. Open alarms are not here; they are already in the operator's queue and arrive over the push stream."},"disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disposition","description":"The disposition label as the History list shows it"},"closed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Closed At","description":"When it was closed"},"closed_by_operator_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Closed By Operator Id","description":"The closing operator (`reviewed` only)"},"closed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Closed By Name","description":"The closing operator's name (`reviewed` only)"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"The ingestor's trigger text for the alarm (\"Motion confirmed by <provider>: <labels>\", or the placeholder \"Something is happening\" when the motion validator named nothing). It is NOT a read of the alarm and the console no longer prints it on the rail; use `vlm_summary`."},"vlm_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vlm Summary","description":"The VLM's one-line read of this alarm: the rationale of the event's NEWEST interpretation row (by created_at, then id), whitespace collapsed and cut at 240 characters on a word boundary with a trailing ellipsis. Null (never the ingestor placeholder, never an older read) when the newest row is not a VLM read: no interpretation, a gate stop (DeepAlert filtered), the fail-safe sentinel, a VLM-fallback envelope, or an empty rationale; also null for every row when the optional lookup failed or is cooling down after a failure. Absent from an older api."}},"type":"object","required":["event_id","camera_id","created_at","offset_seconds","relation","kind"],"title":"AlarmContextItem"},"AlarmContextNeighbour":{"properties":{"camera_id":{"type":"string","title":"Camera Id","description":"A camera that co-fires with the anchor alarm's camera"},"camera_name":{"type":"string","title":"Camera Name","description":"That camera's label"},"cofire_count":{"type":"integer","title":"Cofire Count","description":"Times, over the graph's window, one of the two cameras fired and the other followed within 60 s (both directions summed; storm minutes excluded)"},"expected_cofire":{"type":"number","title":"Expected Cofire","description":"How many co-fires chance alone would give, from the two cameras' firing rates"},"lift":{"type":"number","title":"Lift","description":"cofire_count / expected_cofire. Higher means more likely adjacent"}},"type":"object","required":["camera_id","camera_name","cofire_count","expected_cofire","lift"],"title":"AlarmContextNeighbour"},"AlarmContextResponse":{"properties":{"status":{"type":"string","enum":["ok","degraded","disabled"],"title":"Status","description":"`ok`; `degraded`: the feed query hit its deadline, items and counts are empty, the rail should say it is unavailable and may retry; `disabled`: ALARM_CONTEXT_ENABLED is off, show no rail (nothing else in this body is filled in)."},"enabled":{"type":"boolean","title":"Enabled","description":"False exactly when status is `disabled`"},"anchor":{"anyOf":[{"$ref":"#/components/schemas/AlarmContextAnchor"},{"type":"null"}],"description":"The alarm the rail is built around; null when disabled"},"window":{"anyOf":[{"$ref":"#/components/schemas/AlarmContextWindow"},{"type":"null"}],"description":"The time window the rows were read from; null when disabled"},"scope":{"anyOf":[{"type":"string","enum":["related","site"]},{"type":"null"}],"title":"Scope","description":"Which cameras `items` was drawn from (the request's scope parameter)"},"graph":{"anyOf":[{"$ref":"#/components/schemas/AlarmContextGraph"},{"type":"null"}],"description":"The anchor camera's co-firing neighbours and the state of the graph; null when disabled"},"items":{"items":{"$ref":"#/components/schemas/AlarmContextItem"},"type":"array","title":"Items","description":"Newest first, at most `limit`"},"truncated":{"type":"boolean","title":"Truncated","description":"True when more rows matched than `limit` returned","default":false},"counts":{"anyOf":[{"$ref":"#/components/schemas/AlarmContextCountsByRelation"},{"type":"null"}],"description":"Totals for the whole window, not just the returned page, split into the related cameras and the rest of the site. Drives lines like '169 more at the rest of the site'."}},"type":"object","required":["status","enabled"],"title":"AlarmContextResponse"},"AlarmContextWindow":{"properties":{"start":{"type":"string","format":"date-time","title":"Start","description":"Start of the window the rows are drawn from (alarm time)"},"end":{"type":"string","format":"date-time","title":"End","description":"End of the window (alarm time)"},"before_minutes":{"type":"integer","title":"Before Minutes","description":"Minutes before the anchor alarm that were read"},"after_minutes":{"type":"integer","title":"After Minutes","description":"Minutes after the anchor alarm that were read"}},"type":"object","required":["start","end","before_minutes","after_minutes"],"title":"AlarmContextWindow"},"AlarmHeatLayer":{"properties":{"window":{"type":"string","enum":["h24","d7"],"title":"Window","description":"Time window ending at window_end.","examples":["d7"]},"source":{"type":"string","enum":["queued","all"],"title":"Source","description":"'queued' = alarms that reached the operator queue (same rule as events_queued on GET /logs/summary); 'all' = every motion event, including those the DeepAlert gate or the agent closed first.","examples":["queued"]},"object_class":{"type":"string","enum":["person","vehicle"],"title":"Object Class","description":"DeepAlert object class.","examples":["person"]},"cells":{"items":{"items":{"type":"number"},"type":"array"},"type":"array","title":"Cells","description":"Sparse [cell_index, weight] pairs over a grid_w x grid_h grid (row-major, cell_index = y * grid_w + x) in the zone editor's stretched frame. Each moving object adds about 1 in total; boxes that sit still for the whole alarm (parked cars) are left out.","examples":[[[1234,2.5]]]}},"type":"object","required":["window","source","object_class","cells"],"title":"AlarmHeatLayer"},"AlarmHeatSummary":{"properties":{"window":{"type":"string","enum":["h24","d7"],"title":"Window","description":"Time window ending at window_end.","examples":["d7"]},"source":{"type":"string","enum":["queued","all"],"title":"Source","description":"Same meaning as on a layer.","examples":["queued"]},"alarms":{"type":"integer","title":"Alarms","description":"Events in this window and source.","examples":[404]},"alarms_with_people":{"type":"integer","title":"Alarms With People","description":"Of those, how many had at least one moving person box.","examples":[387]},"alarms_with_vehicles":{"type":"integer","title":"Alarms With Vehicles","description":"Of those, how many had at least one moving vehicle box.","examples":[3]},"alarms_with_objects":{"type":"integer","title":"Alarms With Objects","description":"Of those, how many had at least one moving box of either class.","examples":[387]}},"type":"object","required":["window","source","alarms","alarms_with_people","alarms_with_vehicles","alarms_with_objects"],"title":"AlarmHeatSummary"},"AlarmShowResponse":{"properties":{"status":{"type":"string","enum":["more","complete","truncated"],"title":"Status","description":"`more`: this page is fine and `next_cursor` continues the pass. `complete`: the last page; nothing after it in scope as of `snapshot.as_of`. `truncated`: the pass hit `limits.pass_ceiling`; the eligible rows counted in `omitted` will NOT be returned (the oldest are always the ones returned)."},"snapshot":{"$ref":"#/components/schemas/PagedSnapshot","description":"The pass's pinned snapshot instant and this page's read time"},"window":{"$ref":"#/components/schemas/ShowWindow","description":"The pinned window every page of the pass reads"},"anchor":{"$ref":"#/components/schemas/ShowAnchor","description":"The alarm the scene is anchored on"},"scope":{"$ref":"#/components/schemas/PagedScope","description":"What was read"},"items":{"items":{"$ref":"#/components/schemas/ShowItem"},"type":"array","title":"Items","description":"This page's clips, oldest first"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque; send it back with the same parameters. Null on the last page. An invalid cursor is 422 `CURSOR_INVALID`, an expired one 422 `CURSOR_EXPIRED`"},"counts":{"$ref":"#/components/schemas/ShowCounts","description":"Page-one observations: `total` and `by_state` repeat unchanged from page one on every page; `by_camera` is page one only (null afterwards)"},"limits":{"$ref":"#/components/schemas/PagedLimits","description":"The page and pass limits in force"},"omitted":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Omitted","description":"Set only when `truncated`: the eligible rows after the last returned key at this read (the page's own filters and keyset), at least this many when `omitted_capped`. A read-time count, not page 1's total minus the rows returned."},"omitted_capped":{"type":"boolean","title":"Omitted Capped","description":"True when the bounded remainder count hit its cap (10,000): `omitted` is then a lower bound. Always false unless `truncated`.","default":false}},"type":"object","required":["status","snapshot","window","anchor","scope","items","counts","limits"],"title":"AlarmShowResponse"},"AnalyticsBreakdownRow":{"properties":{"disposition":{"type":"string","title":"Disposition","description":"OperatorDisposition value.","examples":["False alarm - no human activity"]},"count":{"type":"integer","title":"Count","description":"Dispositions of this kind in the range.","examples":[22]}},"type":"object","required":["disposition","count"],"title":"AnalyticsBreakdownRow"},"AnalyticsHourlyVolume":{"properties":{"timezone":{"type":"string","title":"Timezone","description":"IANA zone the hours are bucketed in.","examples":["America/Los_Angeles"]},"days":{"items":{"type":"number"},"type":"array","title":"Days","description":"24 entries: elapsed hours of exposure per local hour in [range_start, range_end), including fractional hours and DST. Divide counts by this for alarms per hour."},"operator_counts":{"items":{"type":"integer"},"type":"array","title":"Operator Counts","description":"24 entries: operator dispositions of alarms fired in [range_start, range_end), grouped by local firing hour, operator filter applied. Closure may be after the window."},"agent_counts":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Agent Counts","description":"24 entries: agent-mode dispositions by hour; null unless include_agent (always zero under an operator filter)."},"median_seconds":{"items":{"anyOf":[{"type":"number"},{"type":"null"}]},"type":"array","title":"Median Seconds","description":"24 entries: median operator triage seconds for alarms fired in that hour; null for empty hours."}},"type":"object","required":["timezone","days","operator_counts","median_seconds"],"title":"AnalyticsHourlyVolume"},"AnalyticsMetrics":{"properties":{"resolved":{"type":"integer","title":"Resolved","description":"Operator dispositions logged in the range.","examples":[31]},"auto_archived":{"type":"integer","title":"Auto Archived","description":"Events the AI suggested dismissing that no operator reviewed (0 when filtering by operator).","examples":[14]},"avg_response_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Response Seconds","description":"Average seconds from event creation to operator disposition; null when no dispositions.","examples":[42.5]},"median_response_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Response Seconds","description":"Median event-to-disposition seconds over the same set as avg_response_seconds; null when none.","examples":[25.0]},"p90_response_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 Response Seconds","description":"90th-percentile event-to-disposition seconds; null when none.","examples":[211.0]},"agent_evaluations":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Agent Evaluations","description":"Agent-mode evaluations (actor='agent') in the same window and parent/site/camera scope. When include_agent is false they are EXCLUDED from every other figure and this is how many were left out.","examples":[9268]}},"type":"object","required":["resolved","auto_archived"],"title":"AnalyticsMetrics"},"AnalyticsOperatorHistogram":{"properties":{"operator_id":{"type":"string","title":"Operator Id","description":"Operator UUID."},"counts":{"items":{"type":"integer"},"type":"array","title":"Counts","description":"Dispositions per bin (len = len(bin_edges_seconds) - 1)."}},"type":"object","required":["operator_id","counts"],"title":"AnalyticsOperatorHistogram"},"AnalyticsOperatorRow":{"properties":{"id":{"type":"string","title":"Id","description":"Operator UUID."},"first_name":{"type":"string","title":"First Name","description":"Operator first name.","examples":["Sonny"]},"last_name":{"type":"string","title":"Last Name","description":"Operator last name.","examples":["Tai"]},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At","description":"Operator's last heartbeat (drives the online dot)."},"reviewed":{"type":"integer","title":"Reviewed","description":"Dispositions this operator logged in the range.","examples":[18]},"avg_response_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Response Seconds","description":"Average event-to-disposition seconds; null when none.","examples":[38.0]},"median_response_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Response Seconds","description":"Median event-to-disposition seconds; null when none.","examples":[24.0]},"p90_response_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 Response Seconds","description":"90th-percentile event-to-disposition seconds; null when none.","examples":[180.0]},"false_alarm_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"False Alarm Pct","description":"Share of this operator's dispositions marked false alarm; null when none.","examples":[71.0]},"escalation_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Escalation Pct","description":"Share of this operator's dispositions escalated (dispatch or voice down); null when none.","examples":[22.0]}},"type":"object","required":["id","first_name","last_name","reviewed"],"title":"AnalyticsOperatorRow"},"AnalyticsProvider":{"type":"string","enum":["none","deepalert"],"title":"AnalyticsProvider"},"AnalyticsRecentCamera":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Camera row UUID."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Camera label.","examples":["Loading dock"]},"camera_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id","description":"Hardware camera id.","examples":["cam01"]}},"type":"object","title":"AnalyticsRecentCamera"},"AnalyticsRecentRef":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Referenced record UUID."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Display name of the referenced record."}},"type":"object","title":"AnalyticsRecentRef"},"AnalyticsRecentRow":{"properties":{"evaluation_id":{"type":"string","title":"Evaluation Id","description":"Operator evaluation UUID."},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Event UUID."},"event_created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Event Created At","description":"When the event was created."},"evaluated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Evaluated At","description":"When the operator logged the disposition."},"response_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Response Seconds","description":"Seconds from event creation to disposition.","examples":[48.0]},"disposition":{"type":"string","title":"Disposition","description":"OperatorDisposition value.","examples":["False alarm - no human activity"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Operator notes on the disposition."},"operator":{"$ref":"#/components/schemas/AnalyticsRecentRef","description":"Operator who logged the disposition."},"camera":{"$ref":"#/components/schemas/AnalyticsRecentCamera","description":"Camera the event came from."},"site":{"$ref":"#/components/schemas/AnalyticsRecentRef","description":"Site the camera belongs to."},"parent":{"$ref":"#/components/schemas/AnalyticsRecentRef","description":"Parent (client) the site belongs to."}},"type":"object","required":["evaluation_id","disposition","operator","camera","site","parent"],"title":"AnalyticsRecentRow"},"AnalyticsTriageDistribution":{"properties":{"bin_edges_seconds":{"items":{"type":"number"},"type":"array","title":"Bin Edges Seconds","description":"Log-spaced bin edges in seconds (0.4 s .. 1 h); values outside clamp into the first/last bin."},"team_median_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Team Median Seconds","description":"Exact median triage seconds for the whole team in scope (ignores the operator filter); null when none."},"team_p90_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Team P90 Seconds","description":"Exact 90th-percentile triage seconds for the whole team in scope; null when none."},"team":{"items":{"type":"integer"},"type":"array","title":"Team","description":"Operator dispositions per bin across the whole parent/site/camera scope, ignoring the operator filter - the team reference curve."},"agent":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Agent","description":"Agent-mode dispositions per bin; null unless include_agent."},"by_operator":{"items":{"$ref":"#/components/schemas/AnalyticsOperatorHistogram"},"type":"array","title":"By Operator","description":"Per-operator counts (same scope as `team`), so a client can draw one curve per selected operator or sum any selection."}},"type":"object","required":["bin_edges_seconds","team","by_operator"],"title":"AnalyticsTriageDistribution"},"AnsweredResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Call row id."},"answered_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answered By Name","description":"Whoever answered first."},"recorded":{"type":"boolean","title":"Recorded","description":"True when THIS operator is the recorded answerer; false when another console won."}},"type":"object","required":["id","recorded"],"title":"AnsweredResponse"},"AppBrandingResponse":{"properties":{"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"Dealer whose branding is returned"},"enabled":{"type":"boolean","title":"Enabled","description":"Whether customer screens use dealer branding"},"app_name":{"type":"string","title":"App Name","description":"Configured display name, or dealer name when unset"},"logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Logo Url","description":"Short-lived private logo URL; never persist it"},"logo_expires_in":{"type":"integer","title":"Logo Expires In","description":"Logo URL lifetime in seconds, zero when absent","default":0},"has_logo":{"type":"boolean","title":"Has Logo","description":"Whether a logo is stored, including while branding is disabled"},"revision":{"type":"integer","title":"Revision","description":"Current revision required by the next save"}},"type":"object","required":["parent_id","enabled","app_name","has_logo","revision"],"title":"AppBrandingResponse"},"ArchiveRequest":{"properties":{"archived":{"type":"boolean","title":"Archived","description":"True archives the thread, False reopens it. Two-way rather than one-way so an archived thread with no further inbound is not stranded out of reach.","default":true,"examples":[true]}},"type":"object","title":"ArchiveRequest"},"ArmSiteRequest":{"properties":{"minutes":{"type":"integer","maximum":120.0,"minimum":1.0,"title":"Minutes","description":"How long the site's cameras should stay on for testing, in minutes, even though the schedule has them off. 1-120; anything above 120 is a 422 here, so the service's clamp never fires through this API and the response's `clamped` is always false. The site's disarm_max_minutes deliberately does NOT apply.","examples":[15,30,60,120]}},"type":"object","required":["minutes"],"title":"ArmSiteRequest"},"ArmStateResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site whose cameras this concerns."},"arm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Until","description":"When the test window ends by itself and the cameras go back to their schedule. Ending a test early sets this to the moment it ended rather than nulling it, so the window that really happened stays readable historically — which means a non-null value does NOT mean 'armed for testing'. Read `arm_active` for that. Expiry is implicit; nothing sweeps this column.","examples":["2026-09-18T04:30:00Z"]},"arm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Set At","description":"When the current test window began. The resolver's predicate is the window between this and arm_until, so a historical read is not repainted by a test started later. An extend inside a live window keeps the original value.","examples":["2026-09-18T04:00:00Z"]},"arm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arm By","description":"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. Retained after the window is closed early, alongside the closed window.","examples":["operator:6f1c8f2a-..."]},"arm_active":{"type":"boolean","title":"Arm Active","description":"Whether this site's cameras are on FOR A TEST right now — the window predicate, resolved server-side. Always false on an end-arm response. Never derive this from arm_until.","default":false,"examples":[true,false]},"minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minutes","description":"Minutes actually applied, after clamping to the 120-minute product ceiling; null on an end-arm.","examples":[15]},"clamped":{"type":"boolean","title":"Clamped","description":"True when the request asked for longer than the arm's ceiling and was shortened. ALWAYS FALSE THROUGH THIS API TODAY: the request model refuses anything above 120 minutes and the arm clamps to that same ceiling only (never to the site's disarm_max_minutes). Kept for contract symmetry with DisarmStateResponse and so that a later decision to clamp arms to the site cap is not a contract change.","default":false,"examples":[false]},"monitoring_now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Now","description":"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. A live test arm reads 'monitored' — it is not a fifth value; read arm_active to tell a test apart from the schedule doing it. 'disarmed' still wins over a test arm, though a successful arm cannot return it (the route refuses while a disarm is live). On an end-arm response this is what the SCHEDULE leaves behind, which is the whole point: ending a test on an off-schedule site reports 'off_schedule'.","examples":["monitored","off_schedule","disarmed","unscheduled","live_view_only"]},"schedule_monitoring_now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schedule Monitoring Now","description":"The current schedule-only monitoring state, ignoring Live View only flags, the test arm and the disarm window. Resolved at the same instant as monitoring_now. This is what lets the console say 'on for a test; the schedule has this site off until 7:00 AM'.","examples":["monitored","off_schedule","unscheduled"]},"monitoring_next_change_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Monitoring Next Change At","description":"The next instant this site's monitored/off state flips BY SCHEDULE ALONE — the test window is deliberately ignored, because you already have arm_until above. Null when nothing changes within 7 days and for an 'unscheduled' site. Aligned to a 15-minute boundary in the site's own timezone.","examples":["2026-09-18T13:00:00Z"]}},"type":"object","required":["site_id"],"title":"ArmStateResponse","description":"The site's live TEST ARM state after a console arm or end-arm (0104).\n\nThe mirror of `DisarmStateResponse`: the same four fields the site\nserializers carry for the arm window, so the console can apply the response\nto its cached site without a refetch, plus what the request resolved to and\nthe two derived schedule fields.\n\nThe two features are ONE state machine with two directions, so the same\ntraps apply — above all, a non-null `arm_until` does NOT mean armed."},"AssignSiteIntegrationResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site the connection is now assigned to."},"integration":{"$ref":"#/components/schemas/SiteIntegrationResponse","description":"The connection, with its full `site_ids` after this call."},"already_assigned":{"type":"boolean","title":"Already Assigned","description":"True when the connection was already assigned to this site and nothing was written (the call is idempotent)."}},"type":"object","required":["site_id","integration","already_assigned"],"title":"AssignSiteIntegrationResponse"},"AttachmentOut":{"properties":{"kind":{"type":"string","title":"Kind","description":"What the file is. 'snapshot' is the event's alert-frame JPEG. An OPEN string, NOT an enum — inbound customer media arrives in a later phase as a new value with no migration, so never switch on it exhaustively.","examples":["snapshot"]},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"Presigned GET URL, minted at read time and valid 3600s. Null when presigning failed; the record is still returned. Presigned BLIND — a non-null URL does NOT prove the object still exists, so probe it."},"content_type":{"type":"string","title":"Content Type","description":"MIME type as sent. 'application/octet-stream' when unrecorded.","examples":["image/jpeg"]},"offset_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Offset Ms","description":"For an operator-picked snapshot: milliseconds into the event's clip the frame was cut from. Absent for the ingestor's alert-frame poster and for every other kind.","examples":[7300]}},"type":"object","required":["kind","content_type"],"title":"AttachmentOut","description":"One media file that rode with a message.\n\nTHE S3 KEY IS INTERNAL AND IS DELIBERATELY NOT EXPOSED. What is stored is\nthe key; what is served is a fresh presigned URL minted for this read."},"AuditAuditorSummary":{"properties":{"auditor_key":{"type":"string","title":"Auditor Key","description":"Stable grouping key surviving account deletion; legacy-unknown groups audits whose identity was already lost."},"auditor_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Auditor Id","description":"The person who made the audits; null after account deletion."},"auditor_name":{"type":"string","title":"Auditor Name","description":"Snapshot of the auditor's name."},"audited":{"type":"integer","title":"Audited","description":"Calls this person audited in the window (a lead's replacement of a peer audit counts for the lead)."},"upheld":{"type":"integer","title":"Upheld","description":"Of those, calls they upheld."},"overturned":{"type":"integer","title":"Overturned","description":"Of those, ACTIVE overturns: confirmed, and not reversed on dispute."},"overturned_agent":{"type":"integer","title":"Overturned Agent","description":"Of `overturned`, the AI agent's calls (auto-confirmed agent false alarms included).","default":0},"overturned_operator":{"type":"integer","title":"Overturned Operator","description":"Of `overturned`, human operators' calls (`overturned` - `overturned_agent`).","default":0},"pending_review":{"type":"integer","title":"Pending Review","description":"Of those, peer overturns still awaiting a lead's confirmation."}},"type":"object","required":["auditor_key","auditor_name","audited","upheld","overturned","pending_review"],"title":"AuditAuditorSummary"},"AuditDeciderOut":{"properties":{"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"Who made the call; null when the decider is the AI agent."},"operator_name":{"type":"string","title":"Operator Name","description":"First and last name; \"Verdun Agent\" for the AI agent."},"decision":{"type":"string","title":"Decision","description":"OperatorDisposition MEMBER NAME."},"is_agent":{"type":"boolean","title":"Is Agent","description":"True when the decision is the AI agent's close.","default":false}},"type":"object","required":["operator_name","decision"],"title":"AuditDeciderOut"},"AuditMineResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CallAuditListItem"},"type":"array","title":"Items","description":"The caller's in-scope audits created within the requested days, newest activity first, capped at 100."}},"type":"object","required":["items"],"title":"AuditMineResponse"},"AuditOperatorSummary":{"properties":{"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"Deciding operator; null after account deletion."},"operator_name":{"type":"string","title":"Operator Name","description":"Snapshot of the deciding operator's name."},"audited":{"type":"integer","title":"Audited","description":"Audits of this operator's calls in the window."},"upheld":{"type":"integer","title":"Upheld","description":"Calls upheld in the window."},"overturned":{"type":"integer","title":"Overturned","description":"ACTIVE overturns only: confirmed, and not reversed on dispute."},"disputes_pending":{"type":"integer","title":"Disputes Pending","description":"Window audits with a dispute awaiting a ruling."}},"type":"object","required":["operator_name","audited","upheld","overturned","disputes_pending"],"title":"AuditOperatorSummary"},"AuditOverturnCounts":{"properties":{"pending":{"type":"integer","title":"Pending","description":"Peer overturns awaiting lead review."},"confirmed":{"type":"integer","title":"Confirmed","description":"Confirmed and neither disputed nor reversed."},"rejected":{"type":"integer","title":"Rejected","description":"Rejected by a lead (no points)."},"disputed":{"type":"integer","title":"Disputed","description":"Dispute awaiting a ruling."},"reversed":{"type":"integer","title":"Reversed","description":"Dispute reversed the overturn (no points)."}},"type":"object","required":["pending","confirmed","rejected","disputed","reversed"],"title":"AuditOverturnCounts"},"AuditOverturnDeciderCounts":{"properties":{"operator":{"type":"integer","title":"Operator","description":"Overturns of a human operator's call."},"agent":{"type":"integer","title":"Agent","description":"Overturns of the AI agent's close."}},"type":"object","required":["operator","agent"],"title":"AuditOverturnDeciderCounts"},"AuditOverturnsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/CallAuditListItem"},"type":"array","title":"Items","description":"One page of overturned calls: pending first (oldest first), then newest activity."},"total":{"type":"integer","title":"Total","description":"Rows matching the status lens and filters."},"counts":{"$ref":"#/components/schemas/AuditOverturnCounts","description":"Per-status counts over the same days / operator / auditor / decider filters, ignoring `status`."},"decider_counts":{"$ref":"#/components/schemas/AuditOverturnDeciderCounts","description":"The current status lens split by who decided the call, over the same days / operator / auditor filters, ignoring `decider`."}},"type":"object","required":["items","total","counts","decider_counts"],"title":"AuditOverturnsResponse"},"AuditSummaryResponse":{"properties":{"pending_review_count":{"type":"integer","title":"Pending Review Count","description":"Peer overturns awaiting lead confirmation (all time, not windowed).","default":0},"sample_pct":{"type":"integer","title":"Sample Pct","description":"AUDIT_SAMPLE_PCT."},"needs_audit_count":{"type":"integer","title":"Needs Audit Count","description":"Calls in the window selected for audit (a human call that is sampled or rushed, or a sampled agent close) with no audit yet or a peer ruling that a lead may replace."},"pending_disputes":{"items":{"$ref":"#/components/schemas/CallAuditListItem"},"type":"array","title":"Pending Disputes","description":"Disputes awaiting a ruling, oldest first."},"by_operator":{"items":{"$ref":"#/components/schemas/AuditOperatorSummary"},"type":"array","title":"By Operator","description":"Audit counts by deciding operator for the requested window; the AI agent's audits form one \"Verdun Agent\" bucket with a null operator_id."},"by_auditor":{"items":{"$ref":"#/components/schemas/AuditAuditorSummary"},"type":"array","title":"By Auditor","description":"Audit counts by the person who audited, for the requested window, most audits first."},"recent":{"items":{"$ref":"#/components/schemas/CallAuditListItem"},"type":"array","title":"Recent","description":"Up to 50 audits, newest ruling/dispute activity first."}},"type":"object","required":["sample_pct","needs_audit_count","pending_disputes","by_operator","recent"],"title":"AuditSummaryResponse"},"BboxModel":{"properties":{"x1":{"type":"number","title":"X1","description":"Bounding box top-left x coordinate"},"y1":{"type":"number","title":"Y1","description":"Bounding box top-left y coordinate"},"x2":{"type":"number","title":"X2","description":"Bounding box bottom-right x coordinate"},"y2":{"type":"number","title":"Y2","description":"Bounding box bottom-right y coordinate"}},"type":"object","required":["x1","y1","x2","y2"],"title":"BboxModel"},"BillingAccount":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Account id."},"name":{"type":"string","title":"Name","description":"Account name.","examples":["Zeigler Auto"]},"rate_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Cents","description":"This account's override, or null when it inherits the client rate.","examples":[null]}},"type":"object","required":["id","name"],"title":"BillingAccount"},"BillingCamera":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Camera id."},"label":{"type":"string","title":"Label","description":"Camera label.","examples":["Front Lot PTZ"]},"rate_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Cents","description":"This camera's own override, or null when it inherits.","examples":[null]},"effective_rate_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Effective Rate Cents","description":"The rate this camera bills at, or null when no level up its chain has a rate (unpriced).","examples":[4500]},"rate_source":{"anyOf":[{"type":"string","enum":["parent","account","site","camera"]},{"type":"null"}],"title":"Rate Source","description":"Which level the effective rate comes from; null when unpriced.","examples":["parent"]},"live_view_only":{"type":"boolean","title":"Live View Only","description":"Live View only by its own flag or its site's: not counted toward camera_count, MRR or unpriced.","examples":[false]}},"type":"object","required":["id","label","live_view_only"],"title":"BillingCamera"},"BillingConcessionResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Concession id."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client it was given to."},"parent_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Name","description":"That client's name now.","examples":["Northgate Security"]},"title":{"type":"string","title":"Title","description":"What was given.","examples":["First month free"]},"details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Details","description":"Free-text notes; null if none."},"starts_on":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Starts On","description":"Start date, if dated.","examples":["2026-11-01"]},"ends_on":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Ends On","description":"End date, if it ends.","examples":["2027-10-31"]},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When it was added."},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name","description":"Who added it, as named then.","examples":["Ada Owner"]},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When it was last edited; null if never."},"updated_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated By Name","description":"Who saved it last, as named then.","examples":["Ada Owner"]}},"type":"object","required":["id","parent_id","title"],"title":"BillingConcessionResponse"},"BillingHistoryEntry":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"History row id."},"changed_at":{"type":"string","format":"date-time","title":"Changed At","description":"When the change was saved."},"changed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Changed By Name","description":"Who saved it, as named at the time; null if unknown.","examples":["Ada Owner"]},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client the node sat under when changed."},"parent_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Name","description":"That client's name at the time.","examples":["Northgate Security"]},"level":{"type":"string","enum":["parent","account","site","camera"],"title":"Level","description":"Which level was changed.","examples":["site"]},"target_id":{"type":"string","format":"uuid","title":"Target Id","description":"The changed client, account, site or camera."},"target_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Name","description":"Its name at the time.","examples":["Lakeside Chevrolet"]},"field":{"type":"string","enum":["rate","billable","contracted_arr"],"title":"Field","description":"Which value changed.","examples":["rate"]},"old_rate_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Old Rate Cents","description":"Previous per-camera rate in cents (null = inherited / not set). Only for field 'rate'.","examples":[4500]},"new_rate_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"New Rate Cents","description":"New per-camera rate in cents (null = cleared, inherits). Only for field 'rate'.","examples":[4000]},"old_billable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Old Billable","description":"Previous billable flag. Only for field 'billable'.","examples":[true]},"new_billable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"New Billable","description":"New billable flag. Only for field 'billable'.","examples":[false]},"old_contracted_arr_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Old Contracted Arr Cents","description":"Previous contracted ARR in cents (null = not recorded). Only for field 'contracted_arr', always level 'parent'.","examples":[12000000]},"new_contracted_arr_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"New Contracted Arr Cents","description":"New contracted ARR in cents (null = cleared). Only for field 'contracted_arr'.","examples":[15000000]}},"type":"object","required":["id","changed_at","parent_id","level","target_id","field"],"title":"BillingHistoryEntry"},"BillingParent":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Client id."},"name":{"type":"string","title":"Name","description":"Client name.","examples":["Imperial Security"]},"rate_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Cents","description":"The client's base per-camera monthly rate in cents; null when not set.","examples":[4500]},"mrr_cents":{"type":"integer","title":"Mrr Cents","description":"Monthly revenue from this client's billable sites, in cents.","examples":[540000]},"arr_cents":{"type":"integer","title":"Arr Cents","description":"Live ARR: this client's MRR x 12, in cents.","examples":[6480000]},"contracted_arr_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Contracted Arr Cents","description":"The annual amount the client contracted for, in cents, entered by the owner; null when not recorded.","examples":[12000000]},"live_to_contracted_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Live To Contracted Ratio","description":"arr_cents / contracted_arr_cents (0.5 = live is half the contract, shown as 50%); null when no contract is recorded or it is $0.","examples":[0.54]},"billable_sites":{"type":"integer","title":"Billable Sites","description":"Billable sites.","examples":[12]},"billable_cameras":{"type":"integer","title":"Billable Cameras","description":"Cameras on billable sites, not counting Live View only ones.","examples":[120]},"unpriced_cameras":{"type":"integer","title":"Unpriced Cameras","description":"Cameras on billable sites with no rate.","examples":[0]},"live_view_only_cameras":{"type":"integer","title":"Live View Only Cameras","description":"Live View only cameras on any of the client's sites; never billed.","examples":[0]},"accounts":{"items":{"$ref":"#/components/schemas/BillingAccount"},"type":"array","title":"Accounts","description":"The client's live accounts."},"sites":{"items":{"$ref":"#/components/schemas/BillingSite"},"type":"array","title":"Sites","description":"The client's live sites."}},"type":"object","required":["id","name","mrr_cents","arr_cents","billable_sites","billable_cameras","unpriced_cameras","live_view_only_cameras","accounts","sites"],"title":"BillingParent"},"BillingSite":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Site id."},"name":{"type":"string","title":"Name","description":"Site name.","examples":["Zeigler Chevy Schaumburg"]},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"The site's live account, or null when it sits directly under the client."},"stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stage","description":"'test' or 'live' (display label).","examples":["live"]},"billable":{"type":"boolean","title":"Billable","description":"Whether this site's cameras count toward MRR.","examples":[true]},"rate_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Cents","description":"This site's own override, or null when it inherits.","examples":[null]},"inherited_rate_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Inherited Rate Cents","description":"What a camera here without its own override bills at (site, then account, then client).","examples":[4500]},"inherited_rate_source":{"anyOf":[{"type":"string","enum":["parent","account","site","camera"]},{"type":"null"}],"title":"Inherited Rate Source","description":"Which level inherited_rate_cents comes from; null when unpriced.","examples":["parent"]},"live_view_only":{"type":"boolean","title":"Live View Only","description":"The whole site is Live View only, so none of its cameras count.","examples":[false]},"camera_count":{"type":"integer","title":"Camera Count","description":"Non-deleted cameras on the site that count, i.e. not Live View only.","examples":[12]},"live_view_only_cameras":{"type":"integer","title":"Live View Only Cameras","description":"Non-deleted Live View only cameras on the site; never billed.","examples":[0]},"mrr_cents":{"type":"integer","title":"Mrr Cents","description":"This site's monthly revenue in cents; 0 when not billable.","examples":[54000]},"unpriced_cameras":{"type":"integer","title":"Unpriced Cameras","description":"Counted cameras with no rate anywhere up their chain (0 when not billable).","examples":[0]},"cameras":{"items":{"$ref":"#/components/schemas/BillingCamera"},"type":"array","title":"Cameras","description":"The site's cameras."}},"type":"object","required":["id","name","billable","live_view_only","camera_count","live_view_only_cameras","mrr_cents","unpriced_cameras","cameras"],"title":"BillingSite"},"BillingTotals":{"properties":{"mrr_cents":{"type":"integer","title":"Mrr Cents","description":"Monthly recurring revenue across every client, in cents.","examples":[1250000]},"arr_cents":{"type":"integer","title":"Arr Cents","description":"Annual recurring revenue: MRR x 12, in cents.","examples":[15000000]},"billable_sites":{"type":"integer","title":"Billable Sites","description":"Billable sites.","examples":[40]},"non_billable_sites":{"type":"integer","title":"Non Billable Sites","description":"Sites marked not billable.","examples":[5]},"billable_cameras":{"type":"integer","title":"Billable Cameras","description":"Cameras on billable sites, not counting Live View only ones.","examples":[420]},"unpriced_cameras":{"type":"integer","title":"Unpriced Cameras","description":"Cameras on billable sites with no rate anywhere up their chain.","examples":[0]},"live_view_only_cameras":{"type":"integer","title":"Live View Only Cameras","description":"Live View only cameras across every site; never billed.","examples":[0]},"contracted_arr_cents":{"type":"integer","title":"Contracted Arr Cents","description":"Sum of every recorded contracted ARR, in cents.","examples":[30000000]},"contracted_live_arr_cents":{"type":"integer","title":"Contracted Live Arr Cents","description":"Live ARR of just the clients that have a contracted ARR, in cents: the numerator of live_to_contracted_ratio.","examples":[18000000]},"live_to_contracted_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Live To Contracted Ratio","description":"contracted_live_arr_cents / contracted_arr_cents; null when no contract is recorded or they sum to $0.","examples":[0.6]}},"type":"object","required":["mrr_cents","arr_cents","billable_sites","non_billable_sites","billable_cameras","unpriced_cameras","live_view_only_cameras","contracted_arr_cents","contracted_live_arr_cents"],"title":"BillingTotals"},"Body_create_demo_event_upload_events_demo_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"prompt_profile":{"type":"string","enum":["production","demo"],"title":"Prompt Profile","default":"production"},"monitoring_profile":{"type":"string","enum":["security","safety"],"title":"Monitoring Profile","default":"security"}},"type":"object","required":["file"],"title":"Body_create_demo_event_upload_events_demo_post"},"Body_moderate_comment_essay_engagement_comments__comment_id__moderate_post":{"properties":{"token":{"type":"string","title":"Token","description":"Moderation token.","default":""},"action":{"type":"string","title":"Action","description":"`approve` or `delete`.","default":""}},"type":"object","title":"Body_moderate_comment_essay_engagement_comments__comment_id__moderate_post"},"Body_post_site_summary_unsubscribe_site_summaries_unsubscribe_post":{"properties":{"token":{"anyOf":[{"type":"string","maxLength":1024},{"type":"null"}],"title":"Token"}},"type":"object","title":"Body_post_site_summary_unsubscribe_site_summaries_unsubscribe_post"},"Body_save_app_branding_parents__parent_id__app_branding_put":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Use dealer branding on customer screens"},"app_name":{"type":"string","maxLength":40,"title":"App Name","description":"Customer-facing display name; required when enabled","default":""},"revision":{"type":"integer","minimum":0.0,"title":"Revision","description":"Revision returned by the last read"},"remove_logo":{"type":"boolean","title":"Remove Logo","description":"Remove the saved logo","default":false},"file":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"File","description":"Optional PNG, JPEG or WebP logo, at most 2 MB"}},"type":"object","required":["enabled","revision"],"title":"Body_save_app_branding_parents__parent_id__app_branding_put"},"Body_submit_application_careers_applications_post":{"properties":{"full_name":{"type":"string","maxLength":200,"title":"Full Name"},"email":{"type":"string","maxLength":320,"title":"Email"},"role":{"type":"string","maxLength":100,"title":"Role"},"phone":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Phone"},"link":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Link"},"github":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Github"},"note":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"title":"Note"},"company":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Company"},"resume":{"type":"string","contentMediaType":"application/octet-stream","title":"Resume"}},"type":"object","required":["full_name","email","role","resume"],"title":"Body_submit_application_careers_applications_post"},"Body_submit_consent_sms_opt_in_consent_post":{"properties":{"full_name":{"type":"string","maxLength":200,"title":"Full Name"},"phone":{"type":"string","maxLength":50,"title":"Phone"},"consent":{"type":"string","maxLength":20,"title":"Consent"},"email":{"anyOf":[{"type":"string","maxLength":320},{"type":"null"}],"title":"Email"},"organization":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Organization"},"company":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Company"},"consent_version":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Consent Version"},"idempotency_key":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Idempotency Key"}},"type":"object","required":["full_name","phone","consent"],"title":"Body_submit_consent_sms_opt_in_consent_post"},"Body_upload_site_floorplan_sites__site_id__floorplan_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"},"width":{"type":"integer","maximum":20000.0,"minimum":1.0,"title":"Width"},"height":{"type":"integer","maximum":20000.0,"minimum":1.0,"title":"Height"}},"type":"object","required":["file","width","height"],"title":"Body_upload_site_floorplan_sites__site_id__floorplan_post"},"BridgePoolTargetResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera's UUID (cameras.id). NOT cameras.camera_id, which is the human-readable per-site slug — this payload deliberately mirrors the ingestor camera-config contract, where `id` means the same thing."},"integration_id":{"type":"string","format":"uuid","title":"Integration Id","description":"The cloud-VMS connection that mints this device's streaming grant: GET /internal/integrations/{integration_id}/cameras/{external_device_id}/stream."},"external_device_id":{"type":"string","title":"External Device Id","description":"The vendor's own camera id, used to request the grant.","examples":["3a1b0f4c-1d2e-4f5a-9b8c-7d6e5f4a3b2c"]},"stream_path":{"type":"string","title":"Stream Path","description":"The RTSP path the bridge must publish on. DERIVED FROM THE CAMERA'S STORED SOURCE URL, not recomputed from configuration, so the path the bridge publishes and the path the ingestor pulls cannot drift apart.","examples":["3a1b0f4c-1d2e-4f5a-9b8c-7d6e5f4a3b2c"]},"provider":{"type":"string","title":"Provider","description":"Which puller the bridge must run: `avertx` -> the OSP WebSocket puller, anything else -> the HLS URL puller. ADDED 2026-10-20 (the sixth key; the pool contract's seventh). The bridge ignores unknown keys by design, so this can ship before the bridge reads it; a bridge that does not know it keeps pulling every camera by URL, which is what every camera was before AvertX existed.","examples":["verkada","turing","avertx"]},"monitoring_active":{"type":"boolean","title":"Monitoring Active","description":"Whether this camera's schedule says it should be monitored right now, computed server-side at one shared instant for the whole response. False means the bridge must stop pulling: the stream costs ~1 Mbps and nothing downstream would look at it."},"shard":{"type":"integer","maximum":999.0,"minimum":0.0,"title":"Shard","description":"The pool shard that owns this camera, 0-999, PARSED FROM THE HOST OF ITS STORED ADDRESS (bridge-pool-s{shard}.verdun.internal:8554). A shard task publishes only the rows carrying its own index; it needs no shard count and no hash."}},"type":"object","required":["id","integration_id","external_device_id","stream_path","provider","monitoring_active","shard"],"title":"BridgePoolTargetResponse"},"BridgeTargetResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera's UUID (cameras.id). NOT cameras.camera_id, which is the human-readable per-site slug — this payload deliberately mirrors the ingestor camera-config contract, where `id` means the same thing."},"integration_id":{"type":"string","format":"uuid","title":"Integration Id","description":"The cloud-VMS connection that mints this device's streaming grant: GET /internal/integrations/{integration_id}/cameras/{external_device_id}/stream."},"external_device_id":{"type":"string","title":"External Device Id","description":"The vendor's own camera id, used to request the grant.","examples":["3a1b0f4c-1d2e-4f5a-9b8c-7d6e5f4a3b2c"]},"stream_path":{"type":"string","title":"Stream Path","description":"The RTSP path the bridge must publish on. DERIVED FROM THE CAMERA'S STORED SOURCE URL, not recomputed from configuration, so the path the bridge publishes and the path the ingestor pulls cannot drift apart.","examples":["3a1b0f4c-1d2e-4f5a-9b8c-7d6e5f4a3b2c"]},"provider":{"type":"string","title":"Provider","description":"Which puller the bridge must run: `avertx` -> the OSP WebSocket puller, anything else -> the HLS URL puller. ADDED 2026-10-20 (the sixth key; the pool contract's seventh). The bridge ignores unknown keys by design, so this can ship before the bridge reads it; a bridge that does not know it keeps pulling every camera by URL, which is what every camera was before AvertX existed.","examples":["verkada","turing","avertx"]},"monitoring_active":{"type":"boolean","title":"Monitoring Active","description":"Whether this camera's schedule says it should be monitored right now, computed server-side at one shared instant for the whole response. False means the bridge must stop pulling: the stream costs ~1 Mbps and nothing downstream would look at it."}},"type":"object","required":["id","integration_id","external_device_id","stream_path","provider","monitoring_active"],"title":"BridgeTargetResponse"},"BulkCloseSurface":{"type":"string","enum":["site_bulk","camera_bulk","rail_bulk"],"title":"BulkCloseSurface","description":"Which console surface sent a bulk close (`POST /eval/operator/bulk`).\nWritten, verbatim, as the claim_log `surface` of every line of that call\nand nothing else. `site_bulk` (By site, Close all) is the default when the\nclient sends nothing; the single-event close is always `single` and never\nuses this enum."},"BulkOnboardDefaults":{"properties":{"ingest_mode":{"$ref":"#/components/schemas/CameraIngestMode","description":"Applied to every onboarded camera; individual cameras can be edited afterwards.","default":"motion_clip"},"motion_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Motion Config","description":"Extensible motion settings with bounded known keys (timings, thresholds, detector geometry/rate and RTSP buffer mode); see app/services/motion_config_validation.py. Unknown keys are preserved.","examples":[{"debounce_seconds":30,"post_roll_seconds":5,"pre_roll_seconds":5}]},"analytics_provider":{"$ref":"#/components/schemas/AnalyticsProvider","description":"Analytics vendor validating triggered clips before upload.","default":"none"},"analytics_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Analytics Config","description":"Opaque per-vendor analytics settings interpreted by the ingestor."}},"type":"object","title":"BulkOnboardDefaults"},"BulkOnboardDevice":{"properties":{"device_id":{"type":"string","title":"Device Id","description":"Vendor device id exactly as returned by GET /nvrs/{id}/devices (Nx device UUID, ONVIF profile token, or Hikvision channel number)."},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Display label; defaults to the NVR-reported device name."},"camera_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id","description":"Verdun camera_id; defaults to a slug of the label, uniquified per site."},"transcode_h264":{"type":"boolean","title":"Transcode H264","description":"Ask the NVR to re-encode this camera's stream to H.264 (appends codec=h264 to the Nx relay URL). For H.265 cameras, which the ingest pipeline cannot decode. Costs NVR CPU per stream. Ignored for non-Nx NVRs (Hikvision: switch the sub stream to H.264 on the NVR).","default":false}},"type":"object","required":["device_id"],"title":"BulkOnboardDevice"},"BulkOnboardRequest":{"properties":{"devices":{"items":{"$ref":"#/components/schemas/BulkOnboardDevice"},"type":"array","minItems":1,"title":"Devices","description":"NVR devices to onboard."},"defaults":{"$ref":"#/components/schemas/BulkOnboardDefaults","description":"Settings applied to every onboarded camera."}},"type":"object","required":["devices"],"title":"BulkOnboardRequest"},"BulkOnboardResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/BulkOnboardResult"},"type":"array","title":"Results","description":"Per-device outcome, in request order."},"created":{"type":"integer","title":"Created","description":"Number of cameras created."},"failed":{"type":"integer","title":"Failed","description":"Number of devices that failed."},"skipped":{"type":"integer","title":"Skipped","description":"Number of devices skipped because they were already onboarded."}},"type":"object","required":["results","created","failed","skipped"],"title":"BulkOnboardResponse"},"BulkOnboardResult":{"properties":{"device_id":{"type":"string","title":"Device Id","description":"Vendor device id this result is for (Nx device UUID, ONVIF profile token, or Hikvision channel number)."},"status":{"type":"string","enum":["created","failed","skipped_already_onboarded"],"title":"Status","description":"What happened to this device."},"camera_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id","description":"The created camera's camera_id (slug), when created."},"camera_uuid":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Camera Uuid","description":"The created camera's UUID, when created."},"kvs_stream_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kvs Stream Name","description":"The created camera's KVS stream name, when created."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure detail, when failed."}},"type":"object","required":["device_id","status"],"title":"BulkOnboardResult"},"BulkOperatorEvalFailure":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"The event that was NOT disposed"},"status":{"type":"integer","title":"Status","description":"The status the single-event route would have answered: 404 unknown, 403 out of scope or missing the disposition permission, 409 already disposed, 423 held: another operator holds a live claim on it (it is NOT gone and NOT closed: keep it queued with its notes and use Take over first)."},"reason":{"type":"string","title":"Reason","description":"Why, in the single-event route's words"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code","description":"Machine code for a refusal that has one. `claim_held` on a 423 item (kept distinct from the 409 'already closed'); absent otherwise."},"claim":{"anyOf":[{"$ref":"#/components/schemas/AlarmClaim"},{"type":"null"}],"description":"On a 423 `claim_held` item: the live claim that refused it (who holds the alarm and when it lapses). Absent otherwise. Only central staff can be refused, so this never reaches a customer-side caller."},"reservation":{"anyOf":[{"$ref":"#/components/schemas/Reservation"},{"type":"null"}],"description":"Claim-lock R5 (Focus). On a 423 `claim_held` item for an alarm RESERVED for another operator's focus and with no live claim: the reservation that refused it (`claim` is then absent). Absent otherwise."}},"type":"object","required":["event_id","status","reason"],"title":"BulkOperatorEvalFailure"},"BulkOperatorEvalRequest":{"properties":{"event_ids":{"items":{"type":"string"},"type":"array","maxItems":500,"minItems":1,"title":"Event Ids","description":"The queued events to close out with ONE disposition - the site story's \"Close all N\". Duplicates are collapsed. Each event is disposed in its own transaction under the same rules as the single-event route (tenant scope, disposition permission, one evaluation per event); the response reports every id that could not be disposed instead of failing the batch.","examples":[["6f1c1c1e-0000-4000-8000-000000000001"]]},"trace_context":{"properties":{"close_surface":{"type":["string","null"],"enum":["list","by_site","story","review_from_story","next_alarm","focus","deep_link","history","unknown",null],"maxLength":32},"entry_source":{"type":["string","null"],"enum":["list","by_site","story","next_alarm","focus","deep_link","review_rail","history","notification","unknown",null],"maxLength":32}},"type":["object","null"],"title":"Trace Context","description":"Optional client close provenance. close_surface and entry_source are optional; invalid containers or values are ignored, never a reason to reject the close."},"operator_disposition":{"$ref":"#/components/schemas/OperatorDisposition","description":"The disposition applied to every event. Only the two non-escalating dispositions are accepted in bulk: a talk-down or a dispatch is a single incident's decision and goes through the single-event route.","examples":["False alarm - no human activity","Non-threatening human activity"]},"notes":{"type":"string","title":"Notes","description":"The note written on every event's evaluation.","examples":["Employee walking the lot, badged in at the side door."]},"closeout":{"anyOf":[{"$ref":"#/components/schemas/CloseoutEntry"},{"type":"null"}],"description":"Structured close-out record, written on every event. Same shape and same CLOSEOUT_REQUIRED enforcement as the single-event route. closeout.ai_feedback is REJECTED here (422): the close-out is shared by every event, so a per-event answer cannot live in it. Use ai_feedback_event_id / ai_feedback instead."},"ai_feedback_event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Feedback Event Id","description":"The ONE event the site story's AI-feedback question was about (owner 2026-10-03: \"selected clip only\"). Must be one of event_ids, and only with SUSPICIOUS_MONITOR. ai_feedback_asked on that event is decided by the same server rule as the single-event route (capture flag on, real VLM verdict); every other event stays unasked. Valid WITHOUT ai_feedback: the question was shown and left blank.","examples":["6f1c1c1e-0000-4000-8000-000000000001"]},"ai_feedback":{"anyOf":[{"$ref":"#/components/schemas/AiFeedbackBlock"},{"type":"null"}],"description":"The operator's answer for ai_feedback_event_id only, stored on that event's close-out alone. Requires ai_feedback_event_id."},"story_alarm_count":{"anyOf":[{"type":"integer","maximum":100000.0,"minimum":1.0},{"type":"null"}],"title":"Story Alarm Count","description":"How many alarms the WHOLE site story closed, across every batch (the client splits a story into batches of at most 500). Only with ai_feedback_event_id; stored as provenance beside that event's answer. Omitted = stored as null (unknown); never inferred from this request's own count.","examples":[12]},"surface":{"$ref":"#/components/schemas/BulkCloseSurface","description":"Which console surface sent this bulk close: `site_bulk` (By site, Close all), `camera_bulk` or `rail_bulk`. Optional; absent = `site_bulk`, so existing clients are unchanged. It is written as the claim-log `surface` of EVERY claim_log line of this call (closes, refusals, errors) and changes nothing else. Any other value is a 422.","default":"site_bulk","examples":["rail_bulk"]}},"type":"object","required":["event_ids","operator_disposition","notes"],"title":"BulkOperatorEvalRequest"},"BulkOperatorEvalResponse":{"properties":{"disposed":{"items":{"type":"string"},"type":"array","title":"Disposed","description":"Event ids that now carry the disposition, in request order"},"failed":{"items":{"$ref":"#/components/schemas/BulkOperatorEvalFailure"},"type":"array","title":"Failed","description":"Event ids that could not be disposed, each with the reason. A clip another operator triaged between the operator opening the site and closing it out shows here as 409 rather than silently vanishing from the batch."},"request_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Id","description":"This call's id: the `request=` value on every claim_log line it wrote. A console can echo it as `closes` on `POST /eval/operator/bulk/undo` so the undo's log lines name the close they reverse. Log correlation only; absent from an older api."}},"type":"object","required":["disposed","failed"],"title":"BulkOperatorEvalResponse"},"BulkUndoOperatorEvalFailure":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"The event that was NOT reopened"},"status":{"type":"integer","title":"Status","description":"The status the single-event route would use: 404 unknown / hidden / not closed, 403 out of scope or not the caller's close, 409 not undoable, audited or too late."},"code":{"type":"string","title":"Code","description":"Machine code: `not_found`, `not_closed` (it is already open: a second click, or a retry after a lost answer), `forbidden`, `not_yours`, `not_undoable` (not a bulk false-alarm / non-threatening close), `audited`, `too_late`, `error`."},"reason":{"type":"string","title":"Reason","description":"Why, in the single-event undo route's words"}},"type":"object","required":["event_id","status","code","reason"],"title":"BulkUndoOperatorEvalFailure"},"BulkUndoOperatorEvalRequest":{"properties":{"event_ids":{"items":{"type":"string"},"type":"array","maxItems":1000,"minItems":1,"title":"Event Ids","description":"The events a site-story \"Close all\" just closed (the `disposed` ids the bulk close returned), up to 1,000 per request (the close takes 500). Duplicates are collapsed. Each event is reopened in its own transaction, so some can be refused while the rest still come back.","examples":[["6f1c1c1e-0000-4000-8000-000000000001"]]},"surface":{"$ref":"#/components/schemas/BulkCloseSurface","description":"Which console surface made the close being undone. Written as the claim-log `surface` of every `close_undo` line of this call and changes nothing else. One of `site_bulk`, `camera_bulk` or `rail_bulk`, the same three the bulk close accepts. Optional; absent = `site_bulk`. Any other value is a 422.","default":"site_bulk","examples":["site_bulk"]},"closes":{"anyOf":[{"type":"string","maxLength":32,"pattern":"^[A-Za-z0-9_-]+$"},{"type":"null"}],"title":"Closes","description":"The `request_id` the bulk close answered with, echoed back so the claim log can tie this undo to that close. Log correlation only: it is never read for authority.","examples":["a1b2c3d4e5f6"]}},"type":"object","required":["event_ids"],"title":"BulkUndoOperatorEvalRequest"},"BulkUndoOperatorEvalResponse":{"properties":{"reopened":{"items":{"type":"string"},"type":"array","title":"Reopened","description":"Event ids that are open again, in request order. The alarms come back UNCLAIMED: the story holds no claims, and claiming hundreds of alarms would lock out teammates."},"failed":{"items":{"$ref":"#/components/schemas/BulkUndoOperatorEvalFailure"},"type":"array","title":"Failed","description":"Event ids that stayed closed, each with the reason, in request order"},"request_id":{"type":"string","title":"Request Id","description":"This call's id: the `request=` value on its claim_log lines"}},"type":"object","required":["reopened","failed","request_id"],"title":"BulkUndoOperatorEvalResponse"},"ByDayHours":{"properties":{"mode":{"type":"string","const":"by_day","title":"Mode","description":"Different hours on different days."},"days":{"items":{"$ref":"#/components/schemas/DayHours"},"type":"array","maxItems":7,"minItems":1,"title":"Days","description":"One row per weekday, each at most once."}},"additionalProperties":false,"type":"object","required":["mode","days"],"title":"ByDayHours"},"CallAuditDisputeOut":{"properties":{"note":{"type":"string","title":"Note","description":"The decider's dispute note."},"at":{"type":"string","format":"date-time","title":"At","description":"When the dispute was filed."},"status":{"type":"string","enum":["pending","reversed","stands"],"title":"Status","description":"pending until a different lead rules."},"ruled_by_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Ruled By Id","description":"The ruling lead; null while pending or once that account is deleted."},"ruled_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ruled By Name","description":"Snapshot of the ruling lead's name."},"ruled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ruled At","description":"When the ruling was made."}},"type":"object","required":["note","at","status"],"title":"CallAuditDisputeOut"},"CallAuditListItem":{"properties":{"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"The audited alarm."},"evaluation_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Evaluation Id","description":"The audited evaluation; null once that evaluation or its deciding account is deleted."},"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"The decider; null once that account is deleted, and always null when the decider is the AI agent."},"operator_name":{"type":"string","title":"Operator Name","description":"Snapshot of the decider's name; \"Verdun Agent\" when the decider is the AI agent."},"decision":{"type":"string","title":"Decision","description":"The decider's OperatorDisposition MEMBER NAME at audit time."},"verdict":{"type":"string","enum":["upheld","overturned"],"title":"Verdict","description":"The lead's ruling on the original decision."},"correct_disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Correct Disposition","description":"Overturned only: the disposition it should have been (member name)."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Overturned only: the auditor's reason."},"auditor_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Auditor Id","description":"The auditing lead; null once that account is deleted."},"auditor_name":{"type":"string","title":"Auditor Name","description":"Snapshot of the auditing lead's name."},"ad_hoc":{"type":"boolean","title":"Ad Hoc","description":"True when the call was neither sampled nor rushed: a lead picked it themselves."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the audit was made."},"decider_is_agent":{"type":"boolean","title":"Decider Is Agent","description":"True when the audited decision was the AI agent's close (not a human operator's). Nobody loses points on such an audit and nobody can dispute it.","default":false},"points":{"type":"integer","title":"Points","description":"-1000 (SCORE_OVERTURN) charged to the decider while an overturn is active (confirmed, not reversed on dispute), else 0. Always 0 when the decider is the AI agent."},"catch_points":{"type":"integer","title":"Catch Points","description":"Credited to a PEER auditor while their overturn is active, else 0 (a lead's overturn earns no catch): +1000 (SCORE_CATCH) for a human decider, +250 (SCORE_AGENT_CATCH) when the decider is the AI agent, 0 when `auto_confirmed`.","default":0},"auto_confirmed":{"type":"boolean","title":"Auto Confirmed","description":"A peer's overturn of an AI agent false alarm (Non-threatening -> False alarm): confirmed at once with no lead review (reviewed_by_id null) and worth 0 points. Only agent misses go to a lead and earn the agent catch.","default":false},"dispute":{"anyOf":[{"$ref":"#/components/schemas/CallAuditDisputeOut"},{"type":"null"}],"description":"The decider's dispute and its ruling, if any."},"auditor_is_lead":{"type":"boolean","title":"Auditor Is Lead","description":"Snapshot: the auditor was an owner or monitoring manager when they ruled (false = a peer operator).","default":true},"review_status":{"anyOf":[{"type":"string","enum":["pending","confirmed","rejected"]},{"type":"null"}],"title":"Review Status","description":"Overturned only (null on an upheld audit). A lead's overturn is confirmed at once; a peer's is pending until a lead confirms or rejects it. Only a confirmed overturn costs points."},"reviewed_by_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Reviewed By Id","description":"The lead who confirmed or rejected the overturn (the auditor, for a lead's own); null while pending or once that account is deleted."},"reviewed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewed By Name","description":"Snapshot of the reviewing lead's name."},"reviewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reviewed At","description":"When the overturn was confirmed or rejected."},"review_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Note","description":"The reviewing lead's optional note."},"superseded_peer_audit":{"anyOf":[{"$ref":"#/components/schemas/CallAuditRecord"},{"type":"null"}],"description":"Immutable original peer ruling saved before a lead replaced it; visible under the same audit permissions."},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site of the audited alarm."},"camera_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Name","description":"Camera label of the audited alarm."}},"type":"object","required":["event_id","operator_name","decision","verdict","auditor_name","ad_hoc","created_at","points"],"title":"CallAuditListItem"},"CallAuditOut":{"properties":{"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"The audited alarm."},"evaluation_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Evaluation Id","description":"The audited evaluation; null once that evaluation or its deciding account is deleted."},"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"The decider; null once that account is deleted, and always null when the decider is the AI agent."},"operator_name":{"type":"string","title":"Operator Name","description":"Snapshot of the decider's name; \"Verdun Agent\" when the decider is the AI agent."},"decision":{"type":"string","title":"Decision","description":"The decider's OperatorDisposition MEMBER NAME at audit time."},"verdict":{"type":"string","enum":["upheld","overturned"],"title":"Verdict","description":"The lead's ruling on the original decision."},"correct_disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Correct Disposition","description":"Overturned only: the disposition it should have been (member name)."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Overturned only: the auditor's reason."},"auditor_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Auditor Id","description":"The auditing lead; null once that account is deleted."},"auditor_name":{"type":"string","title":"Auditor Name","description":"Snapshot of the auditing lead's name."},"ad_hoc":{"type":"boolean","title":"Ad Hoc","description":"True when the call was neither sampled nor rushed: a lead picked it themselves."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the audit was made."},"decider_is_agent":{"type":"boolean","title":"Decider Is Agent","description":"True when the audited decision was the AI agent's close (not a human operator's). Nobody loses points on such an audit and nobody can dispute it.","default":false},"points":{"type":"integer","title":"Points","description":"-1000 (SCORE_OVERTURN) charged to the decider while an overturn is active (confirmed, not reversed on dispute), else 0. Always 0 when the decider is the AI agent."},"catch_points":{"type":"integer","title":"Catch Points","description":"Credited to a PEER auditor while their overturn is active, else 0 (a lead's overturn earns no catch): +1000 (SCORE_CATCH) for a human decider, +250 (SCORE_AGENT_CATCH) when the decider is the AI agent, 0 when `auto_confirmed`.","default":0},"auto_confirmed":{"type":"boolean","title":"Auto Confirmed","description":"A peer's overturn of an AI agent false alarm (Non-threatening -> False alarm): confirmed at once with no lead review (reviewed_by_id null) and worth 0 points. Only agent misses go to a lead and earn the agent catch.","default":false},"dispute":{"anyOf":[{"$ref":"#/components/schemas/CallAuditDisputeOut"},{"type":"null"}],"description":"The decider's dispute and its ruling, if any."},"auditor_is_lead":{"type":"boolean","title":"Auditor Is Lead","description":"Snapshot: the auditor was an owner or monitoring manager when they ruled (false = a peer operator).","default":true},"review_status":{"anyOf":[{"type":"string","enum":["pending","confirmed","rejected"]},{"type":"null"}],"title":"Review Status","description":"Overturned only (null on an upheld audit). A lead's overturn is confirmed at once; a peer's is pending until a lead confirms or rejects it. Only a confirmed overturn costs points."},"reviewed_by_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Reviewed By Id","description":"The lead who confirmed or rejected the overturn (the auditor, for a lead's own); null while pending or once that account is deleted."},"reviewed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewed By Name","description":"Snapshot of the reviewing lead's name."},"reviewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reviewed At","description":"When the overturn was confirmed or rejected."},"review_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Note","description":"The reviewing lead's optional note."},"superseded_peer_audit":{"anyOf":[{"$ref":"#/components/schemas/CallAuditRecord"},{"type":"null"}],"description":"Immutable original peer ruling saved before a lead replaced it; visible under the same audit permissions."}},"type":"object","required":["event_id","operator_name","decision","verdict","auditor_name","ad_hoc","created_at","points"],"title":"CallAuditOut"},"CallAuditRecord":{"properties":{"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"The audited alarm."},"evaluation_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Evaluation Id","description":"The audited evaluation; null once that evaluation or its deciding account is deleted."},"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"The decider; null once that account is deleted, and always null when the decider is the AI agent."},"operator_name":{"type":"string","title":"Operator Name","description":"Snapshot of the decider's name; \"Verdun Agent\" when the decider is the AI agent."},"decision":{"type":"string","title":"Decision","description":"The decider's OperatorDisposition MEMBER NAME at audit time."},"verdict":{"type":"string","enum":["upheld","overturned"],"title":"Verdict","description":"The lead's ruling on the original decision."},"correct_disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Correct Disposition","description":"Overturned only: the disposition it should have been (member name)."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Overturned only: the auditor's reason."},"auditor_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Auditor Id","description":"The auditing lead; null once that account is deleted."},"auditor_name":{"type":"string","title":"Auditor Name","description":"Snapshot of the auditing lead's name."},"ad_hoc":{"type":"boolean","title":"Ad Hoc","description":"True when the call was neither sampled nor rushed: a lead picked it themselves."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the audit was made."},"decider_is_agent":{"type":"boolean","title":"Decider Is Agent","description":"True when the audited decision was the AI agent's close (not a human operator's). Nobody loses points on such an audit and nobody can dispute it.","default":false},"points":{"type":"integer","title":"Points","description":"-1000 (SCORE_OVERTURN) charged to the decider while an overturn is active (confirmed, not reversed on dispute), else 0. Always 0 when the decider is the AI agent."},"catch_points":{"type":"integer","title":"Catch Points","description":"Credited to a PEER auditor while their overturn is active, else 0 (a lead's overturn earns no catch): +1000 (SCORE_CATCH) for a human decider, +250 (SCORE_AGENT_CATCH) when the decider is the AI agent, 0 when `auto_confirmed`.","default":0},"auto_confirmed":{"type":"boolean","title":"Auto Confirmed","description":"A peer's overturn of an AI agent false alarm (Non-threatening -> False alarm): confirmed at once with no lead review (reviewed_by_id null) and worth 0 points. Only agent misses go to a lead and earn the agent catch.","default":false},"dispute":{"anyOf":[{"$ref":"#/components/schemas/CallAuditDisputeOut"},{"type":"null"}],"description":"The decider's dispute and its ruling, if any."},"auditor_is_lead":{"type":"boolean","title":"Auditor Is Lead","description":"Snapshot: the auditor was an owner or monitoring manager when they ruled (false = a peer operator).","default":true},"review_status":{"anyOf":[{"type":"string","enum":["pending","confirmed","rejected"]},{"type":"null"}],"title":"Review Status","description":"Overturned only (null on an upheld audit). A lead's overturn is confirmed at once; a peer's is pending until a lead confirms or rejects it. Only a confirmed overturn costs points."},"reviewed_by_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Reviewed By Id","description":"The lead who confirmed or rejected the overturn (the auditor, for a lead's own); null while pending or once that account is deleted."},"reviewed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewed By Name","description":"Snapshot of the reviewing lead's name."},"reviewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reviewed At","description":"When the overturn was confirmed or rejected."},"review_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Review Note","description":"The reviewing lead's optional note."}},"type":"object","required":["event_id","operator_name","decision","verdict","auditor_name","ad_hoc","created_at","points"],"title":"CallAuditRecord"},"CallOwnerRequest":{"properties":{"contact_id":{"type":"string","title":"Contact Id","description":"Site contact to call; must belong to the event's site","examples":["2dd7154e-21dc-408f-ac25-b2918d591e29"]},"number":{"type":"string","enum":["primary","afterhours"],"title":"Number","description":"Which of the contact's phone numbers on file to call","default":"primary"},"channel":{"type":"string","enum":["phone_bridge","browser_call"],"title":"Channel","description":"How the operator's leg is placed: a PSTN callback bridge or the browser softphone.","default":"phone_bridge","examples":["browser_call"]},"operator_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Number","description":"The operator's callback number; the bridge rings this leg first, then dials the contact when answered. phone_bridge only — browser_call has no callback leg.","examples":["(847) 555-0142"]},"idempotency_key":{"anyOf":[{"type":"string","maxLength":64,"minLength":8},{"type":"null"}],"title":"Idempotency Key","description":"Client-generated key for this call intent. Retries and double-submits with the same key return the original attempt instead of placing a second call. A key already used for a DIFFERENT action on this event is a 409, never a returned row of the other kind.","examples":["9c9be1a2-4a6e-4a71-b8a0-2f6d1f6f2f5e"]}},"type":"object","required":["contact_id"],"title":"CallOwnerRequest","description":"Call the site's property contact from the review console.\n\nNO CALLEE NUMBER IS ACCEPTED. The client names a CONTACT and which of that\ncontact's stored lines to use; the server resolves the actual number from\nthe contact row, exactly as notify-owner does. `operator_number` is the\noperator's own callback leg, the same field DispatchCallRequest carries."},"Camera":{"properties":{"name":{"type":"string","maxLength":200,"title":"Name","description":"Camera label or channel name.","default":""},"host":{"type":"string","maxLength":255,"title":"Host","description":"Camera IP or hostname, when not reached through the recorder.","default":""},"port":{"type":"string","maxLength":20,"title":"Port","description":"Camera port(s), when not the recorder's.","default":""},"username":{"type":"string","maxLength":200,"title":"Username","description":"Camera login, when not the recorder's.","default":""},"password":{"type":"string","maxLength":500,"format":"password","title":"Password","description":"Camera password; stored in Secrets Manager, never emailed.","default":"","writeOnly":true},"notes":{"type":"string","maxLength":2000,"title":"Notes","description":"Anything specific to this camera.","default":""}},"additionalProperties":false,"type":"object","title":"Camera"},"CameraAdapter":{"type":"string","enum":["generic_onvif","hanwha_onvif"],"title":"CameraAdapter"},"CameraAgreementRow":{"properties":{"camera_id":{"type":"string","title":"Camera Id","description":"Camera row UUID."},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Camera label.","examples":["Loading dock"]},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site display name.","examples":["Downtown Plaza"]},"agent_mode":{"type":"boolean","title":"Agent Mode","description":"Whether the camera is currently armed."},"reviewed":{"type":"integer","title":"Reviewed","description":"Human dispositions for this camera in range.","examples":[31]},"with_recommendation":{"type":"integer","title":"With Recommendation","description":"Reviews covered by a recommendation.","examples":[29]},"agreement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Agreement Rate","description":"4-class agreement percentage; null when uncovered.","examples":[89.7]},"gate_would_dismiss":{"type":"integer","title":"Gate Would Dismiss","description":"Reviews the gate would have auto-dismissed.","examples":[14]},"gate_wrong":{"type":"integer","title":"Gate Wrong","description":"Would-have-dismissed reviews the human escalated.","examples":[0]},"gate_precision":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gate Precision","description":"Gate precision for this camera; null when the gate never fired.","examples":[100.0]},"agent_incident_types_count":{"type":"integer","title":"Agent Incident Types Count","description":"How many incident types the agent may process on this camera (of the full taxonomy).","default":0,"examples":[12]},"gate_blocked_by_camera_types":{"type":"integer","title":"Gate Blocked By Camera Types","description":"Reviews the global gate would dismiss but this camera's classifier selection blocks.","default":0,"examples":[2]}},"type":"object","required":["camera_id","agent_mode","reviewed","with_recommendation","gate_would_dismiss","gate_wrong"],"title":"CameraAgreementRow"},"CameraAlarmVolume":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera row id (not the hardware camera_id)."},"queued_24h":{"type":"integer","title":"Queued 24H","description":"Events from this camera that reached the operator queue in the 24 hours ending at window_end. Same definition as events_queued on GET /logs/summary: closed by the DeepAlert gate or the agent before any operator saw them does not count.","examples":[42]},"queued_7d":{"type":"integer","title":"Queued 7D","description":"The same count over the 7 days ending at window_end.","examples":[210]}},"type":"object","required":["camera_id","queued_24h","queued_7d"],"title":"CameraAlarmVolume"},"CameraBrand":{"type":"string","enum":["hanwha","dahua"],"title":"CameraBrand"},"CameraConnectionResult":{"properties":{"camera_id":{"type":"string","title":"Camera Id","description":"Camera row id, as sent."},"outcome":{"type":"string","enum":["reachable","auth_failed","unreachable","stream_error","inconclusive","no_stream_url","not_tested"],"title":"Outcome","description":"reachable: the stream answered DESCRIBE. auth_failed: reached, but the stored credentials were refused. unreachable: no TCP connection (refused, no route, timeout, DNS). stream_error: reached, but the camera or recorder returned an error. inconclusive: answered in a way that proves nothing. no_stream_url: nothing stored to probe. not_tested: skipped (time budget, or another camera on the same recorder already had its credentials refused, so this one was spared a lockout attempt)."},"detail":{"type":"string","title":"Detail","description":"Fixed operator-facing sentence for the outcome. Never the probe's raw error text."},"probe_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Probe Status","description":"The underlying rtsp_probe status, for support. Null when no probe ran."},"rtsp_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rtsp Status","description":"RTSP status code of the final response, when one arrived."},"codec":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Codec","description":"Video codec named in the SDP (h264/h265) when the stream answered."},"credentials_unproven":{"type":"boolean","title":"Credentials Unproven","description":"True when a credential was sent for this camera and not proven accepted (refused, or the exchange was cut short after Authorization). The server stops that recorder account's remaining channels on it; a client splitting one site across several calls should stop too, to avoid feeding the recorder's lockout counter.","default":false}},"type":"object","required":["camera_id","outcome","detail"],"title":"CameraConnectionResult"},"CameraDownEmailDraft":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site the e-mail is about. Every camera_ids entry must belong to it."},"camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":200,"minItems":1,"title":"Camera Ids","description":"The down cameras the e-mail reports. Must be visible to the caller and belong to site_id."},"to":{"items":{"type":"string"},"type":"array","maxItems":10,"minItems":1,"title":"To","description":"Recipient addresses: the site's contacts and/or addresses the operator typed in. Each one gets its own message (no CC). Syntax-checked; duplicates are dropped case-insensitively.","examples":[["owner@example.com"]]},"subject":{"type":"string","maxLength":200,"minLength":1,"title":"Subject","description":"Subject line. Newlines are flattened before sending."},"body":{"type":"string","maxLength":8000,"minLength":1,"title":"Body","description":"Plain-text body, sent as written."}},"type":"object","required":["site_id","camera_ids","to","subject","body"],"title":"CameraDownEmailDraft","description":"One site's outage e-mail, as the operator drafted it."},"CameraDownRecipientResult":{"properties":{"email":{"type":"string","title":"Email","description":"The address, normalised to lower case."},"status":{"type":"string","enum":["sent","failed","unknown"],"title":"Status","description":"sent: SES accepted it. failed: SES rejected it. unknown: SES gave no definite answer (timeout); it may have been delivered, so do not resend blindly."}},"type":"object","required":["email","status"],"title":"CameraDownRecipientResult"},"CameraDownSiteResult":{"properties":{"site_id":{"type":"string","title":"Site Id","description":"The site the e-mail is about."},"camera_ids":{"items":{"type":"string"},"type":"array","title":"Camera Ids","description":"Camera row ids included in the site's draft."},"recipients":{"items":{"$ref":"#/components/schemas/CameraDownRecipientResult"},"type":"array","title":"Recipients","description":"One outcome per recipient, after duplicates are dropped."},"sent":{"type":"integer","title":"Sent","description":"Recipients whose message SES accepted."},"failed":{"type":"integer","title":"Failed","description":"Recipients whose message SES rejected."},"unknown":{"type":"integer","title":"Unknown","description":"Recipients for whom SES gave no definite answer; they may have received it."}},"type":"object","required":["site_id","camera_ids","recipients","sent","failed","unknown"],"title":"CameraDownSiteResult"},"CameraGroupMemberResponse":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Member camera."},"camera_label":{"type":"string","title":"Camera Label","description":"The camera's display name.","examples":["01 - Front entrance"]},"other_group_names":{"items":{"type":"string"},"type":"array","title":"Other Group Names","description":"The OTHER live camera groups this camera also belongs to. Present so the picker can warn before a shared camera - a business park's common entrance is the motivating case - is quieted by a schedule its other tenants did not ask for."}},"type":"object","required":["camera_id","camera_label"],"title":"CameraGroupMemberResponse"},"CameraGroupMembersResponse":{"properties":{"group_id":{"type":"string","format":"uuid","title":"Group Id","description":"The camera group."},"members":{"items":{"$ref":"#/components/schemas/CameraGroupMemberResponse"},"type":"array","title":"Members","description":"Live member cameras, by label."}},"type":"object","required":["group_id","members"],"title":"CameraGroupMembersResponse","description":"The camera group's membership as it stands after the request.\n\nThe PUT is a REPLACE-SET: this payload is the authoritative new state, not\na delta. A camera absent from it is no longer a member, and this group's\nschedule no longer contributes to that camera's monitoring state."},"CameraGroupModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera group ID."},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site this camera group belongs to. Camera groups never span sites."},"name":{"type":"string","title":"Name","description":"Display name, unique (case-insensitively) among the site's live camera groups.","examples":["Tenant A"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Free-form note about what this camera group covers.","examples":["Suite 200 - enters past cameras 1-3"]},"camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Camera Ids","description":"Live member cameras. A camera may belong to several camera groups at once."},"has_schedule":{"type":"boolean","title":"Has Schedule","description":"Whether this camera group carries a monitoring schedule. FALSE MEANS INERT: membership alone never changes a camera's monitoring state. When true, this group's week replaces the site's for every member camera that has no camera-level override; overlapping scheduled groups combine under 'off wins'.","examples":[false]}},"type":"object","required":["id","site_id","name","has_schedule"],"title":"CameraGroupModel","description":"One camera group and its live membership.\n\n`camera_ids` lists LIVE cameras only. Retired cameras keep their membership\nrows (retirement is not deletion) but never appear on an active surface."},"CameraHealthHistoryPoint":{"properties":{"hour_start":{"type":"string","format":"date-time","title":"Hour Start","description":"Start of the UTC hour this entry covers."},"state":{"type":"string","title":"State","description":"Connectivity for that hour, from the same vocabulary as the row's connectivity field.","examples":["online"]}},"type":"object","required":["hour_start","state"],"title":"CameraHealthHistoryPoint"},"CameraHealthIssue":{"properties":{"key":{"type":"string","title":"Key","description":"Stable issue identifier: frozen, obstructed, blur, occlusion, moved, dark, bright, flapping, bitrate_spike, deepalert_degraded, camera_down. 'obstructed' is the AI view check (confirmed on two checks hours apart); 'occlusion' is the reference-image comparison.","examples":["blur"]},"category":{"type":"string","title":"Category","description":"'image' (needs a captured frame), 'stream' (ingest transport), or 'pipeline' (downstream analytics).","examples":["image"]},"severity":{"type":"string","title":"Severity","description":"'info', 'warn', or 'crit'. Anything above 'info' marks the camera as needing attention.","examples":["warn"]},"label":{"type":"string","title":"Label","description":"Short operator-facing title.","examples":["Blurred image"]},"detail":{"type":"string","title":"Detail","description":"One-sentence explanation including the measured value.","examples":["Sharpness 0.31 is below 0.50 — the lens may be out of focus or dirty."]}},"type":"object","required":["key","category","severity","label","detail"],"title":"CameraHealthIssue"},"CameraHealthReference":{"properties":{"url":{"type":"string","title":"Url","description":"Presigned GET URL for the camera's reference image."},"set_at":{"type":"string","format":"date-time","title":"Set At","description":"When the reference image was adopted."},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds url stays valid, counted from generated_at.","examples":[3600]}},"type":"object","required":["url","set_at","expires_in"],"title":"CameraHealthReference"},"CameraHealthRow":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Camera row id."},"camera_label":{"type":"string","title":"Camera Label","description":"Operator-facing camera name.","examples":["Loading dock"]},"camera_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id","description":"Hardware camera id.","examples":["cam01"]},"kvs_stream_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kvs Stream Name","description":"KVS stream name.","examples":["dev-home-cam01"]},"integration":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Integration","description":"Camera integration.","examples":["Axis"]},"ingest_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ingest Mode","description":"'motion_clip' or 'continuous'. Only motion_clip cameras can produce health snapshots.","examples":["motion_clip"]},"ptz":{"type":"boolean","title":"Ptz","description":"Pan/tilt/zoom flag; when true the 'moved' issue is suppressed."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"Client (parent) id."},"parent_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Name","description":"Client company name.","examples":["Sentinel Integrations"]},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"Site id."},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site name.","examples":["Harbor Yard"]},"monitoring_active":{"type":"boolean","title":"Monitoring Active","description":"Whether the camera's schedule has it monitored right now."},"connectivity":{"type":"string","title":"Connectivity","description":"'online', 'degraded', 'offline', 'off_schedule', or 'unknown'.","examples":["online"]},"connectivity_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Connectivity Reason","description":"Why, when there is a reason: the pipeline reason, 'never_reported', or the unrecognized pipeline status itself.","examples":["heartbeat_stale"]},"connectivity_since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Connectivity Since","description":"When the connectivity CLASS last changed. Null until the camera's first telemetry. Does not move on a healthy/filtering flip — both are 'online'."},"pipeline_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pipeline Status","description":"Raw telemetry-owned pipeline status the connectivity was derived from.","examples":["healthy"]},"pipeline_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pipeline Reason","description":"Raw telemetry-owned pipeline reason."},"detection_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Mode","description":"Camera's DeepAlert routing mode: 'legacy' | 'ii_gated'; null means legacy. On an 'ii_gated' camera the object-of-interest gate runs inside the analysis pipeline, so ingestion's own DeepAlert validator signal is not meaningful for it.","examples":["ii_gated"]},"ii_deepalert_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ii Deepalert State","description":"Last observed analysis-pipeline DeepAlert outcome for an 'ii_gated' camera: 'ok' | 'error' | 'timeout'. Null means no observation yet — delegated/unknown, never 'healthy'. Event-driven and sticky: it moves only when a motion event produces an evaluation callback, and the last observation stands until the next one.","examples":["ok"]},"ii_deepalert_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ii Deepalert At","description":"When the observation behind ii_deepalert_state was made — the creation time of the event it came from, not a receipt time. Null when there has been no observation. Render 'ok' as a DATED observation ('ok as of <this>'), never as an unbounded current-health claim: the value is sticky and only a later event can replace it."},"last_frame_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Frame At","description":"Most recent frame the ingest worker reported."},"last_motion_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Motion At","description":"Most recent motion the ingest worker reported."},"last_pipeline_telemetry_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Pipeline Telemetry At","description":"When telemetry was last received for this camera."},"health_sample_requested_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Health Sample Requested At","description":"When an operator last asked this camera for an on-demand health sample (POST /analytics/camera-health/recheck). Null when never requested. A later latest_sample.received_at means a sample arrived after the request, not necessarily that it answered the request: scheduled samples, delayed uploads, and retries can also arrive later. This timestamp does not correlate a sample with a specific request. Only motion_clip cameras can answer."},"snapshot_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snapshot Unavailable Reason","description":"Why there is no thumbnail, in precedence order: 'continuous_ingest' (no ring buffer, so no frame is ever extracted), then 'never_sampled' (no sample within retention), then 'no_frame_in_latest_sample' (the camera reported but could not extract a frame), else null.","examples":["continuous_ingest"]},"latest_sample":{"anyOf":[{"$ref":"#/components/schemas/CameraHealthSample"},{"type":"null"}],"description":"Newest sample within the 7-day retention window; null when the camera has never been sampled."},"reference":{"anyOf":[{"$ref":"#/components/schemas/CameraHealthReference"},{"type":"null"}],"description":"The camera's reference image, when one is set."},"samples_24h":{"items":{"$ref":"#/components/schemas/CameraHealthSamplePoint"},"type":"array","title":"Samples 24H","description":"24-hour sample series, ascending by captured_at, at most 48 points. The newest point is always retained. Over the cap the oldest other on-demand points are dropped first, then the oldest points of any kind."},"reconnects_24h":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Reconnects 24H","description":"Reconnect deltas summed over ALL samples received in the last 24 hours, including on-demand points omitted from samples_24h. Null when no samples were received in that window; zero means samples exist and recorded no reconnects.","examples":[6]},"history_24h":{"items":{"$ref":"#/components/schemas/CameraHealthHistoryPoint"},"type":"array","title":"History 24H","description":"Exactly 24 hourly connectivity states, ascending, ending with the current hour."},"uptime_24h_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Uptime 24H Pct","description":"Online+degraded hours over MONITORED hours. Null (not zero) when the camera was off-schedule for all 24 hours.","examples":[95.8]},"view_check":{"anyOf":[{"$ref":"#/components/schemas/CameraHealthViewCheck"},{"type":"null"}],"description":"The newest AI view check by the frame's receipt time frozen at check time (server clock) within the 7-day retention window, positive or negative. Null when the camera has no checked sample, and always null in shadow mode (CAMERA_VIEW_CHECK_ISSUE_ENABLED off, the default)."},"issues":{"items":{"$ref":"#/components/schemas/CameraHealthIssue"},"type":"array","title":"Issues","description":"Server-derived issues, evaluated against the published thresholds."},"needs_attention":{"type":"boolean","title":"Needs Attention","description":"True when connectivity is offline/degraded/unknown, or any issue is above 'info' severity. Drives the default sort."}},"type":"object","required":["id","camera_label","ptz","parent_id","site_id","monitoring_active","connectivity","samples_24h","reconnects_24h","history_24h","issues","needs_attention"],"title":"CameraHealthRow"},"CameraHealthSample":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Sample row id."},"captured_at":{"type":"string","format":"date-time","title":"Captured At","description":"Worker clock at frame capture."},"received_at":{"type":"string","format":"date-time","title":"Received At","description":"When the API received the sample; compare with captured_at to spot a skewed worker."},"snapshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snapshot Url","description":"Presigned GET URL for the captured JPEG. Null when the sample has no frame, or when presigning this one row failed (the rest of the response is still served)."},"snapshot_expires_in":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Snapshot Expires In","description":"Seconds snapshot_url stays valid, counted from generated_at. Null when there is no URL.","examples":[3600]},"sharpness":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sharpness","description":"Normalized sharpness 0..1; higher is sharper.","examples":[0.82]},"luminance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Luminance","description":"Mean grayscale luminance 0..1.","examples":[0.47]},"occlusion_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Occlusion Pct","description":"Share of the frame that is flat and unlike the reference. Null when no reference image is set.","examples":[3.1]},"shift_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Shift Pct","description":"How far the view has moved from the reference. Null when no reference image is set.","examples":[1.5]},"identical_run":{"type":"integer","title":"Identical Run","description":"Consecutive samples ending here that share an identical frame hash; 1 when the hash changed, is null, or this is the first point. Derived on each read from the camera's full 24-hour sample set (not from the truncated samples_24h list, so a run is never cut short by the payload cap). A sample with no 24-hour samples behind it — the 7-day fallback — reports 1. Runs are counted over SCHEDULED samples only: an on-demand sample (on_demand=true) is not part of any run and reports 1.","examples":[1]},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width","description":"Probed stream width.","examples":[1920]},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height","description":"Probed stream height.","examples":[1080]},"fps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fps","description":"Probed average frame rate.","examples":[15.0]},"bitrate_kbps":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bitrate Kbps","description":"Ingest bitrate of the sampled segment.","examples":[2048]},"reconnects":{"type":"integer","title":"Reconnects","description":"Runner restarts since the previous sample (a delta, not a running total).","examples":[0]},"on_demand":{"type":"boolean","title":"On Demand","description":"True when this sample answered an operator's re-check request rather than the hourly schedule. Excluded from the frozen-frame run and from the bitrate baseline; otherwise an ordinary sample.","examples":[false]}},"type":"object","required":["id","captured_at","received_at","identical_run","reconnects","on_demand"],"title":"CameraHealthSample","description":"The camera's most recent sample within the 7-day retention window — NOT\nrestricted to the last 24 hours, so an off-schedule camera keeps showing\nits last frame with an honest age."},"CameraHealthSamplePoint":{"properties":{"captured_at":{"type":"string","format":"date-time","title":"Captured At","description":"Worker clock at frame capture."},"bitrate_kbps":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bitrate Kbps","description":"Ingest bitrate, kbps.","examples":[2048]},"sharpness":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sharpness","description":"Normalized sharpness 0..1.","examples":[0.82]},"luminance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Luminance","description":"Mean luminance 0..1.","examples":[0.47]},"occlusion_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Occlusion Pct","description":"Occlusion vs the reference image.","examples":[3.1]},"shift_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Shift Pct","description":"Shift vs the reference image.","examples":[1.5]},"identical_run":{"type":"integer","title":"Identical Run","description":"Consecutive samples ending at this point that share an identical frame hash. Derived on each read from the camera's full 24-hour sample set, so a point at the start of this (truncated) list continues the run it belongs to rather than restarting at 1. Counted over scheduled samples only; an on-demand point reports 1.","examples":[1]},"reconnects":{"type":"integer","title":"Reconnects","description":"Reconnects in this interval.","examples":[0]},"on_demand":{"type":"boolean","title":"On Demand","description":"True when this point is an on-demand re-check sample rather than an hourly one.","examples":[false]}},"type":"object","required":["captured_at","identical_run","reconnects","on_demand"],"title":"CameraHealthSamplePoint","description":"One point of the 24-hour series behind the sparklines."},"CameraHealthSpeakerRow":{"properties":{"speaker_id":{"type":"string","format":"uuid","title":"Speaker Id","description":"The speaker."},"label":{"type":"string","title":"Label","description":"Display name, as registered.","examples":["Rear lot horn"]},"map_location_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing prose for where the speaker physically is."},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site the speaker is at."},"site_name":{"type":"string","title":"Site Name","description":"That site's name."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client the site belongs to."},"parent_name":{"type":"string","title":"Parent Name","description":"That client's company name."},"health_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Health Error","description":"Why the last check failed: unreachable, auth_rejected, not_a_speaker, clip_missing or error. Null on a row last checked before reasons were recorded; clients should read a null or an unknown code as a generic failure.","examples":["unreachable"]},"last_healthcheck_at":{"type":"string","format":"date-time","title":"Last Healthcheck At","description":"When the speaker was last checked."},"unhealthy_since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Unhealthy Since","description":"The time of the first recorded failed check in this episode; the device may have failed earlier. Null means it was already failing before this was tracked."}},"type":"object","required":["speaker_id","label","site_id","site_name","parent_id","parent_name","last_healthcheck_at"],"title":"CameraHealthSpeakerRow","description":"One speaker whose last health check failed, for the Camera health tab.\n\nA DISCLOSURE BOUNDARY like `TalkSpeakerResponse`: it names a place and a\nfailure, never how the device is reached. No host, port, username,\ntransport, serial or credentials, ever."},"CameraHealthSpeakersSummary":{"properties":{"total":{"type":"integer","title":"Total","description":"Enabled, live speakers in the caller's scope (retired and disabled are never counted)."},"healthy":{"type":"integer","title":"Healthy","description":"Of those, how many passed their last check."},"unhealthy":{"type":"integer","title":"Unhealthy","description":"Of those, how many failed their last check (the rows below)."},"unknown":{"type":"integer","title":"Unknown","description":"Of those, how many have never been checked. Not down, and not listed; counted so an empty list is never read as 'all answered'."},"last_checked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Checked At","description":"The newest check time across those speakers, so a stalled poll does not read as all clear. Null when none was ever checked."}},"type":"object","required":["total","healthy","unhealthy","unknown"],"title":"CameraHealthSpeakersSummary"},"CameraHealthThresholds":{"properties":{"sharpness_min":{"type":"number","title":"Sharpness Min","description":"Sharpness below this raises the 'blur' issue.","examples":[0.5]},"occlusion_pct_max":{"type":"number","title":"Occlusion Pct Max","description":"Occlusion above this raises the 'occlusion' issue.","examples":[25.0]},"shift_pct_max":{"type":"number","title":"Shift Pct Max","description":"Shift above this raises the 'moved' issue (suppressed on PTZ cameras).","examples":[30.0]},"frozen_identical_run":{"type":"integer","title":"Frozen Identical Run","description":"Consecutive identical frames at or above this raise the critical 'frozen' issue.","examples":[3]},"luminance_min":{"type":"number","title":"Luminance Min","description":"Luminance below this raises the 'dark' issue.","examples":[0.2]},"luminance_max":{"type":"number","title":"Luminance Max","description":"Luminance above this raises the 'bright' issue.","examples":[0.9]},"reconnects_max":{"type":"integer","title":"Reconnects Max","description":"Total 24h reconnects at or above this raise the 'flapping' issue.","examples":[5]},"bitrate_spike_ratio":{"type":"number","title":"Bitrate Spike Ratio","description":"Latest bitrate at or above this multiple of the 24h average raises the informational 'bitrate_spike' issue.","examples":[2.0]},"camera_down_stale_seconds":{"type":"integer","title":"Camera Down Stale Seconds","description":"Armed with no fresh frame for at least this long (and telemetry itself still arriving) raises the critical 'camera_down' issue; also the basis for judging whether the camera's last monitored window closed dark. Sticky: stays set after the window ends and clears only when a fresh frame arrives while the camera is armed — an off-schedule frame (e.g. a live-view heartbeat) does not prove the monitored-window outage is over and does not clear it.","examples":[90]},"view_obstructed_coverage_min":{"type":"integer","title":"View Obstructed Coverage Min","description":"An AI view check counts toward the 'obstructed' issue only when it reports the view obstructed, the kind is not 'glare', and coverage_pct is at least this.","examples":[25]},"view_check_confirm_hours":{"type":"integer","title":"View Check Confirm Hours","description":"The 'obstructed' issue needs the latest AI view check AND the most recent check received at least this many hours before it (server clock) to both count; a single positive check raises nothing.","examples":[3]},"view_flag_quiet_days":{"type":"integer","title":"View Flag Quiet Days","description":"Operator view flags (GET /camera-health/flags): after an operator clears a flag, the camera raises no new flag for this many days unless it gets worse.","examples":[7]},"view_flag_worse_by_pct":{"type":"integer","title":"View Flag Worse By Pct","description":"Operator view flags: inside the quiet period a new flag opens only when the obstruction covers at least this many percentage points more of the frame than the cleared one did.","examples":[15]}},"type":"object","required":["sharpness_min","occlusion_pct_max","shift_pct_max","frozen_identical_run","luminance_min","luminance_max","reconnects_max","bitrate_spike_ratio","camera_down_stale_seconds","view_obstructed_coverage_min","view_check_confirm_hours","view_flag_quiet_days","view_flag_worse_by_pct"],"title":"CameraHealthThresholds","description":"The server's issue thresholds, published on every response so the UI\nrenders the same numbers the server decided with and the two can never\ndrift. Never hardcode these client-side."},"CameraHealthViewCheck":{"properties":{"checked_at":{"type":"string","format":"date-time","title":"Checked At","description":"When the verdict was recorded."},"captured_at":{"type":"string","format":"date-time","title":"Captured At","description":"Worker clock at capture of the frame that was judged. Display only: checks are ordered by received_at."},"received_at":{"type":"string","format":"date-time","title":"Received At","description":"When the API received the judged frame (server clock). Checks are ordered by this, and the 'obstructed' confirm gap is measured on it."},"obstructed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Obstructed","description":"Whether the model judged the view obstructed. A single positive check does not raise the 'obstructed' issue on its own.","examples":[true]},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"What the model saw: none, object, dirty_lens, web_or_insect, water_or_condensation, glare, out_of_focus, or other. 'glare' never raises the issue.","examples":["web_or_insect"]},"coverage_pct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Coverage Pct","description":"Roughly how much of the frame the obstruction covers, 0-100.","examples":[40]},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"The model's one-sentence explanation, at most 300 characters.","examples":["A spider web and water droplets cover the upper half of the lens."]},"on_demand":{"type":"boolean","title":"On Demand","description":"True when the judged frame was an on-demand re-check sample.","examples":[false]}},"type":"object","required":["checked_at","captured_at","received_at","on_demand"],"title":"CameraHealthViewCheck","description":"The newest AI view check of this camera: a vision model's verdict on\none health-sample frame. Checked every few hours, plus soon after an\non-demand re-check sample arrives."},"CameraIncidentCounts":{"properties":{"camera_id":{"type":"string","title":"Camera Id","description":"Camera row UUID (not the hardware camera_id)."},"site_id":{"type":"string","title":"Site Id","description":"Site UUID the camera belongs to."},"count":{"type":"integer","title":"Count","description":"Total AI detections on this camera in the window.","examples":[45]},"by_type":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Type","description":"Per-incident-type counts on this camera.","examples":[{"LOITERING":3,"NORMAL":40}]}},"type":"object","required":["camera_id","site_id","count","by_type"],"title":"CameraIncidentCounts"},"CameraIngestMode":{"type":"string","enum":["continuous","motion_clip"],"title":"CameraIngestMode"},"CameraIntegration":{"type":"string","enum":["RTSP","Network Optix","Axis","Avigilon","Milestone","Verkada","Turing","Hikvision","AvertX"],"title":"CameraIntegration"},"CameraMetadataFormat":{"type":"string","enum":["onvif"],"title":"CameraMetadataFormat"},"CameraModel":{"type":"string","enum":["ANV-L7012R"],"title":"CameraModel"},"CameraMonitoringProfile":{"type":"string","enum":["security","safety"],"title":"CameraMonitoringProfile"},"CameraPauseCameraModel":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The camera row id."},"camera_label":{"type":"string","title":"Camera Label","description":"The camera's display name."},"monitoring_state":{"type":"string","enum":["monitored","heightened","off"],"title":"Monitoring State","description":"What the camera is doing right now (schedule, disarm and test arm applied). A pause does not change it: a paused camera stays Monitored or Heightened."},"pausable":{"type":"boolean","title":"Pausable","description":"Whether it can be paused right now."},"unavailable_reason":{"anyOf":[{"type":"string","enum":["live_view_only","off","already_paused","blocked"]},{"type":"null"}],"title":"Unavailable Reason","description":"Why not, when pausable is false. 'blocked': ops has blocked pausing this camera while an experiment runs on it."},"paused":{"anyOf":[{"$ref":"#/components/schemas/CameraPauseWindowModel"},{"type":"null"}],"description":"The live pause, when there is one."},"minutes_paused_last_hour":{"type":"integer","title":"Minutes Paused Last Hour","description":"Minutes this camera was paused in the last rolling hour (a live pause counts so far).","default":0}},"type":"object","required":["camera_id","camera_label","monitoring_state","pausable"],"title":"CameraPauseCameraModel"},"CameraPauseLimitsModel":{"properties":{"allowed_minutes":{"items":{"type":"integer"},"type":"array","title":"Allowed Minutes","description":"The preset lengths.","examples":[[5,10,15]]},"batches_last_hour":{"type":"integer","title":"Batches Last Hour","description":"Pause presses at this site in the last rolling hour."},"max_batches_per_hour":{"type":"integer","title":"Max Batches Per Hour","description":"Presses the site may take per rolling hour."},"max_camera_minutes_per_hour":{"type":"integer","title":"Max Camera Minutes Per Hour","description":"Paused minutes one camera may take per rolling hour."},"can_override":{"type":"boolean","title":"Can Override","description":"The caller is an ops lead and may pause past the limits with a reason."}},"type":"object","required":["allowed_minutes","batches_last_hour","max_batches_per_hour","max_camera_minutes_per_hour","can_override"],"title":"CameraPauseLimitsModel"},"CameraPauseRequest":{"properties":{"camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":8,"minItems":1,"title":"Camera Ids","description":"Cameras at this site to pause now. All-or-nothing: one camera that cannot be paused refuses the whole request and names it."},"minutes":{"type":"integer","enum":[5,10,15],"title":"Minutes","description":"How long to pause: 5, 10 or 15 minutes. It ends by itself; there is no extend.","examples":[10]},"reason":{"type":"string","enum":["delivery","maintenance","cleaning","other"],"title":"Reason","description":"Why the operator is pausing. 'other' requires a note.","examples":["delivery"]},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Note","description":"Free text kept on the pause and in the Activity Log. Staff only; never shown to customers.","examples":["Linen truck at dock, driver on the phone with the manager"]},"request_id":{"type":"string","format":"uuid","title":"Request Id","description":"Client-generated idempotency key for this press. A retry with the same id returns the rows the first attempt wrote and uses up no limit."},"limit_override":{"type":"boolean","title":"Limit Override","description":"An ops lead only: pause past the per-site and per-camera limits, or past the rule that stops every monitored camera at a site being paused. Requires a note. The pause is still capped at 15 minutes.","default":false}},"type":"object","required":["camera_ids","minutes","reason","request_id"],"title":"CameraPauseRequest"},"CameraPauseWindowModel":{"properties":{"starts_at":{"type":"string","format":"date-time","title":"Starts At","description":"When the pause began."},"ends_at":{"type":"string","format":"date-time","title":"Ends At","description":"When new alarms start reaching the queue again (the pause ends by itself here)."},"minutes":{"type":"integer","title":"Minutes","description":"The length chosen: 5, 10 or 15.","examples":[10]},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"One of the four request reasons.","examples":["delivery"]},"by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"By Name","description":"The operator who paused (snapshot). Null for customer-side logins."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"The operator's note. Null for customer-side logins."}},"type":"object","required":["starts_at","ends_at","minutes"],"title":"CameraPauseWindowModel","description":"A camera's pause that is live right now."},"CameraPipelineSummaryModel":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"UUID of the camera"},"camera_label":{"type":"string","title":"Camera Label","description":"Camera label","examples":["Front Porch"]},"kvs_stream_name":{"type":"string","title":"Kvs Stream Name","description":"KVS stream name","examples":["site-cam-01"]},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site the camera belongs to"},"parent_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Name","description":"Client the camera belongs to"},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Whether incident intelligence (AI interpretation) is currently on for this camera"},"events_total":{"type":"integer","title":"Events Total","description":"Events recorded in the window"},"events_ai":{"type":"integer","title":"Events Ai","description":"Events in the window that received an AI interpretation"},"events_raw_motion":{"type":"integer","title":"Events Raw Motion","description":"Events in the window queued as raw motion without AI interpretation (incident intelligence off at ingest)"},"events_stranded":{"type":"integer","title":"Events Stranded","description":"Events in the window still awaiting an AI evaluation — not queue-visible; aged into history if none arrives within 48h"},"agent_dismissed":{"type":"integer","title":"Agent Dismissed","description":"Events in the window automatically dismissed by agent mode (agent auto-dismiss decisions), never surfaced to the human queue","default":0},"gate_filtered":{"type":"integer","title":"Gate Filtered","description":"Events in the window closed before AI review by a pre-processing gate (gate_stop_auto_dismiss): DeepAlert finding no object of interest, the person confirmer (r50) not confirming DeepAlert's person, or the vehicle-flash confirmer matching a parked vehicle. DeepAlert is ~94% of it (prod, 24h to 2026-10-11). Most never reach the operator queue; an early-broadcast card is shown until the verdict closes it","default":0},"events_queued":{"type":"integer","title":"Events Queued","description":"Events created in the window that reached the operator queue: queue-visible (verdict landed, II-off, or early-broadcast), minus those the agent closed in their own eval (auto-dismiss, routing ignore, DeepAlert gate stop) before any operator saw them","default":0},"stranded_backlog":{"type":"integer","title":"Stranded Backlog","description":"Current count of events awaiting an AI evaluation (all time, unexpired) — non-zero means evals in flight or a failing pipeline"},"last_event_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Event At","description":"Most recent event in the window"},"last_ai_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Ai At","description":"Most recent AI-interpreted event in the window"},"pipeline_status":{"type":"string","title":"Pipeline Status","description":"Derived current camera pipeline state","default":"unknown"},"pipeline_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pipeline Reason","description":"Stable reason code for the current state"},"last_healthcheck_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Healthcheck At"},"last_frame_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Frame At"},"last_motion_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Motion At"},"last_deepalert_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Deepalert At"},"motions_detected":{"type":"integer","title":"Motions Detected","default":0},"validator_rejected":{"type":"integer","title":"Validator Rejected","default":0},"validator_confirmed":{"type":"integer","title":"Validator Confirmed","default":0},"validator_fail_open":{"type":"integer","title":"Validator Fail Open","default":0},"clip_failures":{"type":"integer","title":"Clip Failures","default":0},"upload_failures":{"type":"integer","title":"Upload Failures","default":0},"event_create_failures":{"type":"integer","title":"Event Create Failures","default":0},"ai_submit_failures":{"type":"integer","title":"Ai Submit Failures","default":0},"ai_submit_queue_full":{"type":"integer","title":"Ai Submit Queue Full","description":"Clips refused at a full AI-submit queue in the window (never reached the AI)","default":0},"ai_submit_stranded":{"type":"integer","title":"Ai Submit Stranded","description":"Queued AI submissions abandoned at worker shutdown in the window (never reached the AI)","default":0}},"type":"object","required":["camera_id","camera_label","kvs_stream_name","ii_enabled","events_total","events_ai","events_raw_motion","events_stranded","stranded_backlog"],"title":"CameraPipelineSummaryModel"},"CameraPipelineTelemetryRecord":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id"},"observed_at":{"type":"string","format":"date-time","title":"Observed At"},"healthy":{"type":"boolean","title":"Healthy"},"counters_only":{"type":"boolean","title":"Counters Only","description":"Late statistics from a stopped worker; do not update camera health or heartbeat freshness.","default":false},"monitoring_active":{"type":"boolean","title":"Monitoring Active","default":true},"worker_id":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Worker Id"},"image_version":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Image Version"},"frames_received":{"type":"integer","minimum":0.0,"title":"Frames Received","default":0},"motions_detected":{"type":"integer","minimum":0.0,"title":"Motions Detected","default":0},"validator_confirmed":{"type":"integer","minimum":0.0,"title":"Validator Confirmed","default":0},"validator_rejected":{"type":"integer","minimum":0.0,"title":"Validator Rejected","default":0},"validator_fail_open":{"type":"integer","minimum":0.0,"title":"Validator Fail Open","default":0},"clip_failures":{"type":"integer","minimum":0.0,"title":"Clip Failures","default":0},"upload_failures":{"type":"integer","minimum":0.0,"title":"Upload Failures","default":0},"event_create_failures":{"type":"integer","minimum":0.0,"title":"Event Create Failures","default":0},"ai_submit_failures":{"type":"integer","minimum":0.0,"title":"Ai Submit Failures","default":0},"ai_submit_queued":{"type":"integer","minimum":0.0,"title":"Ai Submit Queued","description":"AI submissions enqueued since the previous telemetry flush","default":0},"ai_submit_retried":{"type":"integer","minimum":0.0,"title":"Ai Submit Retried","description":"AI submissions retried since the previous telemetry flush","default":0},"ai_submit_queue_full":{"type":"integer","minimum":0.0,"title":"Ai Submit Queue Full","description":"AI submissions refused since the previous flush because the queue was full (clip never reached the AI)","default":0},"ai_submit_stranded":{"type":"integer","minimum":0.0,"title":"Ai Submit Stranded","description":"Queued AI submissions abandoned at worker shutdown since the previous flush (clip never reached the AI)","default":0},"ai_submit_queue_depth":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Ai Submit Queue Depth","description":"Point-in-time AI-submit queue depth at flush (gauge, not a counter — stored as the bucket's high-water mark); null when not reported"},"ai_submit_inflight":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Ai Submit Inflight","description":"Point-in-time in-flight AI submissions at flush (gauge, not a counter — stored as the bucket's high-water mark); null when not reported"},"last_frame_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Frame At"},"last_motion_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Motion At"},"last_deepalert_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Deepalert At"},"deepalert_state":{"type":"string","enum":["not_configured","healthy","degraded"],"title":"Deepalert State","default":"not_configured"},"last_error_code":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Last Error Code"},"last_error_message":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Last Error Message"}},"type":"object","required":["camera_id","observed_at","healthy"],"title":"CameraPipelineTelemetryRecord"},"CameraResumeRequest":{"properties":{"camera_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":8},{"type":"null"}],"title":"Camera Ids","description":"Cameras to resume alarms on. Omit (or null) to resume every camera at the site that is paused now. Idempotent: a camera that is not paused is left alone."}},"type":"object","title":"CameraResumeRequest"},"CameraResumedModel":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The camera."},"monitoring_state":{"type":"string","enum":["monitored","heightened","off"],"title":"Monitoring State","description":"What the camera is doing now that the pause is gone. 'off' means its schedule (or a disarm) has it off: the console must not say monitoring resumed."}},"type":"object","required":["camera_id","monitoring_state"],"title":"CameraResumedModel"},"CameraSpeakersResponse":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The camera."},"speakers":{"items":{"$ref":"#/components/schemas/SpeakerResponse"},"type":"array","title":"Speakers","description":"Mapped, enabled, live speakers in failover order — what an operator's Talk Down would announce on."},"agent_enabled_speaker_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Agent Enabled Speaker Ids","description":"Subset of speakers[].id that the AI agent is armed to fire automatically for this camera. Empty means the camera is human-triggered only: the agent's voice-down fails closed there exactly as on an unmapped camera."}},"type":"object","required":["camera_id","speakers","agent_enabled_speaker_ids"],"title":"CameraSpeakersResponse","description":"Which speakers serve a camera, in the order delivery would try them.\n\n`speakers` is the MANUAL view: every live mapping, which is exactly what an\noperator's Talk Down would announce on. It is resolved through the same\nquery builder delivery uses, so it is what would actually announce — not a\ntidied-up list.\n\nThe AI agent's view is narrower, because a mapping arms the operator while\n`agent_trigger_enabled` arms the agent (default false). Rather than hide\nthat, `agent_enabled_speaker_ids` names the subset the agent's automated\nvoice-down would resolve — the same subset the policy ledger reports\navailability from. An empty list there with a non-empty `speakers` means\n\"humans only on this camera\", which is the pilot's posture by design."},"CameraViewFlag":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Flag id."},"status":{"type":"string","title":"Status","description":"'open' (needs a look), 'confirmed' (an operator agreed the view is blocked), 'cleared' (an operator judged it usable), or 'resolved' (two checks hours apart read clear after confirmation and customer notification).","examples":["open"]},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera row id."},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Operator-facing camera name.","examples":["Back of Building Driveway"]},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"Site id."},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site name.","examples":["Harbor Yard"]},"site_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Timezone","description":"The site's IANA zone, for rendering times and the day/night split.","examples":["America/Chicago"]},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"Client (parent) id."},"parent_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Name","description":"Client company name.","examples":["Sentinel Integrations"]},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"The latest qualifying verdict's kind.","examples":["web_or_insect"]},"coverage_pct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Coverage Pct","description":"The latest qualifying verdict's coverage, 0-100.","examples":[40]},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"The latest qualifying verdict's explanation."},"opened_at":{"type":"string","format":"date-time","title":"Opened At","description":"When the item opened."},"opened_kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opened Kind","description":"Immutable kind at opening; null for legacy items."},"opened_coverage_pct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Opened Coverage Pct","description":"Immutable coverage at opening; null for legacy items."},"opened_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opened Reason","description":"Immutable reason at opening; null for legacy items."},"opening_checks":{"items":{"$ref":"#/components/schemas/CameraViewFlagCheck"},"type":"array","title":"Opening Checks","description":"The two opening verdicts, preserved after sample pruning. Images may expire."},"first_viewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Viewed At","description":"First actual item open by staff; list polling does not count."},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At","description":"When two clear checks resolved a confirmed, notified item."},"last_raised_at":{"type":"string","format":"date-time","title":"Last Raised At","description":"When a confirmed raise last refreshed it."},"raise_count":{"type":"integer","title":"Raise Count","description":"Confirmed raises counted on this item (1 at opening).","examples":[3]},"checks_obstructed":{"type":"integer","title":"Checks Obstructed","description":"View checks of this camera in the last 7 days that qualified (obstructed, not glare, coverage at or above view_obstructed_coverage_min). Rendered as 'N of M checks in the last 7 days'.","examples":[3]},"checks_total":{"type":"integer","title":"Checks Total","description":"All view checks of this camera in the last 7 days (the same set as checks, uncapped).","examples":[4]},"day_snapshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Day Snapshot Url","description":"Presigned URL of the newest checked daylight frame (07:00-18:59 site-local), null if none in the last 7 days."},"night_snapshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Night Snapshot Url","description":"Presigned URL of the newest checked night frame, null if none in the last 7 days."},"checks":{"items":{"$ref":"#/components/schemas/CameraViewFlagCheck"},"type":"array","title":"Checks","description":"Up to 24 newest checks of this camera in the last 7 days, newest first."},"recent_alarms":{"items":{"$ref":"#/components/schemas/CameraViewFlagAlarm"},"type":"array","title":"Recent Alarms","description":"Up to 5 newest alarms on this camera since 24 hours before opened_at, newest first."},"confirmed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmed By Name","description":"Who confirmed it."},"confirmed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmed At","description":"When it was confirmed."},"notified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Notified At","description":"When the customer was last e-mailed about it."},"notified_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notified By Name","description":"Who sent that e-mail."},"notify_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notify Summary","description":"What that send did.","examples":["Emailed 2 recipients"]},"cleared_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cleared By Name","description":"Who cleared it."},"cleared_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cleared At","description":"When it was cleared."},"clear_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clear Reason","description":"The operator's reason for clearing."},"quiet_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Quiet Until","description":"After a clear: no new flag for this camera before this time unless coverage rises by view_flag_worse_by_pct."},"contacts":{"items":{"$ref":"#/components/schemas/CameraViewFlagContact"},"type":"array","title":"Contacts","description":"The site's contacts with an e-mail address (falling back to the client's primary contact), for seeding recipients."}},"type":"object","required":["id","status","camera_id","site_id","parent_id","opened_at","last_raised_at","raise_count","checks_obstructed","checks_total","checks","recent_alarms","contacts"],"title":"CameraViewFlag","description":"One operator work item from the AI view check."},"CameraViewFlagAlarm":{"properties":{"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"Alarm (event) id."},"at":{"type":"string","format":"date-time","title":"At","description":"When the alarm was created."},"incident_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incident Type","description":"The AI's incident type, null when no interpretation landed.","examples":["INTRUSION"]},"outcome_label":{"type":"string","title":"Outcome Label","description":"History's outcome wording: the operator disposition label, 'Auto-dismissed' for the agent's dismissals, 'Expired' for one that aged out unreviewed, else 'Pending'.","examples":["False alarm - no human activity"]},"auto_dismissed":{"type":"boolean","title":"Auto Dismissed","description":"True when the agent dismissed it."}},"type":"object","required":["event_id","at","outcome_label","auto_dismissed"],"title":"CameraViewFlagAlarm"},"CameraViewFlagCheck":{"properties":{"sample_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Sample Id","description":"The health sample this verdict was recorded on. Null only on a legacy opening verdict saved without it."},"received_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Received At","description":"When the server received the judged frame, frozen at claim time (view_frame_received_at). Ordering and the 7-day window use this, never the worker's capture clock."},"checked_at":{"type":"string","format":"date-time","title":"Checked At","description":"When the AI verdict was recorded."},"captured_at":{"type":"string","format":"date-time","title":"Captured At","description":"Worker clock at capture of the judged frame (display only; ordering uses the server's receipt time)."},"light":{"type":"string","title":"Light","description":"'day' when the frame arrived between 07:00 and 18:59 site-local time, else 'night'. Night haze and IR glare are the common false raises.","examples":["night"]},"obstructed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Obstructed","description":"The model's verdict for this frame."},"kind":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kind","description":"none, object, dirty_lens, web_or_insect, water_or_condensation, glare, out_of_focus, or other.","examples":["web_or_insect"]},"coverage_pct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Coverage Pct","description":"Roughly how much of the frame is covered, 0-100.","examples":[40]},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"The model's one-sentence explanation."},"snapshot_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snapshot Url","description":"Presigned GET URL for the judged frame (1h). Null when the frame has no stored image or presigning failed."}},"type":"object","required":["checked_at","captured_at","light"],"title":"CameraViewFlagCheck"},"CameraViewFlagContact":{"properties":{"name":{"type":"string","title":"Name","description":"Contact name.","examples":["Dana Ruiz"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"The contact's role at the site; 'Primary contact' for the client fallback.","examples":["Facilities manager"]},"email":{"type":"string","title":"Email","description":"A deliverable-looking address on file.","examples":["facilities@example.com"]}},"type":"object","required":["name","email"],"title":"CameraViewFlagContact"},"CameraViewFlagCounts":{"properties":{"open":{"type":"integer","title":"Open","description":"Open items in scope.","examples":[2]},"confirmed":{"type":"integer","title":"Confirmed","description":"Confirmed items in scope.","examples":[1]}},"type":"object","required":["open","confirmed"],"title":"CameraViewFlagCounts"},"CameraViewFlagViewed":{"properties":{"first_viewed_at":{"type":"string","format":"date-time","title":"First Viewed At","description":"First time a staff member opened this item."}},"type":"object","required":["first_viewed_at"],"title":"CameraViewFlagViewed"},"CameraViewFlagsNotifyLogEntry":{"properties":{"sent_at":{"type":"string","format":"date-time","title":"Sent At","description":"When the send was recorded, just after SES accepted it."},"by_name":{"type":"string","title":"By Name","description":"Who sent it.","examples":["Ana Lee"]},"cameras":{"items":{"type":"string"},"type":"array","title":"Cameras","description":"The cameras the e-mail was about.","examples":[["SE Service (3)","West Gate .7"]]},"recipients_sent":{"type":"integer","title":"Recipients Sent","description":"Recipients SES accepted.","examples":[2]},"recipients_failed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Recipients Failed","description":"Recipients SES rejected. Null for older site e-mails, which did not record it.","examples":[0]},"site_email":{"type":"boolean","title":"Site Email","description":"Sent from the site's Notify customer (true) or a single camera's notify (false)."}},"type":"object","required":["sent_at","by_name","cameras","recipients_sent","site_email"],"title":"CameraViewFlagsNotifyLogEntry"},"CameraViewFlagsNotifyLogResponse":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/CameraViewFlagsNotifyLogEntry"},"type":"array","title":"Entries","description":"Verified customer e-mails about this site's camera views, newest first, at most 50. Requires a recorded site at send time or a trusted backfill of that fact. Older unattributed rows are omitted and remain in Activity Log; an empty list does not mean no e-mail was ever sent."}},"type":"object","required":["entries"],"title":"CameraViewFlagsNotifyLogResponse"},"CamerasModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera ID."},"camera_label":{"type":"string","title":"Camera Label","description":"The label of the camera.","examples":["SD Sewing Company"]},"camera_id":{"type":"string","title":"Camera Id","description":"The camera ID.","examples":["cam01"]},"kvs_stream_name":{"type":"string","title":"Kvs Stream Name","description":"The KVS stream name.","examples":["dev-home-cam01"]},"ip_address":{"type":"string","title":"Ip Address","description":"The IP address of the camera.","examples":["192.0.0.1"]},"port":{"type":"string","title":"Port","description":"The port of the camera.","examples":["554"]},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"Camera account username (the password is never returned). Absent for customer roles.","examples":["admin"]},"integration":{"$ref":"#/components/schemas/CameraIntegration","description":"The integration of the camera.","examples":["Axis"]},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Incident intelligence on/off; when false motion events still record and alert, but skip AI classification.","default":true},"live_view_only":{"type":"boolean","title":"Live View Only","description":"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.","default":false},"agent_mode":{"type":"boolean","title":"Agent Mode","description":"Agent mode on/off for this camera.","default":false},"agent_observation_minutes":{"type":"integer","title":"Agent Observation Minutes","description":"Post-voice-down observation window in minutes.","default":10},"agent_incident_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Agent Incident Types","description":"Incident types the agent may process at all on this camera."},"incident_routing":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Incident Routing","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rag Context","description":"Per-camera scene context injected into the LLM classification prompt."},"map_x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x position (0..1) on the site map."},"map_y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y position (0..1) on the site map."},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude on the site map."},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude on the site map."},"map_location_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose shown on queue rows and mini-maps.","examples":["NE corner — loading dock"]},"map_heading":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Heading","description":"Direction the device faces, degrees clockwise from map-up; null = not aimed.","examples":[225]}},"type":"object","required":["id","camera_label","camera_id","kvs_stream_name","ip_address","port","integration"],"title":"CamerasModel"},"CancelClaimActingRequest":{"properties":{"generation":{"type":"string","format":"uuid","title":"Generation","description":"`claim.generation` the console holds."},"action_token":{"type":"string","maxLength":64,"minLength":1,"title":"Action Token","description":"The token sent with the set this cancels."},"reason":{"type":"string","enum":["cancelled","panel_closed","completed"],"title":"Reason","description":"`cancelled`: the operator backed out. `panel_closed`: dismissed or navigated away. `completed`: the action finished."}},"additionalProperties":false,"type":"object","required":["generation","action_token","reason"],"title":"CancelClaimActingRequest","description":"Body of `POST /events/{id}/claim/acting/cancel`."},"ChangePasswordRequest":{"properties":{"current_password":{"type":"string","minLength":1,"title":"Current Password","description":"The operator's current password (verified by Cognito)"},"new_password":{"type":"string","minLength":8,"title":"New Password","description":"The operator's chosen new password"}},"type":"object","required":["current_password","new_password"],"title":"ChangePasswordRequest","description":"A signed-in operator changing their own password. Cognito verifies\n`current_password` against the caller's access token, so this can only ever\nchange the caller's own password — no target is (or can be) named."},"CheckCameraViewFlagNowResponse":{"properties":{"requested_at":{"type":"string","format":"date-time","title":"Requested At","description":"Server time of the frame request. The answer is the item's first check with received_at at or after this. Inside the re-check cooldown it is the pending request's stamp, which this call rides."},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The item's camera."},"already_pending":{"type":"boolean","title":"Already Pending","description":"True when a frame request was already pending (re-check cooldown); no new request was stamped."},"woke_camera":{"type":"boolean","title":"Woke Camera","description":"True when the camera was outside its monitoring schedule (no stream running) and this call woke it the way live view does, for about 420 s, so its first frame can answer; motion and alarms stay suppressed. The answer takes longer (expected_within_seconds reflects it).","default":false},"expected_within_seconds":{"type":"integer","title":"Expected Within Seconds","description":"How long the answer usually takes (ingestor poll + one sample + one vision call).","examples":[60]}},"type":"object","required":["requested_at","camera_id","already_pending","expected_within_seconds"],"title":"CheckCameraViewFlagNowResponse"},"ClaimActingCancelResponse":{"properties":{"claim":{"anyOf":[{"$ref":"#/components/schemas/AlarmClaim"},{"type":"null"}],"description":"The caller's own live claim; null when the alarm is closed"},"activity":{"anyOf":[{"$ref":"#/components/schemas/ActivityPayload"},{"type":"null"}],"description":"The activity after the call (a live session can still make it non-null)"},"cleared":{"type":"boolean","title":"Cleared","description":"True only when this call cleared the intent"},"outcome":{"type":"string","enum":["cleared","already_clear","alarm_closed"],"title":"Outcome","description":"`cleared`: this call ended it. `already_clear`: nothing current (a retried cancel). `alarm_closed`: the close already cleared it."}},"type":"object","required":["cleared","outcome"],"title":"ClaimActingCancelResponse"},"ClaimActingResponse":{"properties":{"claim":{"$ref":"#/components/schemas/AlarmClaim","description":"The caller's own live claim"},"activity":{"anyOf":[{"$ref":"#/components/schemas/ActivityPayload"},{"type":"null"}],"description":"The activity after the write"},"recorded":{"type":"boolean","title":"Recorded","description":"True when the intent is recorded (a new write or an exact replay of the current one). False when the token belongs to an action that already finished (cancelled, completed, closed, lapsed, taken over) or was replaced by a newer pick: it is never revived or applied, `activity` is the state as it is, and a new pick needs a new token.","default":true},"replay":{"type":"boolean","title":"Replay","description":"True when nothing was written because this token and generation were already seen (recorded or finished)","default":false}},"type":"object","required":["claim"],"title":"ClaimActingResponse"},"ClaimCaptureRequest":{"properties":{"claimed_by":{"type":"string","maxLength":128,"minLength":1,"title":"Claimed By","description":"Stable ingestor instance id (task/host id, plus /shard-{i}-{n} in sharded modes).","examples":["task-1a2b3c/shard-0-4"]}},"additionalProperties":false,"type":"object","required":["claimed_by"],"title":"ClaimCaptureRequest"},"ClaimCaptureResponse":{"properties":{"capture_id":{"type":"string","format":"uuid","title":"Capture Id","description":"Capture id."},"claimed_by":{"type":"string","title":"Claimed By","description":"The holder."},"claimed_at":{"type":"string","format":"date-time","title":"Claimed At","description":"When the claim was taken."},"claim_expires_at":{"type":"string","format":"date-time","title":"Claim Expires At","description":"Lease end (180 s); no renewal route in v1."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Authoritative start of the recording."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"End of the recording window."},"s3_key":{"type":"string","title":"S3 Key","description":"Upload exactly here. Reclaims use a distinct -attempt-N key; the claim key overrides the feed hint."}},"type":"object","required":["capture_id","claimed_by","claimed_at","claim_expires_at","window_start","window_end","s3_key"],"title":"ClaimCaptureResponse"},"ClaimEventResponse":{"properties":{"claim":{"anyOf":[{"$ref":"#/components/schemas/AlarmClaim"},{"type":"null"}],"description":"The live claim on this alarm, whoever holds it (compare `operator_id` with your own). Null when nobody holds it: the alarm has left the active queue, or the caller is an owner or monitoring manager, who never take claims. Always null for customer-side callers, who cannot read staff identity."},"renewed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Renewed","description":"Present only on `POST /events/{id}/claim/renew`: true when the caller's own live claim was renewed (`claim` is then the renewed claim), false when it was NOT (the claim was lost or never held: `claim` is another operator's live claim, or null). A renew never acquires. Absent on an ordinary claim."},"reservation":{"anyOf":[{"$ref":"#/components/schemas/Reservation"},{"type":"null"}],"description":"Claim-lock R5 (Focus). Present only when an ACQUIRE was answered with `claim: null` because the alarm is RESERVED for another operator's focus: a free claim must not outrank a reservation, so nothing was taken. Taking it is Take over (`expected_focus_id`). Absent otherwise."}},"type":"object","required":["claim"],"title":"ClaimEventResponse"},"ClaimNextCounts":{"properties":{"waiting":{"type":"integer","title":"Waiting","description":"Open alarms nobody holds or has reserved for another operator (inside the caller's scope)."},"held_by_others":{"type":"integer","title":"Held By Others","description":"Open alarms live-held by another operator."},"held_by_you":{"type":"integer","title":"Held By You","description":"Open alarms live-held by the caller."},"reserved_for_others":{"type":"integer","title":"Reserved For Others","description":"Not live-held alarms under another operator's valid Focus."},"in_session_seen":{"type":"integer","title":"In Session Seen","description":"Alarms this scan skipped for a live call / announcement / intent. A LOWER BOUND (seen in this scan), not a total."}},"type":"object","required":["waiting","held_by_others","held_by_you","reserved_for_others","in_session_seen"],"title":"ClaimNextCounts"},"ClaimNextRequest":{"properties":{"request_id":{"type":"string","format":"uuid","title":"Request Id","description":"One per key press, generated by the console. Reuse it ONLY to retry that same press (a timeout, a lost response, a 503 `unknown`): the server answers a recorded press from its record and never selects a second alarm. A deliberate new press uses a new id. The record is kept for 24 hours.","examples":["3fa85f64-5717-4562-b3fc-2c963f66afa6"]},"source":{"type":"string","enum":["queue_key","queue_button","review_key","review_button"],"title":"Source","description":"Where the press came from. Logged and recorded; it never changes the selection.","examples":["queue_key"]}},"type":"object","required":["request_id","source"],"title":"ClaimNextRequest"},"ClaimNextResponse":{"properties":{"status":{"type":"string","enum":["claimed","none"],"title":"Status","description":"`claimed`: an alarm (see `current`). `none`: nothing could be claimed (see `reason`)."},"replay":{"type":"boolean","title":"Replay","description":"True when this answers a RECORDED press (a retry of the same `request_id`); a replay never selects, stamps or extends anything."},"current":{"anyOf":[{"type":"string","enum":["yours","taken_over","closed","lapsed","reserved","out_of_scope","unavailable"]},{"type":"null"}],"title":"Current","description":"`claimed` only. The alarm's state NOW (read under the event row lock), never a promise of ownership the caller no longer has. A fresh handout is always `yours`. A replay may be any value; navigate automatically only for `yours`."},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"The alarm. Omitted for `out_of_scope` and `unavailable` replays."},"claim":{"anyOf":[{"$ref":"#/components/schemas/AlarmClaim"},{"type":"null"}],"description":"Present for `yours` and `taken_over`."},"alarm":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Alarm","description":"The queue row (the `GET /operator/events/all` row shape) for `yours`; null if hydration did not finish in its budget (the claim stands and `event_id` identifies the alarm: fetch it)."},"tier":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tier","description":"A display / log LABEL (`critical`, `high`, `medium`) of the handed-out alarm. Never an ordering input."},"queued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Queued At","description":"When the handed-out alarm entered the queue (ISO 8601, UTC): the clock the card shows."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"`none` only: `empty`, `all_held`, `all_reserved`, `all_in_session`, `contended`, `scan_budget` or `not_a_claimant`."},"counts":{"anyOf":[{"$ref":"#/components/schemas/ClaimNextCounts"},{"type":"null"}],"description":"`none` only: grouped counts inside the caller's scope. Null for an owner and for a replayed `none`."},"recorded_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recorded At","description":"Replay only: when the press was recorded."},"as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of","description":"Replay only: the server time `current` describes."}},"type":"object","required":["status","replay"],"title":"ClaimNextResponse"},"ClaimNextUnavailableBody":{"properties":{"code":{"type":"string","title":"Code","description":"`NEXT_UNAVAILABLE`"},"detail":{"type":"string","title":"Detail","description":"A fixed message; never database text."},"outcome":{"type":"string","enum":["not_applied","unknown"],"title":"Outcome","description":"`not_applied`: this call established that the `request_id` has no record AND its allocation was rolled back (nothing was claimed): the id may be abandoned. `unknown`: anything else (a failure before the record could be looked up, while replaying it, or after the COMMIT may have been sent): keep the `request_id` and retry it; the server's record decides."},"reason":{"type":"string","title":"Reason","description":"`deadline`, `lock_timeout`, `db_error`, `commit_unknown` or `duplicate_request`."}},"type":"object","required":["code","detail","outcome","reason"],"title":"ClaimNextUnavailableBody"},"ClaimRequest":{"properties":{"force":{"type":"boolean","title":"Force","description":"Take a thread another operator has claimed. Without it, a foreign claim is a 409.","default":false,"examples":[false]}},"type":"object","title":"ClaimRequest"},"ClaimSiteResponse":{"properties":{"claims":{"additionalProperties":{"$ref":"#/components/schemas/AlarmClaim"},"type":"object","title":"Claims","description":"Live claim per event id for every queued alarm at the site after this call: the caller's new claims plus any another operator already held (those are not taken over). Empty for owners and monitoring managers, who never take claims, and for customer-side callers. While the site claim is retired (`SITE_CLAIM_ENABLED` off, the default) nothing is taken or renewed and this is just the live claims at the site as they are."}},"type":"object","required":["claims"],"title":"ClaimSiteResponse"},"ClaimStatusResponse":{"properties":{"claim":{"anyOf":[{"$ref":"#/components/schemas/AlarmClaim"},{"type":"null"}],"description":"The live claim on this alarm, or null (free, lapsed, closed, not on the active queue)."},"activity":{"anyOf":[{"$ref":"#/components/schemas/ActivityPayload"},{"type":"null"}],"description":"Escalation activity on this alarm, or null. Present even when `claim` is null. The key is omitted entirely while CLAIM_ESCALATION_ENABLED is off."},"reservations":{"anyOf":[{"additionalProperties":{"$ref":"#/components/schemas/Reservation"},"type":"object"},{"type":"null"}],"title":"Reservations","description":"Claim-lock R5 (Focus): the reservation on this alarm, keyed by event id (empty when it is not reserved). The key is omitted entirely while FOCUS_ENABLED is off."},"focus":{"anyOf":[{"items":{"oneOf":[{"$ref":"#/components/schemas/FocusRecord"},{"$ref":"#/components/schemas/StoryHoldRecord"}]},"type":"array"},{"type":"null"}],"title":"Focus","description":"Claim-lock R5 (Focus): the active focus records at this alarm's site. Omitted while FOCUS_ENABLED is off."}},"type":"object","required":["claim"],"title":"ClaimStatusResponse"},"ClassScore":{"properties":{"action":{"type":"string","title":"Action","description":"One of the four recommendation classes.","examples":["false_alarm"]},"precision":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Precision","description":"Of reviews where the AI recommended this class, % the human agreed; null when never recommended.","examples":[92.1]},"recall":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Recall","description":"Of reviews the human classified this way, % the AI also recommended it; null when the human never chose it.","examples":[88.4]},"predicted":{"type":"integer","title":"Predicted","description":"Reviews where the AI recommended this class.","examples":[38]},"support":{"type":"integer","title":"Support","description":"Reviews the human classified this way.","examples":[40]}},"type":"object","required":["action","predicted","support"],"title":"ClassScore"},"ClearCameraViewFlagRequest":{"properties":{"reason":{"type":"string","maxLength":60,"minLength":1,"title":"Reason","description":"Why the view is usable despite the AI flag; shown on the cleared item. 1-60 characters.","examples":["Night haze, view is usable"]}},"type":"object","required":["reason"],"title":"ClearCameraViewFlagRequest"},"ClearPolicyRunsResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the Sandbox policy-test runs were cleared.","examples":[true]},"deleted":{"type":"integer","title":"Deleted","description":"Number of policy-test runs removed for the demo clip.","examples":[6]}},"type":"object","required":["success","deleted"],"title":"ClearPolicyRunsResponse"},"Client":{"properties":{"end_customer":{"type":"string","maxLength":200,"title":"End Customer","description":"Dealer's client business name; internal only.","default":""},"app_branding":{"type":"string","maxLength":10,"title":"App Branding","description":"Dealer wants its name and logo on the app: yes, no, or discuss.","default":""}},"additionalProperties":false,"type":"object","title":"Client"},"ClockInResponse":{"properties":{"punch":{"$ref":"#/components/schemas/TimePunchModel","description":"The open punch (the existing one when the caller was already clocked in - clock-in is idempotent)."}},"type":"object","required":["punch"],"title":"ClockInResponse"},"ClockOutResponse":{"properties":{"punch":{"$ref":"#/components/schemas/TimePunchModel","description":"The closed punch, clock_out_at now set."}},"type":"object","required":["punch"],"title":"ClockOutResponse"},"CloseoutEntry":{"properties":{"schema_version":{"type":"integer","const":1,"title":"Schema Version","description":"Closeout record schema version. Pinned to 1 — a client announcing a shape this server does not implement is rejected rather than silently downgraded into version-1 storage.","default":1,"examples":[1]},"observed":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Observed","description":"Free text: who/what, where, doing what. Customer-visible in site reports. Surrounding whitespace is stripped and a blank value is normalized to null.","examples":["Two people on foot crossing the west lot toward the loading dock."]},"actions":{"items":{"type":"string","enum":["reviewed_clip","viewed_live","checked_other_cameras","voice_down","called_owner","texted_owner","emailed_owner","called_police","called_guard","no_action_needed"]},"type":"array","maxItems":32,"title":"Actions","description":"What the operator did, from a closed vocabulary. At most 32 entries on the wire; de-duplicated server-side, first occurrence wins, order preserved.","examples":[["reviewed_clip","viewed_live","called_owner"]]},"outcome":{"anyOf":[{"type":"string","enum":["no_activity","benign_explained","subject_left","subject_remained","dispatched","owner_notified","handed_off"]},{"type":"null"}],"title":"Outcome","description":"How the event ended. Defaults to no_activity on a false-alarm disposition that submits a closeout without one.","examples":["subject_left"]},"outcome_note":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Outcome Note","description":"Optional one-line qualifier on the outcome. Customer-visible. Blank is normalized to null.","examples":["delivery driver, left 22:41"]},"ai_feedback":{"anyOf":[{"$ref":"#/components/schemas/AiFeedbackBlock"},{"type":"null"}],"description":"Internal operator judgment on the AI's own call. NEVER customer-visible (see AiFeedbackBlock's docstring). Additive: a v1 closeout written before this field existed simply lacks the key."}},"additionalProperties":false,"type":"object","title":"CloseoutEntry","description":"Structured operator close-out: what was observed, what was done, how it\nended. Stored as one JSONB blob on operator_evaluations.closeout and\nsnapshotted into training_samples.operator_closeout.\n\nTWO INDEPENDENT GATES, and conflating them is the mistake to avoid:\n\n  * SHAPE (this model) is validated on EVERY request carrying a closeout,\n    in every deployment, flag or no flag.\n  * REQUIRED-NESS (which fields a given disposition must fill) lives in\n    EvalService, gated on CLOSEOUT_REQUIRED, because it depends on the\n    disposition AND on deployment state — neither of which belongs in a\n    schema that gets published as openapi.json.\n\nThat split is why every field except schema_version is optional HERE. The\nfalse-alarm fast path may legitimately post a closeout carrying only an\noutcome, and a model-level `observed: str` would 422 it before the matrix\ngot a say. Optional-here, required-there is what lets ONE schema serve both\nthe flag-off rollout window and the flag-on steady state.\n\n`observed` and `outcome_note` are CUSTOMER-VISIBLE (brief §5 emails them in\nsite reports): no operator names, no internal jargon. The copy rule is\nshown inline in the ops-view form; nothing here can enforce it.\n\nUNKNOWN KEYS ARE REJECTED (extra=\"forbid\"), unlike most request models in\nthis repo. This record is versioned and persisted verbatim: with the\ndefault extra=\"ignore\" a client typo such as `outcome_notes` would return\n200 and be silently discarded before persistence, which is exactly the\ndata loss a \"shape is always validated\" contract promises cannot happen.\nA future shape change bumps schema_version rather than adding keys the\nold server would have to tolerate. (PR #173 review, P2.)"},"CommitContinuedClipRequest":{"properties":{"seconds":{"type":"integer","maximum":30.0,"minimum":1.0,"title":"Seconds","description":"Length of the uploaded continued clip, in whole seconds (1..30)","examples":[10]},"s3_key":{"type":"string","maxLength":1024,"title":"S3 Key","description":"The S3 key the continued clip was uploaded to. Must equal the key derived from the event's clip key (clips/<rest> -> clips-continued/<rest>), else 422","examples":["clips-continued/site-cam-01/2026/10-17/21-10-42-100353-657677f7a9b7.mp4"]}},"type":"object","required":["seconds","s3_key"],"title":"CommitContinuedClipRequest"},"CommitContinuedClipResponse":{"properties":{"ok":{"type":"boolean","title":"Ok","description":"Always true on 200, including a repeat commit (idempotent, nothing re-written or re-broadcast)","default":true}},"type":"object","title":"CommitContinuedClipResponse"},"CompleteCaptureRequest":{"properties":{"claimed_by":{"type":"string","maxLength":128,"minLength":1,"title":"Claimed By","description":"The claim holder."},"claim_key":{"type":"string","maxLength":512,"minLength":1,"title":"Claim Key","description":"The s3_key returned by this exact claim, required for every outcome as its attempt identifier."},"outcome":{"type":"string","enum":["captured","unavailable","failed"],"title":"Outcome","description":"captured | unavailable | failed."},"reason":{"anyOf":[{"type":"string","enum":["buffer_expired","stream_retention_expired","no_footage","codec_unsupported","camera_off_schedule","camera_offline","camera_retired","assemble_failed","upload_failed","kvs_error","lease_expired","shutdown","internal_error","ingestor_lost"]},{"type":"null"}],"title":"Reason","description":"Required for unavailable/failed; null for captured."},"detail":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Detail","description":"Diagnostics only. Stored server-side, NEVER returned to any client. Never URLs or credentials."},"s3_key":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"S3 Key","description":"Required for captured and must equal the server-assigned key; null otherwise."},"size_bytes":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Size Bytes","description":"Uploaded object size (captured)."},"captured_ms":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Captured Ms","description":"Milliseconds of footage inside the window (captured; nullable only for continuous cameras)."},"start_offset_ms":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Start Offset Ms","description":"Milliseconds after window_start where footage begins (captured; nullable only for continuous cameras)."}},"additionalProperties":false,"type":"object","required":["claimed_by","claim_key","outcome"],"title":"CompleteCaptureRequest"},"CompleteCaptureResponse":{"properties":{"capture_id":{"type":"string","format":"uuid","title":"Capture Id","description":"Capture id."},"status":{"type":"string","title":"Status","description":"Terminal status recorded."}},"type":"object","required":["capture_id","status"],"title":"CompleteCaptureResponse"},"CompleteNewPasswordRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Email address of the invited operator"},"session":{"type":"string","title":"Session","description":"Cognito session returned by the NEW_PASSWORD_REQUIRED login challenge"},"new_password":{"type":"string","minLength":8,"title":"New Password","description":"The operator's chosen permanent password"}},"type":"object","required":["email","session","new_password"],"title":"CompleteNewPasswordRequest"},"ConfidenceBand":{"type":"string","enum":["LOW","MEDIUM","HIGH"],"title":"ConfidenceBand"},"ConfirmForgotPasswordRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Email address the reset code was sent to"},"code":{"type":"string","minLength":1,"title":"Code","description":"The confirmation code Cognito emailed to the user"},"new_password":{"type":"string","minLength":8,"title":"New Password","description":"The user's chosen new permanent password"}},"type":"object","required":["email","code","new_password"],"title":"ConfirmForgotPasswordRequest"},"ConfusionCell":{"properties":{"recommended":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recommended","description":"AI recommended_action; null = eval carried no recommendation.","examples":["non_threatening"]},"disposition":{"type":"string","title":"Disposition","description":"Human disposition mapped to the same 4-class space.","examples":["non_threatening"]},"count":{"type":"integer","title":"Count","description":"Reviews in this cell.","examples":[41]}},"type":"object","required":["disposition","count"],"title":"ConfusionCell"},"Connection":{"properties":{"vpn":{"type":"boolean","title":"Vpn","description":"Customer prefers a site-to-site VPN over forwarded ports."},"cellular":{"type":"boolean","title":"Cellular","description":"Site runs on cellular or a trailer (bandwidth-limited)."},"recorder":{"type":"string","maxLength":100,"title":"Recorder","description":"NVR/VMS or camera brand.","default":""},"host":{"type":"string","maxLength":255,"title":"Host","description":"Public IP or hostname of the recorder or cameras.","default":""},"port":{"type":"string","maxLength":20,"title":"Port","description":"Forwarded port(s), e.g. RTSP 554.","default":""},"username":{"type":"string","maxLength":200,"title":"Username","description":"Recorder login (ideally a view-only account for Verdun).","default":""},"password":{"type":"string","maxLength":500,"format":"password","title":"Password","description":"Recorder password; stored in Secrets Manager, never emailed.","default":"","writeOnly":true},"camera_count":{"type":"string","maxLength":10,"title":"Camera Count","description":"Number of cameras, as stated by the customer.","default":""},"tech_contact":{"type":"string","maxLength":300,"title":"Tech Contact","description":"Who to ask about cameras/network when details are missing.","default":""},"cameras":{"items":{"$ref":"#/components/schemas/Camera"},"type":"array","maxItems":2000,"title":"Cameras","description":"Per-camera rows, usually from an imported spreadsheet."}},"additionalProperties":false,"type":"object","required":["vpn","cellular"],"title":"Connection"},"Contact":{"properties":{"order":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Order","description":"Position in the call list, 1 = called first."},"first_name":{"type":"string","maxLength":100,"minLength":1,"title":"First Name","description":"First name."},"last_name":{"type":"string","maxLength":100,"title":"Last Name","description":"Last name.","default":""},"role":{"type":"string","maxLength":200,"title":"Role","description":"Role, e.g. General manager.","default":""},"phone":{"type":"string","maxLength":50,"minLength":1,"title":"Phone","description":"Mobile phone."},"afterhours_phone":{"type":"string","maxLength":50,"title":"Afterhours Phone","description":"After-hours phone; defaults to the mobile phone.","default":""},"email":{"type":"string","maxLength":320,"title":"Email","description":"Email; contacts with one get the nightly report.","default":""},"preference":{"type":"string","enum":["call","text","email"],"title":"Preference","description":"How they prefer to be reached.","default":"call"},"site":{"type":"string","maxLength":200,"title":"Site","description":"Site name this contact is for; empty means all sites.","default":""},"app_access":{"type":"boolean","title":"App Access","description":"Wants a login to the Verdun app.","default":false}},"additionalProperties":false,"type":"object","required":["order","first_name","phone"],"title":"Contact"},"ContactAvailabilityWindow":{"properties":{"days":{"items":{"type":"integer"},"type":"array","maxItems":7,"minItems":1,"title":"Days","description":"Days the window starts on, Monday-first: 0 = Monday ... 6 = Sunday.","examples":[[0,1,2,3,4]]},"start":{"type":"string","title":"Start","description":"Start time, HH:MM on a 24-hour clock, site-local.","examples":["08:00"]},"end":{"type":"string","title":"End","description":"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.","examples":["17:00"]}},"type":"object","required":["days","start","end"],"title":"ContactAvailabilityWindow","description":"One recurring weekly window when a site contact is the one to reach.\n\nEvaluated in the SITE's timezone (`sites.timezone`), never the operator's:\n\"Mon-Fri 08:00-17:00\" means business hours where the property is.\n\nDAYS ARE MONDAY-FIRST INTEGERS (0 = Monday ... 6 = Sunday), the same\nconvention as the 7-row monitoring-schedule grid.\n\nOVERNIGHT WINDOWS: when `end` is earlier than `start` the window wraps past\nmidnight, and `days` names the day it STARTS on. {days: [4], start: \"18:00\",\nend: \"06:00\"} is Friday 6pm through Saturday 6am."},"ContinuedClipOfferResponse":{"properties":{"accept":{"type":"boolean","title":"Accept","description":"True: cut and upload the continued clip, then commit it. False: upload nothing"},"reason":{"type":"string","title":"Reason","description":"eligible | verdict_pending (accepted) or demo_event | no_clip | already_continued | agent_closed (declined). An open string: log it, do not switch on it exhaustively","examples":["eligible","verdict_pending","agent_closed"]}},"type":"object","required":["accept","reason"],"title":"ContinuedClipOfferResponse"},"ConversationListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ConversationSummary"},"type":"array","title":"Items","description":"One page, newest activity first."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Keyset cursor for the next page. Keyset, not offset: inbound replies reorder this list continuously."}},"type":"object","required":["items"],"title":"ConversationListResponse"},"ConversationMessage":{"properties":{"id":{"type":"string","title":"Id","description":"The message id. Also the `cid` echoed back by the provider."},"conversation_id":{"type":"string","title":"Conversation Id","description":"The thread it belongs to."},"direction":{"type":"string","enum":["outbound","inbound"],"title":"Direction","description":"Who sent it."},"body":{"type":"string","title":"Body","description":"The message text."},"status":{"type":"string","title":"Status","description":"Same vocabulary and the same monotonic rank as the incident timeline's SMS rows.","examples":["delivered"]},"error_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Code","description":"Provider failure reason, bounded at 255 characters."},"event_metadata_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Metadata Id","description":"The incident this message was about, if any. Cleared rather than cascaded if that event is deleted."},"notification_log_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notification Log Id","description":"The mirror row on that incident's timeline. Null for a thread-only message."},"operator_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Id","description":"Who sent it. Null on inbound."},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Snapshot of their name."},"occurred_at":{"type":"string","format":"date-time","title":"Occurred At","description":"Provider timestamp inbound, send time outbound. The thread's sort key."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When we wrote the row."},"attachments":{"anyOf":[{"items":{"$ref":"#/components/schemas/AttachmentOut"},"type":"array"},{"type":"null"}],"title":"Attachments","description":"Media attached to this message, presigned at read time. NULL — never [] — when the message carried none, so there is one state on the wire for 'no attachments'."},"share_link":{"anyOf":[{"$ref":"#/components/schemas/ShareLinkBrief"},{"type":"null"}],"description":"The login-free clip link minted with this message, if any. Null on every inbound row and on every outbound row sent without include_clip_link. ABSENT on an older backend — treat absent as null."}},"type":"object","required":["id","conversation_id","direction","body","status","occurred_at"],"title":"ConversationMessage","description":"One message in a thread.\n\n`provider_sid` IS DELIBERATELY NOT EXPOSED: it is provider-internal, the UI\nhas no use for it, and it is the correlation key for our own webhook\nhandling."},"ConversationSummary":{"properties":{"id":{"type":"string","title":"Id","description":"The thread id."},"verdun_number":{"type":"string","title":"Verdun Number","description":"Our line, E.164.","examples":["+18555550119"]},"customer_number":{"type":"string","title":"Customer Number","description":"The customer's number, E.164.","examples":["+16195550142"]},"contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Name","description":"Snapshot of the contact at resolution time. Null when the number could not be pinned to one person.","examples":["Jamie Rivera (Owner)"]},"site_contact_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Contact Id","description":"The resolved contact, if exactly one matched. Null makes the thread read-only (consent cannot be checked without it)."},"parent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Id","description":"Client. Null only for a thread orphaned by a hard client delete."},"parent_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Name","description":"Client name, for the list row."},"site_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Id","description":"Site. **Null means a PARENT-LEVEL thread** — the number belongs to contacts at several sites of one client — not a missing field. It is visible only to a scope holding the whole client."},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site name, for the list row."},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"The SITE'S account, resolved at read time. Null for a client-wide thread and for a site directly under the client."},"account_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Name","description":"Account name, for the list row."},"status":{"type":"string","enum":["open","archived"],"title":"Status","description":"An inbound or an outbound reopens an archived thread."},"unread_count":{"type":"integer","title":"Unread Count","description":"Conversation-level and SHARED across operators in v1, not per viewer."},"last_message_at":{"type":"string","format":"date-time","title":"Last Message At","description":"Last activity. The list's sort key, never null."},"last_inbound_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Inbound At","description":"Last message from the customer."},"last_outbound_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Outbound At","description":"Last message from us."},"last_message_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Preview","description":"First 160 characters of the newest body. Staff-only; it never reaches an activity-log entry."},"last_message_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Status","description":"Delivery state of the LAST message, provider vocabulary (queued / sent / delivered / failed / undelivered; `received` on an inbound). Null with no messages. For the list row's badge.","examples":["delivered"]},"last_message_operator_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Operator Id","description":"Who sent the last message. Null on an inbound."},"last_message_operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Message Operator Name","description":"Their name, snapshotted when the message was sent. Null on an inbound."},"last_message_direction":{"anyOf":[{"type":"string","enum":["outbound","inbound"]},{"type":"null"}],"title":"Last Message Direction","description":"Direction of the previewed message."},"claimed_by_operator_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claimed By Operator Id","description":"Who is answering this thread."},"claimed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Claimed By Name","description":"Their name, for the list row."},"claimed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Claimed At","description":"When they claimed it."},"last_event":{"anyOf":[{"$ref":"#/components/schemas/LastEventSummary"},{"type":"null"}],"description":"Newest incident this thread carries a message about."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When the thread was opened."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"Last write to the thread row."}},"type":"object","required":["id","verdun_number","customer_number","status","unread_count","last_message_at"],"title":"ConversationSummary"},"ConversationThreadResponse":{"properties":{"conversation":{"$ref":"#/components/schemas/ConversationSummary","description":"The thread."},"messages":{"items":{"$ref":"#/components/schemas/ConversationMessage"},"type":"array","title":"Messages","description":"One page of messages, newest first."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Keyset cursor for older messages."}},"type":"object","required":["conversation","messages"],"title":"ConversationThreadResponse"},"ConversationsForEventResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ConversationSummary"},"type":"array","title":"Items","description":"Threads carrying at least one message about this event — NOT every thread at the event's site."}},"type":"object","required":["items"],"title":"ConversationsForEventResponse"},"CopyShiftWeekRequest":{"properties":{"source_week_start":{"type":"string","format":"date","title":"Source Week Start","description":"Center-local first day of the week being copied.","examples":["2026-09-14"]},"target_week_start":{"type":"string","format":"date","title":"Target Week Start","description":"Center-local first day of the week being written. Must be a non-zero multiple of 7 days from source_week_start.","examples":["2026-09-21"]},"replace":{"type":"boolean","title":"Replace","description":"When the target week already holds shifts: false = write nothing and report `target_not_empty`; true = delete them first so the target becomes an exact copy of the source.","default":false}},"type":"object","required":["source_week_start","target_week_start"],"title":"CopyShiftWeekRequest","description":"Duplicate one week's shift blocks onto another week.\n\nWeeks are addressed by their first day and must be a whole number of weeks\napart, so every block lands on the same WEEKDAY it left - that is what\n\"the same schedule next week\" means. The endpoint is the one-click \"copy\nto next week\" on the Admin > Shifts grid, but any two aligned weeks work."},"CopyShiftWeekResponse":{"properties":{"status":{"type":"string","enum":["copied","target_not_empty"],"title":"Status","description":"`copied` = the target week now mirrors the source week. `target_not_empty` = the target already held shifts and `replace` was false, so NOTHING was written; retry with replace=true to overwrite."},"copied":{"type":"integer","title":"Copied","description":"Shift blocks created in the target week."},"removed":{"type":"integer","title":"Removed","description":"Pre-existing target-week blocks deleted (replace=true only)."},"existing":{"type":"integer","title":"Existing","description":"Blocks the target week held before the call (the count behind target_not_empty)."},"target_week_start":{"type":"string","format":"date","title":"Target Week Start","description":"Echo of the request's target week."}},"type":"object","required":["status","copied","removed","existing","target_week_start"],"title":"CopyShiftWeekResponse"},"CoverageGrantModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The grant ID."},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id","description":"Parent granted wholesale - covers every current AND future site under it (null for site grants)."},"site_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Id","description":"Individually granted site (null for parent grants)."}},"type":"object","required":["id"],"title":"CoverageGrantModel"},"CoverageGroupMemberModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The operator ID."},"first_name":{"type":"string","title":"First Name","description":"Member's first name.","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"Member's last name.","examples":["Johnson"]},"role":{"$ref":"#/components/schemas/OperatorRole","description":"The member's role."}},"type":"object","required":["id","first_name","last_name","role"],"title":"CoverageGroupMemberModel","description":"One standing member of a coverage group.\n\nfirst_name/last_name rather than a composed display name, matching every\nother operator sub-payload; `role` so the surface can render the\n\"structurally scoped roles cannot be members\" state without a second fetch.\n\nMEMBERSHIP CONFERS SCOPE, NEVER PERMISSION: this member sees the sites this\ngroup's grants cover (off-shift included -- seating says who is on the desk,\nnot who has access); their capabilities remain exactly their role's."},"CoverageGroupModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The coverage group ID."},"name":{"type":"string","title":"Name","description":"Display name of the group.","examples":["Overnight High-Risk"]},"color":{"type":"string","title":"Color","description":"Display color used in schedule grids and chips.","examples":["#4DFFD2"]},"grants":{"items":{"$ref":"#/components/schemas/CoverageGrantModel"},"type":"array","title":"Grants","description":"The parent/site grants that define the group's sites."},"site_count":{"type":"integer","title":"Site Count","description":"Distinct sites currently covered (parent grants expanded).","examples":[42]},"members":{"items":{"$ref":"#/components/schemas/CoverageGroupMemberModel"},"type":"array","title":"Members","description":"Operators who are standing members of this group. Their tenant scope includes this group's sites -- SCOPE ONLY, never a permission. Deleting a group with members is refused (409)."},"member_count":{"type":"integer","title":"Member Count","description":"How many operators are standing members of this group.","default":0,"examples":[3]}},"type":"object","required":["id","name","color","grants","site_count"],"title":"CoverageGroupModel"},"CreateAIEvalRequest":{"properties":{"schema_version":{"type":"string","title":"Schema Version","description":"Schema version"},"tenant_id":{"type":"string","format":"uuid","title":"Tenant Id","description":"Tenant ID"},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"Site ID"},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera ID"},"clip_id":{"type":"string","format":"uuid","title":"Clip Id","description":"Clip ID"},"job_id":{"type":"string","title":"Job Id","description":"Job ID"},"detections":{"items":{"$ref":"#/components/schemas/DetectionModel"},"type":"array","title":"Detections","description":"Detections"},"roi_hits":{"items":{"$ref":"#/components/schemas/ROIHitModel"},"type":"array","title":"Roi Hits","description":"ROI hits"},"keyframes":{"items":{"$ref":"#/components/schemas/KeyframeModel"},"type":"array","title":"Keyframes","description":"Keyframes"},"incident_type":{"$ref":"#/components/schemas/IncidentType","description":"Incident type"},"severity":{"$ref":"#/components/schemas/Severity","description":"Severity level"},"confidence_band":{"$ref":"#/components/schemas/ConfidenceBand","description":"Confidence band"},"suggested_disposition":{"$ref":"#/components/schemas/SuggestedDisposition","description":"Suggested disposition"},"recommended_action":{"anyOf":[{"$ref":"#/components/schemas/RecommendedAction"},{"type":"null"}],"description":"Operator-aligned action recommendation (prompt v2+); absent from older pipelines"},"matched_exception":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched Exception","description":"Heightened monitoring: the site exception label the AI cited when clearing human activity as non-threatening; absent from older pipelines and outside heightened windows"},"subjects":{"items":{"$ref":"#/components/schemas/SubjectDescriptorRequest"},"type":"array","maxItems":4,"title":"Subjects","description":"Visible person/vehicle appearance descriptors for conservative cross-camera matching"},"needs_human_review":{"type":"boolean","title":"Needs Human Review","description":"Whether human review is needed"},"rationale":{"type":"string","title":"Rationale","description":"Rationale for the result"},"llm_provider":{"type":"string","title":"Llm Provider","description":"LLM provider"},"llm_model":{"type":"string","title":"Llm Model","description":"LLM model"},"llm_prompt_version":{"type":"string","title":"Llm Prompt Version","description":"LLM prompt version"},"detection_provider":{"type":"string","title":"Detection Provider","description":"Detection provider"},"detection_model_version":{"type":"string","title":"Detection Model Version","description":"Detection model version"},"pipeline_version":{"type":"string","title":"Pipeline Version","description":"Pipeline version"},"roi_config_version":{"type":"string","title":"Roi Config Version","description":"ROI config version"},"failovers":{"items":{"$ref":"#/components/schemas/FailoversModel"},"type":"array","title":"Failovers","description":"Failover events"},"decode_ms":{"type":"integer","title":"Decode Ms","description":"Decode time in ms"},"detect_ms":{"type":"integer","title":"Detect Ms","description":"Detection time in ms"},"llm_ms":{"type":"integer","title":"Llm Ms","description":"LLM time in ms"},"total_ms":{"type":"integer","title":"Total Ms","description":"Total processing time in ms"},"cost_usd":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Cost Usd","description":"Cost in USD"},"debug":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Debug","description":"Debug information"},"terminal_stage":{"type":"string","minLength":1,"title":"Terminal Stage","description":"'interpretation' | 'object_gate' — see round-0b design §7","default":"interpretation"},"gate_outcome":{"anyOf":[{"$ref":"#/components/schemas/GateOutcomeModel"},{"type":"null"}],"description":"Populated only when terminal_stage == 'object_gate'; not cross-validated against terminal_stage here on purpose (ii's own contract already enforces the invariant at the source — round-0 design §1.3/OQ-11)"},"detection_stages":{"anyOf":[{"items":{"$ref":"#/components/schemas/DetectionStageModel"},"type":"array"},{"type":"null"}],"title":"Detection Stages","description":"Itemized per-stage breakdown of the detection/gate cascade, when available"},"backup_assessment":{"title":"Backup Assessment","description":"VLM fallback marker (any JSON value): present = this row came from a backup model and is advisory only; never decisive. See app.contracts.backup_assessment"}},"type":"object","required":["schema_version","tenant_id","site_id","camera_id","clip_id","job_id","detections","roi_hits","keyframes","incident_type","severity","confidence_band","suggested_disposition","needs_human_review","rationale","llm_provider","llm_model","llm_prompt_version","detection_provider","detection_model_version","pipeline_version","roi_config_version","failovers","decode_ms","detect_ms","llm_ms","total_ms","cost_usd"],"title":"CreateAIEvalRequest"},"CreateAccountRequest":{"properties":{"name":{"type":"string","title":"Name","description":"The name of the account (the client's customer).","examples":["Northgate Logistics"]},"primary_contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Name","description":"Primary contact's name at the account.","examples":["Dana Reyes"]},"primary_contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Phone","description":"Primary contact's phone number at the account.","examples":["8015551234"]},"primary_contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Email","description":"Primary contact's email address at the account.","examples":["dana@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes about the account.","examples":["Four dealership lots; night monitoring only."]}},"type":"object","required":["name"],"title":"CreateAccountRequest"},"CreateAccountResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the account."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client (parent) this account belongs to."},"name":{"type":"string","title":"Name","description":"Name of the account (the client's customer).","examples":["Northgate Logistics"]},"primary_contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Name","description":"Primary contact's name at the account.","examples":["Dana Reyes"]},"primary_contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Phone","description":"Primary contact's phone number at the account.","examples":["8015551234"]},"primary_contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Email","description":"Primary contact's email address at the account.","examples":["dana@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes about the account.","examples":["Four dealership lots; night monitoring only."]}},"type":"object","required":["id","parent_id","name"],"title":"CreateAccountResponse"},"CreateActivityRequest":{"properties":{"request_id":{"type":"string","format":"uuid","title":"Request Id","description":"Client-generated idempotency UUID"},"contact_id":{"type":"string","format":"uuid","title":"Contact Id","description":"Site contact whose enrolled customer app should receive this notification"},"message":{"type":"string","maxLength":1000,"title":"Message","description":"Optional message visible to this customer","default":""}},"type":"object","required":["request_id","contact_id"],"title":"CreateActivityRequest"},"CreateCakeClaimRequest":{"properties":{"full_name":{"type":"string","maxLength":200,"minLength":1,"title":"Full Name","description":"Contact name."},"dealership":{"type":"string","maxLength":200,"minLength":1,"title":"Dealership","description":"Company name (legacy field name retained for compatibility)."},"email":{"type":"string","maxLength":320,"format":"email","title":"Email","description":"Company email address."},"contact_reference":{"type":"string","maxLength":200,"title":"Contact Reference","description":"Honeypot; leave empty.","default":""},"photo":{"type":"string","contentMediaType":"application/octet-stream","title":"Photo","description":"Cake photo: JPEG, PNG or WebP, at most 5 MiB."}},"additionalProperties":false,"type":"object","required":["full_name","dealership","email","photo"],"title":"CreateCakeClaimRequest"},"CreateCakeClaimResponse":{"properties":{"status":{"type":"string","const":"ok","title":"Status","description":"Email provider accepted the claim.","default":"ok"}},"type":"object","title":"CreateCakeClaimResponse"},"CreateCakeReferralRequest":{"properties":{"full_name":{"type":"string","maxLength":200,"minLength":1,"title":"Full Name","description":"Referrer name."},"email":{"type":"string","maxLength":320,"format":"email","title":"Email","description":"Referrer's email address; personal and work addresses are accepted."},"company_name":{"type":"string","maxLength":200,"minLength":1,"title":"Company Name","description":"Referrer's company name."},"business_name":{"type":"string","maxLength":200,"minLength":1,"title":"Business Name","description":"Business nominated to receive a cake."},"commercial_address":{"type":"string","maxLength":500,"minLength":8,"title":"Commercial Address","description":"Full commercial delivery address; subject to manual verification."},"business_contact":{"type":"string","maxLength":200,"minLength":1,"title":"Business Contact","description":"Recipient point of contact name at the nominated business."},"business_contact_email":{"type":"string","maxLength":320,"format":"email","title":"Business Contact Email","description":"Recipient point of contact email address."},"fit_reason":{"type":"string","maxLength":1500,"minLength":10,"title":"Fit Reason","description":"Why the business is a fit for monitoring."},"contact_reference":{"type":"string","maxLength":200,"title":"Contact Reference","description":"Honeypot; leave empty.","default":""}},"additionalProperties":false,"type":"object","required":["full_name","email","company_name","business_name","commercial_address","business_contact","business_contact_email","fit_reason"],"title":"CreateCakeReferralRequest"},"CreateCallAuditRequest":{"properties":{"verdict":{"type":"string","enum":["upheld","overturned"],"title":"Verdict","description":"Uphold the original decision (a human operator's call or the AI agent's close), or overturn it."},"correct_disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Correct Disposition","description":"Overturned only (required there, forbidden on upheld): the disposition the call should have been, as an OperatorDisposition MEMBER NAME. One of FALSE_ALARM, SUSPICIOUS_MONITOR, ESCALATE_VOICE_DOWN, ESCALATE_DISPATCH, and different from the original decision (the operator's, or the agent's).","examples":["SUSPICIOUS_MONITOR"]},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Overturned only (required there, forbidden on upheld): why the call was wrong. Must not be blank; up to 2000 characters after stripping."}},"additionalProperties":false,"type":"object","required":["verdict"],"title":"CreateCallAuditRequest"},"CreateCameraGroupRequest":{"properties":{"name":{"type":"string","maxLength":80,"minLength":1,"title":"Name","description":"Display name, unique (case-insensitively) among the site's live camera groups.","examples":["Tenant A"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Free-form note about what this camera group covers.","examples":["Suite 200 - enters past cameras 1-3"]},"camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Camera Ids","description":"Initial members. Every camera must be a live camera at this same site; a camera may belong to several camera groups at once."}},"type":"object","required":["name"],"title":"CreateCameraGroupRequest","description":"Create a camera group at a site, optionally with its members.\n\nA brand-new camera group changes NOTHING about monitoring: a group with no\nschedule contributes nothing to a camera's state (membership alone is\ninert). It starts affecting cameras only once\nPUT /camera-groups/{id}/monitoring-schedule gives it a week."},"CreateCameraRequest":{"properties":{"camera_label":{"type":"string","title":"Camera Label","description":"The label of the camera.","examples":["SD Sewing Company"]},"camera_id":{"type":"string","title":"Camera Id","description":"The camera ID.","examples":["cam01"]},"kvs_stream_name":{"anyOf":[{"type":"string","maxLength":256,"minLength":1,"pattern":"^[a-zA-Z0-9_.-]+$"},{"type":"null"}],"title":"Kvs Stream Name","description":"The KVS stream name. OMIT to have the server generate a unique one (recommended). Must be unique among live cameras account-wide and cannot be changed after creation.","examples":["dev-home-cam01"]},"ip_address":{"type":"string","title":"Ip Address","description":"The IP address of the camera.","examples":["192.0.0.1"]},"port":{"type":"string","title":"Port","description":"The port of the camera.","examples":["554"]},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"Camera account username. Required for every integration EXCEPT the cloud VMSs (Verkada, Turing), whose cameras have no local account.","examples":["admin"]},"password":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Password","description":"Camera account password. WRITE-ONLY — no endpoint ever returns it. Required for every integration EXCEPT the cloud VMSs (Verkada, Turing), whose cameras have no local account.","examples":["<PASSWORD>"]},"integration":{"$ref":"#/components/schemas/CameraIntegration","description":"The integration of the camera.","examples":["Axis"]},"adapter":{"anyOf":[{"$ref":"#/components/schemas/CameraAdapter"},{"type":"null"}],"description":"The adapter of the camera."},"brand":{"anyOf":[{"$ref":"#/components/schemas/CameraBrand"},{"type":"null"}],"description":"The brand of the camera."},"model":{"anyOf":[{"$ref":"#/components/schemas/CameraModel"},{"type":"null"}],"description":"The model of the camera."},"metadata_format":{"anyOf":[{"$ref":"#/components/schemas/CameraMetadataFormat"},{"type":"null"}],"description":"The metadata format of the camera."},"rtsp_url":{"type":"string","title":"Rtsp Url","description":"Full RTSP URL including credentials. Stored in Secrets Manager; reads return it with the password replaced by 'REDACTED'. Characters that would make the credential ambiguous ('/', '?', '#' in a password) must be percent-encoded.","examples":["rtsp://admin:s3cret@10.0.0.1:554/stream1"]},"rag_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rag Context","description":"Free-form scene context passed to the AI pipeline for this camera.","examples":["Loading dock, forklifts active until 22:00"]},"ingest_mode":{"$ref":"#/components/schemas/CameraIngestMode","description":"continuous = 24/7 KVS archiving; motion_clip = clips uploaded only on motion/analytics trigger.","default":"continuous"},"motion_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Motion Config","description":"Extensible motion settings with bounded known keys (timings, thresholds, detector geometry/rate and RTSP buffer mode); see app/services/motion_config_validation.py. Unknown keys are preserved.","examples":[{"debounce_seconds":30,"post_roll_seconds":5,"pre_roll_seconds":5}]},"analytics_provider":{"$ref":"#/components/schemas/AnalyticsProvider","description":"Analytics vendor that validates triggered clips before upload; 'none' means frame-diff only.","default":"none"},"analytics_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Analytics Config","description":"Opaque per-vendor analytics settings interpreted by the ingestor."},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Incident intelligence on/off. When false the camera's motion events still record and alert in the UI, but clips are not sent to the AI pipeline for classification.","default":true},"fire_watch":{"type":"boolean","title":"Fire Watch","description":"Fire-screen gate (site-admin-only; default off since 2026-10-01). When true, motion that the analytics provider rejected is still screened for smoke/flame before being discarded. FIRE is not in any production prompt profile, so leave it off unless a camera specifically needs the screen.","default":false},"agent_mode":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Agent Mode","description":"Agent mode at creation. Omit for the default: armed at a newly onboarded site or one with any armed camera, shadow at an all-shadow site. false = born in shadow (anyone may ask for it; e.g. a calibration period at an untuned site). true = born armed (site-admin-only; a non-admin's true falls back to the default) and requires ii_enabled, else 409."},"monitoring_profile":{"$ref":"#/components/schemas/CameraMonitoringProfile","description":"Which AI prompt template classifies this camera's clips (site-admin-only): 'security' = intrusion/theft/robbery (default), 'safety' = slips/falls/person-down.","default":"security"}},"type":"object","required":["camera_label","camera_id","ip_address","port","integration","rtsp_url"],"title":"CreateCameraRequest"},"CreateCameraResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera ID."},"camera_label":{"type":"string","title":"Camera Label","description":"The label of the camera.","examples":["SD Sewing Company"]},"camera_id":{"type":"string","title":"Camera Id","description":"The camera ID.","examples":["cam01"]},"kvs_stream_name":{"type":"string","title":"Kvs Stream Name","description":"The KVS stream name.","examples":["dev-home-cam01"]},"ip_address":{"type":"string","title":"Ip Address","description":"The IP address of the camera.","examples":["192.0.0.1"]},"port":{"type":"string","title":"Port","description":"The port of the camera.","examples":["554"]},"username":{"type":"string","title":"Username","description":"Camera account username (the password is never returned).","examples":["admin"]},"integration":{"$ref":"#/components/schemas/CameraIntegration","description":"The integration of the camera.","examples":["Axis"]},"adapter":{"anyOf":[{"$ref":"#/components/schemas/CameraAdapter"},{"type":"null"}],"description":"The adapter of the camera.","examples":["hanwha_onvif"]},"brand":{"anyOf":[{"$ref":"#/components/schemas/CameraBrand"},{"type":"null"}],"description":"The brand of the camera.","examples":["hanwha"]},"model":{"anyOf":[{"$ref":"#/components/schemas/CameraModel"},{"type":"null"}],"description":"The model of the camera.","examples":["ANV-L7012R"]},"metadata_format":{"anyOf":[{"$ref":"#/components/schemas/CameraMetadataFormat"},{"type":"null"}],"description":"The metadata format of the camera.","examples":["onvif"]},"rtsp_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rtsp Url","description":"The camera's RTSP URL with its password redacted. Never the working credentialed URL. See EditCameraRequest.rtsp_url for the write contract (blank or a REDACTED-bearing echo means keep).","examples":["rtsp://admin:REDACTED@10.0.0.1:554/stream1"]},"rag_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rag Context","description":"Free-form scene context passed to the AI pipeline for this camera.","examples":["Loading dock, forklifts active until 22:00"]},"ingest_mode":{"$ref":"#/components/schemas/CameraIngestMode","description":"continuous = 24/7 KVS archiving; motion_clip = clips uploaded only on motion/analytics trigger.","default":"continuous"},"motion_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Motion Config","description":"Extensible motion settings with bounded known keys (timings, thresholds, detector geometry/rate and RTSP buffer mode); see app/services/motion_config_validation.py. Unknown keys are preserved.","examples":[{"debounce_seconds":30,"post_roll_seconds":5,"pre_roll_seconds":5}]},"analytics_provider":{"$ref":"#/components/schemas/AnalyticsProvider","description":"Analytics vendor validating triggered clips before upload; 'none' means frame-diff only.","default":"none"},"analytics_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Analytics Config","description":"Opaque per-vendor analytics settings interpreted by the ingestor."},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Incident intelligence on/off; when false motion events still record and alert, but skip AI classification.","default":true},"fire_watch":{"type":"boolean","title":"Fire Watch","description":"Fire-screen gate; when true, analytics-rejected motion is still screened for smoke/flame before being discarded.","default":true},"monitoring_profile":{"$ref":"#/components/schemas/CameraMonitoringProfile","description":"Which AI prompt template classifies this camera's clips (owner-only): 'security' = intrusion/theft/robbery, 'safety' = slips/falls/person-down.","default":"security"},"live_view_only":{"type":"boolean","title":"Live View Only","description":"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.","default":false},"display_rotation":{"type":"integer","title":"Display Rotation","description":"Degrees counter-clockwise the recorded frame is turned for display (0 or 180). Set by an owner for an upside-down camera; clips are then stamped with a display-matrix tag and the camera's zones are stored in the rotated (upright) space.","default":0},"agent_mode":{"type":"boolean","title":"Agent Mode","description":"Agent mode on/off; when true the backend acts on AI recommendations (auto-dismiss / voice-down / escalate-upgrade) instead of holding every clip for human review.","default":false},"agent_observation_minutes":{"type":"integer","title":"Agent Observation Minutes","description":"Post-voice-down observation window in minutes before an alert auto-dismisses (agent mode).","default":10},"agent_incident_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Agent Incident Types","description":"Incident types the agent may process at all on this camera; unselected types go to the human queue. Auto-dismiss is additionally bounded by the global safety allowlist."},"incident_routing":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Incident Routing","description":"Per-camera incident routing: incident type -> 'ignore' (dropped and auto-dismissed as not monitored) | 'humans' (operator queue) | 'agent'. A type absent from the map routes to 'humans'; UNKNOWN is never present. Supersedes agent_incident_types, which is now derived from this and kept for one release. 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.","examples":[{"FIRE":"ignore","LOITERING":"agent","NORMAL":"humans"}]},"detection_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Mode","description":"DeepAlert Phase B routing mode (owner-only, written via PUT /cameras/{id}/detection-mode): 'legacy' | 'ii_gated'; null means legacy. Only meaningful when analytics_provider == 'deepalert'.","examples":["ii_gated"]},"ptz":{"type":"boolean","title":"Ptz","description":"Whether this camera is pan/tilt/zoom; when true the 'camera moved' health issue is suppressed.","default":false},"integration_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Integration Id","description":"The cloud-VMS connection (site_integrations.id) this camera was onboarded from; null for NVR and direct-RTSP cameras."},"external_device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Device Id","description":"The VENDOR's own camera id within that connection; null unless integration_id is set.","examples":["3a1b0f4c-1d2e-4f5a-9b8c-7d6e5f4a3b2c"]},"integration_device_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Integration Device Info","description":"Snapshot of what the vendor reported about the device at import (name, model, site, codec); null unless integration_id is set."},"live_view_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Live View Until","description":"On-demand live view is active while now() < this timestamp (motion_clip cameras)."},"healthy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Healthy","description":"The healthy status of the camera.","examples":[true,false]},"last_healthcheck_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Healthcheck At","description":"The last healthcheck at of the camera."},"map_x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x position (0..1) on the site map.","examples":[0.42]},"map_y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y position (0..1) on the site map.","examples":[0.63]},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude on the site map.","examples":[42.0443]},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude on the site map.","examples":[-87.9825]},"map_location_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose shown on queue rows and mini-maps.","examples":["NE corner — loading dock"]},"map_heading":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Heading","description":"Direction the device faces, degrees clockwise from map-up; null = not aimed.","examples":[225]},"deepalert_threshold":{"anyOf":[{"$ref":"#/components/schemas/DeepAlertThresholdModel"},{"type":"null"}],"description":"Read-only, server-resolved DeepAlert sensitivity for this camera (see DeepAlertThresholdModel). Never part of analytics_config; a client must not write it back."}},"type":"object","required":["id","camera_label","camera_id","kvs_stream_name","ip_address","port","username","integration"],"title":"CreateCameraResponse"},"CreateCoverageGroupRequest":{"properties":{"name":{"type":"string","maxLength":120,"minLength":1,"title":"Name","description":"Display name of the coverage group.","examples":["Overnight High-Risk"]},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"Display color (hex). Server assigns one when omitted.","examples":["#fbbf24"]}},"type":"object","required":["name"],"title":"CreateCoverageGroupRequest"},"CreateCoverageGroupResponse":{"properties":{"group":{"$ref":"#/components/schemas/CoverageGroupModel","description":"The created coverage group."}},"type":"object","required":["group"],"title":"CreateCoverageGroupResponse"},"CreateDemoCollectionRequest":{"properties":{"name":{"type":"string","maxLength":32,"minLength":1,"title":"Name","description":"Owner-defined Demo tab name. Case-insensitively unique; 'all' is reserved.","examples":["dealership"]}},"type":"object","required":["name"],"title":"CreateDemoCollectionRequest"},"CreateEssayCommentRequest":{"properties":{"name":{"type":"string","maxLength":60,"minLength":1,"title":"Name","description":"Commenter display name."},"body":{"type":"string","maxLength":2000,"minLength":1,"title":"Body","description":"Comment text."},"contact_reference":{"type":"string","maxLength":200,"title":"Contact Reference","description":"Honeypot; real readers leave empty.","default":""}},"type":"object","required":["name","body"],"title":"CreateEssayCommentRequest"},"CreateEssayCommentResponse":{"properties":{"status":{"type":"string","const":"pending","title":"Status","description":"Comment accepted and awaiting owner approval.","default":"pending"}},"type":"object","title":"CreateEssayCommentResponse"},"CreateEventRequest":{"properties":{"zone":{"type":"string","title":"Zone","description":"Zone code","examples":["A"]},"clip_s3_url":{"type":"string","title":"Clip S3 Url","description":"S3 URL","examples":["https://s3.amazonaws.com"]},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"Mapped summary column","examples":["Emergency!"]},"motion_score":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Motion Score","description":"Mapped motion score column","examples":["0.9"]},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Mapped source column","examples":["rtsp_motion"]},"detection_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Mode","description":"The ingestor runner's detection_mode snapshot for this clip; when present it overrides the live camera row for the response's routing fields. Normalized to lowercase; values outside {legacy, ii_gated} degrade to None (camera-row fallback) rather than rejecting the alarm write","examples":["legacy","ii_gated"]},"occurred_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Occurred At","description":"When the motion actually started (clip trigger time). The schedule gate is evaluated at this instant so clip-upload latency at an hour boundary can't misclassify the event. Absent = evaluated at receipt time.","examples":["2026-07-09T05:59:30+00:00"]},"camera_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id","description":"The ingestor runner's own api camera id for this stream, captured once at the processor's construction. When present, create_event 409s if it disagrees with the camera actually resolved by kvs_stream_name. Malformed values degrade to None (the check is skipped) rather than rejecting the alarm write.","examples":["3fa85f64-5717-4562-b3fc-2c963f66afa6"]}},"type":"object","required":["zone","clip_s3_url"],"title":"CreateEventRequest"},"CreateEventResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of event","examples":["UUID Of some sort"]},"kvs_stream_name":{"type":"string","title":"Kvs Stream Name","description":"KVS stream name","examples":["dev-home-cam01"]},"zone":{"type":"string","title":"Zone","description":"Zone code","examples":["A"]},"clip_s3_url":{"type":"string","title":"Clip S3 Url","description":"S3 URL","examples":["https://s3.amazonaws.com"]},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"Mapped summary column","examples":["Emergency!"]},"motion_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Motion Score","description":"Mapped motion score column","examples":["0.9"]},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Mapped source column","examples":["rtsp_motion"]},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"Tenant ID","examples":["UUID of some sort"]},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera ID","examples":["UUID of some sort"]},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"Site ID","examples":["UUID of some sort"]},"rag_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rag Context","description":"Camera scene context for the LLM prompt","examples":["Cleaning crew expected 19:00-21:00 nightly"]},"site_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Timezone","description":"IANA timezone of the site","examples":["America/Los_Angeles"]},"event_local_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Local Time","description":"Event time in the site's local timezone (ISO 8601)","examples":["2026-07-09T19:42:00-07:00"]},"monitoring_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring State","description":"Schedule mode at the event time: 'monitored' or 'heightened' ('off' events are suppressed)","examples":["heightened"]},"heightened_exceptions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Heightened Exceptions","description":"JSON-encoded exception list; null outside heightened windows","examples":["[{\"label\": \"Uniformed security\", \"description\": \"Navy uniforms with badge\"}]"]},"ignore_zones":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ignore Zones","description":"JSON-encoded ignore=true masking polygons (points only, percent 0-100 of the frame) for the AI pipeline to redact before analysis; null when the camera has none","examples":["[{\"points\": [{\"x\": 10, \"y\": 20}, {\"x\": 80, \"y\": 20}, {\"x\": 80, \"y\": 90}]}]"]},"monitoring_profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Profile","description":"The camera's monitoring profile: 'security' (intrusion/theft/robbery) or 'safety' (slips/falls/person-down)","examples":["security"]},"detection_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Mode","description":"Camera's DeepAlert routing state: 'legacy' | 'ii_gated'; null means legacy","examples":["ii_gated"]},"deepalert_sensor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deepalert Sensor Id","description":"Provisioned DeepAlert sensor id; populated only when detection_mode == 'ii_gated'","examples":["sensor-abc123"]},"deepalert_confidence_threshold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Deepalert Confidence Threshold","description":"The DeepAlert confidence floor (0-100, lower = more sensitive) in force for this event: the camera's valid override, else the fleet default. On the ii_gated path it is always a number (null only for a corrupt persisted tuple, which ii stamps ii_fail_safe); null off the ii_gated path. Persisted per event and returned unchanged on a dedupe retry","examples":[25]},"deepalert_confidence_threshold_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deepalert Confidence Threshold Source","description":"Where that value came from: 'camera' (a valid per-camera override) | 'fleet_default' | 'invalid_override' (the stored override was unusable; the fleet default applied). Null off the ii_gated path. Old ingestors drop this key; ii then stamps the provenance 'unknown'","examples":["fleet_default"]},"deepalert_confidence_threshold_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Deepalert Confidence Threshold Revision","description":"The fleet-default revision the value was resolved under (the epoch key; see PUT /settings/deepalert-default-confidence-threshold). Null off the ii_gated path","examples":[0]},"deepalert_classes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deepalert Classes","description":"Owner-configured DeepAlert classes as a canonical comma-joined string ('people' | 'vehicles' | 'people,vehicles'); null means both. Populated only when detection_mode == 'ii_gated'","examples":["people"]},"fire_watch":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Fire Watch","description":"Whether the fire-screen fallback should run for this camera's gate-negative clips","examples":[true]},"display_rotation":{"type":"integer","title":"Display Rotation","description":"The camera's display rotation (degrees CCW, 0 or 180) that ignore_zones is expressed against. The ingestor compares it with the rotation it stamped on the clip and transforms ignore_zones into the clip's space on a mismatch. 0 = no rotation or an api that predates the field.","default":0,"examples":[180]},"deduplicated":{"type":"boolean","title":"Deduplicated","description":"True when this response describes an existing event (a retry of an already-successful create), not a new insert","default":false,"examples":[false]}},"type":"object","required":["id","kvs_stream_name","zone","clip_s3_url","parent_id","camera_id","site_id"],"title":"CreateEventResponse"},"CreateExternalTalkdownRequest":{"properties":{"provider":{"$ref":"#/components/schemas/ExternalTalkdownProvider","description":"Which vendor console the operator drove. Only Verkada Command today.","examples":["verkada_command"]},"mode":{"$ref":"#/components/schemas/ExternalTalkdownMode","description":"`mic` = the operator's own microphone through the vendor console. `clip` = the bundled deterrent clip played into the console's capture.","examples":["mic"]},"client_ref":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,64}$","title":"Client Ref","description":"The extension's per-attempt id, minted once per click. THE IDEMPOTENCY KEY: re-sending this request with the same value returns the existing row rather than opening a second one. Reusing one across two different events is a client bug and is refused with 409.","examples":["a1b2c3d4e5f6"]}},"additionalProperties":false,"type":"object","required":["provider","mode","client_ref"],"title":"CreateExternalTalkdownRequest","description":"Open the audit row for ONE talk-down attempt through a vendor console.\n\nTHERE IS NO TIMESTAMP FIELD HERE, AND NO STATUS FIELD. A new row is always\n`requested`, and `requested_at` is the SERVER's clock — the browser that\nsends this is the one piece of the system whose clock nobody controls, and\n\"when did an operator make sound at a customer site\" is exactly the kind of\nfact that must not be client-assertable.\n\nTHERE IS NO `event_id` IN THE BODY either: it is the path segment, so the\ntwo can never disagree.\n\nUNKNOWN KEYS ARE REJECTED rather than ignored. A client that sends\n`connected_at` or `status` has misunderstood the contract, and silently\ndropping the field would let it believe the value landed. That is the whole\nreason for `extra=\"forbid\"` on a request model in a repo that mostly does\nnot use it."},"CreateInquiryRequest":{"properties":{"first_name":{"type":"string","maxLength":100,"minLength":1,"title":"First Name","description":"First name."},"last_name":{"type":"string","maxLength":100,"title":"Last Name","description":"Optional last name.","default":""},"email":{"type":"string","maxLength":320,"format":"email","title":"Email","description":"Reply-to email address."},"phone":{"type":"string","maxLength":50,"minLength":1,"title":"Phone","description":"Contact phone number."},"property_type":{"type":"string","enum":["Commercial","Residential","Office","Retail store","Construction site","Warehouse","Industrial","Other"],"title":"Property Type","description":"Type of property to monitor."},"protection_type":{"type":"string","enum":["Building","Multifamily property","Equipment","Employees","Inventory","Vehicles","Perimeter","Other"],"title":"Protection Type","description":"What the visitor wants to protect."},"message":{"type":"string","maxLength":5000,"minLength":1,"title":"Message","description":"Monitoring needs."},"contact_reference":{"type":"string","maxLength":200,"title":"Contact Reference","description":"Honeypot; real visitors leave empty.","default":""}},"additionalProperties":false,"type":"object","required":["first_name","email","phone","property_type","protection_type","message"],"title":"CreateInquiryRequest"},"CreateInquiryResponse":{"properties":{"status":{"type":"string","const":"ok","title":"Status","description":"Inquiry accepted.","default":"ok"}},"type":"object","title":"CreateInquiryResponse"},"CreateIntegrationRequest":{"properties":{"provider":{"$ref":"#/components/schemas/IntegrationProvider","description":"Which cloud VMS this connection targets. A client may hold MANY connections per provider (one per end customer's vendor organization); each is assigned to the sites it serves, and a site uses at most one connection per provider.","examples":["verkada","turing"]},"label":{"type":"string","minLength":1,"title":"Label","description":"Operator-facing name for this connection. Unique per client and provider (compared trimmed and case-insensitively).","examples":["Northgate Command"]},"api_key":{"type":"string","format":"password","title":"Api Key","description":"Provider credential: a Verkada API key, or a Turing Vision third-party access token. WRITE-ONLY: stored in Secrets Manager and never returned by any endpoint, log line, or activity-log entry. Mint a dedicated, view-only credential for Verdun rather than reusing an administrative one.","writeOnly":true,"examples":["<API_KEY>"]},"org_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Id","description":"Provider organization id. NOT a secret — stored in the clear for display, and also inside the credential secret. Optional at this layer, and NOT required by every provider: Verkada scopes every stream URL by org and rejects a connection without one, while a Turing access token is already org-scoped and takes no org parameter on any endpoint.","examples":["ceb8ff18-0000-0000-0000-000000000000"]},"username":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Username","description":"AvertX only, and required for it: the AvertX Connect sign-in email of the Verdun user the customer added to their account (Manage Users). With provider=avertx, `api_key` is that user's PASSWORD and `org_id` is the account's company code (the `cm=` value in connect.avertx.com URLs, e.g. `Z6WQ7G`). Not a secret; stored inside the credential secret. Ignored by Verkada and Turing.","examples":["support@verdun.ai"]},"command_short_name":{"anyOf":[{"type":"string","maxLength":63,"pattern":"^[a-z0-9-]{1,63}$"},{"type":"null"}],"title":"Command Short Name","description":"The vendor console's organization short name (the subdomain in https://<short-name>.command.verkada.com/...). Owner-entered and never fetched from the provider. Lowercase letters, digits and hyphens only. Send null or an empty string to clear it.","examples":["northgate-security"]},"site_ids":{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":100,"title":"Site Ids","description":"Sites to assign this connection to on creation, in the same transaction. Every site must be a live site of this client and none may already use a connection of this provider. Duplicates are ignored.","examples":[["3fa85f64-5717-4562-b3fc-2c963f66afa6"]]}},"type":"object","required":["provider","label","api_key"],"title":"CreateIntegrationRequest"},"CreateNvrRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Display name for this NVR.","examples":["Warehouse DW Spectrum"]},"kind":{"$ref":"#/components/schemas/NvrKind","description":"NVR/VMS vendor.","default":"dw_spectrum"},"host":{"type":"string","title":"Host","description":"Hostname or IP of the NVR's media server (DW Spectrum / Nx), Open Network Bridge (Milestone XProtect), or ISAPI web server (Hikvision / LTS / HiLook).","examples":["64.183.7.186"]},"port":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Port","description":"Control port Verdun connects to. Vendor-specific — omit to use the vendor default: 7001 for DW Spectrum / Nx (media server: REST API + RTSP relay), 580 for Milestone XProtect (Open Network Bridge ONVIF port; RTSP on 554 is discovered via GetStreamUri, not configured here), 80 for Hikvision / LTS / HiLook (ISAPI HTTP port; 443 and 8443 are treated as HTTPS).","examples":["7001","580","80"]},"rtsp_port":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rtsp Port","description":"Hikvision / LTS / HiLook only: the TCP port Verdun reaches the NVR's RTSP service on from outside the site (often 554; sites that port-forward may use another, e.g. 8554). Omit for 554. Ignored, and not stored, for other NVR kinds.","examples":["554","8554"]},"username":{"type":"string","title":"Username","description":"NVR service-account username.","examples":["verdun-service"]},"password":{"type":"string","title":"Password","description":"NVR service-account password (stored in Secrets Manager only).","examples":["<PASSWORD>"]}},"type":"object","required":["name","host","username","password"],"title":"CreateNvrRequest"},"CreateOperatorEvalRequest":{"properties":{"trace_context":{"properties":{"close_surface":{"type":["string","null"],"enum":["list","by_site","story","review_from_story","next_alarm","focus","deep_link","history","unknown",null],"maxLength":32},"entry_source":{"type":["string","null"],"enum":["list","by_site","story","next_alarm","focus","deep_link","review_rail","history","notification","unknown",null],"maxLength":32}},"type":["object","null"],"title":"Trace Context","description":"Optional client close provenance. close_surface and entry_source are optional; invalid containers or values are ignored, never a reason to reject the close."},"operator_disposition":{"$ref":"#/components/schemas/OperatorDisposition","description":"The operator's disposition","examples":["False alarm - no human activity","Escalate Dispatch"]},"notes":{"type":"string","title":"Notes","description":"A note about this operator","examples":["This is a note."]},"closeout":{"anyOf":[{"$ref":"#/components/schemas/CloseoutEntry"},{"type":"null"}],"description":"Structured close-out record (observed / actions / outcome). Optional on the wire so pre-CloseoutForm clients keep working; its SHAPE is always validated, while per-disposition required-ness is enforced only when CLOSEOUT_REQUIRED=true. When it carries `observed`, the server mirrors that text into `notes`."}},"type":"object","required":["operator_disposition","notes"],"title":"CreateOperatorEvalRequest"},"CreateOperatorMissBoxRequest":{"properties":{"frame_timestamp_ms":{"type":"integer","maximum":2147483647.0,"minimum":0.0,"title":"Frame Timestamp Ms","description":"Milliseconds into the clip, read from the browser's decoded playback clock at the moment of Save (prefer requestVideoFrameCallback's mediaTime; see frame_capture_method). Never a frame index -- camera fps is per-camera and some cameras are variable frame rate, so a timestamp is the only stable frame identity. Bounded to the storage column's INTEGER range (2^31-1) so an out-of-range value is a 422, not a 500 at commit. Strict: a JSON boolean or a numeric string is rejected, not coerced.","examples":[4123]},"frame_capture_method":{"type":"string","enum":["rvfc","currentTime"],"title":"Frame Capture Method","description":"Which browser signal produced frame_timestamp_ms: 'rvfc' (requestVideoFrameCallback's metadata.mediaTime, the presented frame) or 'currentTime' (the requestAnimationFrame fallback when rVFC is unavailable). Recorded for the export's drift-bound accounting, never used to gate the write."},"x1":{"type":"number","maximum":1.0,"minimum":0.0,"title":"X1","description":"Left edge of the box, normalized 0-1 as a fraction of the frame's width (same convention as `detections`). Strict: a JSON boolean or a numeric string is rejected, not coerced.","examples":[0.41]},"y1":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Y1","description":"Top edge of the box, normalized 0-1 as a fraction of the frame's height. Strict: a JSON boolean or a numeric string is rejected, not coerced.","examples":[0.55]},"x2":{"type":"number","maximum":1.0,"minimum":0.0,"title":"X2","description":"Right edge of the box, normalized 0-1. Must be greater than x1 (checked below). Strict: a JSON boolean or a numeric string is rejected, not coerced.","examples":[0.48]},"y2":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Y2","description":"Bottom edge of the box, normalized 0-1. Must be greater than y1 (checked below). Strict: a JSON boolean or a numeric string is rejected, not coerced.","examples":[0.71]},"class_label":{"type":"string","const":"person","title":"Class Label","description":"Phase 1 supports 'person' only; the column itself is a plain string so a later phase can add more without a migration.","default":"person"}},"type":"object","required":["frame_timestamp_ms","frame_capture_method","x1","y1","x2","y2"],"title":"CreateOperatorMissBoxRequest","description":"One box an operator drew around a person the AI missed, on the frame\nwhere it's visible. See `app/models/operator_miss_boxes.py` for the\nstorage shape and design of record.\n\n`clip_s3_url` is deliberately NOT a field here: the server snapshots it\nfrom the event's current `clip_s3_url` at write time rather than trusting\na client-supplied S3 key.\n\nEVERY NUMERIC FIELD IS `strict=True` (PR #276 review, Greptile P2):\npydantic's default (lax) mode coerces a JSON boolean to `0`/`1` and a\nnumeric string to its number, so `\"x1\": true` or `\"x1\": \"0.5\"` would\nsilently become a different annotation from the one an operator actually\nsubmitted, and pass validation, and be stored and exported that way.\nStrict mode refuses both -- only a genuine JSON number is accepted."},"CreateOperatorRequest":{"properties":{"first_name":{"type":"string","title":"First Name","description":"The first name of the operator","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"The last name of the operator","examples":["Johnson"]},"email":{"type":"string","title":"Email","description":"The email address of the operator","examples":["JJ@example.com"]},"role":{"$ref":"#/components/schemas/OperatorRole","description":"The role of the operator","examples":["owner","operator"]},"permissions":{"anyOf":[{"additionalProperties":{"type":"boolean"},"propertyNames":{"$ref":"#/components/schemas/OperatorPermission"},"type":"object"},{"type":"null"}],"title":"Permissions","description":"Per-permission overrides. Omit (null) for role defaults.","examples":[{"manage_users":false,"view_alarm_queue":true}]},"parent_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Parent Ids","description":"Parent (client) IDs the operator may access. Omit (null) for none. A site_manager must be given exactly one. For role=location_manager the client is DERIVED from site_ids and this field may be omitted, empty, or exactly the derived parent — sending a different one is a 409.","examples":[["11111111-1111-1111-1111-111111111111"]]},"site_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Site Ids","description":"Optional per-site narrowing within the granted parent(s). Omit (null) or empty for access to all sites of each granted parent. Every site must belong to one of parent_ids. REQUIRED and exactly one entry for role=location_manager (the UI's \"Site Manager\"), whose client is then derived from that site.","examples":[["33333333-3333-3333-3333-333333333333"]]},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"The one account an account_manager is bound to. REQUIRED for role=account_manager and rejected for every other role. The account must be live. Any parent_ids/site_ids sent alongside are ignored: an account manager's parent and site access is derived from the account, so they automatically see sites added to it later.","examples":["55555555-5555-5555-5555-555555555555"]},"all_sites":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"All Sites","description":"Access to every client and site (operator / read_only only). Omit (null) for the default: TRUE for an operator created by an owner or monitoring manager with no parent_ids/site_ids, FALSE otherwise. An explicit true from any other caller is a 403; on a role that cannot hold it, a 409."}},"type":"object","required":["first_name","last_name","email","role"],"title":"CreateOperatorRequest"},"CreateOperatorResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of operator","examples":["<Some UUID of some sort>"]},"first_name":{"type":"string","title":"First Name","description":"The first name of the operator","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"The last name of the operator","examples":["Johnson"]},"email":{"type":"string","title":"Email","description":"The email address of the operator","examples":["JJ@example.com"]},"role":{"$ref":"#/components/schemas/OperatorRole","description":"The role of the operator","examples":["owner","operator"]},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At","description":"The time the operator was last seen"},"last_logged_in":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Logged In","description":"The time the operator last logged in"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"The time the operator was created"},"permissions":{"anyOf":[{"additionalProperties":{"type":"boolean"},"type":"object"},{"type":"null"}],"title":"Permissions","description":"Per-permission overrides ({OperatorPermission value: bool}); null means role defaults apply","examples":[{"manage_users":false,"view_alarm_queue":true}]},"is_primary_owner":{"type":"boolean","title":"Is Primary Owner","description":"The account anchor: cannot be removed or demoted, and only they may edit their own record. Not settable via the API.","default":false},"parents":{"items":{"$ref":"#/components/schemas/OperatorParent"},"type":"array","title":"Parents","description":"The parents of the operator"},"sites":{"items":{"$ref":"#/components/schemas/OperatorSite"},"type":"array","title":"Sites","description":"Per-site access grants (site-manager / site-scoped operators). Empty means access follows `parents` (all sites). Exactly one entry for a location_manager, whose `parents` entry is derived from it."},"accounts":{"items":{"$ref":"#/components/schemas/OperatorAccount"},"type":"array","title":"Accounts","description":"Account access grant (account managers). At most one entry. When present, `parents` and `sites` are empty: both are DERIVED from the account, and the operator sees whichever sites currently carry this account_id — including sites added later. Empty for every other role."},"coverage_groups":{"items":{"$ref":"#/components/schemas/OperatorCoverageGroup"},"type":"array","title":"Coverage Groups","description":"Standing coverage-group membership. The operator's site access is the UNION of these groups' grants and the `parents`/`sites` rows above, which are an ADDITIVE exception list ('also sees...'). SCOPE ONLY, NEVER PERMISSION: a member gains sites, never a capability. Empty for site_manager / account_manager / location_manager (structurally scoped, refused membership) and for owner / monitoring_manager (already unconfined, so a membership would be inert)."},"all_sites":{"type":"boolean","title":"All Sites","description":"EFFECTIVE all-sites access (operator / read_only only; false for every other role). When true the user sees every client and site, including future ones, and their coverage groups and parent/site rows are retained but inert. Set via PUT /operator/{id}/site-access.","default":false},"deactivated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deactivated At","description":"When this user was deactivated (POST /operator/{id}/deactivate), or null while active. A deactivated user cannot log in and holds no seats or coverage groups; their records are kept."}},"type":"object","required":["id","first_name","last_name","email","role","created_at","parents"],"title":"CreateOperatorResponse"},"CreateParentRequest":{"properties":{"company_name":{"type":"string","title":"Company Name","description":"Name of the client","examples":["Joe"]},"type":{"$ref":"#/components/schemas/ParentType","description":"Type of the client","examples":["End User","Internal"]},"primary_contact_name":{"type":"string","title":"Primary Contact Name","description":"Name of the primary contact","examples":["Joe Johnson"]},"primary_contact_phone_number":{"type":"string","title":"Primary Contact Phone Number","description":"Phone number of the primary contact","examples":["1013217890"]},"primary_contact_email":{"type":"string","title":"Primary Contact Email","description":"Email of the primary contact","examples":["JJ@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Notes about the client","examples":["Super important notes"]},"operator_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Operator Ids","description":"List of operator IDs to assign to this parent","default":[]}},"type":"object","required":["company_name","type","primary_contact_name","primary_contact_phone_number","primary_contact_email"],"title":"CreateParentRequest"},"CreateParentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique client id","examples":["<UUID of some sort>"]},"company_name":{"type":"string","title":"Company Name","description":"Name of the client","examples":["Joe"]},"type":{"$ref":"#/components/schemas/ParentType","description":"Type of the client","examples":["End User","Internal"]},"primary_contact_name":{"type":"string","title":"Primary Contact Name","description":"Name of the primary contact","examples":["Joe Johnson"]},"primary_contact_phone_number":{"type":"string","title":"Primary Contact Phone Number","description":"Phone number of the primary contact","examples":["1013217890"]},"primary_contact_email":{"type":"string","title":"Primary Contact Email","description":"Email of the primary contact","examples":["JJ@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Notes about the client","examples":["Super important notes"]},"disarm_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Number","description":"The dealer's SMS line, which their customers text with a site passcode to turn cameras off. Null until one is bought.","examples":["+18015550199"]},"disarm_number_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Number State","description":"'purchasing' means a purchase started and its outcome is unconfirmed -- re-POST to reconcile. 'attached' is display only: inbound texts route regardless of campaign attachment. 'releasing' means the line is being given back to Surge: new disarms are refused for this client until it clears, and a re-DELETE reconciles it.","examples":["purchasing","purchased","attached","releasing"]}},"type":"object","required":["id","company_name","type","primary_contact_name","primary_contact_phone_number","primary_contact_email"],"title":"CreateParentResponse"},"CreateSandboxPolicyRunRequest":{"properties":{"monitoring_state":{"type":"string","enum":["monitored","heightened"],"title":"Monitoring State"},"camera_context":{"anyOf":[{"type":"string","maxLength":6000},{"type":"null"}],"title":"Camera Context"},"event_local_time":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Event Local Time"},"site_timezone":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Site Timezone"},"heightened_exceptions":{"items":{"$ref":"#/components/schemas/HeightenedExceptionModel"},"type":"array","maxItems":20,"title":"Heightened Exceptions"},"prompt_profile":{"type":"string","enum":["production","demo"],"title":"Prompt Profile","default":"demo"},"monitoring_profile":{"type":"string","enum":["security","safety"],"title":"Monitoring Profile","default":"security"}},"type":"object","required":["monitoring_state"],"title":"CreateSandboxPolicyRunRequest","description":"One isolated policy simulation against an existing Demo clip."},"CreateShiftBlockRequest":{"properties":{"group_id":{"type":"string","format":"uuid","title":"Group Id","description":"The coverage group being scheduled."},"shift_date":{"type":"string","format":"date","title":"Shift Date","description":"Center-local calendar date the shift STARTS on.","examples":["2026-08-24"]},"start_minute":{"type":"integer","exclusiveMaximum":1440.0,"minimum":0.0,"title":"Start Minute","description":"Minutes after local midnight of shift_date; multiple of 15.","examples":[1080]},"end_minute":{"type":"integer","maximum":2880.0,"exclusiveMinimum":0.0,"title":"End Minute","description":"Minutes after local midnight of shift_date; multiple of 15, greater than start_minute, and at most start_minute + 1440. Values above 1440 are the overnight case.","examples":[1560]},"operator_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Operator Ids","description":"Seated operators in seat order - index 0 is the PRIMARY. May be empty to create an unassigned block."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes about the shift."},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"Optional per-shift display color (hex). Null = inherit the coverage group's color.","examples":["#4DFFD2"]}},"type":"object","required":["group_id","shift_date","start_minute","end_minute"],"title":"CreateShiftBlockRequest","description":"Create one shift block on a coverage group.\n\nOverlapping another shift of the same group is ALLOWED and expected - a\n15-minute handoff is two shifts overlapping by 15 minutes."},"CreateSiteContactRequest":{"properties":{"first_name":{"type":"string","title":"First Name","description":"First name","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"Last name","examples":["Shmoe"]},"role":{"type":"string","title":"Role","description":"Role","examples":["CTO"]},"primary_phone_number":{"type":"string","title":"Primary Phone Number","description":"Primary phone number","examples":["1231231234"]},"afterhours_phone_number":{"type":"string","title":"Afterhours Phone Number","description":"Afterhours phone number","examples":["1231231234"]},"email":{"type":"string","title":"Email","description":"Email of site contact","examples":["JoeShmoe@example.com"]},"notify_method":{"$ref":"#/components/schemas/SiteContactNotifyMethod","description":"Notification method","examples":["email","call"]},"availability":{"anyOf":[{"items":{"$ref":"#/components/schemas/ContactAvailabilityWindow"},"type":"array","maxItems":14},{"type":"null"}],"title":"Availability","description":"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.","examples":[[{"days":[0,1,2,3,4],"end":"17:00","start":"08:00"}]]}},"type":"object","required":["first_name","last_name","role","primary_phone_number","afterhours_phone_number","email","notify_method"],"title":"CreateSiteContactRequest"},"CreateSiteContactResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the site contact."},"first_name":{"type":"string","title":"First Name","description":"First name of the site contact.","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"Last name of the site contact."},"role":{"type":"string","title":"Role","description":"Role of the site contact."},"primary_phone_number":{"type":"string","title":"Primary Phone Number","description":"Primary phone number of the site contact."},"afterhours_phone_number":{"type":"string","title":"Afterhours Phone Number","description":"Afterhours phone number of the site contact."},"email":{"type":"string","title":"Email","description":"Email of the site contact."},"notify_method":{"$ref":"#/components/schemas/SiteContactNotifyMethod","description":"Notification method of the site contact."},"daily_summary_opt_out":{"type":"boolean","title":"Daily Summary Opt Out","description":"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.","default":false,"examples":[false]},"availability":{"anyOf":[{"items":{"$ref":"#/components/schemas/ContactAvailabilityWindow"},"type":"array","maxItems":14},{"type":"null"}],"title":"Availability","description":"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.","examples":[[{"days":[0,1,2,3,4],"end":"17:00","start":"08:00"}]]}},"type":"object","required":["id","first_name","last_name","role","primary_phone_number","afterhours_phone_number","email","notify_method"],"title":"CreateSiteContactResponse"},"CreateSiteIntakeRequest":{"properties":{"submitter":{"$ref":"#/components/schemas/Submitter","description":"Who filled in the form."},"sites":{"items":{"$ref":"#/components/schemas/Site"},"type":"array","maxItems":50,"minItems":1,"title":"Sites","description":"Sites to onboard."},"contacts":{"items":{"$ref":"#/components/schemas/Contact"},"type":"array","maxItems":100,"title":"Contacts","description":"Call list, in order."},"client":{"$ref":"#/components/schemas/Client","description":"Dealer client details, when onboarding on a client's behalf."},"reporting":{"$ref":"#/components/schemas/Reporting","description":"Reports, timing, and anything else."},"imported_from":{"type":"string","maxLength":300,"title":"Imported From","description":"Spreadsheet file name, when the form was filled by import.","default":""},"authorized":{"type":"boolean","const":true,"title":"Authorized","description":"Submitter confirmed they may grant Verdun access."},"contact_reference":{"type":"string","maxLength":200,"title":"Contact Reference","description":"Honeypot; leave empty.","default":""}},"additionalProperties":false,"type":"object","required":["submitter","sites","client","reporting","authorized"],"title":"CreateSiteIntakeRequest"},"CreateSiteIntakeResponse":{"properties":{"status":{"type":"string","const":"ok","title":"Status","description":"Onboarding details accepted.","default":"ok"}},"type":"object","title":"CreateSiteIntakeResponse"},"CreateSiteRequest":{"properties":{"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The ID of the parent site."},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"Optional account (the client's customer) to group this site under. The account must belong to the same client as parent_id. Omit or null to place the site directly under the client."},"name":{"type":"string","title":"Name","description":"The name of the site.","examples":["San Diego Sewing Site"]},"address":{"type":"string","title":"Address","description":"The address of the site.","examples":["1234 cherry hairy lane"]},"city":{"type":"string","title":"City","description":"The city of the site.","examples":["San Diego"]},"state":{"type":"string","title":"State","description":"The state of the site.","examples":["CA"]},"zip_code":{"type":"string","title":"Zip Code","description":"The zip code of the site.","examples":["123456"]},"type":{"$ref":"#/components/schemas/SiteType","description":"The type of site.","examples":["Education"]},"priority_tier":{"$ref":"#/components/schemas/SitePriority","description":"The priority of the site.","examples":["high","medium"]},"monitoring_hours":{"$ref":"#/components/schemas/MonitoringHours","description":"The monitoring hours of the site.","examples":["Nights"]},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone","description":"IANA timezone the site's monitoring schedule is evaluated in. Omit to use the server default.","examples":["America/Los_Angeles"]},"police_nearest_precinct_name":{"type":"string","title":"Police Nearest Precinct Name","description":"The name of the police nearest precinct.","examples":["SD Police"]},"police_non_emergency_line":{"type":"string","title":"Police Non Emergency Line","description":"The police non-emergency line.","examples":["1234555"]},"police_direct_dispatch_line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Direct Dispatch Line","description":"The direct dispatch line.","examples":["123456"]},"police_jurisdiction_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Jurisdiction Notes","description":"The jurisdiction notes.","examples":["They own here"]},"alarm_license_number":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Alarm License Number","description":"The dealer's alarm license number for this site's state or city.","examples":["127.001463"]},"alarm_license_issuer":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Alarm License Issuer","description":"Who issued the alarm license.","examples":["Illinois","City of Milwaukee"]},"mobile_guard_contract":{"type":"boolean","title":"Mobile Guard Contract","description":"The mobile guard contract is available.","examples":[true,false]},"stage":{"anyOf":[{"type":"string","enum":["live","test"]},{"type":"null"}],"title":"Stage","description":"'test' or 'live' (a display label only). Omit or send null for the default, 'test'.","examples":["test","live"]},"guard_company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Company","description":"Mobile guard company name."},"guard_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Unit","description":"Mobile guard unit or contact identifier."},"guard_contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Contact","description":"Mobile guard dispatch phone."},"contacts":{"items":{"$ref":"#/components/schemas/SiteContactsModel"},"type":"array","title":"Contacts","description":"The list of site contacts."}},"type":"object","required":["parent_id","name","address","city","state","zip_code","type","priority_tier","monitoring_hours","police_nearest_precinct_name","police_non_emergency_line","police_jurisdiction_notes","mobile_guard_contract","contacts"],"title":"CreateSiteRequest"},"CreateSiteResponse":{"properties":{"stage":{"anyOf":[{"type":"string","enum":["live","test"]},{"type":"null"}],"title":"Stage","description":"The stored site stage, a display label only. New sites default to 'test'.","examples":["test","live"]},"closed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Closed","description":"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.","examples":[false]},"onboarding_marks":{"additionalProperties":{"type":"string"},"type":"object","title":"Onboarding Marks","description":"Onboarding checklist marks: mark key -> when it was set (ISO-8601 UTC). Who set a mark is never exposed. Empty when none.","examples":[{"first_night":"2026-10-13T08:00:00.000Z"}]},"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the site"},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"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":{"type":"string","title":"Name","description":"Name of the site","examples":["San Diego Sewing Company - 1"]},"address":{"type":"string","title":"Address","description":"Address of the site","examples":["1234 Hairy Cherry Lane"]},"city":{"type":"string","title":"City","description":"City of the site","examples":["Shanghai"]},"state":{"type":"string","title":"State","description":"State of the site","examples":["CA"]},"zip_code":{"type":"string","title":"Zip Code","description":"Zip code of the site","examples":["78912"]},"type":{"$ref":"#/components/schemas/SiteType","description":"Type of the site","examples":["Education"]},"priority_tier":{"$ref":"#/components/schemas/SitePriority","description":"Priority of the site","examples":["high"]},"monitoring_hours":{"$ref":"#/components/schemas/MonitoringHours","description":"Monitoring hours of the site","examples":["Nights"]},"timezone":{"type":"string","title":"Timezone","description":"IANA timezone the site's monitoring schedule is evaluated in","examples":["America/Los_Angeles"]},"police_nearest_precinct_name":{"type":"string","title":"Police Nearest Precinct Name","description":"Police nearest precinct name","examples":["SD Police hehe"]},"police_non_emergency_line":{"type":"string","title":"Police Non Emergency Line","description":"Police non-emergency line","examples":["1231231234"]},"police_direct_dispatch_line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Direct Dispatch Line","description":"Police direct dispatch line","examples":["32133"]},"police_jurisdiction_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Jurisdiction Notes","description":"Police jurisdiction notes","examples":["This is the police"]},"alarm_license_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alarm License Number","description":"Dealer alarm license number","examples":["127.001463"]},"alarm_license_issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alarm License Issuer","description":"Who issued the alarm license","examples":["Illinois"]},"mobile_guard_contract":{"type":"boolean","title":"Mobile Guard Contract","description":"Mobile guard contract available?","examples":[true,false]},"guard_company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Company","description":"Mobile guard company name."},"guard_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Unit","description":"Mobile guard unit or contact identifier."},"guard_contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Contact","description":"Mobile guard dispatch phone."},"agent_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Context","description":"Site-wide scene context composed into the LLM prompt for every camera at this site.","examples":["Gated storage facility; groundskeeper drives a white pickup weekday mornings."]},"agent_thread_window_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Agent Thread Window Minutes","description":"Incident-thread join window for this site's agent cameras, in minutes; null = global default.","examples":[5]},"heightened_exceptions":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Heightened Exceptions","description":"Owner-curated heightened-monitoring exception categories; null = none.","examples":[[{"description":"Navy uniforms with badge","label":"Uniformed security"}]]},"holidays":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Holidays","description":"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.","examples":[[{"rule":"thanksgiving"},{"date":"2026-12-24","label":"Christmas Eve half day"}]]},"map_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Mode","description":"Map-tab mode: 'geo' (geographic pin) or 'floorplan' (uploaded plan); null = not configured.","examples":["geo"]},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude of the site pin (geo mode).","examples":[42.0442]},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude of the site pin (geo mode).","examples":[-87.9816]},"floorplan_width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Floorplan Width","description":"Natural pixel width of the uploaded floor plan.","examples":[1200]},"floorplan_height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Floorplan Height","description":"Natural pixel height of the uploaded floor plan.","examples":[800]},"has_floorplan":{"type":"boolean","title":"Has Floorplan","description":"Whether a floor-plan image is uploaded (fetch it via GET /sites/{id}/floorplan-url).","default":false,"examples":[true,false]},"disarm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Until","description":"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.","examples":["2026-09-05T21:15:00Z"]},"disarm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Set At","description":"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.","examples":["2026-09-05T19:45:00Z"]},"disarm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm By","description":"Who turned the cameras off: 'sms:<E.164>' for a customer text, 'voice:<E.164>' for a customer call, 'operator:<uuid>' from the console.","examples":["sms:+18015550142","voice:+18015550142","operator:6f1c8f2a-..."]},"disarm_active":{"type":"boolean","title":"Disarm Active","description":"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.","default":false,"examples":[true,false]},"arm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Until","description":"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.","examples":["2026-09-18T04:30:00Z"]},"arm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Set At","description":"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.","examples":["2026-09-18T04:00:00Z"]},"arm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arm By","description":"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.","examples":["operator:6f1c8f2a-..."]},"arm_active":{"type":"boolean","title":"Arm Active","description":"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.","default":false,"examples":[true,false]},"disarm_max_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Disarm Max Minutes","description":"The longest a single disarm may last at this site (1-120).","examples":[120]},"live_view_only":{"type":"boolean","title":"Live View Only","description":"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.","default":false,"examples":[false]},"has_disarm_passcode":{"type":"boolean","title":"Has Disarm Passcode","description":"Whether this site has a disarm passcode. Read the code through the dedicated disarm-passcode endpoint.","default":false,"examples":[true,false]},"disarm_passcode_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Passcode Hint","description":"Last two characters of the stored passcode, for rendering it as '****92'.","examples":["92"]},"disarm_passcode_revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Passcode Revision","description":"Opaque revision that changes whenever the passcode is minted or reset, including when its hint is unchanged."},"monitoring_now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Now","description":"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.","examples":["monitored","off_schedule","disarmed","unscheduled","live_view_only"]},"monitoring_next_change_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Monitoring Next Change At","description":"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.","examples":["2026-09-08T04:30:00Z"]},"contacts":{"items":{"$ref":"#/components/schemas/SiteContactResponseModel"},"type":"array","title":"Contacts","description":"List of site contacts"}},"type":"object","required":["id","name","address","city","state","zip_code","type","priority_tier","monitoring_hours","timezone","police_nearest_precinct_name","police_non_emergency_line","mobile_guard_contract","contacts"],"title":"CreateSiteResponse"},"CreateSpeakerRequest":{"properties":{"label":{"type":"string","title":"Label","description":"Display name for this speaker.","examples":["SE Horn — dumpster"]},"brand":{"$ref":"#/components/schemas/SpeakerBrand","description":"Speaker vendor.","default":"axis"},"model":{"anyOf":[{"$ref":"#/components/schemas/SpeakerModel"},{"type":"null"}],"description":"Speaker model, for inventory and clip tuning."},"host":{"type":"string","title":"Host","description":"IP address of the speaker, reachable from api-backend. IP LITERALS ONLY (IPv4 or IPv6) — a hostname is rejected, because a name can only be validated against what DNS answers at that moment while delivery re-resolves later; hostname support arrives with pinned resolution. Stored canonically (lowercased, IPv6 compressed), so one device has one spelling.","examples":["167.20.111.104","2606:4700::1111"]},"port":{"type":"string","title":"Port","description":"Device web port: the HTTPS port for direct_https (typically 443), the HTTP port for direct_http (typically 80).","default":"443","examples":["443"]},"username":{"type":"string","title":"Username","description":"Device account username.","examples":["root"]},"password":{"type":"string","title":"Password","description":"Device account password. Stored in Secrets Manager only, never returned.","examples":["<PASSWORD>"]},"transport":{"$ref":"#/components/schemas/SpeakerTransport","description":"How api-backend reaches the device: direct_https (default, recommended) or direct_http, which is unencrypted and digest-auth only - use it only when the site cannot forward the HTTPS port. nvr_proxy is rejected until that path exists.","default":"direct_https"},"clip_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clip Id","description":"Id of the pre-loaded deterrent clip on the device. Without it the speaker cannot announce.","examples":["1"]},"clip_params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Clip Params","description":"Playback parameters (volume, repeat). Only known VAPIX keys are sent to the device.","examples":[{"repeat":1,"volume":80}]},"enabled":{"type":"boolean","title":"Enabled","description":"Kill switch: a disabled speaker is skipped by delivery and by the health poll.","default":true},"sip_enabled":{"type":"boolean","title":"Sip Enabled","description":"Whether the AI voice agent may place a SIP call to this speaker. Default false: it stays false until the device has been configured for SIP (a peer-to-peer account and STUN), because there is no state we can infer that from. The agent's speaker resolution reads it; the operator's Talk Down and live talk do not.","default":false},"sip_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sip Account Id","description":"Which of the device's SIP accounts to place the call from. Omit for the device default.","examples":["sip_account_1"]},"mic_enabled":{"type":"boolean","title":"Mic Enabled","description":"Whether this unit's microphone is live. Informational: it tells the agent whether to pause and listen for a reply, rather than leaving ten seconds of silence at a deaf horn.","default":false},"map_x":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x (0 = left edge, 1 = right edge).","examples":[0.42]},"map_y":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y (0 = top edge, 1 = bottom edge).","examples":[0.63]},"map_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude of the speaker pin.","examples":[42.0443]},"map_lng":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude of the speaker pin.","examples":[-87.9825]},"map_location_label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose.","examples":["SE corner — by the dumpster"]}},"type":"object","required":["label","host","username","password"],"title":"CreateSpeakerRequest","description":"Register a network speaker at a site.\n\nThe password is write-only: it goes straight to Secrets Manager and is\nnever stored in a column or returned by any endpoint."},"CreateSupportShiftRequest":{"properties":{"shift_date":{"type":"string","format":"date","title":"Shift Date","description":"Center-local calendar date the shift STARTS on.","examples":["2026-10-14"]},"start_minute":{"type":"integer","exclusiveMaximum":1440.0,"minimum":0.0,"title":"Start Minute","description":"Minutes after local midnight of shift_date; multiple of 15.","examples":[540]},"end_minute":{"type":"integer","maximum":2880.0,"exclusiveMinimum":0.0,"title":"End Minute","description":"Minutes after local midnight of shift_date; multiple of 15, greater than start_minute, and at most start_minute + 1440. Values above 1440 are the overnight case.","examples":[1020]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes about the shift."},"operator_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Operator Ids","description":"People on call, in order: index 0 is the PRIMARY, 1 the backup, 2+ further backups. Each must be a support or owner user (409 ineligible_operator otherwise). May be empty: an unassigned block reads as a gap in the rotation."}},"type":"object","required":["shift_date","start_minute","end_minute"],"title":"CreateSupportShiftRequest","description":"Create one block on the support on-call rotation.\n\nSame wall-clock range contract as a shift block (see OperatorShifts'\ndocstring). Overlapping another support shift is allowed - a handoff is two\noverlapping blocks, and the most-recently-started one is on call."},"CreateTrainingRerunRequest":{"properties":{"prompt_profile":{"type":"string","enum":["production","demo"],"title":"Prompt Profile","description":"Which prompt to replay against: 'production' is the live prompt; 'demo' is the SSM-backed testing-ground prompt (iterable without a redeploy) — the selector makes re-runs a prompt-A/B surface"}},"type":"object","required":["prompt_profile"],"title":"CreateTrainingRerunRequest"},"CustomerActivityRequestResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Request identity"},"recipient_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Recipient Id","description":"Intended customer identity"},"contact_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Contact Id","description":"Selected site contact"},"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"Associated incident"},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"Currently authorized property"},"site_name":{"type":"string","title":"Site Name","description":"Property name"},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera identity"},"camera_name":{"type":"string","title":"Camera Name","description":"Camera label"},"recipient_name":{"type":"string","title":"Recipient Name","description":"Customer name at the time of request"},"message":{"type":"string","title":"Message","description":"Customer-visible context supplied by the operator"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the notification was created"},"push_status":{"type":"string","title":"Push Status","description":"Provider acceptance, never proof of delivery"},"push_accepted":{"type":"integer","title":"Push Accepted","description":"Devices whose provider accepted the notification"},"push_attempted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Push Attempted At","description":"When the last provider attempt began. A 'sending' status older than ten minutes may be retried; younger ones are still uncertain"}},"type":"object","required":["id","recipient_id","contact_id","event_id","site_id","site_name","camera_id","camera_name","recipient_name","message","created_at","push_status","push_accepted","push_attempted_at"],"title":"CustomerActivityRequestResponse","description":"Customer-visible request fields, with no staff identity."},"DayHours":{"properties":{"day":{"type":"string","enum":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],"title":"Day","description":"Weekday this row covers; hours belong to the day they start."},"monitored":{"type":"boolean","title":"Monitored","description":"Whether the site is watched on this day."},"all_day":{"type":"boolean","title":"All Day","description":"Watched for the full 24 hours; start/end are then ignored."},"start":{"type":"string","maxLength":10,"title":"Start","description":"Start time, 24-hour HH:MM.","default":""},"end":{"type":"string","maxLength":10,"title":"End","description":"End time, 24-hour HH:MM; earlier than start means overnight.","default":""}},"additionalProperties":false,"type":"object","required":["day","monitored","all_day"],"title":"DayHours"},"DeepAlertThresholdConflictResponse":{"properties":{"code":{"type":"string","title":"Code","description":"Machine code.","examples":["STALE_REVISION"]},"message":{"type":"string","title":"Message","description":"Human-readable reason."},"value":{"type":"number","title":"Value","description":"The current fleet value."},"revision":{"type":"integer","title":"Revision","description":"The current fleet revision."}},"type":"object","required":["code","message","value","revision"],"title":"DeepAlertThresholdConflictResponse"},"DeepAlertThresholdImpactResponse":{"properties":{"revision":{"type":"integer","title":"Revision","description":"The fleet revision the counts were taken against. Send it as expected_revision; a 409 means refresh this resource."},"value":{"type":"number","title":"Value","description":"The fleet default in force at that revision."},"ii_gated_following_fleet":{"type":"integer","title":"Ii Gated Following Fleet","description":"ii_gated cameras with no override: they move with the fleet value."},"ii_gated_with_override":{"items":{"$ref":"#/components/schemas/DeepAlertThresholdOverrideCount"},"type":"array","title":"Ii Gated With Override","description":"ii_gated cameras carrying a valid per-camera override, grouped by value. They do not move."},"ii_gated_invalid_override":{"type":"integer","title":"Ii Gated Invalid Override","description":"ii_gated cameras whose stored override is unusable; they follow the fleet value (and need a deliberate repair in the camera form)."},"legacy_path_not_affected":{"type":"integer","title":"Legacy Path Not Affected","description":"DeepAlert cameras on the legacy / II-off path. The fleet value does not affect them."}},"type":"object","required":["revision","value","ii_gated_following_fleet","ii_gated_with_override","ii_gated_invalid_override","legacy_path_not_affected"],"title":"DeepAlertThresholdImpactResponse"},"DeepAlertThresholdModel":{"properties":{"path":{"type":"string","enum":["ii","ingestion","none"],"title":"Path","description":"'ii' = the camera is ii_gated (incident intelligence owns the DeepAlert gate); 'ingestion' = a DeepAlert camera on the legacy / II-off path, served by the ingestion server's own validator; 'none' = no DeepAlert."},"effective":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Effective","description":"The number api forwards. ii path: the valid override, else the fleet default, so always a number. ingestion path: the valid override, else null (api does not know the ingestion default). none: null.","examples":[25.0]},"source":{"anyOf":[{"type":"string","enum":["camera","fleet_default","invalid_override","ingestion_default"]},{"type":"null"}],"title":"Source","description":"'camera' = a valid per-camera override; 'fleet_default' = no override, the fleet default applies (ii path); 'invalid_override' = the stored value is unusable and the fleet default applies (ii path; saving the camera form needs a deliberate repair); 'ingestion_default' = no valid override on the ingestion path, the ingestion server's own default applies (not reported). Null when path is 'none'."},"fleet_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fleet Revision","description":"The fleet-default revision in force (ii path only), else null."},"override":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Override","description":"The stored per-camera override when it is a VALID number, else null."},"override_valid":{"type":"boolean","title":"Override Valid","description":"True iff a valid per-camera override is stored.","default":false},"invalid_override":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invalid Override","description":"A short repr of a stored but unusable override, so the console can say 'stored value X is invalid'; null otherwise. Never forwarded to the vendor."}},"type":"object","required":["path"],"title":"DeepAlertThresholdModel","description":"The READ-ONLY, server-resolved DeepAlert sensitivity a camera runs at,\nbuilt by the same resolver create-event uses, so the console shows exactly\nwhat api forwards to incident-intelligence (ii). It is a top-level key on a\ncamera read and NEVER part of `analytics_config`, which stays the raw\nstored blob the form round-trips (a console must never serialize this block\nback into analytics_config).\n\nA missing block means UNKNOWN (an older backend), not 'inherits the fleet\ndefault'."},"DeepAlertThresholdOverrideCount":{"properties":{"value":{"type":"number","title":"Value","description":"The per-camera override value.","examples":[30.0]},"cameras":{"type":"integer","title":"Cameras","description":"ii_gated cameras carrying exactly this valid override.","examples":[3]}},"type":"object","required":["value","cameras"],"title":"DeepAlertThresholdOverrideCount"},"DeleteAccountResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the account was retired.","examples":[true]},"deleted_sites":{"type":"integer","title":"Deleted Sites","description":"Number of LIVE sites retired alongside the account. Always 0 unless the request set delete_sites=true.","examples":[0]}},"type":"object","required":["success","deleted_sites"],"title":"DeleteAccountResponse"},"DeleteCameraGroupResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the camera group was retired. Soft delete: its schedule stops contributing to every member camera immediately, while the membership and schedule rows are retained so 'what used to silence this camera' stays answerable.","examples":[true]}},"type":"object","required":["success"],"title":"DeleteCameraGroupResponse"},"DeleteCameraHealthReferenceResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera whose reference image was cleared."}},"type":"object","required":["id"],"title":"DeleteCameraHealthReferenceResponse"},"DeleteCameraResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the camera was successfully deleted","examples":[true,false]}},"type":"object","required":["success"],"title":"DeleteCameraResponse"},"DeleteClientResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the client was successfully deleted.","examples":[true,false]}},"type":"object","required":["success"],"title":"DeleteClientResponse"},"DeleteCoverageGroupResponse":{"properties":{"deleted_group_id":{"type":"string","format":"uuid","title":"Deleted Group Id","description":"The ID of the deleted coverage group."}},"type":"object","required":["deleted_group_id"],"title":"DeleteCoverageGroupResponse"},"DeleteEventResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the event was successfully deleted.","examples":[true,false]}},"type":"object","required":["success"],"title":"DeleteEventResponse"},"DeleteIntegrationResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the connection was removed. Cameras already onboarded from it are untouched."}},"type":"object","required":["success"],"title":"DeleteIntegrationResponse"},"DeleteNvrResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the NVR record was deleted."}},"type":"object","required":["success"],"title":"DeleteNvrResponse"},"DeleteOperatorMissBoxResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the box was soft-deleted."}},"type":"object","required":["success"],"title":"DeleteOperatorMissBoxResponse"},"DeleteOperatorResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the operator was successfully deleted.","examples":[true,false]}},"type":"object","required":["success"],"title":"DeleteOperatorResponse"},"DeleteScheduleResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the schedule was successfully deleted","examples":[true,false]}},"type":"object","required":["success"],"title":"DeleteScheduleResponse"},"DeleteShiftBlockResponse":{"properties":{"deleted_shift_id":{"type":"string","format":"uuid","title":"Deleted Shift Id","description":"The ID of the deleted shift block."}},"type":"object","required":["deleted_shift_id"],"title":"DeleteShiftBlockResponse"},"DeleteSiteContactResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the site was successfully deleted.","examples":[true,false]}},"type":"object","required":["success"],"title":"DeleteSiteContactResponse"},"DeleteSiteResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the site was successfully deleted.","examples":[true,false]}},"type":"object","required":["success"],"title":"DeleteSiteResponse"},"DeleteSpeakerResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"Whether the speaker was retired."}},"type":"object","required":["success"],"title":"DeleteSpeakerResponse"},"DeleteSupportShiftResponse":{"properties":{"deleted_shift_id":{"type":"string","format":"uuid","title":"Deleted Shift Id","description":"The ID of the deleted support shift."}},"type":"object","required":["deleted_shift_id"],"title":"DeleteSupportShiftResponse"},"DeliveryLatencyBand":{"properties":{"p50_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 S","description":"Median of the daily motion-to-queue p50s. Null when no complete day had eligible alarms.","examples":[17.9]},"p90_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 S","description":"Median of the daily motion-to-queue p90s. Null when no complete day had eligible alarms.","examples":[21.3]},"days":{"type":"integer","title":"Days","description":"How many of the seven days contributed a motion-to-queue value. Fewer than 7 means a partial baseline (a new deploy, or whole days on which the fleet delivered nothing) — say so rather than drawing a confident envelope.","examples":[7]}},"type":"object","required":["days"],"title":"DeliveryLatencyBand","description":"The rolling 7-day daily baseline the current window is read against,\nshaped like `SystemHealthDailyBand`.\n\nEACH NUMBER IS A MEDIAN OF DAILY VALUES OF THE SAME STATISTIC, so it is\ncomparable to the fleet number it sits under: `p50_s` is the median of the\nseven daily p50s, `p90_s` the median of the seven daily p90s. It is NOT a\np50/p90 over one daily series (round-0 OQ-3, ruling (b)). Built from\nCOMPLETE UTC days only: the band runs from midnight seven days ago to\nTODAY'S UTC MIDNIGHT, so it is exactly the seven preceding whole days and\ntoday's alarms — the thing being read against it — are never in it. `days`\nis how much history the band actually had."},"DeliveryLatencyCamera":{"properties":{"camera_id":{"type":"string","title":"Camera Id","description":"Camera id.","examples":["2b1d3f2e-6e78-4d9b-93d6-3c1f9e40a1d4"]},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Operator-facing camera name.","examples":["23 - NE Lot"]},"site_id":{"type":"string","title":"Site Id","description":"Id of the site the camera belongs to. The stable key for filtering or grouping this table by site; `site_name` is the label for the same entity and is not unique across parents.","examples":["9f2c0f31-5d0a-4a6e-8b41-2f7a1d6c0e55"]},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site the camera belongs to.","examples":["Imperial SE"]},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"Id of the account (the dealer's customer) the site sits under. NULL WHEN THE SITE SITS DIRECTLY UNDER ITS PARENT — accounts are an optional level, so null means 'no account', never 'unknown'.","examples":["c41b7a90-3e2d-4f18-9a55-70b6d2c3f8ab"]},"account_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Name","description":"Name of that account. Null exactly when `account_id` is null — the two are never independently null, so an account filter may key on either.","examples":["Northwind Retail"]},"parent_id":{"type":"string","title":"Parent Id","description":"Id of the dealer/parent the camera's site belongs to. The stable key for filtering or grouping this table by client; `parent_name` is the label for the same entity.","examples":["3a7e5c12-8b44-4d21-9e0f-6c5b1a2d3e47"]},"parent_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Name","description":"Dealer/parent company the site belongs to.","examples":["Pioneer Security"]},"events":{"type":"integer","title":"Events","description":"Eligible alarms for this camera in the window. Never below the min-events floor the payload publishes in `min_camera_events`.","examples":[184]},"p50_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 S","description":"Median motion-to-queue, in seconds.","examples":[24.1]},"p90_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 S","description":"90th percentile motion-to-queue, in seconds. The sort key for this table.","examples":[58.9]},"clip_p50_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Clip P50 S","description":"Median clip-acquisition leg, in seconds.","examples":[22.6]},"queue_p50_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Queue P50 S","description":"Median AI/queue-wait leg, in seconds.","examples":[1.1]},"motion_clock":{"type":"string","enum":["ingestor","camera"],"title":"Motion Clock","description":"Whose clock stamped this camera's motion times. 'camera' means at least one eligible alarm in the window uses the camera's clock (legacy rtsp_motion); 'ingestor' means the ingestion host stamped them (server_motion or ONVIF rtsp_motion_receipt). Mixed windows remain 'camera' until the eligible legacy rows age out. Derived per event from events_metadata.source, falling back to the camera's current ingest mode for missing or unrecognized sources.","examples":["camera"]},"over_60s":{"type":"integer","title":"Over 60S","description":"This camera's eligible alarms whose motion-to-queue total exceeded 60 seconds.","examples":[21]},"clock_suspect_events":{"type":"integer","title":"Clock Suspect Events","description":"This camera's alarms excluded as clock-suspect. Not included in `events`.","examples":[3]},"hourly_p50_s":{"items":{"anyOf":[{"type":"number"},{"type":"null"}]},"type":"array","title":"Hourly P50 S","description":"This camera's hourly median motion-to-queue across the window, oldest first — exactly `window_hours` entries, for a sparkline. ELEMENTS ARE NULLABLE: an hour with no alarm is null, not 0, and the sparkline must draw a gap rather than a dip to the floor."}},"type":"object","required":["camera_id","site_id","parent_id","events","motion_clock","over_60s","clock_suspect_events"],"title":"DeliveryLatencyCamera","description":"One camera's delivery latency, for the per-camera table."},"DeliveryLatencyFleet":{"properties":{"events":{"type":"integer","title":"Events","description":"Eligible alarms in the window — the population every percentile here is over. Excludes sentinels, clock-suspect rows, rows with no reported motion time, rows not yet visible in the queue, and demo uploads.","examples":[4812]},"cameras":{"type":"integer","title":"Cameras","description":"Distinct cameras that contributed at least one eligible alarm.","examples":[61]},"motion_to_queue":{"$ref":"#/components/schemas/DeliveryLatencyPercentiles","description":"The whole path: motion (occurred_at) to the alarm becoming visible to an operator (queued_at). This is the headline number."},"motion_to_clip":{"$ref":"#/components/schemas/DeliveryLatencyPercentiles","description":"Leg 1 — clip acquisition: motion to the clip landing in the database (created_at - occurred_at)."},"clip_to_queue":{"$ref":"#/components/schemas/DeliveryLatencyPercentiles","description":"Leg 2 — AI/queue wait: the clip landing to the alarm becoming visible (queued_at - created_at). Near zero on early-broadcast alarms by construction."},"band_7d":{"$ref":"#/components/schemas/DeliveryLatencyBand","description":"The daily baseline for motion_to_queue over the seven preceding COMPLETE UTC days (ending at today's UTC midnight), always the same seven days regardless of the selected window. Today's alarms are read against it and are not in it."},"over_60s":{"type":"integer","title":"Over 60S","description":"Eligible alarms whose MOTION-TO-QUEUE total exceeded 60 seconds (the whole path, not a single leg).","examples":[38]},"failsafe_sentinels":{"type":"integer","title":"Failsafe Sentinels","description":"Queue-visible alarms in the window that NO AI verdict ever arrived for, surfaced by the 5-minute fail-safe sweep. Excluded from every percentile — they record that the pipeline did not answer, not how fast it did — so a persistent nonzero value is its own signal.","examples":[4]},"early_broadcast_share_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Early Broadcast Share Pct","description":"Share of the eligible alarms that reached the operator BEFORE their AI verdict. Their queue leg is ~0 s by construction, so read the fleet median as a blend of the two populations. Null when the window is empty (an empty window is not 0%).","examples":[71.3]},"clock_suspect_cameras":{"type":"integer","title":"Clock Suspect Cameras","description":"Cameras with at least one clock-suspect alarm in the window. A bad clock and a camera-sourced clock are different claims — see `motion_clock` on the camera rows for the second one.","examples":[2]},"clock_suspect_events":{"type":"integer","title":"Clock Suspect Events","description":"Alarms excluded because their clip leg was under -5 s or over 6 h, i.e. the reported motion time cannot be reconciled with the clip's arrival. Published so the excluded population can be sized rather than left as an unquantified caveat.","default":0,"examples":[57]},"missing_occurred_at":{"type":"integer","title":"Missing Occurred At","description":"Queue-visible alarms excluded because they reported no motion time at all. Read beside `events`: a suspiciously good p50 over a small `events` and a large value here describes a fraction of the fleet's alarms.","default":0,"examples":[11]}},"type":"object","required":["events","cameras","motion_to_queue","motion_to_clip","clip_to_queue","band_7d","over_60s","failsafe_sentinels","clock_suspect_cameras"],"title":"DeliveryLatencyFleet","description":"The whole fleet over the selected window."},"DeliveryLatencyHour":{"properties":{"hour":{"type":"string","format":"date-time","title":"Hour","description":"Bucket START, in UTC. Buckets are on `created_at` (the single RDS clock and the same column the window is anchored on), not on the reported motion time."},"events":{"type":"integer","title":"Events","description":"Eligible alarms in this hour. 0 for a gap.","examples":[210]},"p50_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 S","description":"Median motion-to-queue in this hour, in seconds. Null for an empty hour — not 0.","examples":[17.8]},"p90_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 S","description":"90th percentile motion-to-queue in this hour, in seconds. Null for an empty hour — not 0.","examples":[29.9]}},"type":"object","required":["hour","events"],"title":"DeliveryLatencyHour","description":"One hourly bucket. The series is DENSE — every hour in the window is\npresent in order, oldest first, so `hourly` has exactly `window_hours`\nentries and a client can index by position without rebuilding a grid. An\nhour with no eligible alarms is present with `events: 0` and null\npercentiles."},"DeliveryLatencyPercentiles":{"properties":{"p50_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50 S","description":"Median, in seconds. Null when the bucket had no eligible alarms.","examples":[18.4]},"p90_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90 S","description":"90th percentile, in seconds. Null when the bucket had no eligible alarms.","examples":[31.7]}},"type":"object","title":"DeliveryLatencyPercentiles","description":"A p50/p90 pair over one leg of the delivery path, in seconds.\n\nBOTH ARE NULLABLE AND NULL IS NOT ZERO. An empty bucket is not \"0 s of\nlatency\" — it is an hour in which no alarm was delivered, which on a\nschedule-gated fleet is the normal state of most cameras most of the time.\nRender null as \"—\", never as 0s: a 0 would draw a reassuring dip exactly\nwhere there is no data."},"DetectionModel":{"properties":{"provider":{"type":"string","title":"Provider","description":"Detection provider"},"provider_version":{"type":"string","title":"Provider Version","description":"Detection provider version"},"bbox":{"$ref":"#/components/schemas/BboxModel","description":"Bounding box"},"class_label":{"type":"string","title":"Class Label","description":"Detected class label"},"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence","description":"Detection confidence score; null = the producer made no calibrated claim about this box (ii InterpretationResultV1). Required-nullable: an omitted field is still a validation error."},"frame_index":{"type":"integer","title":"Frame Index","description":"Frame index"},"timestamp_ms":{"type":"integer","title":"Timestamp Ms","description":"Timestamp in milliseconds"},"track_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Track Id","description":"Track ID for object tracking"}},"type":"object","required":["provider","provider_version","bbox","class_label","confidence","frame_index","timestamp_ms"],"title":"DetectionModel","description":"One detected box from ii's `InterpretationResultV1.detections`.\n\n`confidence` is REQUIRED-NULLABLE (`...` with a `| None` type), never\noptional-with-default — see verdun\n`docs/decisions/2026-09-12-vlm-confidence-null.md` and the round-0 design\nin `verdun/scratch/handoff/vlm-confidence-honesty/`. `null` means the\nproducer asserts the box and makes NO calibrated confidence claim about\nit; it is not zero, not low confidence, and not \"the field was\nforgotten\". Keeping the field REQUIRED is what preserves that last\ndistinction: an OMITTED `confidence` still 422s, so a producer that drops\nthe key cannot have its silence read as a deliberate abstention.\n\n`NormalizedFloat | None` rather than `float | None` (round-0 ruling R8):\na present value keeps ii's `[0, 1]`/NaN/inf strictness\n(`strict_numeric.py`), exactly as `GateOutcomeModel.detector_confidence`\nalready does for the nullable gate scalar."},"DetectionStageModel":{"properties":{"stage":{"type":"string","minLength":1,"title":"Stage","description":"'object_detection' | 'fire_screen' | 'interpretation'"},"provider":{"type":"string","minLength":1,"title":"Provider","description":"Stage provider"},"model":{"type":"string","minLength":1,"title":"Model","description":"Stage model identifier"},"ran":{"type":"boolean","title":"Ran","description":"Whether this stage actually executed. Strict bool — a string/int is rejected, not coerced."},"outcome":{"type":"string","title":"Outcome","description":"Stage outcome, e.g. 'alerted', 'not_alerted', 'error', 'timeout'"},"latency_ms":{"type":"integer","minimum":0.0,"title":"Latency Ms","description":"Stage latency in milliseconds. Strict, non-negative int — no float/string coercion."},"cost_usd":{"anyOf":[{"type":"number","minimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Cost Usd","description":"Stage cost in USD. Non-negative Decimal, parsed only from a Decimal or a decimal string — never a float."}},"type":"object","required":["stage","provider","model","ran","outcome","latency_ms","cost_usd"],"title":"DetectionStageModel","description":"DeepAlert Phase B (round-0b design §6): one stage of the\nobject-of-interest gate/cascade that ran (or was attempted) while\nproducing this result — the itemized breakdown behind the top-level\nllm_*/detect_ms/cost_usd aggregates on CreateAIEvalRequest. Exists\nbecause a terminal (object_gate) row can still have spent money (the\nfire-screen cascade step is a paid call even when it returns no\ninterpretation).\n\nstage is plain str, NOT a closed Literal/enum — same forward-\ncompatibility reasoning as GateOutcomeModel.reason (round-0 design\n§1.2). Known values as of this writing: object_detection, fire_screen,\ninterpretation. Every OTHER field keeps ii's original strictness\n(Codex stage-4b review, finding 2, CONFIRMED): ran/latency_ms/cost_usd\nreject coercion the same way ii's contracts/common_v1.py does."},"DisarmInput":{"properties":{"disarm_id":{"type":"string","format":"uuid","title":"Disarm Id","description":"Client-minted idempotency identity; becomes the site_disarms row id"},"duration_minutes":{"type":"integer","enum":[15,30,60],"title":"Duration Minutes","description":"Requested duration; clamped to the site's disarm_max_minutes cap"}},"type":"object","required":["disarm_id","duration_minutes"],"title":"DisarmInput"},"DisarmNumberResponse":{"properties":{"disarm_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Number","description":"The dealer's Surge line in E.164, which their customers text with a site passcode. Null while a purchase is unconfirmed.","examples":["+18015550199"]},"disarm_number_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Number State","description":"'purchasing' means a purchase was started and its outcome is not yet confirmed — re-POST to reconcile against Surge rather than buying a second number. 'attached' is display only: inbound texts route regardless of campaign attachment. 'releasing' means a release is in flight or unconfirmed: this client's sites cannot be disarmed until it clears, and re-DELETE reconciles it.","examples":["purchasing","purchased","attached","releasing"]},"surge_phone_number_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Surge Phone Number Id","description":"Surge's own id for the number, used by the attach webhook and release.","examples":["pn_01H…"]},"adopted":{"type":"boolean","title":"Adopted","description":"True when this call RECONCILED an earlier ambiguous purchase by adopting a number Surge already had, rather than buying a new one.","default":false,"examples":[true,false]}},"type":"object","title":"DisarmNumberResponse","description":"The dealer's one SMS line and where it is in its lifecycle."},"DisarmPasscodeResponse":{"properties":{"passcode":{"type":"string","title":"Passcode","description":"The site's new passcode: six random digits, or the chosen code as typed. Readable again later through GET /sites/{id}/disarm-passcode. The example is redacted on purpose.","examples":["******"]},"has_disarm_passcode":{"type":"boolean","title":"Has Disarm Passcode","description":"Always true on this response; mirrors the site payload's field.","examples":[true]},"disarm_passcode_hint":{"type":"string","title":"Disarm Passcode Hint","description":"Last two characters, for rendering the stored code as '••••92'.","examples":["92"]},"disarm_max_minutes":{"type":"integer","title":"Disarm Max Minutes","description":"The site's current cap, echoed so the instructions panel can name it.","examples":[120]}},"type":"object","required":["passcode","has_disarm_passcode","disarm_passcode_hint","disarm_max_minutes"],"title":"DisarmPasscodeResponse","description":"POST /sites/{id}/disarm-passcode: the newly minted code.\n\nSince 0125 the code is also kept sealed and can be read back by any\nin-scope viewer through GET (`SiteDisarmPasscodeResponse`), so this is no\nlonger the only sight of it. It is still never in a site payload, a log\nline or the activity journal."},"DisarmResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"Scoped property"},"active":{"type":"boolean","title":"Active","description":"Whether the site is disarmed right now, from ANY channel (app, SMS, console) — the shared window predicate, resolved server-side"},"disarm_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Disarm Id","description":"The live site_disarms row while active; null when armed"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"'app', 'sms' or 'console' — who opened the live window"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"Normal monitoring policy resumes at this instant; null when armed"},"server_time":{"type":"string","format":"date-time","title":"Server Time","description":"Server clock for an accurate countdown"},"early_disarmed_cameras":{"type":"integer","title":"Early Disarmed Cameras","description":"Cameras an operator turned off ahead of their schedule (early disarm). When the site itself is not disarmed but this is above zero, `active` is true with a null disarm_id, and a rearm with a null disarm_id turns them back on","default":0}},"type":"object","required":["site_id","active","disarm_id","source","expires_at","server_time"],"title":"DisarmResponse"},"DisarmSettingsResponse":{"properties":{"disarm_max_minutes":{"type":"integer","title":"Disarm Max Minutes","description":"The longest a single disarm may last at this site, after the update.","examples":[120]}},"type":"object","required":["disarm_max_minutes"],"title":"DisarmSettingsResponse"},"DisarmSiteRequest":{"properties":{"minutes":{"type":"integer","maximum":120.0,"minimum":1.0,"title":"Minutes","description":"How long the site's cameras should stop alerting, in minutes. Clamped down to the site's disarm_max_minutes; the response says whether it was.","examples":[15,30,60,120]}},"type":"object","required":["minutes"],"title":"DisarmSiteRequest"},"DisarmStateResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site whose cameras this concerns."},"disarm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Until","description":"When the cameras come back on by themselves. An early re-arm sets this to the moment it ended rather than nulling it, so the window that really happened stays readable historically — which means a non-null value does NOT mean 'disarmed'. Read `disarm_active` for that. Expiry is implicit; nothing sweeps this column."},"disarm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Set At","description":"When the current disarm window began. The resolver's predicate is the window between this and disarm_until, so a historical read is not repainted by a disarm set later."},"disarm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm By","description":"Who turned the cameras off: 'operator:<uuid>' (console), 'sms:<E.164>' for a customer text, 'voice:<E.164>' for a customer call, or 'app:<uuid>' (the customer's mobile app). Retained after an early re-arm, alongside the closed window.","examples":["operator:6f1c…","sms:+18015550142","voice:+18015550142","app:9b2e…"]},"disarm_active":{"type":"boolean","title":"Disarm Active","description":"Whether the cameras are off RIGHT NOW — the window predicate, resolved server-side. Always false on a re-arm response. Never derive this from disarm_until.","default":false,"examples":[true,false]},"minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minutes","description":"Minutes actually applied, after clamping; null on a re-arm.","examples":[90]},"clamped":{"type":"boolean","title":"Clamped","description":"True when the request asked for longer than the site's cap.","default":false,"examples":[true,false]},"monitoring_now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Now","description":"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). On a re-arm response this is what the SCHEDULE leaves behind, which is the whole point: re-arming an off-schedule site does not turn its cameras on.","examples":["monitored","off_schedule","disarmed","unscheduled","live_view_only"]},"schedule_monitoring_now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schedule Monitoring Now","description":"The current schedule-only monitoring state, ignoring Live View only flags and the disarm window. Resolved at the same instant as monitoring_now. A successful disarm reports monitoring_now='disarmed'; this separate field lets the console explain when cameras were already off by schedule.","examples":["monitored","off_schedule","unscheduled"]},"monitoring_next_change_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Monitoring Next Change At","description":"The next instant this site's monitored/off state flips BY SCHEDULE ALONE - the disarm window is deliberately ignored, because you already have disarm_until above. Null when nothing changes within 7 days and for an 'unscheduled' site. Aligned to a 15-minute boundary in the site's own timezone.","examples":["2026-09-08T04:30:00Z"]}},"type":"object","required":["site_id"],"title":"DisarmStateResponse","description":"The site's live disarm state after a console disarm or re-arm.\n\nThe same three fields the site serializers carry, so the console can apply\nthe response to its cached site without a refetch, plus what the request\nactually resolved to."},"DisarmTrustedNumberModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique id of the trusted number"},"phone_number":{"type":"string","title":"Phone Number","description":"Canonical E.164 number","examples":["+18015550142"]},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Who this is","examples":["Jerry - GM"]},"site_contact_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Contact Id","description":"The site contact this was picked from, if any. Provenance only; null once that contact is deleted."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When it was added"},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name","description":"Who added it; null once that operator is deleted.","examples":["Sonny Tai"]}},"type":"object","required":["id","phone_number"],"title":"DisarmTrustedNumberModel","description":"One trusted number. Customer personal data: served only by the\ndedicated routes, never inside a site payload or the site tree."},"DisarmTrustedResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Whether a caller-ID match at this site stands in for the passcode. Default false."},"enabled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Enabled At","description":"When it was switched on; null while off."},"enabled_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enabled By Name","description":"Who switched it on; null while off or once that operator is deleted.","examples":["Dana Reyes"]},"can_manage":{"type":"boolean","title":"Can Manage","description":"Whether THIS caller may switch it and edit the list (owner, monitoring manager, support, Parent Manager or account manager). Everyone else who can see the site sees the state read-only."},"numbers":{"items":{"$ref":"#/components/schemas/DisarmTrustedNumberModel"},"type":"array","title":"Numbers","description":"The trusted list, oldest first. Kept while the toggle is off."}},"type":"object","required":["enabled","can_manage","numbers"],"title":"DisarmTrustedResponse","description":"GET/PUT /sites/{id}/disarm-trusted: a site's trusted-number state."},"DisarmVoiceStatusResponse":{"properties":{"status":{"type":"string","title":"Status","description":"'connected' means the voice platform routes this line's inbound calls to the disarm agent and asks this API who is calling. It does not prove the carrier delivers calls there - only a real call does. 'not_connected' means calls will not be answered (texting is unaffected); `reasons` says why. 'unknown' means the voice platform could not be read or voice is not configured here - never treat it as either answer. 'no_line' means the client has no disarm line.","examples":["connected","not_connected","unknown","no_line"]},"reasons":{"items":{"type":"string"},"type":"array","title":"Reasons","description":"not_connected: not_on_voice_platform, no_agent, wrong_agent, no_webhook, wrong_webhook. unknown: voice_not_configured, voice_platform_unreachable.","examples":[["no_agent","no_webhook"]]},"checked_at":{"type":"string","format":"date-time","title":"Checked At","description":"When the voice platform was read."}},"type":"object","required":["status","checked_at"],"title":"DisarmVoiceStatusResponse","description":"Whether calls to the dealer's disarm line reach the voice-disarm agent."},"DiscoverSpeakersRequest":{"properties":{"host":{"type":"string","maxLength":64,"minLength":1,"title":"Host","description":"Public IP address to probe (IPv4 or IPv6 literal; hostnames are refused, as on speaker create). Validated by the same SSRF guard as a speaker record.","examples":["72.234.89.250"]},"ports":{"anyOf":[{"items":{"type":"integer","maximum":65535.0,"minimum":1.0,"description":"TCP port to probe."},"type":"array","maxItems":16},{"type":"null"}],"title":"Ports","description":"Extra ports to try on top of the common web ports (80, 443, 8080, 8081, 8443, 8000, 8888) and the speaker's own port. At most 16.","examples":[[8384]]},"speaker_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Speaker Id","description":"A live speaker on THIS site. Its port is added to the probe, and its stored credentials are used unless a password is supplied here."},"username":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Username","description":"Device account to try. Defaults to the speaker's stored username when speaker_id is given. Only used together with a password, or with the speaker's stored credentials.","examples":["vuser"]},"password":{"anyOf":[{"type":"string","format":"password","writeOnly":true},{"type":"null"}],"title":"Password","description":"Device password to try for this probe only. Never stored, logged or returned.","examples":["<PASSWORD>"]}},"type":"object","required":["host"],"title":"DiscoverSpeakersRequest","description":"Probe one public IP for Axis speakers. READ-ONLY: nothing is written.\n\nWHY (2026-10-03): two horns sat behind one IP on two forwarded ports, the\nrecord pointed at the wrong one, and nobody could tell from the console.\nThis asks every candidate port which device answers (serial, model,\nfirmware - no credentials needed for that) and, where credentials are\navailable, whether they work there and which clip ids the unit holds.\n\nThe password is write-only and used for this one probe: it is never\nstored, logged or returned."},"DiscoveredClip":{"properties":{"id":{"type":"string","title":"Id","description":"Clip id to put in the speaker's clip_id.","examples":["43"]},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"The clip's name on the device.","examples":["Trespassing"]}},"type":"object","required":["id"],"title":"DiscoveredClip","description":"One clip the device holds. `id` is what a speaker's `clip_id` takes\n(playclip.cgi?clip=<id> plays MediaClip M<id>)."},"DismissScreen":{"properties":{"precision":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Precision","description":"Of AI dismiss-class recommendations, % the human also dismissed.","examples":[97.5]},"recall":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Recall","description":"Of human dismissals, % the AI also recommended dismissing.","examples":[84.0]},"false_dismissals":{"type":"integer","title":"False Dismissals","description":"AI recommended dismiss-class but the human ESCALATED — the dangerous cell.","examples":[2]}},"type":"object","required":["false_dismissals"],"title":"DismissScreen","description":"Binary safety view: dismiss-class (false_alarm + non_threatening) vs\nescalate-class (voice_down + dispatch)."},"DispatchCallRequest":{"properties":{"line":{"type":"string","enum":["police_non_emergency","police_direct_dispatch"],"title":"Line","description":"Which of the site's dispatch lines to bridge the operator to.","examples":["police_non_emergency"]},"channel":{"type":"string","enum":["phone_bridge","browser_call"],"title":"Channel","description":"How the operator's leg is placed: a PSTN callback bridge or the browser softphone.","default":"phone_bridge","examples":["browser_call"]},"operator_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Number","description":"The operator's callback number; the bridge rings this leg first, then dials the dispatch target when answered. phone_bridge only — browser_call has no callback leg.","examples":["(847) 555-0142"]},"idempotency_key":{"anyOf":[{"type":"string","maxLength":64,"minLength":8},{"type":"null"}],"title":"Idempotency Key","description":"Client-generated key for this dispatch intent. Retries and double-submits with the same key return the original attempt instead of placing a second call.","examples":["9c9be1a2-4a6e-4a71-b8a0-2f6d1f6f2f5e"]}},"type":"object","required":["line"],"title":"DispatchCallRequest"},"DispatchTranscriptResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Call row id."},"transcript":{"anyOf":[{"items":{"$ref":"#/components/schemas/TranscriptSegment"},"type":"array"},{"type":"null"}],"title":"Transcript","description":"null until transcribed."},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"Internal call summary; null until analyzed."},"ai_outcome":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Outcome","description":"AI-suggested police outcome, if any."},"ai_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Reference","description":"AI-suggested reference number, if any."}},"type":"object","required":["id"],"title":"DispatchTranscriptResponse"},"DisputeCallAuditRequest":{"properties":{"note":{"type":"string","title":"Note","description":"Why the overturn is wrong. Must not be blank; up to 2000 characters after stripping."}},"additionalProperties":false,"type":"object","required":["note"],"title":"DisputeCallAuditRequest"},"EarlyDisarmCameraModel":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The camera row id."},"camera_label":{"type":"string","title":"Camera Label","description":"The camera's display name."},"schedule_state":{"type":"string","enum":["monitored","heightened","off"],"title":"Schedule State","description":"What the camera's SCHEDULE says right now (camera override, groups or site week, holidays applied), ignoring any disarm or test arm."},"scheduled_off_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled Off At","description":"The next instant its schedule turns it off, within horizon_minutes. Null when the schedule already has it off, or nothing turns it off soon (a 24/7 camera, or a holiday)."},"opens_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Opens At","description":"scheduled_off_at minus 60 minutes: when early disarm becomes available."},"eligible":{"type":"boolean","title":"Eligible","description":"Whether it can be disarmed early right now."},"ineligible_reason":{"anyOf":[{"type":"string","enum":["live_view_only","early_disarmed","site_disarmed","site_armed","already_off","no_scheduled_disarm","too_early"]},{"type":"null"}],"title":"Ineligible Reason","description":"Why not, when eligible is false. 'site_armed': a live test arm would turn the camera back on when the window ended, so it is refused. 'live_view_only': the camera (or its site) is Live View only and raises no alarms, so there is nothing to disarm."},"early_disarm":{"anyOf":[{"$ref":"#/components/schemas/EarlyDisarmWindowModel"},{"type":"null"}],"description":"The live early disarm, when there is one."}},"type":"object","required":["camera_id","camera_label","schedule_state","eligible"],"title":"EarlyDisarmCameraModel"},"EarlyDisarmRequest":{"properties":{"camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":500,"minItems":1,"title":"Camera Ids","description":"Cameras at this site to turn off now. EVERY one must be within 60 minutes of its own scheduled disarm (camera override, camera group or site week, holidays applied); the request is all-or-nothing and 409s naming the first camera that is not."},"reason":{"type":"string","enum":["staff_on_site","customer_request","vendor_delivery","other"],"title":"Reason","description":"Why the operator is disarming early. 'other' requires a note.","examples":["staff_on_site"]},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Note","description":"Free text kept on the camera and in the Activity Log.","examples":["Service manager Dave on site, confirmed by phone"]}},"type":"object","required":["camera_ids","reason"],"title":"EarlyDisarmRequest"},"EarlyDisarmWindowModel":{"properties":{"set_at":{"type":"string","format":"date-time","title":"Set At","description":"When the operator turned the camera off."},"until":{"type":"string","format":"date-time","title":"Until","description":"Its scheduled disarm: the schedule takes over here with no gap."},"by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"By Name","description":"The operator who did it (snapshot). Null for customer-side logins."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"One of the four request reasons.","examples":["staff_on_site"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"The operator's note, if any. Null for customer-side logins."}},"type":"object","required":["set_at","until"],"title":"EarlyDisarmWindowModel","description":"A camera's live early disarm."},"EarlyRearmRequest":{"properties":{"camera_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":500},{"type":"null"}],"title":"Camera Ids","description":"Cameras to turn back on. Omit (or null) to re-arm every camera at the site that is currently disarmed early. Idempotent: a camera that is not disarmed early is left as it is."}},"type":"object","title":"EarlyRearmRequest"},"EditAccountRequest":{"properties":{"name":{"type":"string","title":"Name","description":"The name of the account (the client's customer).","examples":["Northgate Logistics"]},"primary_contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Name","description":"Primary contact's name at the account.","examples":["Dana Reyes"]},"primary_contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Phone","description":"Primary contact's phone number at the account.","examples":["8015551234"]},"primary_contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Email","description":"Primary contact's email address at the account.","examples":["dana@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes about the account.","examples":["Four dealership lots; night monitoring only."]}},"type":"object","required":["name"],"title":"EditAccountRequest"},"EditAccountResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the account."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client (parent) this account belongs to."},"name":{"type":"string","title":"Name","description":"Name of the account (the client's customer).","examples":["Northgate Logistics"]},"primary_contact_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Name","description":"Primary contact's name at the account.","examples":["Dana Reyes"]},"primary_contact_phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Phone","description":"Primary contact's phone number at the account.","examples":["8015551234"]},"primary_contact_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Email","description":"Primary contact's email address at the account.","examples":["dana@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes about the account.","examples":["Four dealership lots; night monitoring only."]}},"type":"object","required":["id","parent_id","name"],"title":"EditAccountResponse"},"EditCameraGroupRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":80,"minLength":1},{"type":"null"}],"title":"Name","description":"New display name. Must stay unique (case-insensitively) among the site's live camera groups; renaming a group to its own current name is a no-op, not a conflict.","examples":["Tenant A"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"New description. Send null explicitly to clear it; omit to leave it unchanged.","examples":["Suite 200 - enters past cameras 1-3"]}},"type":"object","title":"EditCameraGroupRequest","description":"Rename a camera group or change its description.\n\nPATCH semantics, and they are read off `model_fields_set` rather than off\nNone: omitting a field leaves it alone, while sending `description: null`\ngenuinely clears it. Membership is not editable here — that is the\nreplace-set PUT on /members, so a rename can never silently rewrite who is\nin the group."},"EditCameraRequest":{"properties":{"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"Parent ID","examples":["<UUID of some sort"]},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"Site ID","examples":["<UUID of some sort"]},"camera_label":{"type":"string","title":"Camera Label","description":"The label of the camera.","examples":["SD Sewing Company"]},"camera_id":{"type":"string","title":"Camera Id","description":"The camera ID.","examples":["cam01"]},"kvs_stream_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kvs Stream Name","description":"Read-only after creation. Omit, send an empty string, or send the stored value unchanged; any other value is rejected (409).","examples":["dev-home-cam01"]},"ip_address":{"type":"string","title":"Ip Address","description":"The IP address of the camera.","examples":["192.0.0.1"]},"port":{"type":"string","title":"Port","description":"The port of the camera.","examples":["554"]},"username":{"type":"string","title":"Username","description":"The username of the camera.","examples":["admin"]},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password","description":"New camera password. WRITE-ONLY — no endpoint ever returns it. Omitted, null, and empty string ALL leave the stored password unchanged; send a non-empty value only to change it.","examples":["<PASSWORD>"]},"integration":{"$ref":"#/components/schemas/CameraIntegration","description":"The integration of the camera.","examples":["Axis"]},"adapter":{"anyOf":[{"$ref":"#/components/schemas/CameraAdapter"},{"type":"null"}],"description":"The adapter of the camera."},"brand":{"anyOf":[{"$ref":"#/components/schemas/CameraBrand"},{"type":"null"}],"description":"The brand of the camera."},"model":{"anyOf":[{"$ref":"#/components/schemas/CameraModel"},{"type":"null"}],"description":"The model of the camera."},"metadata_format":{"anyOf":[{"$ref":"#/components/schemas/CameraMetadataFormat"},{"type":"null"}],"description":"The metadata format of the camera."},"rtsp_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rtsp Url","description":"Full RTSP URL including credentials. WRITE-ONLY: every endpoint returns it with the password replaced by 'REDACTED'. Omitted, null and empty string ALL leave the stored URL unchanged. A URL whose password is the literal 'REDACTED' keeps the stored credential and applies any host/port/path edit, so a camera can be moved without knowing its password. Submitting a URL with no credentials at all for a camera that has them is rejected — use an explicit empty userinfo (rtsp://:@host/path) to remove them deliberately.","examples":["rtsp://admin:s3cret@10.0.0.1:554/stream1"]},"rag_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rag Context","description":"Free-form scene context passed to the AI pipeline for this camera.","examples":["Loading dock, forklifts active until 22:00"]},"ingest_mode":{"$ref":"#/components/schemas/CameraIngestMode","description":"continuous = 24/7 KVS archiving; motion_clip = clips uploaded only on motion/analytics trigger.","default":"continuous"},"motion_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Motion Config","description":"Extensible motion settings with bounded known keys (timings, thresholds, detector geometry/rate and RTSP buffer mode); see app/services/motion_config_validation.py. Unknown keys are preserved.","examples":[{"debounce_seconds":30,"post_roll_seconds":5,"pre_roll_seconds":5}]},"analytics_provider":{"anyOf":[{"$ref":"#/components/schemas/AnalyticsProvider"},{"type":"null"}],"description":"Analytics vendor that validates triggered clips before upload; 'none' means frame-diff only. OMIT to leave the camera's stored provider unchanged — omission is NOT 'none', because switching to 'none' de-provisions the DeepAlert sensor."},"analytics_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Analytics Config","description":"Opaque per-vendor analytics settings interpreted by the ingestor."},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Incident intelligence on/off. When false the camera's motion events still record and alert in the UI, but clips are not sent to the AI pipeline for classification.","default":true},"fire_watch":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Fire Watch","description":"Fire-screen gate (site-admin-only). When true, motion that the analytics provider rejected is still screened for smoke/flame before being discarded; turn off on cameras where fire coverage adds nothing (LPR views, interior offices). Omitted/null leaves the stored value unchanged — a client predating this field must not re-enable a deliberately disabled camera."},"monitoring_profile":{"anyOf":[{"$ref":"#/components/schemas/CameraMonitoringProfile"},{"type":"null"}],"description":"Which AI prompt template classifies this camera's clips (site-admin-only): 'security' = intrusion/theft/robbery, 'safety' = slips/falls/person-down. Omitted/null leaves the stored value unchanged — a client predating this field must not flip a deliberately chosen profile during an unrelated edit."},"ptz":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ptz","description":"Whether this camera is pan/tilt/zoom; when true the 'camera moved' health issue is suppressed. Omitted/null leaves the stored value unchanged — a client predating this field must not un-mark a deliberately flagged PTZ camera during an unrelated edit. Unlike fire_watch this is not site-admin-only: it states a physical fact about the hardware, not a cost lever."}},"type":"object","required":["parent_id","site_id","camera_label","camera_id","ip_address","port","username","integration"],"title":"EditCameraRequest"},"EditCameraResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera ID."},"camera_label":{"type":"string","title":"Camera Label","description":"The label of the camera.","examples":["SD Sewing Company"]},"camera_id":{"type":"string","title":"Camera Id","description":"The camera ID.","examples":["cam01"]},"kvs_stream_name":{"type":"string","title":"Kvs Stream Name","description":"The KVS stream name.","examples":["dev-home-cam01"]},"ip_address":{"type":"string","title":"Ip Address","description":"The IP address of the camera.","examples":["192.0.0.1"]},"port":{"type":"string","title":"Port","description":"The port of the camera.","examples":["554"]},"username":{"type":"string","title":"Username","description":"Camera account username (the password is never returned).","examples":["admin"]},"integration":{"$ref":"#/components/schemas/CameraIntegration","description":"The integration of the camera.","examples":["Axis"]},"adapter":{"anyOf":[{"$ref":"#/components/schemas/CameraAdapter"},{"type":"null"}],"description":"The adapter of the camera.","examples":["hanwha_onvif"]},"brand":{"anyOf":[{"$ref":"#/components/schemas/CameraBrand"},{"type":"null"}],"description":"The brand of the camera.","examples":["hanwha"]},"model":{"anyOf":[{"$ref":"#/components/schemas/CameraModel"},{"type":"null"}],"description":"The model of the camera.","examples":["ANV-L7012R"]},"metadata_format":{"anyOf":[{"$ref":"#/components/schemas/CameraMetadataFormat"},{"type":"null"}],"description":"The metadata format of the camera.","examples":["onvif"]},"rtsp_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rtsp Url","description":"The camera's RTSP URL with its password redacted. Never the working credentialed URL. See EditCameraRequest.rtsp_url for the write contract (blank or a REDACTED-bearing echo means keep).","examples":["rtsp://admin:REDACTED@10.0.0.1:554/stream1"]},"rag_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rag Context","description":"Free-form scene context passed to the AI pipeline for this camera.","examples":["Loading dock, forklifts active until 22:00"]},"ingest_mode":{"$ref":"#/components/schemas/CameraIngestMode","description":"continuous = 24/7 KVS archiving; motion_clip = clips uploaded only on motion/analytics trigger.","default":"continuous"},"motion_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Motion Config","description":"Extensible motion settings with bounded known keys (timings, thresholds, detector geometry/rate and RTSP buffer mode); see app/services/motion_config_validation.py. Unknown keys are preserved.","examples":[{"debounce_seconds":30,"post_roll_seconds":5,"pre_roll_seconds":5}]},"analytics_provider":{"$ref":"#/components/schemas/AnalyticsProvider","description":"Analytics vendor validating triggered clips before upload; 'none' means frame-diff only.","default":"none"},"analytics_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Analytics Config","description":"Opaque per-vendor analytics settings interpreted by the ingestor."},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Incident intelligence on/off; when false motion events still record and alert, but skip AI classification.","default":true},"fire_watch":{"type":"boolean","title":"Fire Watch","description":"Fire-screen gate; when true, analytics-rejected motion is still screened for smoke/flame before being discarded.","default":true},"monitoring_profile":{"$ref":"#/components/schemas/CameraMonitoringProfile","description":"Which AI prompt template classifies this camera's clips (owner-only): 'security' = intrusion/theft/robbery, 'safety' = slips/falls/person-down.","default":"security"},"live_view_only":{"type":"boolean","title":"Live View Only","description":"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.","default":false},"display_rotation":{"type":"integer","title":"Display Rotation","description":"Degrees counter-clockwise the recorded frame is turned for display (0 or 180). Set by an owner for an upside-down camera; clips are then stamped with a display-matrix tag and the camera's zones are stored in the rotated (upright) space.","default":0},"agent_mode":{"type":"boolean","title":"Agent Mode","description":"Agent mode on/off; when true the backend acts on AI recommendations (auto-dismiss / voice-down / escalate-upgrade) instead of holding every clip for human review.","default":false},"agent_observation_minutes":{"type":"integer","title":"Agent Observation Minutes","description":"Post-voice-down observation window in minutes before an alert auto-dismisses (agent mode).","default":10},"agent_incident_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Agent Incident Types","description":"Incident types the agent may process at all on this camera; unselected types go to the human queue. Auto-dismiss is additionally bounded by the global safety allowlist."},"incident_routing":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Incident Routing","description":"Per-camera incident routing: incident type -> 'ignore' (dropped and auto-dismissed as not monitored) | 'humans' (operator queue) | 'agent'. A type absent from the map routes to 'humans'; UNKNOWN is never present. Supersedes agent_incident_types, which is now derived from this and kept for one release. 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.","examples":[{"FIRE":"ignore","LOITERING":"agent","NORMAL":"humans"}]},"detection_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Mode","description":"DeepAlert Phase B routing mode (owner-only, written via PUT /cameras/{id}/detection-mode): 'legacy' | 'ii_gated'; null means legacy. Only meaningful when analytics_provider == 'deepalert'.","examples":["ii_gated"]},"ptz":{"type":"boolean","title":"Ptz","description":"Whether this camera is pan/tilt/zoom; when true the 'camera moved' health issue is suppressed.","default":false},"integration_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Integration Id","description":"The cloud-VMS connection (site_integrations.id) this camera was onboarded from; null for NVR and direct-RTSP cameras."},"external_device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Device Id","description":"The VENDOR's own camera id within that connection; null unless integration_id is set.","examples":["3a1b0f4c-1d2e-4f5a-9b8c-7d6e5f4a3b2c"]},"integration_device_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Integration Device Info","description":"Snapshot of what the vendor reported about the device at import (name, model, site, codec); null unless integration_id is set."},"live_view_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Live View Until","description":"On-demand live view is active while now() < this timestamp (motion_clip cameras)."},"healthy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Healthy","description":"The healthy status of the camera.","examples":[true,false]},"last_healthcheck_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Healthcheck At","description":"The last healthcheck at of the camera."},"map_x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x position (0..1) on the site map.","examples":[0.42]},"map_y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y position (0..1) on the site map.","examples":[0.63]},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude on the site map.","examples":[42.0443]},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude on the site map.","examples":[-87.9825]},"map_location_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose shown on queue rows and mini-maps.","examples":["NE corner — loading dock"]},"map_heading":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Heading","description":"Direction the device faces, degrees clockwise from map-up; null = not aimed.","examples":[225]},"deepalert_threshold":{"anyOf":[{"$ref":"#/components/schemas/DeepAlertThresholdModel"},{"type":"null"}],"description":"Read-only, server-resolved DeepAlert sensitivity for this camera (see DeepAlertThresholdModel). Never part of analytics_config; a client must not write it back."}},"type":"object","required":["id","camera_label","camera_id","kvs_stream_name","ip_address","port","username","integration"],"title":"EditCameraResponse"},"EditNvrRequest":{"properties":{"name":{"type":"string","title":"Name","description":"Display name for this NVR.","examples":["Warehouse DW Spectrum"]},"host":{"type":"string","title":"Host","description":"Hostname or IP of the NVR's media server (DW Spectrum / Nx), Open Network Bridge (Milestone XProtect), or ISAPI web server (Hikvision / LTS / HiLook).","examples":["64.183.7.186"]},"port":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Port","description":"Control port Verdun connects to. Vendor-specific — omit to use the vendor default for this NVR's kind: 7001 for DW Spectrum / Nx (media server: REST API + RTSP relay), 580 for Milestone XProtect (Open Network Bridge ONVIF port; RTSP on 554 is discovered via GetStreamUri, not configured here), 80 for Hikvision / LTS / HiLook (ISAPI HTTP port; 443 and 8443 are treated as HTTPS).","examples":["7001","580","80"]},"rtsp_port":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rtsp Port","description":"Hikvision / LTS / HiLook only: the TCP port Verdun reaches the NVR's RTSP service on. Omit to keep the stored value. Ignored for other NVR kinds.","examples":["554","8554"]},"username":{"type":"string","title":"Username","description":"NVR service-account username.","examples":["verdun-service"]},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password","description":"New service-account password; omit to keep the stored one."}},"type":"object","required":["name","host","username"],"title":"EditNvrRequest"},"EditOperatorRequest":{"properties":{"first_name":{"type":"string","title":"First Name","description":"The first name of the operator","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"The last name of the operator","examples":["Johnson"]},"email":{"type":"string","title":"Email","description":"The email address of the operator","examples":["JJ@example.com"]},"role":{"$ref":"#/components/schemas/OperatorRole","description":"The role of the operator","examples":["owner","operator"]},"permissions":{"anyOf":[{"additionalProperties":{"type":"boolean"},"propertyNames":{"$ref":"#/components/schemas/OperatorPermission"},"type":"object"},{"type":"null"}],"title":"Permissions","description":"Per-permission overrides. Omit (null) to leave unchanged.","examples":[{"manage_users":false,"view_alarm_queue":true}]},"parent_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Parent Ids","description":"Full set of parent (client) IDs the operator may access. Omit (null) to leave unchanged. A site_manager must be given exactly one. For role=location_manager the client is DERIVED from the effective site and rewritten on every edit; send it omitted, empty, or exactly the derived parent — a different one is a 409.","examples":[["11111111-1111-1111-1111-111111111111"]]},"site_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Site Ids","description":"Full set of per-site grants (narrowing within granted parents). Omit (null) to leave unchanged; send [] to clear all site narrowing. Every site must belong to a granted parent. For role=location_manager the EFFECTIVE set must be exactly one site — so converting a multi-site parent manager into the role must narrow in the same request, else 409; converting OUT keeps the rows.","examples":[["33333333-3333-3333-3333-333333333333"]]},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"The one account an account_manager is bound to. Omit (null) to leave the existing grant unchanged, matching parent_ids/site_ids. REQUIRED (stored or sent) for role=account_manager and rejected for every other role; converting away from account_manager clears the grant automatically. Any parent_ids/site_ids sent alongside role=account_manager are ignored — that access is derived from the account.","examples":["55555555-5555-5555-5555-555555555555"]}},"type":"object","required":["first_name","last_name","email","role"],"title":"EditOperatorRequest"},"EditOperatorResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of operator","examples":["<Some UUID of some sort>"]},"first_name":{"type":"string","title":"First Name","description":"The first name of the operator","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"The last name of the operator","examples":["Johnson"]},"email":{"type":"string","title":"Email","description":"The email address of the operator","examples":["JJ@example.com"]},"role":{"$ref":"#/components/schemas/OperatorRole","description":"The role of the operator","examples":["owner","operator"]},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At","description":"The time the operator was last seen"},"last_logged_in":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Logged In","description":"The time the operator last logged in"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"The time the operator was created"},"permissions":{"anyOf":[{"additionalProperties":{"type":"boolean"},"type":"object"},{"type":"null"}],"title":"Permissions","description":"Per-permission overrides ({OperatorPermission value: bool}); null means role defaults apply","examples":[{"manage_users":false,"view_alarm_queue":true}]},"is_primary_owner":{"type":"boolean","title":"Is Primary Owner","description":"The account anchor: cannot be removed or demoted, and only they may edit their own record. Not settable via the API.","default":false},"parents":{"items":{"$ref":"#/components/schemas/OperatorParent"},"type":"array","title":"Parents","description":"The parents of the operator"},"sites":{"items":{"$ref":"#/components/schemas/OperatorSite"},"type":"array","title":"Sites","description":"Per-site access grants (site-manager / site-scoped operators). Empty means access follows `parents` (all sites). Exactly one entry for a location_manager, whose `parents` entry is derived from it."},"accounts":{"items":{"$ref":"#/components/schemas/OperatorAccount"},"type":"array","title":"Accounts","description":"Account access grant (account managers). At most one entry. When present, `parents` and `sites` are empty: both are DERIVED from the account, and the operator sees whichever sites currently carry this account_id — including sites added later. Empty for every other role."},"coverage_groups":{"items":{"$ref":"#/components/schemas/OperatorCoverageGroup"},"type":"array","title":"Coverage Groups","description":"Standing coverage-group membership. The operator's site access is the UNION of these groups' grants and the `parents`/`sites` rows above, which are an ADDITIVE exception list ('also sees...'). SCOPE ONLY, NEVER PERMISSION: a member gains sites, never a capability. Empty for site_manager / account_manager / location_manager (structurally scoped, refused membership) and for owner / monitoring_manager (already unconfined, so a membership would be inert)."},"all_sites":{"type":"boolean","title":"All Sites","description":"EFFECTIVE all-sites access (operator / read_only only; false for every other role). When true the user sees every client and site, including future ones, and their coverage groups and parent/site rows are retained but inert. Set via PUT /operator/{id}/site-access.","default":false},"deactivated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deactivated At","description":"When this user was deactivated (POST /operator/{id}/deactivate), or null while active. A deactivated user cannot log in and holds no seats or coverage groups; their records are kept."}},"type":"object","required":["id","first_name","last_name","email","role","created_at","parents"],"title":"EditOperatorResponse"},"EditParentRequest":{"properties":{"company_name":{"type":"string","title":"Company Name","description":"Name of the client","examples":["Joe"]},"type":{"$ref":"#/components/schemas/ParentType","description":"Type of the client","examples":["End User","Internal"]},"primary_contact_name":{"type":"string","title":"Primary Contact Name","description":"Name of the primary contact","examples":["Joe Johnson"]},"primary_contact_phone_number":{"type":"string","title":"Primary Contact Phone Number","description":"Phone number of the primary contact","examples":["1013217890"]},"primary_contact_email":{"type":"string","title":"Primary Contact Email","description":"Email of the primary contact","examples":["JJ@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Notes about the client","examples":["Super important notes"]}},"type":"object","required":["company_name","type","primary_contact_name","primary_contact_phone_number","primary_contact_email"],"title":"EditParentRequest"},"EditParentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique client id","examples":["<UUID of some sort>"]},"company_name":{"type":"string","title":"Company Name","description":"Name of the client","examples":["Joe"]},"type":{"$ref":"#/components/schemas/ParentType","description":"Type of the client","examples":["End User","Internal"]},"primary_contact_name":{"type":"string","title":"Primary Contact Name","description":"Name of the primary contact","examples":["Joe Johnson"]},"primary_contact_phone_number":{"type":"string","title":"Primary Contact Phone Number","description":"Phone number of the primary contact","examples":["1013217890"]},"primary_contact_email":{"type":"string","title":"Primary Contact Email","description":"Email of the primary contact","examples":["JJ@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Notes about the client","examples":["Super important notes"]},"disarm_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Number","description":"The dealer's SMS line, which their customers text with a site passcode to turn cameras off. Null until one is bought.","examples":["+18015550199"]},"disarm_number_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Number State","description":"'purchasing' means a purchase started and its outcome is unconfirmed -- re-POST to reconcile. 'attached' is display only: inbound texts route regardless of campaign attachment. 'releasing' means the line is being given back to Surge: new disarms are refused for this client until it clears, and a re-DELETE reconciles it.","examples":["purchasing","purchased","attached","releasing"]}},"type":"object","required":["id","company_name","type","primary_contact_name","primary_contact_phone_number","primary_contact_email"],"title":"EditParentResponse"},"EditSiteContactInput":{"properties":{"first_name":{"type":"string","title":"First Name","description":"The first name of the site contact.","examples":["1231231234"]},"last_name":{"type":"string","title":"Last Name","description":"The last name of the site contact.","examples":["1231231234"]},"role":{"type":"string","title":"Role","description":"The role of the site contact.","examples":["Security Manager"]},"primary_phone_number":{"type":"string","title":"Primary Phone Number","description":"The primary phone number of the site contact.","examples":["1231231234"]},"afterhours_phone_number":{"type":"string","title":"Afterhours Phone Number","description":"The afterhours phone number of the site contact.","examples":["1231231234"]},"email":{"type":"string","title":"Email","description":"The email of the site contact.","examples":["JJ@example.com"]},"notify_method":{"$ref":"#/components/schemas/SiteContactNotifyMethod","description":"The notify method of the site contact.","examples":["email"]},"availability":{"anyOf":[{"items":{"$ref":"#/components/schemas/ContactAvailabilityWindow"},"type":"array","maxItems":14},{"type":"null"}],"title":"Availability","description":"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.","examples":[[{"days":[0,1,2,3,4],"end":"17:00","start":"08:00"}]]},"id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Id","description":"Existing contact on THIS site to update in place. Omit for a new contact. An unknown id, an id from another site, or the same id twice in one request is a 422 — never a create."}},"type":"object","required":["first_name","last_name","role","primary_phone_number","afterhours_phone_number","email","notify_method"],"title":"EditSiteContactInput","description":"EDIT-ONLY input: the request fields plus an OPTIONAL `id`.\n\n`id` IS A REFERENCE TO AN EXISTING CONTACT ON THIS SITE, NEVER A PRIMARY\nKEY TO CREATE WITH. SiteContactsModel's no-id rule (see its comment above)\nis preserved exactly: `edit_site` resolves this id against the site's own\ncontacts and updates that row in place, and the CREATE path pops the field\nbefore anything reaches the ORM constructor. A caller therefore still\ncannot choose a primary key, and an id for a contact on another site is a\n422 rather than a re-parenting.\n\nWHY IT EXISTS AT ALL: `edit_site` used to delete every contact on the site\nand recreate them from the request, so an ordinary site edit (renaming the\nsite, fixing the address) changed every contact's id — orphaning every\nlinked SMS consent bound to them. Preserving ids is what makes consent\nsurvive routine maintenance.\n\nOPTIONAL, so no flag day: ops-view can keep posting contacts without ids\nwhile the backend matches them by canonical primary phone, and starts\nechoing ids from SiteContactResponseModel whenever it likes."},"EditSiteContactRequest":{"properties":{"first_name":{"type":"string","title":"First Name","description":"First name","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"Last name","examples":["Shmoe"]},"role":{"type":"string","title":"Role","description":"Role","examples":["CTO"]},"primary_phone_number":{"type":"string","title":"Primary Phone Number","description":"Primary phone number","examples":["1231231234"]},"afterhours_phone_number":{"type":"string","title":"Afterhours Phone Number","description":"Afterhours phone number","examples":["1231231234"]},"email":{"type":"string","title":"Email","description":"Email of site contact","examples":["JoeShmoe@example.com"]},"notify_method":{"$ref":"#/components/schemas/SiteContactNotifyMethod","description":"Notification method","examples":["email","call"]},"availability":{"anyOf":[{"items":{"$ref":"#/components/schemas/ContactAvailabilityWindow"},"type":"array","maxItems":14},{"type":"null"}],"title":"Availability","description":"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.","examples":[[{"days":[0,1,2,3,4],"end":"17:00","start":"08:00"}]]}},"type":"object","required":["first_name","last_name","role","primary_phone_number","afterhours_phone_number","email","notify_method"],"title":"EditSiteContactRequest"},"EditSiteContactResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the site contact."},"first_name":{"type":"string","title":"First Name","description":"First name of the site contact.","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"Last name of the site contact."},"role":{"type":"string","title":"Role","description":"Role of the site contact."},"primary_phone_number":{"type":"string","title":"Primary Phone Number","description":"Primary phone number of the site contact."},"afterhours_phone_number":{"type":"string","title":"Afterhours Phone Number","description":"Afterhours phone number of the site contact."},"email":{"type":"string","title":"Email","description":"Email of the site contact."},"notify_method":{"$ref":"#/components/schemas/SiteContactNotifyMethod","description":"Notification method of the site contact."},"daily_summary_opt_out":{"type":"boolean","title":"Daily Summary Opt Out","description":"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.","default":false,"examples":[false]},"availability":{"anyOf":[{"items":{"$ref":"#/components/schemas/ContactAvailabilityWindow"},"type":"array","maxItems":14},{"type":"null"}],"title":"Availability","description":"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.","examples":[[{"days":[0,1,2,3,4],"end":"17:00","start":"08:00"}]]}},"type":"object","required":["id","first_name","last_name","role","primary_phone_number","afterhours_phone_number","email","notify_method"],"title":"EditSiteContactResponse"},"EditSiteRequest":{"properties":{"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The ID of the parent site."},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"Optional account (the client's customer) to group this site under. The account must belong to the same client as parent_id. OMIT the field to leave the current assignment unchanged; send null to clear it."},"name":{"type":"string","title":"Name","description":"The name of the site.","examples":["San Diego Sewing Site"]},"address":{"type":"string","title":"Address","description":"The address of the site.","examples":["1234 cherry hairy lane"]},"city":{"type":"string","title":"City","description":"The city of the site.","examples":["San Diego"]},"state":{"type":"string","title":"State","description":"The state of the site.","examples":["CA"]},"zip_code":{"type":"string","title":"Zip Code","description":"The zip code of the site.","examples":["123456"]},"type":{"$ref":"#/components/schemas/SiteType","description":"The type of site.","examples":["Education"]},"priority_tier":{"$ref":"#/components/schemas/SitePriority","description":"The priority of the site.","examples":["high","medium"]},"monitoring_hours":{"$ref":"#/components/schemas/MonitoringHours","description":"The monitoring hours of the site.","examples":["Nights"]},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone","description":"IANA timezone the site's monitoring schedule is evaluated in. Omit to keep the current value.","examples":["America/Los_Angeles"]},"police_nearest_precinct_name":{"type":"string","title":"Police Nearest Precinct Name","description":"The name of the police nearest precinct.","examples":["SD Police"]},"police_non_emergency_line":{"type":"string","title":"Police Non Emergency Line","description":"The police non-emergency line.","examples":["1234555"]},"police_direct_dispatch_line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Direct Dispatch Line","description":"The direct dispatch line.","examples":["123456"]},"police_jurisdiction_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Jurisdiction Notes","description":"The jurisdiction notes.","examples":["They own here"]},"alarm_license_number":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Alarm License Number","description":"The dealer's alarm license number for this site's state or city.","examples":["127.001463"]},"alarm_license_issuer":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Alarm License Issuer","description":"Who issued the alarm license.","examples":["Illinois","City of Milwaukee"]},"mobile_guard_contract":{"type":"boolean","title":"Mobile Guard Contract","description":"The mobile guard contract is available.","examples":[true,false]},"stage":{"anyOf":[{"type":"string","enum":["live","test"]},{"type":"null"}],"title":"Stage","description":"'test' or 'live' (a display label only). Omit or send null to keep the current stage.","examples":["test","live"]},"guard_company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Company","description":"Mobile guard company name. Omit to preserve the stored value; null clears it."},"guard_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Unit","description":"Mobile guard unit or contact identifier. Omit to preserve the stored value; null clears it."},"guard_contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Contact","description":"Mobile guard dispatch phone. Omit to preserve the stored value; null clears it."},"contacts":{"items":{"$ref":"#/components/schemas/EditSiteContactInput"},"type":"array","title":"Contacts","description":"The list of site contacts."}},"type":"object","required":["parent_id","name","address","city","state","zip_code","type","priority_tier","monitoring_hours","police_nearest_precinct_name","police_non_emergency_line","police_jurisdiction_notes","mobile_guard_contract","contacts"],"title":"EditSiteRequest"},"EditSiteResponse":{"properties":{"stage":{"anyOf":[{"type":"string","enum":["live","test"]},{"type":"null"}],"title":"Stage","description":"The stored site stage, a display label only.","examples":["test","live"]},"closed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Closed","description":"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.","examples":[false]},"onboarding_marks":{"additionalProperties":{"type":"string"},"type":"object","title":"Onboarding Marks","description":"Onboarding checklist marks: mark key -> when it was set (ISO-8601 UTC). Who set a mark is never exposed. Empty when none.","examples":[{"first_night":"2026-10-13T08:00:00.000Z"}]},"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the site"},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"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":{"type":"string","title":"Name","description":"Name of the site","examples":["San Diego Sewing Company - 1"]},"address":{"type":"string","title":"Address","description":"Address of the site","examples":["1234 Hairy Cherry Lane"]},"city":{"type":"string","title":"City","description":"City of the site","examples":["Shanghai"]},"state":{"type":"string","title":"State","description":"State of the site","examples":["CA"]},"zip_code":{"type":"string","title":"Zip Code","description":"Zip code of the site","examples":["78912"]},"type":{"$ref":"#/components/schemas/SiteType","description":"Type of the site","examples":["Education"]},"priority_tier":{"$ref":"#/components/schemas/SitePriority","description":"Priority of the site","examples":["high"]},"monitoring_hours":{"$ref":"#/components/schemas/MonitoringHours","description":"Monitoring hours of the site","examples":["Nights"]},"timezone":{"type":"string","title":"Timezone","description":"IANA timezone the site's monitoring schedule is evaluated in","examples":["America/Los_Angeles"]},"police_nearest_precinct_name":{"type":"string","title":"Police Nearest Precinct Name","description":"Police nearest precinct name","examples":["SD Police hehe"]},"police_non_emergency_line":{"type":"string","title":"Police Non Emergency Line","description":"Police non-emergency line","examples":["1231231234"]},"police_direct_dispatch_line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Direct Dispatch Line","description":"Police direct dispatch line","examples":["32133"]},"police_jurisdiction_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Jurisdiction Notes","description":"Police jurisdiction notes","examples":["This is the police"]},"alarm_license_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alarm License Number","description":"Dealer alarm license number","examples":["127.001463"]},"alarm_license_issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alarm License Issuer","description":"Who issued the alarm license","examples":["Illinois"]},"mobile_guard_contract":{"type":"boolean","title":"Mobile Guard Contract","description":"Mobile guard contract available?","examples":[true,false]},"guard_company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Company","description":"Mobile guard company name."},"guard_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Unit","description":"Mobile guard unit or contact identifier."},"guard_contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Contact","description":"Mobile guard dispatch phone."},"agent_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Context","description":"Site-wide scene context composed into the LLM prompt for every camera at this site.","examples":["Gated storage facility; groundskeeper drives a white pickup weekday mornings."]},"agent_thread_window_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Agent Thread Window Minutes","description":"Incident-thread join window for this site's agent cameras, in minutes; null = global default.","examples":[5]},"heightened_exceptions":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Heightened Exceptions","description":"Owner-curated heightened-monitoring exception categories; null = none.","examples":[[{"description":"Navy uniforms with badge","label":"Uniformed security"}]]},"holidays":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Holidays","description":"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.","examples":[[{"rule":"thanksgiving"},{"date":"2026-12-24","label":"Christmas Eve half day"}]]},"map_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Mode","description":"Map-tab mode: 'geo' (geographic pin) or 'floorplan' (uploaded plan); null = not configured.","examples":["geo"]},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude of the site pin (geo mode).","examples":[42.0442]},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude of the site pin (geo mode).","examples":[-87.9816]},"floorplan_width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Floorplan Width","description":"Natural pixel width of the uploaded floor plan.","examples":[1200]},"floorplan_height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Floorplan Height","description":"Natural pixel height of the uploaded floor plan.","examples":[800]},"has_floorplan":{"type":"boolean","title":"Has Floorplan","description":"Whether a floor-plan image is uploaded (fetch it via GET /sites/{id}/floorplan-url).","default":false,"examples":[true,false]},"disarm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Until","description":"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.","examples":["2026-09-05T21:15:00Z"]},"disarm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Set At","description":"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.","examples":["2026-09-05T19:45:00Z"]},"disarm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm By","description":"Who turned the cameras off: 'sms:<E.164>' for a customer text, 'voice:<E.164>' for a customer call, 'operator:<uuid>' from the console.","examples":["sms:+18015550142","voice:+18015550142","operator:6f1c8f2a-..."]},"disarm_active":{"type":"boolean","title":"Disarm Active","description":"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.","default":false,"examples":[true,false]},"arm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Until","description":"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.","examples":["2026-09-18T04:30:00Z"]},"arm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Set At","description":"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.","examples":["2026-09-18T04:00:00Z"]},"arm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arm By","description":"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.","examples":["operator:6f1c8f2a-..."]},"arm_active":{"type":"boolean","title":"Arm Active","description":"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.","default":false,"examples":[true,false]},"disarm_max_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Disarm Max Minutes","description":"The longest a single disarm may last at this site (1-120).","examples":[120]},"live_view_only":{"type":"boolean","title":"Live View Only","description":"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.","default":false,"examples":[false]},"has_disarm_passcode":{"type":"boolean","title":"Has Disarm Passcode","description":"Whether this site has a disarm passcode. Read the code through the dedicated disarm-passcode endpoint.","default":false,"examples":[true,false]},"disarm_passcode_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Passcode Hint","description":"Last two characters of the stored passcode, for rendering it as '****92'.","examples":["92"]},"disarm_passcode_revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Passcode Revision","description":"Opaque revision that changes whenever the passcode is minted or reset, including when its hint is unchanged."},"monitoring_now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Now","description":"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.","examples":["monitored","off_schedule","disarmed","unscheduled","live_view_only"]},"monitoring_next_change_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Monitoring Next Change At","description":"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.","examples":["2026-09-08T04:30:00Z"]},"contacts":{"items":{"$ref":"#/components/schemas/SiteContactResponseModel"},"type":"array","title":"Contacts","description":"List of site contacts"}},"type":"object","required":["id","name","address","city","state","zip_code","type","priority_tier","monitoring_hours","timezone","police_nearest_precinct_name","police_non_emergency_line","mobile_guard_contract","contacts"],"title":"EditSiteResponse"},"EditSpeakerRequest":{"properties":{"label":{"type":"string","title":"Label","description":"Display name for this speaker.","examples":["SE Horn — dumpster"]},"brand":{"$ref":"#/components/schemas/SpeakerBrand","description":"Speaker vendor.","default":"axis"},"model":{"anyOf":[{"$ref":"#/components/schemas/SpeakerModel"},{"type":"null"}],"description":"Speaker model, for inventory and clip tuning."},"host":{"type":"string","title":"Host","description":"IP address of the speaker, reachable from api-backend. IP LITERALS ONLY (IPv4 or IPv6) — a hostname is rejected, because a name can only be validated against what DNS answers at that moment while delivery re-resolves later; hostname support arrives with pinned resolution. Stored canonically (lowercased, IPv6 compressed), so one device has one spelling.","examples":["167.20.111.104","2606:4700::1111"]},"port":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Port","description":"Device web port: the HTTPS port for direct_https (typically 443), the HTTP port for direct_http (typically 80). Omit or send null to leave the stored port unchanged, including custom forwarded ports.","examples":["443"]},"username":{"type":"string","title":"Username","description":"Device account username.","examples":["root"]},"password":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Password","description":"New device password. Omit to leave the stored one unchanged. Never returned.","examples":["<PASSWORD>"]},"transport":{"anyOf":[{"$ref":"#/components/schemas/SpeakerTransport"},{"type":"null"}],"description":"How api-backend reaches the device: direct_https (recommended) or direct_http, which is unencrypted and digest-auth only - use it only when the site cannot forward the HTTPS port. OMIT TO LEAVE IT UNCHANGED. nvr_proxy is rejected until that path exists."},"clip_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clip Id","description":"Id of the pre-loaded deterrent clip on the device.","examples":["1"]},"clip_params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Clip Params","description":"Playback parameters (volume, repeat).","examples":[{"repeat":1,"volume":80}]},"enabled":{"type":"boolean","title":"Enabled","description":"Kill switch: a disabled speaker is skipped by delivery and by the health poll.","default":true},"sip_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Sip Enabled","description":"Whether the AI voice agent may place a SIP call to this speaker. OMIT OR SEND null TO LEAVE IT UNCHANGED — this is not a full-replace field, because a client that does not know about it must never be able to disarm the AI announcement by saving an unrelated edit. Send true/false to change it. Arming it is a real safety decision: it stays false until the device has been configured for SIP (a peer-to-peer account and STUN), because there is no state we can infer that from. The agent's speaker resolution reads it; the operator's Talk Down and live talk do not."},"sip_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sip Account Id","description":"Which of the device's SIP accounts to place the call from. OMIT OR SEND null TO LEAVE IT UNCHANGED. To CLEAR it — i.e. go back to the device default — send an empty string \"\". Null and empty string mean different things on this field precisely because it is unchanged-by-default.","examples":["sip_account_1"]},"mic_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mic Enabled","description":"Whether this unit's microphone is live. OMIT OR SEND null TO LEAVE IT UNCHANGED; send true/false to change it. Informational: it tells the agent whether to pause and listen for a reply, rather than leaving ten seconds of silence at a deaf horn."},"map_x":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x.","examples":[0.42]},"map_y":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y.","examples":[0.63]},"map_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude of the speaker pin.","examples":[42.0443]},"map_lng":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude of the speaker pin.","examples":[-87.9825]},"map_location_label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose.","examples":["SE corner — by the dumpster"]}},"type":"object","required":["label","host","username"],"title":"EditSpeakerRequest","description":"Full replacement of a speaker's configuration, with keep-on-omit fields.\n\n`password` is the first: OMITTING it means \"unchanged\", the same contract\nas EditNvrRequest, so a label fix never requires re-sending a device\npassword.\n\nTHE THREE SIP FIELDS FOLLOW THAT SAME CONTRACT, and they have to. This is\na full-replace body, and the shipped console round-trips only the fields it\nknows about — so a `False` default on `sip_enabled` would mean that every\nvolume change saved from the Speakers tab silently disarms the AI voice\nannouncement for that speaker, with no error and no log, until the console\nlearns the field. A client that never mentions them cannot change them.\n\nClearing rather than keeping `sip_account_id` is spelled `\"\"` (see its\ndescription); `null`/omitted is always \"keep\".\n\n`transport` and `port` also stay unchanged when omitted or null. An\nunrelated edit must preserve HTTP and custom router-forwarded ports."},"EmailOwnerRequest":{"properties":{"contact_id":{"type":"string","title":"Contact Id","description":"Site contact to e-mail; must belong to the event's site","examples":["2dd7154e-21dc-408f-ac25-b2918d591e29"]},"subject":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Subject","description":"Subject line. Omitted or blank falls back to a server-composed default naming the incident, site and camera.","examples":["Verdun Security: Intrusion at Example Yard (North Gate)"]},"message":{"type":"string","maxLength":5000,"minLength":1,"title":"Message","description":"Message body, sent verbatim above a fixed footer. AI-drafted client-side; the operator owns the final text. Whitespace only is rejected.","examples":["We saw two people on foot near the loading dock at 23:14."]},"attach_snapshot":{"type":"boolean","title":"Attach Snapshot","description":"Attach the event's alert-frame JPEG to the e-mail. 409 SNAPSHOT_UNAVAILABLE when this event has no snapshot - nothing is written and nothing is sent in that case. Absent or false behaves exactly as before.","default":false,"examples":[false]},"snapshot_offset_ms":{"anyOf":[{"type":"integer","maximum":600000.0,"minimum":0.0},{"type":"null"}],"title":"Snapshot Offset Ms","description":"WHICH moment of the clip to attach, as cut by `POST /events/{id}/snapshot-frame`. Only meaningful with `attach_snapshot` (422 otherwise). Absent, the alert-frame poster is attached.","examples":[7300]},"include_clip_link":{"type":"boolean","title":"Include Clip Link","description":"Append a login-free, expiring link to this event's clip, above the footer. The URL is added by the SERVER. 503 when clip links are not configured on this deployment, 409 when the event has no clip.","default":false}},"type":"object","required":["contact_id","message"],"title":"EmailOwnerRequest","description":"E-mail the site's property contact about an event.\n\nNO ADDRESS IS ACCEPTED FROM THE CLIENT, the same rule the call and text\nactions follow for numbers: the destination is resolved from the named\ncontact's row, so a caller can never mail an arbitrary address through us."},"EnrollContactRequest":{"properties":{"slot":{"type":"string","enum":["primary","afterhours"],"title":"Slot","description":"Which of the contact's two numbers to enroll. The consent is bound to this exact (contact, slot) at creation, so it can never authorize a send to the other number.","default":"primary","examples":["primary"]}},"type":"object","title":"EnrollContactRequest"},"EscalationCamera":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Camera id."},"label":{"type":"string","title":"Label","description":"Camera label (an identifier, never scrubbed)."}},"type":"object","required":["id","label"],"title":"EscalationCamera"},"EscalationCaptureSummary":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Capture id; fetch playback from GET /events/{id}/captures."},"trigger_kind":{"type":"string","title":"Trigger Kind","description":"Which action it follows: a voice-down kind (ai_announcement, operator_ai_announcement, live_talk, external_talkdown, clip_talkdown), or operator_alarm, which is the operator-raised alarm's OWN clip (30 s before it was raised to 15 s after), not an after-voice-down recording."},"status":{"type":"string","title":"Status","description":"pending, claimed, captured, unavailable or failed."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why it is unavailable/failed."},"offset_seconds":{"type":"integer","title":"Offset Seconds","description":"Seconds from the alarm to the start of the recording."},"captured_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Captured Seconds","description":"Footage recorded inside the window (null when not measured)."},"start_offset_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Start Offset Seconds","description":"Where the footage begins inside the window."},"partial":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Partial","description":"True when less than the full window was recorded."}},"type":"object","required":["id","trigger_kind","status","offset_seconds"],"title":"EscalationCaptureSummary"},"EscalationClip":{"properties":{"available_hint":{"type":"boolean","title":"Available Hint","description":"The alarm has a clip key and is inside the storage window (or retained). A hint, never a HEAD."},"retained":{"type":"boolean","title":"Retained","description":"A starred copy exists."}},"type":"object","required":["available_hint","retained"],"title":"EscalationClip"},"EscalationKpis":{"properties":{"escalate_seconds":{"$ref":"#/components/schemas/KpiMedian","description":"Median seconds from alarm to escalation."},"voice_down_seconds":{"$ref":"#/components/schemas/KpiMedian","description":"Median seconds from alarm to transmitted or played voice down."},"dispatcher_answered_seconds":{"$ref":"#/components/schemas/KpiMedian","description":"Median seconds from alarm to dispatcher answer."},"contact_reached_seconds":{"$ref":"#/components/schemas/KpiMedian","description":"Median seconds from alarm to contact reached."},"closed_seconds":{"$ref":"#/components/schemas/KpiMedian","description":"Median seconds from alarm to closeout."}},"type":"object","required":["escalate_seconds","voice_down_seconds","dispatcher_answered_seconds","contact_reached_seconds","closed_seconds"],"title":"EscalationKpis"},"EscalationOutcome":{"properties":{"code":{"type":"string","title":"Code","description":"Close-out outcome word, or agent_cleared / closed / expired."},"tone":{"type":"string","enum":["good","warn","bad","neutral"],"title":"Tone","description":"Colour hint."}},"type":"object","required":["code","tone"],"title":"EscalationOutcome"},"EscalationParent":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Client (parent) id."},"name":{"type":"string","title":"Name","description":"Client name."}},"type":"object","required":["id","name"],"title":"EscalationParent"},"EscalationResult":{"properties":{"code":{"type":"string","title":"Code","description":"Stable machine word (answered, delivered, no_answer, played, subject_left, ...)."},"tone":{"type":"string","enum":["good","warn","bad","neutral"],"title":"Tone","description":"good | warn | bad | neutral - the UI colour, decided server-side."}},"type":"object","required":["code","tone"],"title":"EscalationResult"},"EscalationRow":{"properties":{"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"The alarm."},"site":{"$ref":"#/components/schemas/EscalationSite","description":"Site containing the event camera."},"parent":{"$ref":"#/components/schemas/EscalationParent","description":"Client owning the event."},"camera":{"$ref":"#/components/schemas/EscalationCamera","description":"Camera that raised the alarm."},"incident_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incident Type","description":"AI incident class, when known."},"kind":{"type":"string","enum":["voice_down","dispatch"],"title":"Kind","description":"Escalate - Voice Down or Escalate - Dispatch."},"escalated_by":{"type":"string","enum":["agent","operator"],"title":"Escalated By","description":"Who owns the escalation (the operator's when both acted)."},"agent_voice_down":{"type":"boolean","title":"Agent Voice Down","description":"The AI agent also played a deterrent on this alarm."},"operator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator","description":"The escalating operator, projected like actor_name; null for agent-only rows."},"t0":{"type":"string","format":"date-time","title":"T0","description":"When the alarm entered the operator queue (shared queue clock); event creation is the fallback when queue visibility is unknown."},"state":{"type":"string","enum":["open","closed","expired"],"title":"State","description":"Open, closed out, or expired without a close-out."},"outcome":{"anyOf":[{"$ref":"#/components/schemas/EscalationOutcome"},{"type":"null"}],"description":"Null while open."},"timings":{"$ref":"#/components/schemas/EscalationTimings","description":"Milestone offsets from the alarm in seconds."},"poster_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Poster Url","description":"Blind presign of the alert snapshot; the image may 404."},"clip":{"$ref":"#/components/schemas/EscalationClip","description":"Availability hints for the original alarm clip."},"captures":{"items":{"$ref":"#/components/schemas/EscalationCaptureSummary"},"type":"array","title":"Captures","description":"After-voice-down recordings for this alarm."},"steps":{"items":{"$ref":"#/components/schemas/EscalationStep"},"type":"array","title":"Steps","description":"Timeline steps ordered by timestamp."}},"type":"object","required":["event_id","site","parent","camera","kind","escalated_by","agent_voice_down","t0","state","timings","clip","captures","steps"],"title":"EscalationRow"},"EscalationSite":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Site id."},"name":{"type":"string","title":"Name","description":"Site name."},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone","description":"IANA zone the UI renders this row's clock in."}},"type":"object","required":["id","name"],"title":"EscalationSite"},"EscalationStep":{"properties":{"kind":{"type":"string","title":"Kind","description":"alarm, raised (an operator raised it from Live View), ai_assessment, opened, escalated, announcement, announcement_failed, ai_call_ended, live_talk, live_talk_end, external_talkdown, external_talkdown_end, owner_call, owner_sms, owner_email, contact_reply, dispatch_call, dispatch_connected, dispatch_ended, closed, expired."},"actor":{"type":"string","enum":["system","agent","operator"],"title":"Actor","description":"Who acted: the system, the AI agent, or an operator."},"actor_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Actor Name","description":"Operator display name for ops-lead callers; the literal 'Operator' for everyone else; null for system/agent steps."},"at":{"type":"string","format":"date-time","title":"At","description":"Absolute UTC instant of the step."},"offset_seconds":{"type":"integer","title":"Offset Seconds","description":"Seconds since the alarm landed in the queue (t0), never negative."},"since_previous_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Since Previous Seconds","description":"Seconds since the previous step; null on the first."},"label":{"type":"string","title":"Label","description":"Short English label."},"detail":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Detail","description":"Structured facts for the step. Customer-side callers receive only an allow-listed subset of keys."},"result":{"anyOf":[{"$ref":"#/components/schemas/EscalationResult"},{"type":"null"}],"description":"Outcome of the step, when it has one."},"capture_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Capture Id","description":"The after-voice-down capture that follows this step (voice steps only)."},"derived":{"type":"boolean","title":"Derived","description":"True when the instant is derived, not recorded (the 'escalated' step is the first escalating action).","default":false}},"type":"object","required":["kind","actor","at","offset_seconds","label"],"title":"EscalationStep"},"EscalationTimings":{"properties":{"escalate_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Escalate Seconds","description":"Alarm to the first escalating action."},"voice_down_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Voice Down Seconds","description":"Alarm to the first voice down transmitted or played."},"dispatcher_answered_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Dispatcher Answered Seconds","description":"Alarm to the dispatcher answering (derived instant)."},"contact_reached_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Contact Reached Seconds","description":"Alarm to the first contact call answered or first SMS delivered."},"closed_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Closed Seconds","description":"Alarm to close-out."}},"type":"object","title":"EscalationTimings"},"EscalationWindow":{"properties":{"start":{"type":"string","format":"date-time","title":"Start","description":"Inclusive UTC start of the requested window."},"end":{"type":"string","format":"date-time","title":"End","description":"UTC end of the requested window."}},"type":"object","required":["start","end"],"title":"EscalationWindow"},"EssayCommentItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Comment id."},"name":{"type":"string","title":"Name","description":"Commenter display name."},"body":{"type":"string","title":"Body","description":"Approved comment text."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Submission time (UTC)."}},"type":"object","required":["id","name","body","created_at"],"title":"EssayCommentItem"},"EssayLikeResponse":{"properties":{"likes":{"type":"integer","title":"Likes","description":"Total likes for the essay after the change."},"liked":{"type":"boolean","title":"Liked","description":"Whether this visitor now likes the essay."}},"type":"object","required":["likes","liked"],"title":"EssayLikeResponse"},"EventCaptureResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Capture id."},"trigger_kind":{"type":"string","title":"Trigger Kind","description":"Which voice down this follows: ai_announcement, operator_ai_announcement, live_talk, external_talkdown or clip_talkdown."},"status":{"type":"string","title":"Status","description":"pending, claimed, captured, unavailable or failed."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why a capture is unavailable/failed (stable code; the UI owns the wording)."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"The moment the voice down played; the recording starts here."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"window_start plus the requested duration."},"duration_seconds":{"type":"integer","title":"Duration Seconds","description":"Requested length (30 in v1)."},"captured_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Captured Seconds","description":"Footage actually inside the window (null when not measured, e.g. continuous cameras)."},"start_offset_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Start Offset Seconds","description":"Seconds after window_start where the footage begins (null when not measured)."},"partial":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Partial","description":"True when less than the full window was captured. Null unless captured with measured length."},"available":{"type":"boolean","title":"Available","description":"True when a playable clip exists right now (captured and the object is still in storage)."},"clip_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clip Url","description":"Presigned URL (1 h) when available."},"expires_in":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In","description":"Seconds until clip_url expires."},"display_rotation":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Display Rotation","description":"Display rotation (degrees CCW: 0, 90, 180 or 270) already applied by this capture's own container tag, from the object's S3 `display-rotation` metadata. 0 = untagged; null = unknown (not available, or the HEAD failed). A player must not turn a clip again that is tagged non-zero."}},"type":"object","required":["id","trigger_kind","status","window_start","window_end","duration_seconds","available"],"title":"EventCaptureResponse"},"EventOperatorInstructionsResponse":{"properties":{"account":{"anyOf":[{"$ref":"#/components/schemas/OperatorInstructionsLevel"},{"type":"null"}],"description":"The account the event's site is grouped under. Null when the site sits directly under the client (no account) or when its account is retired."},"site":{"$ref":"#/components/schemas/OperatorInstructionsLevel","description":"The site the event's camera belongs to."},"camera":{"$ref":"#/components/schemas/OperatorInstructionsLevel","description":"The camera the event came from; `name` is the camera label."}},"type":"object","required":["site","camera"],"title":"EventOperatorInstructionsResponse","description":"Every operator instruction that applies to one event, resolved server-side.\n\nTHE READ THE REVIEW PANEL MAKES. Resolving camera -> site -> account here\nrather than in the client is what keeps the instruction text OFF the queue\nbundle and off both SSE broadcasts: those payloads are built for every event\nan operator can see and are held open all shift, while this is one scoped\nfetch for the one event actually being reviewed. Same shape as the\n`ai-eval` and `clip-url` per-event reads, and guarded the same way.\n\nTHREE LEVELS, ALL THREE POSSIBLY EMPTY. `account` is null for a site that\nsits directly under the client with no account, and also for a site whose\naccount has been retired — a retired account grants nothing and its text\nmust not be served. `site` and `camera` are always present for a real event.\n\nDEMO EVENTS ANSWER 404 (Round-0 ruling Q7), not a null camera: they are\ncamera-less by construction and the review panel never shows one, so a\nfourth shape would be dead code the client still had to handle.\n\nOut-of-scope and unknown event ids are indistinguishable — both 404 — so\nthis route is not an event-id oracle."},"EventSnapshotResponse":{"properties":{"source":{"type":"string","enum":["poster","clip_frame"],"title":"Source","description":"`poster` = the ingestor's alert-frame JPEG (events before 2026-09-10); `clip_frame` = a frame cut from the clip where the AI boxed a person or vehicle, else at the alarm's trigger moment."},"offset_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Offset Ms","description":"For `clip_frame`, the clip offset the frame was cut at; null for `poster`."},"url":{"type":"string","title":"Url","description":"Presigned image URL. A bearer: never persist it, never log it."},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until `url` expires"}},"type":"object","required":["source","url","expires_in"],"title":"EventSnapshotResponse"},"EventsModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of event","examples":["UUID Of some sort"]},"kvs_stream_name":{"type":"string","title":"Kvs Stream Name","description":"KVS stream name","examples":["dev-home-cam01"]},"zone":{"type":"string","title":"Zone","description":"Zone code","examples":["A"]},"clip_s3_url":{"type":"string","title":"Clip S3 Url","description":"S3 URL","examples":["https://s3.amazonaws.com"]},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"Mapped summary column","examples":["Emergency!"]},"motion_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Motion Score","description":"Mapped motion score column","examples":["0.9"]},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"Mapped source column","examples":["rtsp_motion"]},"is_demo":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Demo","description":"Owner-uploaded showcase clip (Demo tab only)"},"starred_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Starred At","description":"Set when an owner starred the event"},"thread_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Thread Id","description":"Threat watch this event belongs to after subject correlation"},"correlation_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Correlation Status","description":"threat_trigger, related_sighting, or standalone possible_match"},"candidate_thread_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Candidate Thread Id","description":"Candidate threat watch for a standalone possible_match"},"occurred_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Occurred At","description":"Ingestor-reported motion time (display/audit; ordering keys on created_at)"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"Event creation time"},"monitoring_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring State","description":"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.","examples":["monitored","heightened","off"]},"queued_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Queued At","description":"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":{"type":"boolean","title":"Has Continued Clip","description":"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.","default":false},"continued_clip_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Continued Clip Seconds","description":"Length of the continued clip in seconds; null when has_continued_clip is false.","examples":[10]},"raised_by_operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Raised By Operator Id","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raised By Name","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raised Incident Type","description":"The incident type the operator chose when raising the alarm (an IncidentType name). Null for every other event.","examples":["INTRUSION"]},"raised_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raised Note","description":"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":{"anyOf":[{"$ref":"#/components/schemas/OperatorAlarmCaptureModel"},{"type":"null"}],"description":"Operator-raised alarms only (key absent otherwise): the state of the alarm's own clip recording. status is pending | claimed (still recording: poll again), captured (clip_s3_url is set), or unavailable | failed (no clip will come; reason says why). Null on an operator-raised alarm means no recording was requested (capture disabled), i.e. no clip."}},"type":"object","required":["id","kvs_stream_name","zone","clip_s3_url"],"title":"EventsModel"},"ExternalTalkdownFailReason":{"type":"string","enum":["timeout","not_logged_in","phone_not_found","busy","extension_error"],"title":"ExternalTalkdownFailReason"},"ExternalTalkdownMode":{"type":"string","enum":["mic","clip"],"title":"ExternalTalkdownMode"},"ExternalTalkdownProvider":{"type":"string","enum":["verkada_command"],"title":"ExternalTalkdownProvider"},"ExternalTalkdownResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The talk-down record id."},"event_metadata_id":{"type":"string","format":"uuid","title":"Event Metadata Id","description":"The alarm this was about."},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The camera whose vendor console was driven."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client, denormalised so a tenant-scoped read needs no join."},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site, denormalised for the same reason."},"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"Who talked. Null once that account has been deleted — the record outlives it."},"provider":{"$ref":"#/components/schemas/ExternalTalkdownProvider","description":"Which vendor console was driven.","examples":["verkada_command"]},"mode":{"$ref":"#/components/schemas/ExternalTalkdownMode","description":"Operator microphone, or the bundled deterrent clip.","examples":["mic"]},"status":{"$ref":"#/components/schemas/ExternalTalkdownStatus","description":"Where the attempt got to. `ended` and `failed` are terminal.","examples":["connected"]},"fail_reason":{"anyOf":[{"$ref":"#/components/schemas/ExternalTalkdownFailReason"},{"type":"null"}],"description":"Why it never happened. Only ever set on a failed row.","examples":["timeout"]},"client_ref":{"type":"string","title":"Client Ref","description":"The extension's per-attempt id; the idempotency key this row was created under.","examples":["a1b2c3d4e5f6"]},"requested_at":{"type":"string","format":"date-time","title":"Requested At","description":"When the operator pressed the button, on the SERVER's clock."},"connected_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Connected At","description":"When the vendor console reported the call up. Null if it never did."},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ended At","description":"When the attempt reached a terminal state."},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms","description":"connected_at to ended_at in milliseconds. Null when the call never connected — which is the honest answer, not zero."},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail","description":"Free-text attempt trail. Never a URL or a token."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"Row creation timestamp."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"Row update timestamp."}},"type":"object","required":["id","event_metadata_id","camera_id","parent_id","site_id","provider","mode","status","client_ref","requested_at"],"title":"ExternalTalkdownResponse","description":"One talk-down attempt through a vendor console.\n\nTHERE IS NO `announced_at` AND THERE MUST NEVER BE ONE. A vendor console\nreports that the CALL came up, never that audio was delivered;\n`agent_actions.announced_at` means positively-verified playback and is what\na quiet auto-dismissal rests on. `status = connected` is the strongest\nhonest fact this path has."},"ExternalTalkdownStatus":{"type":"string","enum":["requested","connected","ended","failed"],"title":"ExternalTalkdownStatus"},"FailoversModel":{"properties":{"provider":{"type":"string","title":"Provider","description":"Provider that failed"},"reason":{"type":"string","title":"Reason","description":"Reason for failover"},"fallback_target":{"type":"string","title":"Fallback Target","description":"Fallback target used"}},"type":"object","required":["provider","reason","fallback_target"],"title":"FailoversModel"},"FirstViewed":{"properties":{"operator_id":{"type":"string","title":"Operator Id","description":"The first central-staff viewer to open the alarm"},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Their display name; null if the operator no longer exists"},"viewed_at":{"type":"string","title":"Viewed At","description":"When that first open happened (ISO 8601, UTC)"}},"type":"object","required":["operator_id","viewed_at"],"title":"FirstViewed"},"FleetBandModel":{"properties":{"start":{"type":"string","format":"date-time","title":"Start","description":"UTC instant the band begins (inclusive)."},"end":{"type":"string","format":"date-time","title":"End","description":"UTC instant the band ends (exclusive)."},"site_count":{"type":"integer","title":"Site Count","description":"Sites with at least one camera being monitored during the band.","examples":[124]},"camera_count":{"type":"integer","title":"Camera Count","description":"Cameras being monitored during the band.","examples":[1871]},"heightened_count":{"type":"integer","title":"Heightened Count","description":"Sites with at least one live camera in heightened mode.","examples":[3]},"operator_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Operator Ids","description":"Operators seated across all coverage groups during the band. EMPTY for callers who may not see center staffing (see operators_visible) - and also genuinely empty when nobody is seated, which is the unattended-coverage case the view exists to surface."}},"type":"object","required":["start","end","site_count","camera_count","heightened_count","operator_ids"],"title":"FleetBandModel","description":"One interval in which the fleet's composition does not change.\n\n`start`/`end` are UTC INSTANTS, not center-local minutes: a site's hours\nfire in the site's own zone and a shift fires in the center's, so a book\nspanning several zones has no single wall clock to lay them on. The client\nrenders them in whatever zone it displays.\n\nCounts only, by design - no colour, no status, no cameras-per-operator\nratio. The load threshold is presentation policy that will become\nper-tenant, and returning it here would force a contract change the day it\ndoes."},"FleetBandOperatorModel":{"properties":{"operator_id":{"type":"string","format":"uuid","title":"Operator Id","description":"The seated operator."},"group_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Group Ids","description":"Coverage groups they are seated on during the band."}},"type":"object","required":["operator_id","group_ids"],"title":"FleetBandOperatorModel"},"FleetBandSiteModel":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The monitored site."},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site display name."},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id","description":"The client this site belongs to."},"parent_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Name","description":"Client display name."},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone","description":"IANA zone this site's monitoring hours fire in.","examples":["America/Chicago"]},"cameras_live":{"type":"integer","title":"Cameras Live","description":"Cameras monitored during the band.","examples":[4]},"cameras_total":{"type":"integer","title":"Cameras Total","description":"Cameras the site has at all - lower live count means overrides are off.","examples":[5]},"heightened":{"type":"boolean","title":"Heightened","description":"At least one live camera is in heightened mode."}},"type":"object","required":["site_id","cameras_live","cameras_total","heightened"],"title":"FleetBandSiteModel"},"FocusListResponse":{"properties":{"focus":{"items":{"oneOf":[{"$ref":"#/components/schemas/FocusRecord"},{"$ref":"#/components/schemas/StoryHoldRecord"}]},"type":"array","title":"Focus","description":"The active focus records in the caller's tenant scope (small): who is focusing."}},"type":"object","required":["focus"],"title":"FocusListResponse"},"FocusRecord":{"properties":{"id":{"type":"string","title":"Id","description":"The focus generation id; a restart is a new id."},"site_id":{"type":"string","title":"Site Id","description":"The site the focus is on."},"scope":{"$ref":"#/components/schemas/FocusScopeOut","description":"What the focus covers: the whole site or one camera."},"operator_id":{"type":"string","title":"Operator Id","description":"The focuser."},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Their display name; null if the operator no longer exists."},"started_at":{"type":"string","title":"Started At","description":"ISO 8601, UTC."},"last_input_at":{"type":"string","title":"Last Input At","description":"Last accepted meaningful input or Keep (ISO 8601, UTC)."},"check_due_at":{"type":"string","title":"Check Due At","description":"`last_input_at` + FOCUS_CHECK_IDLE_SECONDS: when the console asks \"Still focusing?\"."},"check_deadline":{"type":"string","title":"Check Deadline","description":"`check_due_at` + FOCUS_CHECK_ANSWER_SECONDS: with no explicit Keep by then the server releases the focus (and treats it as released at once, before any sweep)."},"check_pending":{"type":"boolean","title":"Check Pending","description":"True while the check is due and unanswered (active records only)."},"check_due_in_seconds":{"type":"integer","title":"Check Due In Seconds","description":"Seconds until `check_due_at`, computed server-side: time it from this against the local clock at receipt."},"check_deadline_in_seconds":{"type":"integer","title":"Check Deadline In Seconds","description":"Seconds until `check_deadline`, computed server-side."},"state":{"type":"string","enum":["active","released"],"title":"State","description":"`active` while reserving alarms; `released` once it has ended."},"released_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Released At","description":"When the focus was released (ISO 8601, UTC); null while active."},"release_reason":{"anyOf":[{"type":"string","enum":["manual","check_timeout","disabled","scope_lost"]},{"type":"null"}],"title":"Release Reason","description":"Why it was released; null while active."}},"type":"object","required":["id","site_id","scope","operator_id","started_at","last_input_at","check_due_at","check_deadline","check_pending","check_due_in_seconds","check_deadline_in_seconds","state"],"title":"FocusRecord","description":"One Focus generation (`id` IS the generation: a restart is a new id)."},"FocusScopeCamera":{"properties":{"kind":{"type":"string","const":"camera","title":"Kind","description":"One camera. Its graph neighbours are NOT included."},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The camera; it must belong to `site_id`."}},"additionalProperties":false,"type":"object","required":["kind","camera_id"],"title":"FocusScopeCamera"},"FocusScopeOut":{"properties":{"kind":{"type":"string","enum":["site","camera"],"title":"Kind","description":"Whether the focus covers the whole site or one camera."},"camera_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id","description":"Set iff `kind` is `camera`."}},"type":"object","required":["kind"],"title":"FocusScopeOut"},"FocusScopeSite":{"properties":{"kind":{"type":"string","const":"site","title":"Kind","description":"Every camera at the site."}},"additionalProperties":false,"type":"object","required":["kind"],"title":"FocusScopeSite"},"ForgotPasswordRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Email address to send a password-reset code to"}},"type":"object","required":["email"],"title":"ForgotPasswordRequest"},"GateOutcomeModel":{"properties":{"reason":{"type":"string","minLength":1,"title":"Reason","description":"Typed reason the object-of-interest gate stopped before a full interpretation ran"},"detector_provider":{"type":"string","minLength":1,"title":"Detector Provider","description":"Which detector/stage produced this outcome, e.g. 'deepalert' | 'fire_screen' | 'none'"},"detector_confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Detector Confidence","description":"Detector confidence at the decisive stage, when available. Finite [0, 1] — rejects bool, out-of-range, NaN, and infinity."}},"type":"object","required":["reason","detector_provider"],"title":"GateOutcomeModel","description":"DeepAlert Phase B (round-0b design §7): typed reason the\nobject-of-interest gate stopped before a full interpretation ran.\nPopulated only when CreateAIEvalRequest.terminal_stage == \"object_gate\".\n\nreason is plain str, NOT a closed Literal/enum, on purpose (round-0\ndesign §1.2 in verdun/scratch/handoff/deepalert-phase-b-api/): ii owns\nand pins the closed set of reason values at the source\n(contracts/interpretation_result_v1.py); re-closing it here would mean\na future ii-added reason value 422s /eval/ai ingestion instead of\nlanding the row. Known values as of this writing: no_object_of_interest,\ndetector_error, detector_timeout, fire_screen_error,\nmisconfigured_detection_config. detector_confidence keeps ii's original\nstrictness (Codex stage-4b review, finding 2, CONFIRMED): a finite\nvalue in [0, 1], never NaN/inf/>1, matching\ncontracts/common_v1.NormalizedFloat."},"GateRejectionReasonCount":{"properties":{"reason":{"type":"string","title":"Reason","description":"gate_outcome.reason value (open string — ii may add new reasons; 'unknown' when the JSONB key is missing/null).","examples":["no_object_of_interest"]},"count":{"type":"integer","title":"Count","description":"Gate-negative rows with this reason in the window.","examples":[8]}},"type":"object","required":["reason","count"],"title":"GateRejectionReasonCount"},"GateRejections":{"properties":{"total":{"type":"integer","title":"Total","description":"Total object_gate rows in the window across the selected scope.","examples":[11]},"by_reason":{"items":{"$ref":"#/components/schemas/GateRejectionReasonCount"},"type":"array","title":"By Reason","description":"Per-reason breakdown, largest first."}},"type":"object","required":["total","by_reason"],"title":"GateRejections","description":"DeepAlert Phase B (round-0b design §7.3/OQ-7): terminal_stage ==\n'object_gate' rows, segmented out of the primary AI incident counts\nabove so a gate-negative never silently counts as an ordinary AI\nNORMAL classification. Zero for every scope until a camera is flipped\nto detection_mode='ii_gated' and incident-intelligence's own Phase B\npipeline ships — both independent of this PR."},"GateSimulation":{"properties":{"would_auto_dismiss":{"type":"integer","title":"Would Auto Dismiss","description":"Reviews that would have passed the auto-dismiss gate.","examples":[58]},"correct":{"type":"integer","title":"Correct","description":"Of those, the human also dismissed (agent true positives).","examples":[57]},"wrong":{"type":"integer","title":"Wrong","description":"Of those, the human escalated — clips agent mode would have wrongly dismissed. Should be ~0 before arming a camera.","examples":[1]},"precision":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Precision","description":"correct / would_auto_dismiss as a percentage.","examples":[98.3]},"coverage":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Coverage","description":"would_auto_dismiss / reviewed as a percentage — the workload agent mode would remove.","examples":[46.8]},"wrong_event_ids":{"items":{"type":"string"},"type":"array","title":"Wrong Event Ids","description":"Up to 20 event UUIDs the gate would have wrongly dismissed, newest first, for drill-down review."},"blocked_by_camera_types":{"type":"integer","title":"Blocked By Camera Types","description":"Reviews the global gate would dismiss but the camera's classifier selection blocks — the coverage cost of narrowing a camera's types.","default":0,"examples":[3]}},"type":"object","required":["would_auto_dismiss","correct","wrong"],"title":"GateSimulation","description":"What the agent engine WOULD have auto-dismissed, using the engine's own\ngate (HIGH confidence, global incident-type allowlist, the camera's\nclassifier selection, no ambiguity) — agent precision/recall against\nhuman ground truth. The thread gate is not simulated."},"GeocodeSiteResponse":{"properties":{"lat":{"type":"number","title":"Lat","description":"Latitude of the geocoded address.","examples":[42.0304]},"lng":{"type":"number","title":"Lng","description":"Longitude of the geocoded address.","examples":[-87.94818]},"label":{"type":"string","title":"Label","description":"The address Amazon Location actually matched — show this so the operator can tell a precise hit from a near miss.","examples":["1601 E Algonquin Rd, Arlington Heights, IL 60005, United States"]},"place_type":{"type":"string","title":"Place Type","description":"Amazon Location match granularity (PointAddress, Street, Locality, …).","examples":["PointAddress"]},"precise":{"type":"boolean","title":"Precise","description":"True when the match is exact enough to save as the site pin without confirmation; false means offer it as a suggestion.","examples":[true,false]}},"type":"object","required":["lat","lng","label","place_type","precise"],"title":"GeocodeSiteResponse"},"GetActivePunchesResponse":{"properties":{"punches":{"items":{"$ref":"#/components/schemas/TimePunchModel"},"type":"array","title":"Punches","description":"Every currently open punch - who is actually on shift right now."}},"type":"object","required":["punches"],"title":"GetActivePunchesResponse"},"GetActivityResponse":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/ActivityEntryModel"},"type":"array","title":"Entries","description":"Activity log entries, newest first"},"total":{"type":"integer","title":"Total","description":"Total number of entries (for pagination)"}},"type":"object","required":["entries","total"],"title":"GetActivityResponse"},"GetAgentAgreementResponse":{"properties":{"range_start":{"type":"string","format":"date-time","title":"Range Start","description":"Start of the analyzed range (UTC)."},"range_end":{"type":"string","format":"date-time","title":"Range End","description":"End of the analyzed range (UTC)."},"totals":{"$ref":"#/components/schemas/AgreementTotals"},"confusion":{"items":{"$ref":"#/components/schemas/ConfusionCell"},"type":"array","title":"Confusion","description":"Sparse confusion matrix, recommendation × human disposition (recommended=null bucket included)."},"classes":{"items":{"$ref":"#/components/schemas/ClassScore"},"type":"array","title":"Classes","description":"Per-class precision/recall over covered reviews."},"dismiss_screen":{"$ref":"#/components/schemas/DismissScreen"},"gate_simulation":{"$ref":"#/components/schemas/GateSimulation"},"cameras":{"items":{"$ref":"#/components/schemas/CameraAgreementRow"},"type":"array","title":"Cameras","description":"Per-camera rows (most-reviewed first) for arming decisions."}},"type":"object","required":["range_start","range_end","totals","confusion","classes","dismiss_screen","gate_simulation","cameras"],"title":"GetAgentAgreementResponse"},"GetAgentAuditSamplingResponse":{"properties":{"false_alarm":{"$ref":"#/components/schemas/AgentAuditSamplingRate","description":"The agent's False alarm closes (where missed people hide)."},"non_threatening":{"$ref":"#/components/schemas/AgentAuditSamplingRate","description":"The agent's Non-threatening human activity closes."}},"type":"object","required":["false_alarm","non_threatening"],"title":"GetAgentAuditSamplingResponse"},"GetAllOperatorsResponse":{"properties":{"operators":{"items":{"$ref":"#/components/schemas/OperatorModel"},"type":"array","title":"Operators","description":"A list of operators"}},"type":"object","required":["operators"],"title":"GetAllOperatorsResponse"},"GetAnalyticsSummaryResponse":{"properties":{"range_start":{"type":"string","format":"date-time","title":"Range Start","description":"Start of the reported range (UTC)."},"range_end":{"type":"string","format":"date-time","title":"Range End","description":"End of the reported range (UTC)."},"metrics":{"$ref":"#/components/schemas/AnalyticsMetrics","description":"Headline counts for the range."},"breakdown":{"items":{"$ref":"#/components/schemas/AnalyticsBreakdownRow"},"type":"array","title":"Breakdown","description":"Disposition counts, largest first."},"operators":{"items":{"$ref":"#/components/schemas/AnalyticsOperatorRow"},"type":"array","title":"Operators","description":"Per-operator performance, most active first (includes operators with zero activity). Monitoring-center staff only (owner/operator roles): site_manager, account_manager, location_manager and read_only logins are never listed, though their dispositions still count in metrics/breakdown."},"recent":{"items":{"$ref":"#/components/schemas/AnalyticsRecentRow"},"type":"array","title":"Recent","description":"Most recent dispositions in the range (max 50)."},"slow":{"items":{"$ref":"#/components/schemas/AnalyticsRecentRow"},"type":"array","title":"Slow","description":"Dispositions in the range whose event-created→disposition time exceeded slow_threshold_seconds, exact over the full range (not limited to the 50-row `recent` cap; capped defensively at 200)."},"include_agent":{"type":"boolean","title":"Include Agent","description":"Whether agent-mode evaluations are included in this response's figures (default false: operator decisions only).","default":false},"distribution":{"anyOf":[{"$ref":"#/components/schemas/AnalyticsTriageDistribution"},{"type":"null"}],"description":"Triage-time distribution for the Operator Performance chart."},"hourly":{"anyOf":[{"$ref":"#/components/schemas/AnalyticsHourlyVolume"},{"type":"null"}],"description":"Alarms triaged per hour of day for the Operator Performance chart."},"slow_total":{"type":"integer","title":"Slow Total","description":"Exact count of over-threshold dispositions in the window; `slow` is capped at 200 for response size, so this is the count to display (e.g. an 'Over target' metric card) — it stays exact even when `slow` is truncated.","examples":[3]}},"type":"object","required":["range_start","range_end","metrics","breakdown","operators","recent","slow","slow_total"],"title":"GetAnalyticsSummaryResponse"},"GetAssignedSiteIntegrationsResponse":{"properties":{"integrations":{"items":{"$ref":"#/components/schemas/SiteIntegrationResponse"},"type":"array","title":"Integrations","description":"The cloud-VMS connections assigned to this site, at most one per provider, ordered by provider. The full owner projection — never a credential."},"available_integrations":{"items":{"$ref":"#/components/schemas/SiteIntegrationOption"},"type":"array","title":"Available Integrations","description":"Same-customer connections selectable for this site. Only ID, provider and display label are exposed; credentials, vendor catalog and sibling assignments are excluded. Existing assignments and live cameras may require removal before switching providers' connections."}},"type":"object","required":["integrations"],"title":"GetAssignedSiteIntegrationsResponse"},"GetBillingHistoryResponse":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/BillingHistoryEntry"},"type":"array","title":"Entries","description":"Changes, newest first."},"total":{"type":"integer","title":"Total","description":"All changes recorded.","examples":[42]}},"type":"object","required":["entries","total"],"title":"GetBillingHistoryResponse"},"GetBillingOverviewResponse":{"properties":{"totals":{"$ref":"#/components/schemas/BillingTotals","description":"Fleet totals."},"parents":{"items":{"$ref":"#/components/schemas/BillingParent"},"type":"array","title":"Parents","description":"Every live client with its accounts, sites and cameras."}},"type":"object","required":["totals","parents"],"title":"GetBillingOverviewResponse"},"GetCameraAlarmHeatResponse":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera row id."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"End of both windows (server clock)."},"grid_w":{"type":"integer","title":"Grid W","description":"Grid width in cells.","examples":[64]},"grid_h":{"type":"integer","title":"Grid H","description":"Grid height in cells.","examples":[36]},"layers":{"items":{"$ref":"#/components/schemas/AlarmHeatLayer"},"type":"array","title":"Layers","description":"One grid per window x source x object class (8)."},"summaries":{"items":{"$ref":"#/components/schemas/AlarmHeatSummary"},"type":"array","title":"Summaries","description":"Alarm counts per window x source (4)."}},"type":"object","required":["camera_id","window_end","grid_w","grid_h","layers","summaries"],"title":"GetCameraAlarmHeatResponse"},"GetCameraAlarmVolumeResponse":{"properties":{"window_end":{"type":"string","format":"date-time","title":"Window End","description":"End of both windows (server clock)."},"cameras":{"items":{"$ref":"#/components/schemas/CameraAlarmVolume"},"type":"array","title":"Cameras","description":"Every live camera in the caller's scope, zeros included."}},"type":"object","required":["window_end","cameras"],"title":"GetCameraAlarmVolumeResponse"},"GetCameraByIdResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera ID."},"camera_label":{"type":"string","title":"Camera Label","description":"The label of the camera.","examples":["SD Sewing Company"]},"camera_id":{"type":"string","title":"Camera Id","description":"The camera ID.","examples":["cam01"]},"kvs_stream_name":{"type":"string","title":"Kvs Stream Name","description":"The KVS stream name.","examples":["dev-home-cam01"]},"ip_address":{"type":"string","title":"Ip Address","description":"The IP address of the camera.","examples":["192.0.0.1"]},"port":{"type":"string","title":"Port","description":"The port of the camera.","examples":["554"]},"username":{"type":"string","title":"Username","description":"Camera account username (the password is never returned).","examples":["admin"]},"integration":{"$ref":"#/components/schemas/CameraIntegration","description":"The integration of the camera.","examples":["Axis"]},"adapter":{"anyOf":[{"$ref":"#/components/schemas/CameraAdapter"},{"type":"null"}],"description":"The adapter of the camera.","examples":["hanwha_onvif"]},"brand":{"anyOf":[{"$ref":"#/components/schemas/CameraBrand"},{"type":"null"}],"description":"The brand of the camera.","examples":["hanwha"]},"model":{"anyOf":[{"$ref":"#/components/schemas/CameraModel"},{"type":"null"}],"description":"The model of the camera.","examples":["ANV-L7012R"]},"metadata_format":{"anyOf":[{"$ref":"#/components/schemas/CameraMetadataFormat"},{"type":"null"}],"description":"The metadata format of the camera.","examples":["onvif"]},"rtsp_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rtsp Url","description":"The camera's RTSP URL with its password redacted. Never the working credentialed URL. See EditCameraRequest.rtsp_url for the write contract (blank or a REDACTED-bearing echo means keep).","examples":["rtsp://admin:REDACTED@10.0.0.1:554/stream1"]},"rag_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rag Context","description":"Free-form scene context passed to the AI pipeline for this camera.","examples":["Loading dock, forklifts active until 22:00"]},"ingest_mode":{"$ref":"#/components/schemas/CameraIngestMode","description":"continuous = 24/7 KVS archiving; motion_clip = clips uploaded only on motion/analytics trigger.","default":"continuous"},"motion_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Motion Config","description":"Extensible motion settings with bounded known keys (timings, thresholds, detector geometry/rate and RTSP buffer mode); see app/services/motion_config_validation.py. Unknown keys are preserved.","examples":[{"debounce_seconds":30,"post_roll_seconds":5,"pre_roll_seconds":5}]},"analytics_provider":{"$ref":"#/components/schemas/AnalyticsProvider","description":"Analytics vendor validating triggered clips before upload; 'none' means frame-diff only.","default":"none"},"analytics_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Analytics Config","description":"Opaque per-vendor analytics settings interpreted by the ingestor."},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Incident intelligence on/off; when false motion events still record and alert, but skip AI classification.","default":true},"fire_watch":{"type":"boolean","title":"Fire Watch","description":"Fire-screen gate; when true, analytics-rejected motion is still screened for smoke/flame before being discarded.","default":true},"monitoring_profile":{"$ref":"#/components/schemas/CameraMonitoringProfile","description":"Which AI prompt template classifies this camera's clips (owner-only): 'security' = intrusion/theft/robbery, 'safety' = slips/falls/person-down.","default":"security"},"live_view_only":{"type":"boolean","title":"Live View Only","description":"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.","default":false},"display_rotation":{"type":"integer","title":"Display Rotation","description":"Degrees counter-clockwise the recorded frame is turned for display (0 or 180). Set by an owner for an upside-down camera; clips are then stamped with a display-matrix tag and the camera's zones are stored in the rotated (upright) space.","default":0},"agent_mode":{"type":"boolean","title":"Agent Mode","description":"Agent mode on/off; when true the backend acts on AI recommendations (auto-dismiss / voice-down / escalate-upgrade) instead of holding every clip for human review.","default":false},"agent_observation_minutes":{"type":"integer","title":"Agent Observation Minutes","description":"Post-voice-down observation window in minutes before an alert auto-dismisses (agent mode).","default":10},"agent_incident_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Agent Incident Types","description":"Incident types the agent may process at all on this camera; unselected types go to the human queue. Auto-dismiss is additionally bounded by the global safety allowlist."},"incident_routing":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Incident Routing","description":"Per-camera incident routing: incident type -> 'ignore' (dropped and auto-dismissed as not monitored) | 'humans' (operator queue) | 'agent'. A type absent from the map routes to 'humans'; UNKNOWN is never present. Supersedes agent_incident_types, which is now derived from this and kept for one release. 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.","examples":[{"FIRE":"ignore","LOITERING":"agent","NORMAL":"humans"}]},"detection_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Mode","description":"DeepAlert Phase B routing mode (owner-only, written via PUT /cameras/{id}/detection-mode): 'legacy' | 'ii_gated'; null means legacy. Only meaningful when analytics_provider == 'deepalert'.","examples":["ii_gated"]},"ptz":{"type":"boolean","title":"Ptz","description":"Whether this camera is pan/tilt/zoom; when true the 'camera moved' health issue is suppressed.","default":false},"integration_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Integration Id","description":"The cloud-VMS connection (site_integrations.id) this camera was onboarded from; null for NVR and direct-RTSP cameras."},"external_device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Device Id","description":"The VENDOR's own camera id within that connection; null unless integration_id is set.","examples":["3a1b0f4c-1d2e-4f5a-9b8c-7d6e5f4a3b2c"]},"integration_device_info":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Integration Device Info","description":"Snapshot of what the vendor reported about the device at import (name, model, site, codec); null unless integration_id is set."},"live_view_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Live View Until","description":"On-demand live view is active while now() < this timestamp (motion_clip cameras)."},"healthy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Healthy","description":"The healthy status of the camera.","examples":[true,false]},"last_healthcheck_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Healthcheck At","description":"The last healthcheck at of the camera."},"map_x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x position (0..1) on the site map.","examples":[0.42]},"map_y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y position (0..1) on the site map.","examples":[0.63]},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude on the site map.","examples":[42.0443]},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude on the site map.","examples":[-87.9825]},"map_location_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose shown on queue rows and mini-maps.","examples":["NE corner — loading dock"]},"map_heading":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Heading","description":"Direction the device faces, degrees clockwise from map-up; null = not aimed.","examples":[225]},"group_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Group Ids","description":"The LIVE camera groups this camera belongs to. Camera groups are site-scoped and a camera may be in several at once; membership alone changes nothing until a group is given a schedule."},"monitoring_source":{"anyOf":[{"$ref":"#/components/schemas/MonitoringSourceModel"},{"type":"null"}],"description":"Which schedule target produces this camera's monitoring state right now — 'disarm' | 'arm' | 'holiday' | 'camera' | 'site' | 'group' | 'default'. Reported so an off camera names the thing whose edit would turn it back on, and so a camera live outside its schedule names the test arm holding it on. See MonitoringSourceModel.source for what each value means."},"deepalert_threshold":{"anyOf":[{"$ref":"#/components/schemas/DeepAlertThresholdModel"},{"type":"null"}],"description":"Read-only, server-resolved DeepAlert sensitivity for this camera (see DeepAlertThresholdModel). Never part of analytics_config; a client must not write it back."}},"type":"object","required":["id","camera_label","camera_id","kvs_stream_name","ip_address","port","username","integration"],"title":"GetCameraByIdResponse"},"GetCameraGroupsResponse":{"properties":{"groups":{"items":{"$ref":"#/components/schemas/CameraGroupModel"},"type":"array","title":"Groups","description":"Live camera groups at this site, by name."}},"type":"object","required":["groups"],"title":"GetCameraGroupsResponse"},"GetCameraHealthAnalyticsResponse":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"Single server instant the whole response was computed at; every expires_in is relative to it."},"thresholds":{"$ref":"#/components/schemas/CameraHealthThresholds","description":"The server's issue thresholds — render from these rather than hardcoding."},"recheck_cooldown_seconds":{"type":"integer","title":"Recheck Cooldown Seconds","description":"A camera re-checked within this many seconds is not re-checked again (POST /analytics/camera-health/recheck counts it as already_pending). Published so the UI shows the window the server enforces.","examples":[120]},"cameras":{"items":{"$ref":"#/components/schemas/CameraHealthRow"},"type":"array","title":"Cameras","description":"One row per in-scope camera, sorted needs-attention first, then site name, then camera label."}},"type":"object","required":["generated_at","thresholds","recheck_cooldown_seconds","cameras"],"title":"GetCameraHealthAnalyticsResponse"},"GetCameraHealthResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Camera UUID","examples":["2b1d3f2e-6e78-4d9b-93d6-3c1f9e40a1d4"]},"healthy":{"type":"boolean","title":"Healthy","description":"Whether the camera is healthy based on the last heartbeat within the threshold","examples":[true]},"last_healthcheck_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Healthcheck At","description":"Timestamp (UTC) of the last health check, if any","examples":["2026-05-07T20:51:53.762+00:00"]}},"type":"object","required":["id","healthy"],"title":"GetCameraHealthResponse"},"GetCameraHealthSpeakersResponse":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"When this answer was built."},"summary":{"$ref":"#/components/schemas/CameraHealthSpeakersSummary","description":"Counts over the caller's scope."},"speakers":{"items":{"$ref":"#/components/schemas/CameraHealthSpeakerRow"},"type":"array","title":"Speakers","description":"Speakers whose last check failed, longest down first (unknown onsets lead), then client, site, label."}},"type":"object","required":["generated_at","summary","speakers"],"title":"GetCameraHealthSpeakersResponse"},"GetCameraSnapshotResponse":{"properties":{"snapshot_url":{"type":"string","title":"Snapshot Url","description":"Presigned S3 URL of the camera's most recent scene snapshot (JPEG)."},"captured_at":{"type":"string","format":"date-time","title":"Captured At","description":"ISO-8601 time the snapshot was captured (S3 object last-modified — the ingestor overwrites one key per camera, so upload time is capture time).","examples":["2026-07-27T12:00:00+00:00"]},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until snapshot_url expires.","examples":[3600]}},"type":"object","required":["snapshot_url","captured_at","expires_in"],"title":"GetCameraSnapshotResponse"},"GetCameraViewFlagsResponse":{"properties":{"flags":{"items":{"$ref":"#/components/schemas/CameraViewFlag"},"type":"array","title":"Flags","description":"Open and confirmed items, plus items cleared or resolved in the last 7 days, most recently raised first."},"counts":{"$ref":"#/components/schemas/CameraViewFlagCounts","description":"How many open and confirmed items are in scope."}},"type":"object","required":["flags","counts"],"title":"GetCameraViewFlagsResponse"},"GetCameraZonesResponse":{"properties":{"zones":{"items":{"$ref":"#/components/schemas/IgnoreZoneModel"},"type":"array","title":"Zones","description":"All ignore zones configured for the camera."},"revision":{"type":"string","title":"Revision","description":"Opaque snapshot revision to send as expected_revision when replacing zones."},"display_rotation":{"type":"integer","title":"Display Rotation","description":"The camera's display rotation (degrees CCW, 0 or 180). Zone points are percent of the frame in the ROTATED (upright) space, i.e. the picture as shown after this rotation; a clip recorded before the rotation took effect carries no tag and shows the recorded frame, so its zones need the same turn to line up.","default":0}},"type":"object","required":["zones","revision"],"title":"GetCameraZonesResponse"},"GetCamerasHealthResponse":{"properties":{"cameras":{"items":{"$ref":"#/components/schemas/GetCameraHealthResponse"},"type":"array","title":"Cameras","description":"Health of every non-retired camera the caller may see (optionally narrowed by site_id / parent_id). Each entry is exactly what GET /cameras/{camera_id}/health returns for that camera."},"stale_after_seconds":{"type":"integer","title":"Stale After Seconds","description":"A camera whose last ingestor health report is older than this reads healthy=false regardless of the flag it last reported.","examples":[300]}},"type":"object","required":["cameras","stale_after_seconds"],"title":"GetCamerasHealthResponse"},"GetCamerasLivestreamResponse":{"properties":{"hls_url":{"type":"string","title":"Hls Url","description":"HLS URL"}},"type":"object","required":["hls_url"],"title":"GetCamerasLivestreamResponse"},"GetCoverageGroupsResponse":{"properties":{"groups":{"items":{"$ref":"#/components/schemas/CoverageGroupModel"},"type":"array","title":"Groups","description":"All coverage groups with their grants."},"ungrouped_site_count":{"type":"integer","title":"Ungrouped Site Count","description":"Sites not covered by any group - alerts from them have no scheduled owner.","examples":[7]}},"type":"object","required":["groups","ungrouped_site_count"],"title":"GetCoverageGroupsResponse"},"GetDeliveryLatencyResponse":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"When this payload was computed. Served from a ~5-minute per-process cache, so two consecutive polls can land on different API tasks and see slightly different values — key any freshness banner off your own fetch time, not off this alone."},"cache_age_seconds":{"type":"integer","title":"Cache Age Seconds","description":"How old the served payload is, in seconds. 0 means this request computed it.","examples":[137]},"window_hours":{"type":"integer","title":"Window Hours","description":"How many CLOCK-HOUR BUCKETS the window is: 24 or 168. `hourly` and every `hourly_p50_s` have exactly this many entries. It is a bucket count, NOT the elapsed span — the last bucket is the hour in progress and is partial, so the real span is `window_end - window_start` = 23 (or 167) whole hours plus the elapsed part of the current hour. A '24h' window at 18:05 therefore does NOT include yesterday 18:05-19:00.","examples":[24]},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Inclusive start of the window, in UTC — the top of the oldest bucket, and the same value as `hourly[0].hour`. Published so a consumer can state the span it was actually given rather than inferring a rolling window from `window_hours`.","examples":["2026-09-15T19:00:00+00:00"]},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Exclusive end of the window, in UTC — `generated_at`, i.e. mid-hour. Truncated to the hour it equals `hourly[-1].hour`, the partial bucket in progress.","examples":["2026-09-16T18:05:11.204000+00:00"]},"fleet":{"$ref":"#/components/schemas/DeliveryLatencyFleet","description":"Fleet-wide numbers over the selected window."},"hourly":{"items":{"$ref":"#/components/schemas/DeliveryLatencyHour"},"type":"array","title":"Hourly","description":"Dense hourly series, oldest first, exactly `window_hours` entries."},"cameras":{"items":{"$ref":"#/components/schemas/DeliveryLatencyCamera"},"type":"array","title":"Cameras","description":"Cameras ranked by p90 motion-to-queue, SLOWEST FIRST: at most `top_cameras` rows, and only cameras with at least `min_camera_events` eligible alarms. The TAIL of this list is the fastest AMONG THE RETURNED ROWS, not a second query — it is the fleet's fastest only while the number of qualifying cameras is at or below `top_cameras`, because the cap is applied from the slow end and drops the fastest cameras first. The cap sits far above the monitored fleet, so in practice this is every camera that clears the floor and the tail really is the fleet's fastest. Ties break by eligible-alarm count descending, then camera id ascending, so the order is stable between polls."},"min_camera_events":{"type":"integer","title":"Min Camera Events","description":"Minimum eligible alarms for a camera to appear in `cameras` at all. Published so the UI labels the rule the server actually applied instead of restating a constant that can drift.","examples":[10]},"top_cameras":{"type":"integer","title":"Top Cameras","description":"Maximum number of camera rows returned. A BOUND, NOT A PAGE SIZE — there is no second page, and the value is deliberately set well above the monitored fleet so `cameras` is in practice the complete ranked list. Published, like `min_camera_events`, so the UI labels the rule the server actually applied instead of restating a constant that can drift.","examples":[200]},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Server-authored caveats, with this window's counts already interpolated. Render them verbatim wherever these numbers are shown — several of them are the difference between reading the median correctly and misreading it."}},"type":"object","required":["generated_at","cache_age_seconds","window_hours","window_start","window_end","fleet","min_camera_events","top_cameras"],"title":"GetDeliveryLatencyResponse","description":"Owner-only fleet alarm-delivery latency: how long an alarm takes to get\nfrom motion at the camera onto an operator's screen, and which cameras are\nslowest.\n\nNothing here is tenant-scoped — these are cross-tenant fleet aggregates\nnaming cameras, sites and parents under every customer, which is why the\nroute is owner-only with no filter parameters.\n\nServed from a ~5-minute per-process cache keyed on the window."},"GetDemoLibraryResponse":{"properties":{"items":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Items","description":"Demo event bundles for this page, newest first"},"total":{"type":"integer","title":"Total","description":"Total matching demo videos across every page"},"limit":{"type":"integer","title":"Limit","description":"Page size used for this response"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Keyset cursor for the next page; null when this page is the last"}},"type":"object","required":["items","total","limit"],"title":"GetDemoLibraryResponse"},"GetEscalationsResponse":{"properties":{"window":{"$ref":"#/components/schemas/EscalationWindow","description":"Requested UTC reporting interval."},"total":{"type":"integer","title":"Total","description":"Rows matching the filters, uncapped by `limit`."},"returned":{"type":"integer","title":"Returned","description":"Rows in this response."},"limit":{"type":"integer","title":"Limit","description":"The applied limit."},"kpi_truncated":{"type":"boolean","title":"Kpi Truncated","description":"True when the filtered set exceeded the server's candidate cap and the KPIs cover the newest rows only.","default":false},"kpis":{"$ref":"#/components/schemas/EscalationKpis","description":"Timing medians over the full filtered candidate set."},"rows":{"items":{"$ref":"#/components/schemas/EscalationRow"},"type":"array","title":"Rows","description":"Escalation rows in newest-first order."}},"type":"object","required":["window","total","returned","limit","kpis","rows"],"title":"GetEscalationsResponse"},"GetEssayEngagementResponse":{"properties":{"likes":{"type":"integer","title":"Likes","description":"Total likes for the essay."},"comments":{"items":{"$ref":"#/components/schemas/EssayCommentItem"},"type":"array","title":"Comments","description":"Approved comments, oldest first (at most 500)."}},"type":"object","required":["likes","comments"],"title":"GetEssayEngagementResponse"},"GetEventAuditResponse":{"properties":{"audit":{"anyOf":[{"$ref":"#/components/schemas/CallAuditOut"},{"type":"null"}],"description":"Leads see any audit; anyone else only an audit where they are the decider or the auditor."},"reasons":{"items":{"type":"string","enum":["sample","rushed"]},"type":"array","title":"Reasons","description":"Why the call is up for audit. Owners, monitoring managers and operators; empty for read-only accounts. An agent close is only ever 'sample' (its 1% sample)."},"decider":{"anyOf":[{"$ref":"#/components/schemas/AuditDeciderOut"},{"type":"null"}],"description":"Null when the evaluation is missing, the event is a demo, or the decision is not auditable (an agent NOT_MONITORED routing drop or an agent escalation). A human scored decision or an agent close (FALSE_ALARM / SUSPICIOUS_MONITOR) is returned, the latter with is_agent true."},"can_audit":{"type":"boolean","title":"Can Audit","description":"The caller is an owner, monitoring manager or operator, a decider exists and is not the caller, and either there is no audit or the caller is a lead and the existing audit is a peer uphold / a still-pending peer overturn (which they may replace)."},"can_review":{"type":"boolean","title":"Can Review","description":"Lead, the audit is an overturn awaiting review, and the caller is not the decider.","default":false},"can_dispute":{"type":"boolean","title":"Can Dispute","description":"The caller is a writable staff decider, the audit is a CONFIRMED overturn and nothing has been disputed yet. Always false for an audit of the AI agent's call."},"can_rule_dispute":{"type":"boolean","title":"Can Rule Dispute","description":"Lead, a dispute is pending, and the caller is neither the decider nor the lead who confirmed the overturn."}},"type":"object","required":["can_audit","can_dispute","can_rule_dispute"],"title":"GetEventAuditResponse"},"GetEventByIdResponse":{"properties":{"event":{"$ref":"#/components/schemas/EventsModel","description":"The event information"},"camera":{"anyOf":[{"$ref":"#/components/schemas/CamerasModel"},{"type":"null"}],"description":"The camera information"},"site":{"anyOf":[{"$ref":"#/components/schemas/SitesModel"},{"type":"null"}],"description":"The site information"},"parent":{"anyOf":[{"$ref":"#/components/schemas/ParentModel"},{"type":"null"}],"description":"The parent information"},"operator_evaluation":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Operator Evaluation","description":"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":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Interpretation Result","description":"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":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Agent Action","description":"Newest agent action for this event (voice-down lifecycle states), if any"},"thread":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Thread","description":"Incident-thread summary when this event is part of a cross-camera thread; GET /threads/{id} returns the full timeline"},"extended_clip_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extended Clip Url","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Poster Url","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Continued Poster Url","description":"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":{"anyOf":[{"$ref":"#/components/schemas/OperatorAlarmCaptureModel"},{"type":"null"}],"description":"Operator-raised alarms only: the state of the alarm's own clip recording, {status, reason}. pending | claimed means the clip is still recording (re-poll GET /events/{event_id}, which sets event.clip_s3_url once it lands); captured means it landed; unavailable | failed means no clip will come. Null/absent on every other event, and on an operator-raised alarm for which no recording was requested."},"talk_down_available":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Talk Down Available","description":"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":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Talk Down Available As Of","description":"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":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ai Announcement Available","description":"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":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ai Announcement Available As Of","description":"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":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Voice Agent Call","description":"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":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ai Feedback Requested","description":"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."}},"type":"object","required":["event"],"title":"GetEventByIdResponse"},"GetEventCapturesResponse":{"properties":{"captures":{"items":{"$ref":"#/components/schemas/EventCaptureResponse"},"type":"array","title":"Captures","description":"Every after-voice-down capture on this alarm, oldest first."}},"type":"object","required":["captures"],"title":"GetEventCapturesResponse"},"GetEventClipUrlResponse":{"properties":{"clip_url":{"type":"string","title":"Clip Url","description":"Freshly presigned URL for the event's clip"},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the presigned URL expires"},"extended_clip_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extended Clip Url","description":"Presigned URL for the event's extended pre-roll (\"Rewind\") clip, valid for the same expires_in as clip_url. 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)."},"continued_clip_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Continued Clip Url","description":"Presigned URL for the event's continued clip (the extra seconds cut when motion was still firing after the clip's post-roll), valid for the same expires_in as clip_url. Null when the alarm has no committed continued clip (event.has_continued_clip false), when the continuation is older than the clip retention window (it has expired from storage; same window as the main clip's clips/ lifecycle) or the presign failed. Unlike extended_clip_url it is NOT blind: it is only minted after the ingestor committed the upload."},"continued_poster_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Continued Poster Url","description":"Presigned URL for the continued clip's poster frame (JPEG), valid for the same expires_in as clip_url. Set under exactly the same conditions as continued_clip_url, else null. Presigned BLIND: the ingestor writes the poster best-effort, so the object may be absent; fall back on a load error."},"clip_display_rotation":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Clip Display Rotation","description":"Display rotation (degrees CCW: 0, 90, 180 or 270) already applied by clip_url's own container tag. 0 = untagged; null = unknown (HEAD failed/timed out or stamp unreadable). Players must not turn a clip again that is tagged non-zero."},"extended_clip_display_rotation":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Extended Clip Display Rotation","description":"Same as clip_display_rotation, for extended_clip_url (Rewind). Inferred from the main clip when the camera was never rotated (not an availability signal); otherwise probed with one HEAD, and then null when the object is absent or the HEAD did not answer within 1 s. Null when there is no extended URL."},"continued_clip_display_rotation":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Continued Clip Display Rotation","description":"Same as clip_display_rotation, for continued_clip_url. Inferred from the main clip when the camera was never rotated (not an availability signal); otherwise probed with one HEAD, and then null when the object is absent or the HEAD did not answer within 1 s. Null when there is no continued clip."}},"type":"object","required":["clip_url","expires_in"],"title":"GetEventClipUrlResponse"},"GetEventHistoryResponse":{"properties":{"items":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Items","description":"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."},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"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)."}},"type":"object","required":["items","total"],"title":"GetEventHistoryResponse"},"GetExternalTalkdownsResponse":{"properties":{"talkdowns":{"items":{"$ref":"#/components/schemas/ExternalTalkdownResponse"},"type":"array","title":"Talkdowns","description":"Every vendor-console talk-down attempt on this event, oldest first. EVENT-scoped, not operator-scoped: a colleague's earlier attempt is part of the same incident's trail."}},"type":"object","required":["talkdowns"],"title":"GetExternalTalkdownsResponse"},"GetFleetBandDetailResponse":{"properties":{"start":{"type":"string","format":"date-time","title":"Start","description":"UTC instant the band begins."},"end":{"type":"string","format":"date-time","title":"End","description":"UTC instant the band ends."},"site_count":{"type":"integer","title":"Site Count","description":"Sites monitored during the band."},"camera_count":{"type":"integer","title":"Camera Count","description":"Cameras monitored during the band."},"heightened_count":{"type":"integer","title":"Heightened Count","description":"Sites with at least one live heightened camera."},"operators":{"items":{"$ref":"#/components/schemas/FleetBandOperatorModel"},"type":"array","title":"Operators","description":"Who is seated, and on which groups. Empty when operators_visible is false."},"operator_names":{"additionalProperties":{"type":"string"},"type":"object","title":"Operator Names","description":"operator_id -> display name. Empty when operators_visible is false."},"operators_visible":{"type":"boolean","title":"Operators Visible","description":"False for callers who may not see center staffing."},"center_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Center Timezone","description":"Configured monitoring-center zone; null when unset."},"sites":{"items":{"$ref":"#/components/schemas/FleetBandSiteModel"},"type":"array","title":"Sites","description":"Monitored sites in the band, grouped-ready (ordered by client then site). Sites with nothing live are omitted."}},"type":"object","required":["start","end","site_count","camera_count","heightened_count","operators","operator_names","operators_visible","sites"],"title":"GetFleetBandDetailResponse","description":"The composition behind one band - what the band popover shows.\n\nSplit from the list endpoint on purpose: a week can hold hundreds of bands\nand a book hundreds of sites, so embedding every band's site list would\nmake the week view carry an order of magnitude more data than it renders."},"GetFleetBandsResponse":{"properties":{"bands":{"items":{"$ref":"#/components/schemas/FleetBandModel"},"type":"array","title":"Bands","description":"Bands in the requested window, ordered by start. Bands with nothing monitored are omitted; bands with sites and NO operators are KEPT - unattended coverage is the point of the view."},"operator_names":{"additionalProperties":{"type":"string"},"type":"object","title":"Operator Names","description":"operator_id -> display name. Empty when operators_visible is false."},"operators_visible":{"type":"boolean","title":"Operators Visible","description":"False for callers who may not see center staffing (site managers). Their counts are still computed over their own sites; only the operator identities are withheld, so the UI should hide the seat column rather than render 'nobody seated'."},"center_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Center Timezone","description":"Configured monitoring-center zone; null when unset.","examples":["America/Chicago"]}},"type":"object","required":["bands","operator_names","operators_visible"],"title":"GetFleetBandsResponse"},"GetIncidentCountsResponse":{"properties":{"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Start of the counted window (UTC)."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"End of the counted window (UTC)."},"total":{"type":"integer","title":"Total","description":"All AI detections in the window across the selected scope.","examples":[412]},"totals":{"items":{"$ref":"#/components/schemas/IncidentTypeCount"},"type":"array","title":"Totals","description":"Per-incident-type totals, largest first."},"sites":{"items":{"$ref":"#/components/schemas/SiteIncidentCounts"},"type":"array","title":"Sites","description":"Per-site rollups (only sites with detections appear)."},"cameras":{"items":{"$ref":"#/components/schemas/CameraIncidentCounts"},"type":"array","title":"Cameras","description":"Per-camera rollups (only cameras with detections appear)."},"gate_rejections":{"$ref":"#/components/schemas/GateRejections","description":"DeepAlert Phase B: object-of-interest gate-negative rows, segmented out of the counts above."}},"type":"object","required":["window_start","window_end","total","totals","sites","cameras","gate_rejections"],"title":"GetIncidentCountsResponse"},"GetIncidentEventsResponse":{"properties":{"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Start of the window (UTC)."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"End of the window (UTC)."},"total":{"type":"integer","title":"Total","description":"Matching events in the window (may exceed len(items) when capped by limit).","examples":[53]},"items":{"items":{"$ref":"#/components/schemas/IncidentEventRow"},"type":"array","title":"Items","description":"Newest-first event rows, capped at the requested limit."}},"type":"object","required":["window_start","window_end","total","items"],"title":"GetIncidentEventsResponse"},"GetIntegrationCamerasResponse":{"properties":{"cameras":{"items":{"$ref":"#/components/schemas/ProviderCameraResponse"},"type":"array","title":"Cameras","description":"Cameras enumerated live from the provider."}},"type":"object","required":["cameras"],"title":"GetIntegrationCamerasResponse"},"GetLeaderboardResponse":{"properties":{"window":{"type":"string","enum":["today","week","month","last_month"],"title":"Window","description":"today since local midnight; week since Monday local midnight; month since the 1st local midnight; last_month = the previous calendar month, closed."},"starts_at":{"type":"string","format":"date-time","title":"Starts At","description":"Inclusive window start in UTC."},"ends_at":{"type":"string","format":"date-time","title":"Ends At","description":"Window end in UTC: now for a live window, this month's 1st local midnight for last_month."},"timezone":{"type":"string","title":"Timezone","description":"Monitoring-center IANA time zone used to derive window boundaries."},"rank_metric":{"type":"string","enum":["score","points_per_hour"],"title":"Rank Metric","description":"Field used to rank the board."},"min_hours":{"type":"number","title":"Min Hours","description":"Minimum hours to appear on the board (0 except week)."},"scoring":{"$ref":"#/components/schemas/LeaderboardScoring","description":"Server-authoritative scoring rules."},"rows":{"items":{"$ref":"#/components/schemas/LeaderboardRow"},"type":"array","title":"Rows","description":"Eligible operators in rank order."},"breakdown":{"anyOf":[{"$ref":"#/components/schemas/LeaderboardBreakdown"},{"type":"null"}],"description":"The caller's own, or the requested operator's for a lead; null for a lead with no operator_id."}},"type":"object","required":["window","starts_at","ends_at","timezone","rank_metric","min_hours","scoring","rows"],"title":"GetLeaderboardResponse"},"GetLiveIntentsResponse":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera UUID with an active live-view request."},"live_view_until":{"type":"string","format":"date-time","title":"Live View Until","description":"Push to KVS while now() < this deadline.","examples":["2026-07-03T21:15:00+00:00"]}},"type":"object","required":["camera_id","live_view_until"],"title":"GetLiveIntentsResponse"},"GetLiveViewActivityResponse":{"properties":{"window_minutes":{"type":"integer","title":"Window Minutes","description":"The window the rows were computed over, ending at generated_at.","examples":[5]},"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"Server clock when the rows were computed (UTC); age rows against this, not the client clock."},"cameras":{"items":{"$ref":"#/components/schemas/LiveViewActivityCamera"},"type":"array","title":"Cameras","description":"Most recent activity first. Empty when nobody was seen in the window."}},"type":"object","required":["window_minutes","generated_at","cameras"],"title":"GetLiveViewActivityResponse","description":"`GET /live-view/activity`: where people were seen just now.\n\nOne row per camera in the caller's scope with at least one event in the\nlast `window_minutes` whose NEWEST interpretation is activity (any incident\ntype except UNKNOWN, NORMAL included, and not an object-gate stop).\nDismissed and agent-auto-dismissed events COUNT - the queue drops a row on\ndisposition, which is exactly when an operator wants to keep watching the\ncamera, so this is deliberately not the queue."},"GetLogsResponse":{"properties":{"entries":{"items":{"$ref":"#/components/schemas/SystemLogEntryModel"},"type":"array","title":"Entries","description":"System log entries, most recently active first"},"total":{"type":"integer","title":"Total","description":"Total number of entries matching the filters (for pagination)"}},"type":"object","required":["entries","total"],"title":"GetLogsResponse"},"GetLogsSummaryResponse":{"properties":{"window_hours":{"type":"integer","title":"Window Hours","description":"Size of the reporting window in hours"},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Inclusive start of the window"},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Inclusive end of the window"},"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"When this summary was computed"},"cameras":{"items":{"$ref":"#/components/schemas/CameraPipelineSummaryModel"},"type":"array","title":"Cameras","description":"Per-camera event-flow health, cameras with stranded events first"}},"type":"object","required":["window_hours","window_start","window_end","generated_at","cameras"],"title":"GetLogsSummaryResponse"},"GetLogsTimeseriesResponse":{"properties":{"window_hours":{"type":"integer","title":"Window Hours","description":"Size of the reporting window in hours"},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Inclusive start of the window"},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Inclusive end of the window"},"bucket_minutes":{"type":"integer","title":"Bucket Minutes","description":"Aggregation interval used by each point"},"group_by":{"type":"string","enum":["none","camera","site"],"title":"Group By"},"truncated":{"type":"boolean","title":"Truncated","default":false},"truncated_count":{"type":"integer","minimum":0.0,"title":"Truncated Count","default":0},"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"When the time series was computed"},"series":{"items":{"$ref":"#/components/schemas/PipelineTimeseriesSeries"},"type":"array","title":"Series"}},"type":"object","required":["window_hours","window_start","window_end","bucket_minutes","group_by","generated_at","series"],"title":"GetLogsTimeseriesResponse"},"GetMonitoringScheduleResponse":{"properties":{"schedule":{"anyOf":[{"$ref":"#/components/schemas/MonitoringScheduleModel"},{"type":"null"}],"description":"The configured schedule at 15-minute grain, or null when none has been saved."}},"type":"object","title":"GetMonitoringScheduleResponse"},"GetNvrDevicesResponse":{"properties":{"stream_access":{"type":"string","title":"Stream Access","description":"Whether the stored account can open video through the NVR's RTSP relay. auth_unsupported = no Basic/Digest offered (enable digest authentication on the user); auth_rejected = credentials refused for video; camera_forbidden = every camera tried answered 403 after Digest (check camera access permissions); rtsp_unreachable = (Hikvision only) the HTTP sign-in works but nothing answered on the RTSP port; unknown = not checked or inconclusive.","default":"unknown","examples":["ok","auth_unsupported","auth_rejected","camera_forbidden","rtsp_unreachable","unknown"]},"devices":{"items":{"$ref":"#/components/schemas/NvrDeviceResponse"},"type":"array","title":"Devices","description":"Cameras enumerated live from the NVR."}},"type":"object","required":["devices"],"title":"GetNvrDevicesResponse"},"GetNvrsResponse":{"properties":{"nvrs":{"items":{"$ref":"#/components/schemas/NvrResponse"},"type":"array","title":"Nvrs","description":"NVRs registered for this site."}},"type":"object","required":["nvrs"],"title":"GetNvrsResponse"},"GetOnShiftGroupsResponse":{"properties":{"as_of":{"type":"string","format":"date-time","title":"As Of","description":"The instant the roster was resolved for."},"groups":{"items":{"$ref":"#/components/schemas/OnShiftGroupModel"},"type":"array","title":"Groups","description":"The coverage groups the caller may see, sorted by name. An unconfined caller sees every group; a confined caller sees only groups holding a grant its tenant scope allows."},"also_clocked_in":{"items":{"$ref":"#/components/schemas/OnShiftOperatorModel"},"type":"array","title":"Also Clocked In","description":"Operators clocked in now and seated on no running shift of ANY coverage group (punches older than 24h are treated as forgotten and left out). Floor-wide, so it is returned only to unconfined callers; a confined caller always gets an empty list."}},"type":"object","required":["as_of","groups","also_clocked_in"],"title":"GetOnShiftGroupsResponse"},"GetOperatorResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of operator","examples":["<Some UUID of some sort>"]},"first_name":{"type":"string","title":"First Name","description":"The first name of the operator","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"The last name of the operator","examples":["Johnson"]},"email":{"type":"string","title":"Email","description":"The email address of the operator","examples":["JJ@example.com"]},"role":{"$ref":"#/components/schemas/OperatorRole","description":"The role of the operator","examples":["owner","operator"]},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At","description":"The time the operator was last seen"},"last_logged_in":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Logged In","description":"The time the operator last logged in"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"The time the operator was created"},"permissions":{"anyOf":[{"additionalProperties":{"type":"boolean"},"type":"object"},{"type":"null"}],"title":"Permissions","description":"Per-permission overrides ({OperatorPermission value: bool}); null means role defaults apply","examples":[{"manage_users":false,"view_alarm_queue":true}]},"is_primary_owner":{"type":"boolean","title":"Is Primary Owner","description":"The account anchor: cannot be removed or demoted, and only they may edit their own record. Not settable via the API.","default":false},"parents":{"items":{"$ref":"#/components/schemas/OperatorParent"},"type":"array","title":"Parents","description":"The parents of the operator"},"sites":{"items":{"$ref":"#/components/schemas/OperatorSite"},"type":"array","title":"Sites","description":"Per-site access grants (site-manager / site-scoped operators). Empty means access follows `parents` (all sites). Exactly one entry for a location_manager, whose `parents` entry is derived from it."},"accounts":{"items":{"$ref":"#/components/schemas/OperatorAccount"},"type":"array","title":"Accounts","description":"Account access grant (account managers). At most one entry. When present, `parents` and `sites` are empty: both are DERIVED from the account, and the operator sees whichever sites currently carry this account_id — including sites added later. Empty for every other role."},"coverage_groups":{"items":{"$ref":"#/components/schemas/OperatorCoverageGroup"},"type":"array","title":"Coverage Groups","description":"Standing coverage-group membership. The operator's site access is the UNION of these groups' grants and the `parents`/`sites` rows above, which are an ADDITIVE exception list ('also sees...'). SCOPE ONLY, NEVER PERMISSION: a member gains sites, never a capability. Empty for site_manager / account_manager / location_manager (structurally scoped, refused membership) and for owner / monitoring_manager (already unconfined, so a membership would be inert)."},"all_sites":{"type":"boolean","title":"All Sites","description":"EFFECTIVE all-sites access (operator / read_only only; false for every other role). When true the user sees every client and site, including future ones, and their coverage groups and parent/site rows are retained but inert. Set via PUT /operator/{id}/site-access.","default":false},"deactivated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deactivated At","description":"When this user was deactivated (POST /operator/{id}/deactivate), or null while active. A deactivated user cannot log in and holds no seats or coverage groups; their records are kept."}},"type":"object","required":["id","first_name","last_name","email","role","created_at","parents"],"title":"GetOperatorResponse"},"GetOrgSettingsResponse":{"properties":{"monitoring_center_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Center Timezone","description":"IANA timezone the monitoring center's operators work in; null when unset (frontend shows site-local time only).","examples":["America/New_York"]},"slow_triage_target_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Slow Triage Target Seconds","description":"Operator Performance 'slow triage' target in seconds (event-created -> disposition); null when unset (frontend falls back to a 90s default).","examples":[90]},"ai_feedback_capture_enabled":{"type":"boolean","title":"Ai Feedback Capture Enabled","description":"Dark-by-default gate for the operator AI-feedback panel. Written ONLY through PUT /settings/ai-feedback-capture-enabled (owner-only) — never this resource's own PUT, which is timezone-only. False until an owner turns it on; there is no meaningful 'unset' distinct from off.","default":false},"deepalert_default_confidence_threshold":{"type":"number","title":"Deepalert Default Confidence Threshold","description":"The FLEET default DeepAlert confidence floor (0-100, lower = more sensitive) an ii_gated camera with no valid per-camera override uses. 20 until an owner changes it through PUT /settings/deepalert-default-confidence-threshold. Read-only here. Does not govern cameras on the legacy / II-off ingestion path.","examples":[20.0]},"deepalert_default_confidence_revision":{"type":"integer","title":"Deepalert Default Confidence Revision","description":"Revision of that fleet value: 0 at the default, +1 on every owner change. Send it back as expected_revision on the owner PUT.","examples":[0]}},"type":"object","required":["deepalert_default_confidence_threshold","deepalert_default_confidence_revision"],"title":"GetOrgSettingsResponse"},"GetParentAccountsResponse":{"properties":{"accounts":{"items":{"$ref":"#/components/schemas/AccountsModel"},"type":"array","title":"Accounts","description":"List of the client's accounts."}},"type":"object","required":["accounts"],"title":"GetParentAccountsResponse"},"GetParentCamerasIngestorResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Camera UUID","examples":["2b1d3f2e-6e78-4d9b-93d6-3c1f9e40a1d4"]},"kvs_stream_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kvs Stream Name","description":"Kinesis Video Streams stream name for this camera","examples":["site-123/cam-001"]},"rtsp_arn":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rtsp Arn","description":"AWS Secrets Manager ARN storing the RTSP URL","examples":["arn:aws:secretsmanager:us-west-2:123456789012:secret:verdun/site/.."]},"adapter":{"anyOf":[{"$ref":"#/components/schemas/CameraAdapter"},{"type":"null"}],"description":"Adapter used by the ingestor (enum)","examples":["generic_onvif"]},"brand":{"anyOf":[{"$ref":"#/components/schemas/CameraBrand"},{"type":"null"}],"description":"Camera brand (enum)","examples":["hanwha"]},"model":{"anyOf":[{"$ref":"#/components/schemas/CameraModel"},{"type":"null"}],"description":"Camera model (enum)","examples":["ANV-L7012R"]},"metadata_format":{"anyOf":[{"$ref":"#/components/schemas/CameraMetadataFormat"},{"type":"null"}],"description":"Metadata format exposed by the camera (enum)","examples":["onvif"]},"ingest_mode":{"$ref":"#/components/schemas/CameraIngestMode","description":"continuous = push 24/7 into KVS; motion_clip = local buffer, upload clips only on trigger","default":"continuous","examples":["motion_clip"]},"motion_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Motion Config","description":"Extensible motion settings with bounded known keys (timings, thresholds, detector geometry/rate and RTSP buffer mode); see app/services/motion_config_validation.py. Unknown keys are preserved.","examples":[{"debounce_seconds":30,"post_roll_seconds":5,"pre_roll_seconds":5}]},"analytics_provider":{"$ref":"#/components/schemas/AnalyticsProvider","description":"Analytics vendor validating triggered clips before upload ('none' = frame-diff only)","default":"none","examples":["none"]},"analytics_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Analytics Config","description":"Opaque per-vendor analytics settings interpreted by the ingestor"},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Incident intelligence on/off: when false motion events still record and alert in the UI, but clips are not sent to the AI pipeline for classification","default":true,"examples":[true]},"fire_watch":{"type":"boolean","title":"Fire Watch","description":"Per-camera fire-screen gate; when false the ingestor must not call /screen/fire for this camera","default":true,"examples":[true]},"detection_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Mode","description":"'legacy' | 'ii_gated' | null (null means legacy). Only meaningful when analytics_provider == 'deepalert'.","examples":["ii_gated"]},"live_view_until":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Live View Until","description":"ISO-8601 deadline: push to KVS for live view while now() < this (motion_clip cameras)","examples":["2026-07-03T21:15:00+00:00"]},"health_sample_requested_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Health Sample Requested At","description":"ISO-8601 time an operator last asked for an on-demand health sample (Analytics -> Camera Health -> Re-check). A value the ingestor has not answered yet means: take one health sample now and post it with on_demand=true. Null when never requested. Not configuration: must not restart the runner.","examples":["2026-10-09T09:30:00+00:00"]},"monitoring_active":{"type":"boolean","title":"Monitoring Active","description":"Schedule gate, resolved server-side at request time in the site's timezone (camera override, else site schedule, else always-on). False = off-schedule: the ingestor must not run this camera except to serve an active live-view intent.","default":true,"examples":[true]},"display_rotation":{"type":"integer","title":"Display Rotation","description":"Degrees counter-clockwise the RECORDED frame must be turned to look upright (0 or 180 today; 90/270 reserved). When non-zero the ingestor stamps each clip's display-matrix tag inside its stream-copy remux (no re-encode) and inverse-rotates ignore_zones for its raw, untagged detector frames, because ignore_zones are stored in the ROTATED (upright) space. An ingestor that predates this field ignores the key and keeps producing untagged clips; a newer one reads a missing key as 0.","default":0,"examples":[180]},"ignore_zones":{"items":{"$ref":"#/components/schemas/IngestorIgnoreZone"},"type":"array","title":"Ignore Zones","description":"Frame regions the ingestor should mask out of motion detection. Only zones with ignore=true are sent (annotation-only zones are withheld); [] when none. This endpoint only exposes the configuration — enforcement lives in ingestion-server, which masks its frame-diff so motion occurring solely inside these polygons produces no clip, no DeepAlert call, and no event. Ingestor versions without mask support ignore the field."}},"type":"object","required":["id"],"title":"GetParentCamerasIngestorResponse"},"GetParentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique client id","examples":["<UUID of some sort>"]},"company_name":{"type":"string","title":"Company Name","description":"Name of the client","examples":["Joe"]},"type":{"$ref":"#/components/schemas/ParentType","description":"Type of the client","examples":["End User","Internal"]},"primary_contact_name":{"type":"string","title":"Primary Contact Name","description":"Name of the primary contact","examples":["Joe Johnson"]},"primary_contact_phone_number":{"type":"string","title":"Primary Contact Phone Number","description":"Phone number of the primary contact","examples":["1013217890"]},"primary_contact_email":{"type":"string","title":"Primary Contact Email","description":"Email of the primary contact","examples":["JJ@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Notes about the client","examples":["Super important notes"]},"disarm_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Number","description":"The dealer's SMS line, which their customers text with a site passcode to turn cameras off. Null until one is bought.","examples":["+18015550199"]},"disarm_number_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Number State","description":"'purchasing' means a purchase started and its outcome is unconfirmed -- re-POST to reconcile. 'attached' is display only: inbound texts route regardless of campaign attachment. 'releasing' means the line is being given back to Surge: new disarms are refused for this client until it clears, and a re-DELETE reconciles it.","examples":["purchasing","purchased","attached","releasing"]}},"type":"object","required":["id","company_name","type","primary_contact_name","primary_contact_phone_number","primary_contact_email"],"title":"GetParentResponse"},"GetParentSitesResponse":{"properties":{"sites":{"items":{"$ref":"#/components/schemas/SitesModel"},"type":"array","title":"Sites","description":"List of sites"}},"type":"object","required":["sites"],"title":"GetParentSitesResponse"},"GetScheduleResponse":{"properties":{"schedule":{"anyOf":[{"$ref":"#/components/schemas/ScheduleModel"},{"type":"null"}],"description":"The configured schedule, or null when none has been saved."}},"type":"object","title":"GetScheduleResponse"},"GetShiftBlocksResponse":{"properties":{"blocks":{"items":{"$ref":"#/components/schemas/ShiftBlockModel"},"type":"array","title":"Blocks","description":"Every shift overlapping the requested range, ordered by start. Overlapping shifts are normal - a 15-minute handoff is two of them."},"operator_names":{"additionalProperties":{"type":"string"},"type":"object","title":"Operator Names","description":"operator_id -> display name for every operator seated in blocks."}},"type":"object","required":["blocks","operator_names"],"title":"GetShiftBlocksResponse"},"GetShiftsResponse":{"properties":{"cells":{"items":{"$ref":"#/components/schemas/ShiftCellModel"},"type":"array","title":"Cells","description":"Every non-empty (group, date, hour) cell in the requested range, seats in order (index 0 = primary)."},"operator_names":{"additionalProperties":{"type":"string"},"type":"object","title":"Operator Names","description":"operator_id -> display name for every operator appearing in cells."}},"type":"object","required":["cells","operator_names"],"title":"GetShiftsResponse"},"GetSiteByIdResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the site"},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"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":{"type":"string","title":"Name","description":"Name of the site","examples":["San Diego Sewing Company - 1"]},"address":{"type":"string","title":"Address","description":"Address of the site","examples":["1234 Hairy Cherry Lane"]},"city":{"type":"string","title":"City","description":"City of the site","examples":["Shanghai"]},"state":{"type":"string","title":"State","description":"State of the site","examples":["CA"]},"zip_code":{"type":"string","title":"Zip Code","description":"Zip code of the site","examples":["78912"]},"type":{"$ref":"#/components/schemas/SiteType","description":"Type of the site","examples":["Education"]},"priority_tier":{"$ref":"#/components/schemas/SitePriority","description":"Priority of the site","examples":["high"]},"monitoring_hours":{"$ref":"#/components/schemas/MonitoringHours","description":"Monitoring hours of the site","examples":["Nights"]},"timezone":{"type":"string","title":"Timezone","description":"IANA timezone the site's monitoring schedule is evaluated in","examples":["America/Los_Angeles"]},"police_nearest_precinct_name":{"type":"string","title":"Police Nearest Precinct Name","description":"Police nearest precinct name","examples":["SD Police hehe"]},"police_non_emergency_line":{"type":"string","title":"Police Non Emergency Line","description":"Police non-emergency line","examples":["1231231234"]},"police_direct_dispatch_line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Direct Dispatch Line","description":"Police direct dispatch line","examples":["32133"]},"police_jurisdiction_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Jurisdiction Notes","description":"Police jurisdiction notes","examples":["This is the police"]},"alarm_license_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alarm License Number","description":"Dealer alarm license number","examples":["127.001463"]},"alarm_license_issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alarm License Issuer","description":"Who issued the alarm license","examples":["Illinois"]},"mobile_guard_contract":{"type":"boolean","title":"Mobile Guard Contract","description":"Mobile guard contract available?","examples":[true,false]},"guard_company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Company","description":"Mobile guard company name."},"guard_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Unit","description":"Mobile guard unit or contact identifier."},"guard_contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Contact","description":"Mobile guard dispatch phone."},"agent_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Context","description":"Site-wide scene context composed into the LLM prompt for every camera at this site.","examples":["Gated storage facility; groundskeeper drives a white pickup weekday mornings."]},"agent_thread_window_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Agent Thread Window Minutes","description":"Incident-thread join window for this site's agent cameras, in minutes; null = global default.","examples":[5]},"heightened_exceptions":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Heightened Exceptions","description":"Owner-curated heightened-monitoring exception categories; null = none.","examples":[[{"description":"Navy uniforms with badge","label":"Uniformed security"}]]},"holidays":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Holidays","description":"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.","examples":[[{"rule":"thanksgiving"},{"date":"2026-12-24","label":"Christmas Eve half day"}]]},"stage":{"anyOf":[{"type":"string","enum":["live","test"]},{"type":"null"}],"title":"Stage","description":"'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'.","examples":["test","live"]},"closed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Closed","description":"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.","examples":[false]},"onboarding_marks":{"additionalProperties":{"type":"string"},"type":"object","title":"Onboarding Marks","description":"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.","examples":[{"first_night":"2026-10-13T08:00:00.000Z"}]},"map_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Mode","description":"Map-tab mode: 'geo' (geographic pin) or 'floorplan' (uploaded plan); null = not configured.","examples":["geo"]},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude of the site pin (geo mode).","examples":[42.0442]},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude of the site pin (geo mode).","examples":[-87.9816]},"floorplan_width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Floorplan Width","description":"Natural pixel width of the uploaded floor plan.","examples":[1200]},"floorplan_height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Floorplan Height","description":"Natural pixel height of the uploaded floor plan.","examples":[800]},"has_floorplan":{"type":"boolean","title":"Has Floorplan","description":"Whether a floor-plan image is uploaded (fetch it via GET /sites/{id}/floorplan-url).","default":false,"examples":[true,false]},"disarm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Until","description":"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.","examples":["2026-09-05T21:15:00Z"]},"disarm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Set At","description":"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.","examples":["2026-09-05T19:45:00Z"]},"disarm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm By","description":"Who turned the cameras off: 'sms:<E.164>' for a customer text, 'voice:<E.164>' for a customer call, 'operator:<uuid>' from the console.","examples":["sms:+18015550142","voice:+18015550142","operator:6f1c8f2a-..."]},"disarm_active":{"type":"boolean","title":"Disarm Active","description":"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.","default":false,"examples":[true,false]},"arm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Until","description":"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.","examples":["2026-09-18T04:30:00Z"]},"arm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Set At","description":"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.","examples":["2026-09-18T04:00:00Z"]},"arm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arm By","description":"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.","examples":["operator:6f1c8f2a-..."]},"arm_active":{"type":"boolean","title":"Arm Active","description":"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.","default":false,"examples":[true,false]},"disarm_max_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Disarm Max Minutes","description":"The longest a single disarm may last at this site (1-120).","examples":[120]},"live_view_only":{"type":"boolean","title":"Live View Only","description":"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.","default":false,"examples":[false]},"has_disarm_passcode":{"type":"boolean","title":"Has Disarm Passcode","description":"Whether this site has a disarm passcode. Read the code through the dedicated disarm-passcode endpoint.","default":false,"examples":[true,false]},"disarm_passcode_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Passcode Hint","description":"Last two characters of the stored passcode, for rendering it as '****92'.","examples":["92"]},"disarm_passcode_revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Passcode Revision","description":"Opaque revision that changes whenever the passcode is minted or reset, including when its hint is unchanged."},"monitoring_now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Now","description":"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.","examples":["monitored","off_schedule","disarmed","unscheduled","live_view_only"]},"monitoring_next_change_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Monitoring Next Change At","description":"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.","examples":["2026-09-08T04:30:00Z"]},"contacts":{"items":{"$ref":"#/components/schemas/SiteContactResponseModel"},"type":"array","title":"Contacts","description":"List of site contacts"}},"type":"object","required":["id","name","address","city","state","zip_code","type","priority_tier","monitoring_hours","timezone","police_nearest_precinct_name","police_non_emergency_line","mobile_guard_contract","contacts"],"title":"GetSiteByIdResponse"},"GetSiteFloorplanUrlResponse":{"properties":{"url":{"type":"string","title":"Url","description":"Presigned GET URL for the site's floor-plan image (1h TTL — refetch on expiry, never store).","examples":["https://bucket.s3.us-west-2.amazonaws.com/floorplans/..."]},"width":{"type":"integer","title":"Width","description":"Natural pixel width of the floor-plan image.","examples":[1200]},"height":{"type":"integer","title":"Height","description":"Natural pixel height of the floor-plan image.","examples":[800]}},"type":"object","required":["url","width","height"],"title":"GetSiteFloorplanUrlResponse"},"GetSiteIntegrationsResponse":{"properties":{"integrations":{"items":{"$ref":"#/components/schemas/SiteIntegrationResponse"},"type":"array","title":"Integrations","description":"The client's cloud-VMS connections. A client may hold many per provider; each lists the sites it serves in `site_ids`."}},"type":"object","required":["integrations"],"title":"GetSiteIntegrationsResponse"},"GetSiteOnShiftResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site asked about."},"as_of":{"type":"string","format":"date-time","title":"As Of","description":"The instant the roster was resolved for."},"group_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Group Id","description":"The site's coverage group (null when no group covers it)."},"group_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Name","description":"Display name of that group.","examples":["Overnight High-Risk"]},"scheduled":{"items":{"$ref":"#/components/schemas/OnShiftOperatorModel"},"type":"array","title":"Scheduled","description":"Everyone seated on a shift of the site's coverage group running now, primary first. Empty when no group covers the site or nobody is scheduled."},"also_clocked_in":{"items":{"$ref":"#/components/schemas/OnShiftOperatorModel"},"type":"array","title":"Also Clocked In","description":"Operators clocked in now who are NOT scheduled on this site (punches older than 24h are treated as forgotten and left out)."}},"type":"object","required":["site_id","as_of","scheduled","also_clocked_in"],"title":"GetSiteOnShiftResponse"},"GetSiteSummariesResponse":{"properties":{"date":{"type":"string","title":"Date","description":"The date this response is about: YYYY-MM-DD, or the literal 'today' for the live view.","examples":["2026-09-05"]},"live":{"type":"boolean","title":"Live","description":"True when every item is a computed 'today so far' row.","examples":[false]},"live_partial":{"type":"boolean","title":"Live Partial","description":"True when the live view was capped and some in-scope sites are missing from `items`. Always false for a stored date.","examples":[false]},"live_cap":{"type":"integer","title":"Live Cap","description":"How many sites the live 'today so far' view computes before it stops and sets `live_partial`. Reported on every response, stored dates included, so a client can say 'showing the first N' without hardcoding the number.","examples":[50]},"view":{"type":"string","title":"View","description":"'customer' when the caller is a customer-side login (account manager / location manager) and the facts are projected, 'internal' otherwise.","examples":["internal"]},"items":{"items":{"$ref":"#/components/schemas/SiteSummaryListItem"},"type":"array","title":"Items","description":"One row per in-scope site that has something to say for the date; sites with neither a summary nor a merged stub are omitted."}},"type":"object","required":["date","live","live_partial","live_cap","view","items"],"title":"GetSiteSummariesResponse"},"GetSiteSummaryEventsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SiteSummaryEvent"},"type":"array","title":"Items","description":"Newest contributing records first."},"total":{"type":"integer","title":"Total","description":"Current matching record count, before pagination; may differ from a stored snapshot."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Inclusive monitoring-window start."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Exclusive monitoring-window end."},"timezone":{"type":"string","title":"Timezone","description":"Site timezone, snapshotted for a stored summary."}},"type":"object","required":["items","total","window_start","window_end","timezone"],"title":"GetSiteSummaryEventsResponse"},"GetSiteSummaryResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Summary UUID.","examples":["9f8e7d6c-1111-4222-8333-444455556666"]},"site_id":{"type":"string","title":"Site Id","description":"Site UUID.","examples":["3f1e0d2c-1111-4222-8333-444455556666"]},"site_name":{"type":"string","title":"Site Name","description":"Site name as it is today (live), for the drawer header.","examples":["Northgate Yard"]},"parent_id":{"type":"string","title":"Parent Id","description":"Parent UUID SNAPSHOTTED on the summary row, not the site's parent today.","examples":["a1b2c3d4-1111-4222-8333-444455556666"]},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"Account UUID snapshotted on the summary row; null when the site was held directly under the parent at generation.","examples":[null]},"local_date":{"type":"string","title":"Local Date","description":"YYYY-MM-DD, site-local, the date this summary is filed under.","examples":["2026-09-05"]},"timezone":{"type":"string","title":"Timezone","description":"IANA zone snapshotted at generation; every wall clock in `facts` is rendered in it.","examples":["America/Los_Angeles"]},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"First instant the summary covers (UTC, inclusive)."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"Instant the window closed (UTC, exclusive)."},"narrative":{"type":"string","title":"Narrative","description":"The prose the e-mail carries.","examples":["Monitoring covered 12 cameras overnight."]},"narrative_source":{"type":"string","title":"Narrative Source","description":"'llm' when the model's narrative passed grounding, 'template' when the deterministic floor was used.","examples":["llm"]},"grounding":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Grounding","description":"The grounding verdict (checked, mismatches, prompt_version, model, reason). NULL for a customer-side reader: mismatches quote a rejected narrative's tokens.","examples":[{"checked":27,"mismatches":[]}]},"facts":{"additionalProperties":true,"type":"object","title":"Facts","description":"The stored fact sheet. Full for an internal reader; the customer projection (seven paths dropped) for a customer-side login (account manager / location manager) — read `view` to know which.","examples":[{"schema_version":1}]},"inclusions":{"items":{"type":"string"},"type":"array","title":"Inclusions","description":"Event ids ticked INTO the customer e-mail beyond the escalations, in insertion order.","examples":[[]]},"settings":{"$ref":"#/components/schemas/GetSiteSummarySettingsResponse","description":"The site's summary settings as they are NOW, not as they were at generation — the drawer edits them from here."},"recipients_preview":{"items":{"$ref":"#/components/schemas/SiteSummaryRecipient"},"type":"array","title":"Recipients Preview","description":"Who a send would reach right now, per site contact, with the reason for each exclusion."},"generated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Generated At","description":"When generation finished (UTC)."},"regenerated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Regenerated At","description":"When the narrative was last regenerated (UTC); null when it never was."},"view":{"type":"string","title":"View","description":"'customer' when `facts` is the projection and `grounding` is withheld, 'internal' otherwise.","examples":["internal"]}},"type":"object","required":["id","site_id","site_name","parent_id","local_date","timezone","window_start","window_end","narrative","narrative_source","facts","inclusions","settings","recipients_preview","view"],"title":"GetSiteSummaryResponse"},"GetSiteSummarySendsResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SiteSummarySendItem"},"type":"array","title":"Items","description":"Attempts for this summary, newest first."}},"type":"object","required":["items"],"title":"GetSiteSummarySendsResponse"},"GetSiteSummarySettingsResponse":{"properties":{"site_id":{"type":"string","title":"Site Id","description":"Site UUID these settings belong to.","examples":["3f1e0d2c-1111-4222-8333-444455556666"]},"email_enabled":{"type":"boolean","title":"Email Enabled","description":"Whether the daily e-mail is sent for this site at all. Defaults to false — a site opts in.","examples":[false]},"send_local_time":{"type":"string","title":"Send Local Time","description":"24h HH:MM wall clock in the site's timezone. The noon pivot reads off it: a morning send closes last night, an evening send closes today.","examples":["07:00"]},"send_on_quiet_days":{"type":"boolean","title":"Send On Quiet Days","description":"Send even when nothing happened. False skips the mail and records a skipped_quiet send row.","examples":[true]},"min_outage_minutes":{"type":"integer","maximum":1440.0,"minimum":1.0,"title":"Min Outage Minutes","description":"Shortest camera outage worth reporting, in minutes. Quantised to 5-minute telemetry buckets downstream, so the rendered value reads 'about N min'.","examples":[10]},"sections":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Sections","description":"Which of the four e-mail sections render: narrative, stats, escalations, outages. Always all four keys.","examples":[{"escalations":true,"narrative":true,"outages":true,"stats":true}]},"is_default":{"type":"boolean","title":"Is Default","description":"True when NO settings row exists and these are the defaults. A row that happens to equal the defaults reads false.","examples":[true]}},"type":"object","required":["site_id","email_enabled","send_local_time","send_on_quiet_days","min_outage_minutes","sections","is_default"],"title":"GetSiteSummarySettingsResponse"},"GetSpeakersResponse":{"properties":{"speakers":{"items":{"$ref":"#/components/schemas/SpeakerListItemResponse"},"type":"array","title":"Speakers","description":"Live speakers registered for this site, each with the cameras it covers."}},"type":"object","required":["speakers"],"title":"GetSpeakersResponse"},"GetStaleAlarmAlertResponse":{"properties":{"stale_alarm_alert_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Stale Alarm Alert Minutes","description":"Threshold set in Admin -> Alerts; null when not set there.","examples":[10]},"default_minutes":{"type":"integer","title":"Default Minutes","description":"What applies when stale_alarm_alert_minutes is null (SLACK_STALE_ALARM_MINUTES, else 5).","examples":[5]},"effective_minutes":{"type":"integer","title":"Effective Minutes","description":"The threshold the sweep uses right now. 0 = alert off.","examples":[10]},"slack_configured":{"type":"boolean","title":"Slack Configured","description":"Whether SLACK_STALE_ALARM_WEBHOOK_URL (#monitoring-alerts) is set. False = nothing posts whatever the threshold."}},"type":"object","required":["default_minutes","effective_minutes","slack_configured"],"title":"GetStaleAlarmAlertResponse"},"GetSupportOnCallResponse":{"properties":{"primary":{"anyOf":[{"$ref":"#/components/schemas/SupportOnCallPerson"},{"type":"null"}],"description":"Seat 0 of the support shift running now (the most-recently-started one when several overlap). Null when no shift is running or it has no seats."},"backups":{"items":{"$ref":"#/components/schemas/SupportOnCallPerson"},"type":"array","title":"Backups","description":"The remaining seats of that shift, in order (seat 1 first)."},"shift_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Shift Id","description":"The support shift that decided this answer; null when none is running."},"as_of":{"type":"string","format":"date-time","title":"As Of","description":"The instant the answer was computed for (UTC); resolved in the monitoring center's timezone."}},"type":"object","required":["backups","as_of"],"title":"GetSupportOnCallResponse"},"GetSupportRosterResponse":{"properties":{"operators":{"items":{"$ref":"#/components/schemas/SupportRosterEntry"},"type":"array","title":"Operators","description":"Everyone who may be put on the support rotation (support and owner users), sorted by name."}},"type":"object","required":["operators"],"title":"GetSupportRosterResponse"},"GetSupportShiftsResponse":{"properties":{"shifts":{"items":{"$ref":"#/components/schemas/SupportShiftModel"},"type":"array","title":"Shifts","description":"Every support shift overlapping the requested range, ordered by start. Overlaps are normal - a handoff is two of them."}},"type":"object","required":["shifts"],"title":"GetSupportShiftsResponse"},"GetSystemHealthResponse":{"properties":{"generated_at":{"type":"string","format":"date-time","title":"Generated At","description":"When this payload was computed. Served from a ~5-minute per-process cache, so two consecutive polls can land on different API tasks and see slightly different values — key any freshness banner off your own fetch time, not off this alone."},"cache_age_seconds":{"type":"integer","title":"Cache Age Seconds","description":"How old the served payload is, in seconds. 0 means this request computed it.","examples":[42]},"fail_open":{"$ref":"#/components/schemas/SystemHealthFailOpen","description":"Clips read without a usable detector verdict."},"gate":{"$ref":"#/components/schemas/SystemHealthGate","description":"Object-gate stop rate and completed vision fire screens."},"queue":{"$ref":"#/components/schemas/SystemHealthQueue","description":"Depth and age of the human review queue, point-in-time."},"heightened":{"$ref":"#/components/schemas/SystemHealthHeightened","description":"Escalation rate inside heightened windows, and how much of it a human called benign."},"camera_accept":{"$ref":"#/components/schemas/SystemHealthCameraAccept","description":"Per-camera accept-rate outliers against the fleet band."},"second_look":{"$ref":"#/components/schemas/SystemHealthSecondLook","description":"Stage-2a shadow trace rate and outcome mix."},"policy":{"$ref":"#/components/schemas/SystemHealthPolicy","description":"Policy-version mix and silent policy-compute fallbacks."}},"type":"object","required":["generated_at","cache_age_seconds","fail_open","gate","queue","heightened","camera_accept","second_look","policy"],"title":"GetSystemHealthResponse","description":"Owner-only fleet health for the detection pipeline itself (System\nHealth). Every block self-describes its window; nothing here is\ntenant-scoped, because these are cross-tenant fleet aggregates with no\nmeaningful per-customer variant."},"GetTalkSpeakersResponse":{"properties":{"speakers":{"items":{"$ref":"#/components/schemas/TalkSpeakerResponse"},"type":"array","title":"Speakers","description":"Live speakers at this site, ordered by label. Excludes retired (soft-deleted) speakers; includes disabled and unhealthy ones, flagged."}},"type":"object","required":["speakers"],"title":"GetTalkSpeakersResponse"},"GetThreadByIdResponse":{"properties":{"thread":{"additionalProperties":true,"type":"object","title":"Thread","description":"The incident thread"},"events":{"items":{"$ref":"#/components/schemas/ThreadEventItem"},"type":"array","title":"Events","description":"Member events ordered by created_at ascending (the camera-hop timeline)"}},"type":"object","required":["thread","events"],"title":"GetThreadByIdResponse"},"GetTrainingClipUrlResponse":{"properties":{"clip_url":{"type":"string","title":"Clip Url","description":"Presigned URL for the sample's clip"},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the presigned URL expires"}},"type":"object","required":["clip_url","expires_in"],"title":"GetTrainingClipUrlResponse"},"GetTrainingSamplesResponse":{"properties":{"samples":{"items":{"$ref":"#/components/schemas/TrainingSampleListItemModel"},"type":"array","title":"Samples","description":"Training samples, newest first"},"total":{"type":"integer","title":"Total","description":"Total matching samples (for pagination)"},"limit":{"type":"integer","title":"Limit","description":"Page size used for this response"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Keyset cursor for the next page; null when this page is the last"}},"type":"object","required":["samples","total","limit"],"title":"GetTrainingSamplesResponse"},"GetTrainingSummaryResponse":{"properties":{"total":{"type":"integer","title":"Total","description":"Samples captured in the requested range"},"by_actor":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Actor","description":"Sample counts keyed by actor ('operator' | 'agent')"},"disagreements":{"type":"integer","title":"Disagreements","description":"Samples where the AI and the human disagreed"},"agreements":{"type":"integer","title":"Agreements","description":"Samples where the AI and the human agreed"},"not_comparable":{"type":"integer","title":"Not Comparable","description":"Samples whose AI carried no recommendation (raw motion, failsafe, pre-v2 payloads)"},"disagreement_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Disagreement Rate","description":"disagreements / (disagreements + agreements), percent; null with no comparable samples"},"capture_failures":{"type":"integer","title":"Capture Failures","description":"Samples whose clip copy did not succeed (repairable)"},"by_incident_type":{"items":{"$ref":"#/components/schemas/TrainingCountModel"},"type":"array","title":"By Incident Type","description":"Sample counts per AI incident type, largest first"},"by_prompt_version":{"items":{"$ref":"#/components/schemas/TrainingCountModel"},"type":"array","title":"By Prompt Version","description":"Sample counts per prompt version, largest first"}},"type":"object","required":["total","by_actor","disagreements","agreements","not_comparable","capture_failures","by_incident_type","by_prompt_version"],"title":"GetTrainingSummaryResponse"},"HTTPValidationError":{"title":"HTTPValidationError","type":"object","description":"Request-body validation failure. `detail` entries carry `loc`/`msg`/`type` (and `ctx`), never the submitted value.","required":["code","message","detail"],"properties":{"code":{"title":"Code","type":"string","enum":["UNPROCESSABLE_ENTITY"]},"message":{"title":"Message","type":"string"},"detail":{"title":"Detail","type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"HeightenedExceptionModel":{"properties":{"label":{"type":"string","maxLength":80,"minLength":1,"title":"Label","description":"Short category name. The AI cites this label verbatim (matched_exception) when it clears activity as this exception.","examples":["Uniformed security"]},"description":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Description","description":"How the AI can recognize this category on camera (uniform, vehicle, typical hours). Null = label only.","examples":["On-site guard patrol — navy uniforms with badge, marked patrol truck"]}},"type":"object","required":["label"],"title":"HeightenedExceptionModel","description":"One customer-approved exception category for heightened monitoring."},"HolidayCatalogEntry":{"properties":{"rule":{"type":"string","title":"Rule","description":"Catalog key. Stable forever — a client stores this, never the label.","examples":["independence_day"]},"label":{"type":"string","title":"Label","description":"Display name for this rule.","examples":["Independence Day"]},"next_date":{"type":"string","title":"Next Date","description":"The next OBSERVED occurrence on or after today's site-local date, 'YYYY-MM-DD'. This is the day the site would be treated as closed.","examples":["2027-07-05"]},"actual_date":{"type":"string","title":"Actual Date","description":"The same occurrence's unshifted calendar date. Equal to next_date unless a fixed-date holiday fell on a weekend (Saturday is observed the Friday before, Sunday the Monday after) — a client renders '(observed)' exactly when they differ.","examples":["2027-07-04"]}},"type":"object","required":["rule","label","next_date","actual_date"],"title":"HolidayCatalogEntry","description":"One recurring holiday the console can offer, and when it next falls."},"HolidayDateEntry":{"properties":{"date":{"type":"string","format":"date","title":"Date","description":"One-off closure date, a real ISO calendar date in the site's own timezone. Never shifted for a weekend — an explicit date means that date.","examples":["2026-12-24"]},"label":{"type":"string","maxLength":80,"minLength":1,"title":"Label","description":"What this closure is, for the console's list. Trimmed of surrounding whitespace and then required to be 1-80 characters. Display-only; it never gates the monitoring flip.","examples":["Christmas Eve half day"]}},"additionalProperties":false,"type":"object","required":["date","label"],"title":"HolidayDateEntry","description":"A one-off closure on an exact site-local calendar date."},"HolidayRuleEntry":{"properties":{"rule":{"type":"string","title":"Rule","description":"Catalog key, from GET /sites/{id}/holidays -> catalog[].rule.","examples":["thanksgiving"]}},"additionalProperties":false,"type":"object","required":["rule"],"title":"HolidayRuleEntry","description":"A recurring named closure, by catalog key."},"IgnoreZoneInput":{"properties":{"name":{"type":"string","title":"Name","description":"Human-readable name of the zone.","examples":["Street traffic"]},"points":{"items":{"$ref":"#/components/schemas/IgnoreZonePoint"},"type":"array","title":"Points","description":"Polygon vertices as percentages of the frame. Minimum 3 points."},"rag_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rag Context","description":"Optional context passed to the AI pipeline for this zone.","examples":["Public sidewalk, pedestrians expected"]},"ignore":{"type":"boolean","title":"Ignore","description":"Whether detections inside this zone are ignored.","default":true,"examples":[true]}},"type":"object","required":["name","points"],"title":"IgnoreZoneInput"},"IgnoreZoneModel":{"properties":{"name":{"type":"string","title":"Name","description":"Human-readable name of the zone.","examples":["Street traffic"]},"points":{"items":{"$ref":"#/components/schemas/IgnoreZonePoint"},"type":"array","title":"Points","description":"Polygon vertices as percentages of the frame. Minimum 3 points."},"rag_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rag Context","description":"Optional context passed to the AI pipeline for this zone.","examples":["Public sidewalk, pedestrians expected"]},"ignore":{"type":"boolean","title":"Ignore","description":"Whether detections inside this zone are ignored.","default":true,"examples":[true]},"id":{"type":"string","format":"uuid","title":"Id","description":"The zone ID."},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The camera this zone belongs to."}},"type":"object","required":["name","points","id","camera_id"],"title":"IgnoreZoneModel"},"IgnoreZonePoint":{"properties":{"x":{"type":"number","maximum":100.0,"minimum":0.0,"title":"X","description":"X coordinate as a percentage (0-100) of the frame width.","examples":[12.5]},"y":{"type":"number","maximum":100.0,"minimum":0.0,"title":"Y","description":"Y coordinate as a percentage (0-100) of the frame height.","examples":[80.0]}},"type":"object","required":["x","y"],"title":"IgnoreZonePoint"},"InboundCallRecord":{"properties":{"id":{"type":"string","title":"Id","description":"inbound_call row id (also the `icid` custom parameter on the ring)."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the call arrived."},"from_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Number","description":"Caller ID as Twilio reported it.","examples":["+13105550123"]},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"The alarm this call is probably about: set only when Verdun called this number (police or owner) in the previous 24 h and exactly one client account is involved."},"dispatch_call_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dispatch Call Id","description":"That earlier outbound call, when matched."},"outcome":{"anyOf":[{"type":"string","enum":["answered","voicemail","missed"]},{"type":"null"}],"title":"Outcome","description":"null while the call is still ringing."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Twilio's final call status.","examples":["completed"]},"answered_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answered By Name","description":"Who took the call (first console to answer)."},"answered_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Answered At","description":"When the answering console reported acceptance."},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds","description":"Provider-reported call duration in seconds."},"transcript":{"anyOf":[{"items":{"$ref":"#/components/schemas/TranscriptSegment"},"type":"array"},{"type":"null"}],"title":"Transcript","description":"Voicemail transcript; null until transcribed (or when never recorded)."},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"Internal one-line summary of the voicemail."}},"type":"object","required":["id","created_at"],"title":"InboundCallRecord"},"IncidentEventRow":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"Event UUID — opens in the review panel."},"detected_at":{"type":"string","format":"date-time","title":"Detected At","description":"When the AI interpretation was recorded (the clock the counts endpoint groups on)."},"incident_type":{"type":"string","title":"Incident Type","description":"IncidentType value.","examples":["INTRUSION"]},"camera_id":{"type":"string","title":"Camera Id","description":"Camera row UUID."},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Camera label.","examples":["Loading dock"]},"site_id":{"type":"string","title":"Site Id","description":"Site UUID."},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site name.","examples":["Sentinel HQ"]},"parent_id":{"type":"string","title":"Parent Id","description":"Parent (client) UUID."}},"type":"object","required":["event_id","detected_at","incident_type","camera_id","site_id","parent_id"],"title":"IncidentEventRow"},"IncidentRoutingMode":{"type":"string","enum":["ignore","humans","agent"],"title":"IncidentRoutingMode","description":"Per-camera routing for ONE incident type (cameras.incident_routing).\n\nIGNORE drops the verdict entirely — recorded as an agent-authored\nNOT_MONITORED dismissal, never queued, never broadcast, never notified.\nHUMANS sends it to the operator queue (what an unselected type did before\nrouting existed). AGENT lets the agent act on it, still bounded by the\nglobal AUTO_DISMISS_INCIDENT_ALLOWLIST, which routing may only narrow.\n\nPlain JSONB storage, so there is NO PG enum behind this and a new mode\nneeds no migration. The behaviour lives in app/services/incident_routing.py,\nwhich re-exports this as `RoutingMode`; UNKNOWN is never routable and a\nmissing key always reads as HUMANS."},"IncidentType":{"type":"string","enum":["INTRUSION","TAILGATING","FIGHTING","ARMED_ROBBERY","VEHICLE_THEFT","FIRE","LOITERING","PACKAGE_LEFT","MAINTENANCE","DELIVERY","MULTI_PERSON","LOW_LIGHT_ACTIVITY","HEIGHTENED_ACTIVITY","PERSON_DOWN","NORMAL","UNKNOWN"],"title":"IncidentType"},"IncidentTypeCount":{"properties":{"incident_type":{"type":"string","title":"Incident Type","description":"IncidentType value. Treated as an open string by clients so enum additions never break the UI.","examples":["INTRUSION"]},"count":{"type":"integer","title":"Count","description":"AI detections of this type in the window.","examples":[57]}},"type":"object","required":["incident_type","count"],"title":"IncidentTypeCount"},"IngestorIgnoreZone":{"properties":{"points":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Points","description":"Polygon vertices as [{\"x\": 0-100, \"y\": 0-100}, ...], percentages of the frame (both the FE editor and the detect branch are stretch-normalized, so percentages map linearly).","examples":[[{"x":10.0,"y":20.0},{"x":80.0,"y":20.0},{"x":80.0,"y":90.0}]]}},"type":"object","title":"IngestorIgnoreZone","description":"A single motion-mask polygon the ingestor applies to frame-diff.\n\nPoints-only on purpose: the ingestor needs geometry to build the mask,\nnot the zone's name/rag_context (annotation for II) or its db id. Kept\npermissive (points as opaque dicts) so a single malformed stored zone\ncan't fail response validation and blank the whole parent's poll — the\ningestor validates each zone's shape defensively and drops bad ones."},"IntegrationProvider":{"type":"string","enum":["verkada","turing","avertx"],"title":"IntegrationProvider"},"KeyframeModel":{"properties":{"frame_index":{"type":"integer","title":"Frame Index","description":"Frame index"},"timestamp_ms":{"type":"integer","title":"Timestamp Ms","description":"Timestamp in milliseconds"},"role":{"$ref":"#/components/schemas/KeyframeRole","description":"Keyframe role"},"image_ref":{"type":"string","title":"Image Ref","description":"Image reference"},"detections":{"items":{"$ref":"#/components/schemas/DetectionModel"},"type":"array","title":"Detections","description":"Detections"}},"type":"object","required":["frame_index","timestamp_ms","role","image_ref","detections"],"title":"KeyframeModel"},"KeyframeRole":{"type":"string","enum":["pre","early","trigger","late","post"],"title":"KeyframeRole"},"KpiMedian":{"properties":{"median":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Median","description":"Median seconds over the full filtered set (half-up)."},"n":{"type":"integer","title":"N","description":"Rows that contributed (timing not null)."}},"type":"object","required":["n"],"title":"KpiMedian"},"LastEventSummary":{"properties":{"id":{"type":"string","title":"Id","description":"The event id."},"occurred_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Occurred At","description":"Ingestor-reported time of the motion. Display only."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When we recorded the event — the single DB clock."},"operator_evaluated":{"type":"boolean","title":"Operator Evaluated","description":"True once a human triaged or dismissed it."},"expired_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expired At","description":"Set when it aged out unreviewed. NOT the same as a dismissal."}},"type":"object","required":["id","operator_evaluated"],"title":"LastEventSummary","description":"The newest incident this thread carries a message about.\n\nNO `closed_at` — `events_metadata` HAS NO SUCH COLUMN, and the two things\nthat could stand in for one mean different things: `operator_evaluated` is\n\"a human triaged or dismissed it\", `expired_at` is \"it aged out unreviewed\".\nBoth are exposed and the UI derives \"open / closed\" rather than the API\ninventing a single field that erases the distinction."},"LastSipTest":{"properties":{"at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"At","description":"When the test was placed."},"passed":{"type":"boolean","title":"Passed","description":"True only when both signals agreed."},"call_id":{"type":"string","format":"uuid","title":"Call Id","description":"The call record, for the detail view."},"summary":{"type":"string","title":"Summary","description":"One sentence a person can act on."}},"type":"object","required":["passed","call_id","summary"],"title":"LastSipTest","description":"The headline of the newest SIP test on a speaker.\n\n`passed` is the two-signal check — the device reported the call Active AND\nthe voice platform reported the same call live. The full timeline lives on\n`GET /speakers/{id}/sip-test`; this is what a list row needs to say whether\na horn is armed."},"LeaderboardBreakdown":{"properties":{"operator_id":{"type":"string","format":"uuid","title":"Operator Id","description":"The deciding operator."},"operator_name":{"type":"string","title":"Operator Name","description":"First and last name."},"handled":{"type":"integer","title":"Handled","description":"Number of individually handled alarms."},"handled_points":{"type":"integer","title":"Handled Points","description":"Points earned for individually handled alarms."},"bulk":{"type":"integer","title":"Bulk","description":"Number of alarms handled through Close all."},"bulk_points":{"type":"integer","title":"Bulk Points","description":"Points earned for bulk-closed alarms."},"fast_pickups":{"type":"integer","title":"Fast Pickups","description":"Non-bulk decisions that earned a pickup bonus."},"pickup_points":{"type":"integer","title":"Pickup Points","description":"Total first-pickup bonus points."},"rushed":{"type":"integer","title":"Rushed","description":"Decisions whose pickup bonus was cancelled for rushing."},"overturns":{"type":"integer","title":"Overturns","description":"Active overturned decisions in the window."},"overturn_points":{"type":"integer","title":"Overturn Points","description":"Penalty points for active overturns."},"catches":{"type":"integer","title":"Catches","description":"Confirmed overturns this operator caught on other operators' calls in the window."},"catch_points":{"type":"integer","title":"Catch Points","description":"Bonus points for those catches."},"score":{"type":"integer","title":"Score","description":"Total points including active overturn penalties and catch bonuses."}},"type":"object","required":["operator_id","operator_name","handled","handled_points","bulk","bulk_points","fast_pickups","pickup_points","rushed","overturns","overturn_points","catches","catch_points","score"],"title":"LeaderboardBreakdown"},"LeaderboardRow":{"properties":{"operator_id":{"type":"string","format":"uuid","title":"Operator Id","description":"The deciding operator."},"operator_name":{"type":"string","title":"Operator Name","description":"First and last name."},"rank":{"type":"integer","title":"Rank","description":"1-based; ties broken by more alarms, then name."},"prev_rank":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prev Rank","description":"Rank one hour ago on the same board; null if the window is younger than an hour or they were not on it."},"score":{"type":"integer","title":"Score","description":"Total points including active overturn penalties and catch bonuses."},"alarms":{"type":"integer","title":"Alarms","description":"Handled plus bulk."},"fast_pickups":{"type":"integer","title":"Fast Pickups","description":"Non-bulk decisions that earned a pickup bonus."},"catches":{"type":"integer","title":"Catches","description":"Other operators' calls this operator overturned that a lead confirmed (and no dispute reversed), confirmed within the window."},"overturns":{"type":"integer","title":"Overturns","description":"This operator's own calls in the window that stand overturned (same rule as the breakdown's `overturns`; each costs `scoring.overturn`)."},"median_pickup_seconds":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Median Pickup Seconds","description":"Median known first-view pickup time in seconds; null without samples."},"hours":{"type":"number","title":"Hours","description":"Hours worked within the requested window."},"hours_source":{"type":"string","enum":["clocked","active"],"title":"Hours Source","description":"clocked uses time punches; active estimates distinct hours containing decisions."},"points_per_hour":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Points Per Hour","description":"Score divided by hours, or null without worked hours."},"is_self":{"type":"boolean","title":"Is Self","description":"Whether this row belongs to the caller."}},"type":"object","required":["operator_id","operator_name","rank","score","alarms","fast_pickups","catches","overturns","hours","hours_source","points_per_hour","is_self"],"title":"LeaderboardRow"},"LeaderboardScoring":{"properties":{"handled":{"type":"integer","title":"Handled","description":"Points for one handled alarm."},"bulk":{"type":"integer","title":"Bulk","description":"Points for one alarm closed with Close all."},"pickup_tiers":{"items":{"items":{"type":"integer"},"type":"array"},"type":"array","title":"Pickup Tiers","description":"[[seconds_under, bonus], ...] for a first-view pickup."},"rushed_under_seconds":{"type":"integer","title":"Rushed Under Seconds","description":"Deciding this fast after the first view zeroes the pickup bonus."},"overturn":{"type":"integer","title":"Overturn","description":"Points for an active overturn (charged to the decider)."},"catch":{"type":"integer","title":"Catch","description":"Points for catching an overturned call: credited to the peer operator whose overturn a lead confirmed."},"agent_catch":{"type":"integer","title":"Agent Catch","description":"Points for catching a MISS of the AI agent (SCORE_AGENT_CATCH): same rule as `catch`, but the overturned decider was the agent's close and the correct call is more serious than the agent's. An overturn of an agent false alarm (Non-threatening -> False alarm) is auto-confirmed and earns 0."}},"type":"object","required":["handled","bulk","pickup_tiers","rushed_under_seconds","overturn","catch","agent_catch"],"title":"LeaderboardScoring"},"LikeRequest":{"properties":{"visitor_id":{"type":"string","format":"uuid4","title":"Visitor Id","description":"Browser-generated random uuid4; only prevents double counting."}},"type":"object","required":["visitor_id"],"title":"LikeRequest"},"LinkConsentRequest":{"properties":{"slot":{"type":"string","enum":["primary","afterhours"],"title":"Slot","description":"Which of the contact's two numbers this consent covers.","examples":["primary"]},"site_contact_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Contact Id","description":"Existing contact to bind to. Mutually exclusive with site_id/contact."},"site_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Id","description":"Site to create the contact under. Requires `contact`."},"contact":{"anyOf":[{"$ref":"#/components/schemas/SiteContactsModel"},{"type":"null"}],"description":"New contact's fields, when creating from the consent. The chosen slot's number is overwritten server-side with the consent's number."}},"type":"object","required":["slot"],"title":"LinkConsentRequest","description":"Bind a reviewed consent to a site contact slot.\n\nTWO SHAPES, EXACTLY ONE OF WHICH MUST BE USED: link to an existing contact\n(`site_contact_id`), or create the contact from the consent (`site_id` plus\n`contact`). The second writes the consent's number into the chosen slot in\nthe same transaction — the link is a claim that this contact owns this\nnumber in this slot, so the claim and the record cannot diverge."},"ListBillingConcessionsResponse":{"properties":{"concessions":{"items":{"$ref":"#/components/schemas/BillingConcessionResponse"},"type":"array","title":"Concessions","description":"Every concession, by client name, oldest first within a client."}},"type":"object","required":["concessions"],"title":"ListBillingConcessionsResponse"},"ListClaimsResponse":{"properties":{"claims":{"additionalProperties":{"$ref":"#/components/schemas/AlarmClaim"},"type":"object","title":"Claims","description":"Live claim per event id for the queued alarms the caller may see (their tenant scope, the queue's own membership predicate), optionally narrowed by `site_id` / `event_ids`. An alarm with no live claim is ABSENT: for every alarm the request covers, absence means nobody holds it. Empty for customer-side callers, who cannot read staff identity."},"activity":{"additionalProperties":{"$ref":"#/components/schemas/ActivityPayload"},"type":"object","title":"Activity","description":"Escalation activity per event id (claim-lock R2): a valid escalation intent (Voice Down opened, Escalate - dispatch picked) and/or live calls, talk-downs and dispatches derived from the session records. NOT inside `claims`: activity outlives the claim (a call keeps going after a lapse or a take-over), so an alarm can be in `activity` without being in `claims`. A keyed read (`event_ids`) covers every queued alarm named; an unfiltered or `site_id`-only read covers the alarms with a live claim. Empty while CLAIM_ESCALATION_ENABLED is off and for customer-side callers."},"reservations":{"additionalProperties":{"$ref":"#/components/schemas/Reservation"},"type":"object","title":"Reservations","description":"Claim-lock R5 (Focus): the alarms reserved for a focuser, per event id. A reservation is NOT a claim: it has no TTL, it lives under the focuser's ordinary claim and outlives it, and an alarm can be here with or without being in `claims` (while a live claim exists the console shows the claim, afterwards the reservation). A keyed read covers every queued alarm named; an unfiltered or `site_id` read adds the alarms that carry a valid reservation. Empty while FOCUS_ENABLED is off and for customer-side callers."},"focus":{"items":{"oneOf":[{"$ref":"#/components/schemas/FocusRecord"},{"$ref":"#/components/schemas/StoryHoldRecord"}]},"type":"array","title":"Focus","description":"Claim-lock R5 (Focus): the active focus records in the caller's tenant scope (narrowed to `site_id` when given): who is focusing. Empty while FOCUS_ENABLED is off."}},"type":"object","required":["claims"],"title":"ListClaimsResponse"},"ListOperatorMissBoxesResponse":{"properties":{"boxes":{"items":{"$ref":"#/components/schemas/OperatorMissBoxModel"},"type":"array","title":"Boxes","description":"Active (non-deleted) boxes on this event, oldest first. Includes every staff caller's boxes, not just the requesting operator's -- ownership only gates PATCH/DELETE, never the read."}},"type":"object","required":["boxes"],"title":"ListOperatorMissBoxesResponse"},"ListParentDisarmsResponse":{"properties":{"sites":{"items":{"$ref":"#/components/schemas/ParentSiteDisarmStateModel"},"type":"array","title":"Sites","description":"The client's sites, ordered by name."}},"type":"object","required":["sites"],"title":"ListParentDisarmsResponse"},"ListSiteDisarmsResponse":{"properties":{"disarms":{"items":{"$ref":"#/components/schemas/SiteDisarmModel"},"type":"array","title":"Disarms","description":"Newest first."},"total":{"type":"integer","title":"Total","description":"Total rows for this site.","examples":[3]}},"type":"object","required":["disarms","total"],"title":"ListSiteDisarmsResponse"},"LiveViewActivityCamera":{"properties":{"open_operator_alarm":{"type":"boolean","title":"Open Operator Alarm","description":"An operator-raised alarm on this camera is still open. Keep activity highlighted regardless of last_activity_at until a subsequent snapshot clears it.","default":false},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera row UUID (not the hardware camera_id) - the key the Live View picker and wall use."},"last_activity_at":{"type":"string","format":"date-time","title":"Last Activity At","description":"created_at of the newest activity event on this camera inside the window (UTC). An open operator-raised alarm counts whatever its age."},"incident_type":{"$ref":"#/components/schemas/IncidentType","description":"The newest interpretation's incident type for that event. Never UNKNOWN and never a gate-stop. NORMAL means past the object gate but ordinary (often agent-auto-dismissed). For an OPEN operator-raised alarm (raised by hand from Live View, no interpretation) it is the type the operator chose.","examples":["LOITERING"]},"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"The event behind last_activity_at, for a review deep link."},"incident_types":{"items":{"$ref":"#/components/schemas/IncidentType"},"type":"array","title":"Incident Types","description":"Every distinct incident type this camera had in the window (each event by its newest interpretation, plus an open operator-raised alarm's chosen type), most recent first. incident_type is the first; the console colours the camera by the most severe.","examples":[["NORMAL","INTRUSION"]]},"events_in_window":{"type":"integer","minimum":1.0,"title":"Events In Window","description":"Activity events on this camera inside the window (each counted by its newest interpretation).","examples":[3]}},"type":"object","required":["camera_id","last_activity_at","incident_type","event_id","events_in_window"],"title":"LiveViewActivityCamera"},"LogAttemptRequest":{"properties":{"contact_id":{"type":"string","title":"Contact Id","description":"Site contact that was contacted; must belong to the event's site","examples":["2dd7154e-21dc-408f-ac25-b2918d591e29"]},"channel":{"type":"string","enum":["call","text"],"title":"Channel","description":"How the operator reached out. E-mail is not offered: the console's own Email tab is the only path that produces one.","examples":["call"]},"result":{"type":"string","enum":["answered","voicemail","no_answer","busy","wrong_number","delivered","not_delivered","failed"],"title":"Result","description":"What happened. Call results: answered, voicemail, no_answer, busy, wrong_number. Text results: delivered, not_delivered, failed.","examples":["voicemail"]},"at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"At","description":"When the attempt happened. Defaults to now; clamped server-side to [event.created_at, now] — an attempt cannot predate the event it is about, and cannot be in the future.","examples":["2026-09-11T22:16:00+00:00"]}},"type":"object","required":["contact_id","channel","result"],"title":"LogAttemptRequest","description":"Record an owner-contact attempt an operator made OFF-SYSTEM.\n\nNOTHING IS SENT BY THIS REQUEST. It records that a human already made\ncontact from a desk phone or a personal handset — which is why the service\napplies neither the consent gates nor the telephony-config gate that its\nsiblings `/notify-owner` and `/call-owner` do. See `log_attempt` for the\nargument; it is a deliberate divergence, not an oversight.\n\nFIELD NAME AND ERROR CODES FOLLOW THE SIBLINGS, not the brief's first\ndraft (round-0 ruling OQ-4). `NotifyOwnerRequest` and `CallOwnerRequest`\nboth take `contact_id`, and both treat a contact from another site as a\n403 — `dispatch_service._resolve_owner_contact`'s docstring says in as many\nwords that the sibling actions must not diverge. A third action on the same\nobject behaving differently is the drift, not the fix."},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email","description":"Email address of the user"},"password":{"type":"string","title":"Password","description":"Password of the user"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"LoginResponse":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id","description":"Authenticated user's unique ID (UUID).","examples":["3fa85f64-5717-4562-b3fc-2c963f66afa6"]},"access_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Access Token","description":"Short-lived access token (JWT).","examples":["eyJhbGciOi...access"]},"id_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id Token","description":"Identity token (JWT) for client-side identity.","examples":["eyJhbGciOi...id"]},"refresh_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refresh Token","description":"Refresh token used to obtain new access tokens.","examples":["def50200..."]},"expires_in":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In","description":"Seconds until the access token expires.","examples":[3600]},"challenge":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Challenge","description":"A Cognito challenge that must be completed before tokens are issued.","examples":["NEW_PASSWORD_REQUIRED"]},"session":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session","description":"Opaque Cognito session for completing the challenge."}},"type":"object","required":["user_id"],"title":"LoginResponse"},"MapSearchResponse":{"properties":{"lat":{"type":"number","title":"Lat","description":"Latitude of the matched address.","examples":[42.0304]},"lng":{"type":"number","title":"Lng","description":"Longitude of the matched address.","examples":[-87.94818]},"label":{"type":"string","title":"Label","description":"The address Amazon Location actually matched — shown back to the operator as search feedback.","examples":["1601 E Algonquin Rd, Arlington Heights, IL 60005, United States"]}},"type":"object","required":["lat","lng","label"],"title":"MapSearchResponse","description":"A geocoded point for the Map tab's address search box.\n\nDisplay-only: the frontend pans/zooms the map to this location and nothing\nis persisted. So, unlike GeocodeSiteResponse, there is no `precise` flag\nand no storage-licensed coordinate — this is a \"look over here\" result, not\na candidate site pin."},"MarkViewedResponse":{"properties":{"first_viewed":{"anyOf":[{"$ref":"#/components/schemas/FirstViewed"},{"type":"null"}],"description":"Who first opened this alarm, whoever that was (compare `operator_id` with your own to tell a first open from a re-open). Null for customer-side callers, who cannot read staff identity, and for an alarm that has left the active queue."}},"type":"object","required":["first_viewed"],"title":"MarkViewedResponse"},"MeResponse":{"properties":{"id":{"type":"string","title":"Id","description":"User ID (opaque string identifier).","examples":["3fa85f64-5717-4562-b3fc-2c963f66afa6"]},"email":{"type":"string","title":"Email","description":"User email address.","examples":["user@example.com"]},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"User first name, if available.","examples":["Alex"]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"User last name, if available.","examples":["Doe"]},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role","description":"User role or group, if available.","examples":["operator"]},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At","description":"Last time the user was seen active.","examples":["2024-01-01T12:34:56Z"]},"last_logged_in":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Logged In","description":"Last login timestamp.","examples":["2024-01-01T12:00:00Z"]},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"User creation timestamp.","examples":["2023-12-15T09:30:00Z"]},"permissions":{"anyOf":[{"additionalProperties":{"type":"boolean"},"type":"object"},{"type":"null"}],"title":"Permissions","description":"Per-permission overrides for this operator; null means role defaults apply.","examples":[{"manage_users":false,"view_analytics":true}]},"claim_cleanup_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Claim Cleanup Enabled","description":"Capability: true when this api has alarm-claim cleanup (claim status read at `GET /events/{id}/claim`, claims read at `GET /events/claims`, renew-only heartbeat at `POST /events/{id}/claim/renew`, agent closes clear claims) switched on (`CLAIM_CLEANUP_ENABLED`). Always present from an api that has the feature, true or false; ABSENT from an older api, which a console must read as false. Those routes are 404 whenever this is not true, and a heartbeat must never fall back to `POST /events/{id}/claim` (that route acquires)."},"claim_refusal_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Claim Refusal Enabled","description":"Capability: true when this api enforces ONE HOLDER PER ALARM (`CLAIM_REFUSAL_ENABLED`, which also requires `claim_cleanup_enabled`): a close (409 `claim_held`, a per-item 423 in the bulk close) and the operator-started Voice down, AI voice announcement, call to the owner, dispatch call and event-scoped Live Talk are refused while ANOTHER operator holds a live claim, and `POST /events/{id}/claim/take-over` works. A console lights Take over only when this is true. Always present from an api that has the feature, true or false; ABSENT from an older api, which a console must read as false. The server's refusal stays authoritative for any stale tab."},"claim_escalation_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Claim Escalation Enabled","description":"Capability: true when this api serves the escalation record (`CLAIM_ESCALATION_ENABLED`, which also requires `claim_cleanup_enabled` and the legacy whole-site claim to be OFF): `POST /events/{id}/claim/acting` and its `/cancel` work, `claim.generation` is filled and the claims reads carry `activity`. A console shows \"Escalation in progress\" and sends the record only when this is true. ABSENT from an older api, which a console must read as false. When it turns false, drop any pending local intent."},"focus_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Focus Enabled","description":"Capability: true when this api serves Focus (`FOCUS_ENABLED`, which also requires `claim_cleanup_enabled` and `claim_refusal_enabled` and the legacy whole-site claim to be OFF): `POST /focus` and its keep / input / release routes and `GET /focus` work, new alarms in an active focus's scope are reserved for the focuser, and the claims reads carry `reservations` and `focus`. ABSENT from an older api, which a console must read as false. When it turns false, drop any local focus state."},"site_hold":{"anyOf":[{"$ref":"#/components/schemas/SiteHoldCapability"},{"type":"null"}],"description":"Capability: `enabled` is true when this api serves the site hold (`SITE_HOLD_ENABLED`, which also requires `claim_cleanup_enabled` and `claim_refusal_enabled` and the legacy whole-site claim to be OFF): `POST /focus` with `origin: \"story\"`, `/heartbeat`, `/leave`, `/take-over` and `/release` for a hold id work, and the claims reads carry story reservations and holds. The other fields are the constants a new hold freezes: heartbeat every `heartbeat_s`, the page counts as open for `open_s` after a heartbeat, an unattended hold returns its alarms after `idle_s` without a heartbeat (the lost-heartbeat backstop), about 30 s after a clean leave (a fixed server constant, not reported here), or `new_alert_s` after a new alert, whichever comes first. ABSENT from an older api, which a console must read as false. When `enabled` turns false, stop heartbeating and drop local hold state."},"claim_next_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Claim Next Enabled","description":"Capability: true when this api serves the Next alarm key (`CLAIM_NEXT_ENABLED`, which also requires `claim_refusal_enabled` (and so claim cleanup) and the legacy whole-site claim to be OFF): `POST /events/claims/next` works, which claims the caller the oldest eligible unheld alarm in one call. ABSENT from an older api, which a console must read as false. The route answers 404 whenever this is not true."},"queue_order":{"anyOf":[{"$ref":"#/components/schemas/QueueOrder"},{"type":"null"}],"description":"Present only while `claim_next_enabled` is true: the hand-out order the queue list's default sort shares with Next. ABSENT = keep today's default sort."},"open_alarms_paged_read":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Open Alarms Paged Read","description":"Capability: true when this api serves `GET /events/open-alarms` (the stable, oldest-first paged read of every open alarm in a scope). Constant true from an api that has it; ABSENT from an older api, which a console must read as false. The read is not gated by `rail_open_list_enabled`."},"rail_open_list_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rail Open List Enabled","description":"Switch (env `RAIL_OPEN_LIST_ENABLED`, default OFF): show the rail's full open list. A console uses the paged read only when this AND `open_alarms_paged_read` are true. Absent from an older api (= false)."},"alarm_show_read":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Alarm Show Read","description":"Capability: true when this api serves `GET /events/{id}/show` (the paged Show scene). Constant true; absent from an older api (= false). Not gated by `alarm_show_enabled`."},"alarm_show_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Alarm Show Enabled","description":"Switch (env `ALARM_SHOW_ENABLED`, default OFF): the Show view. A console shows Show only when this AND `alarm_show_read` are true. Absent from an older api (= false)."},"flood_tool_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Flood Tool Enabled","description":"Switch (env `FLOOD_TOOL_ENABLED`, default OFF): show the rail's \"Label and close together\" tool. Console-only: the bulk close endpoint is unchanged and is not gated by it. Absent from an older api (= false)."},"bulk_close_undo":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Bulk Close Undo","description":"Capability: true when this api serves `POST /eval/operator/bulk/undo` (Undo on the site story's Close all). Constant true; ABSENT from an older api (= false). No env switch."}},"additionalProperties":true,"type":"object","required":["id","email"],"title":"MeResponse"},"MessageResponse":{"properties":{"message":{"type":"string","title":"Message","description":"Human-readable result of the request."}},"type":"object","required":["message"],"title":"MessageResponse","description":"A minimal human-readable acknowledgement for endpoints that intentionally\nreturn no data (e.g. the password-reset flow, which stays non-enumerating —\nthe same message is returned whether or not the account exists)."},"MonitoringHours":{"type":"string","enum":["Nights","All Day","Weekends","Custom"],"title":"MonitoringHours"},"MonitoringScheduleModel":{"properties":{"slots":{"items":{"items":{"type":"string","enum":["monitored","heightened","off"]},"type":"array"},"type":"array","title":"Slots","description":"7 rows (Monday-Sunday) of 96 quarter-hour monitor states."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form operator notes about the schedule.","examples":["Contractor window Thu 14:15-15:00"]},"id":{"type":"string","format":"uuid","title":"Id","description":"The schedule ID."},"site_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Id","description":"The site this schedule belongs to (null for camera overrides and camera-group schedules)."},"camera_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Camera Id","description":"The camera this schedule overrides (null for site and camera-group schedules)."},"group_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Group Id","description":"The CAMERA GROUP this schedule belongs to (null for site and camera schedules). Additive-optional: safe to ignore. A camera group's schedule replaces the site's for every member camera without its own override; overlapping scheduled groups combine under 'off wins'."},"resolution_minutes":{"type":"integer","title":"Resolution Minutes","description":"Authoritative grain: 15 once this schedule has been saved at quarter-hour grain, 60 while it is still an hourly schedule rendered at 15-minute resolution for editing.","examples":[15,60]}},"type":"object","required":["slots","id","resolution_minutes"],"title":"MonitoringScheduleModel","description":"A schedule at 15-minute grain, and which of the THREE targets owns it.\n\nExactly one of site_id / camera_id / group_id is set (DB check\n`monitoring_schedules_one_target`). What that target means to the resolver:\na camera schedule is an OVERRIDE and wins outright; a camera-group schedule\nreplaces the site's for its members (overlapping scheduled groups combine\nunder \"off wins\"); the site schedule is the fallback when neither exists."},"MonitoringSourceModel":{"properties":{"state":{"anyOf":[{"type":"string","enum":["off","monitored","heightened"]},{"type":"null"}],"title":"State","description":"The effective mode resolved with this source; includes heightened, unlike monitoring_active."},"source":{"type":"string","title":"Source","description":"'camera' = a camera-level schedule override (wins outright); 'site' = the site schedule; 'group' = a camera group's schedule; 'holiday' = the site is closed for a configured holiday, so an otherwise-off cell reads monitored (reported only when the layer actually flipped the answer; group_id/group_name are null); 'disarm' = a live text-to-disarm window, which beats every schedule; 'arm' = a live temporary TEST ARM (an operator holding this site's cameras on outside the schedule so an install or a camera move can be tested); it beats every schedule and loses only to 'disarm'; 'default' = no schedule anywhere, so always monitored.","examples":["group"]},"group_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Group Id","description":"The camera group responsible; non-null only when source == 'group'."},"group_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Group Name","description":"That camera group's display name, so a console can render 'off via Tenant A' without a second lookup. Non-null only when source == 'group'.","examples":["Tenant A"]}},"type":"object","required":["source"],"title":"MonitoringSourceModel","description":"WHICH schedule target produced this camera's current monitoring state.\n\nExists because a CAMERA GROUP can now silence a camera. \"off\" with no\nexplanation sends an operator hunting through three places; this names the\none whose edit would actually change the answer.\n\nCustom camera schedules override custom groups, which override the site.\nGroup ties are deterministic, even when the site happens to agree."},"NotifyCameraViewFlagRequest":{"properties":{"recipients":{"items":{"type":"string"},"type":"array","maxItems":10,"minItems":1,"title":"Recipients","description":"Addresses to e-mail, 1-10 (one message each, no CC). Usually seeded from the item's contacts and edited by the operator; syntax-checked only.","examples":[["facilities@example.com"]]},"subject":{"type":"string","maxLength":200,"minLength":1,"title":"Subject","description":"E-mail subject, 1-200 characters.","examples":["Camera view blocked at Harbor Yard - Back of Building Driveway"]},"body":{"type":"string","maxLength":5000,"minLength":1,"title":"Body","description":"Plain-text e-mail body, 1-5000 characters. The newest checked frame is attached as a JPEG when it can be read.","examples":["Hello, our monitoring team noticed something blocking the camera's view..."]}},"type":"object","required":["recipients","subject","body"],"title":"NotifyCameraViewFlagRequest"},"NotifyCameraViewFlagResponse":{"properties":{"sent":{"items":{"type":"string"},"type":"array","title":"Sent","description":"Recipients SES accepted."},"rejected":{"items":{"type":"string"},"type":"array","title":"Rejected","description":"Recipients SES rejected or could not confirm; do not assume these were delivered or undelivered."},"flag":{"anyOf":[{"$ref":"#/components/schemas/CameraViewFlag"},{"type":"null"}],"description":"The item after the send was recorded. Null if the item was deleted or is no longer in the caller's scope (including retirement); the accepted send remains recorded and the mail still went to `sent`."}},"type":"object","required":["sent","rejected"],"title":"NotifyCameraViewFlagResponse"},"NotifyCameraViewFlagsSiteRequest":{"properties":{"flag_ids":{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":12,"minItems":1,"title":"Flag Ids","description":"The items this e-mail covers, 1-12, all at ONE site and each open or confirmed. Open items are confirmed by the send. Each camera's newest checked frame is attached, in this order."},"recipients":{"items":{"type":"string"},"type":"array","maxItems":10,"minItems":1,"title":"Recipients","description":"Addresses to e-mail, 1-10 (one message each, no CC); syntax-checked only.","examples":[["facilities@example.com"]]},"subject":{"type":"string","maxLength":200,"minLength":1,"title":"Subject","description":"E-mail subject, 1-200 characters.","examples":["3 cameras need attention at Harbor Yard"]},"body":{"type":"string","maxLength":5000,"minLength":1,"title":"Body","description":"Plain-text e-mail body, 1-5000 characters."}},"type":"object","required":["flag_ids","recipients","subject","body"],"title":"NotifyCameraViewFlagsSiteRequest"},"NotifyCameraViewFlagsSiteResponse":{"properties":{"sent":{"items":{"type":"string"},"type":"array","title":"Sent","description":"Recipients SES accepted."},"rejected":{"items":{"type":"string"},"type":"array","title":"Rejected","description":"Recipients SES rejected or could not confirm; do not assume these were delivered or undelivered."},"flags":{"items":{"$ref":"#/components/schemas/CameraViewFlag"},"type":"array","title":"Flags","description":"The items after the send was recorded, in request order. An item deleted or no longer in the caller's scope is omitted; its send is still recorded."}},"type":"object","required":["sent","rejected","flags"],"title":"NotifyCameraViewFlagsSiteResponse"},"NotifyOwnerRequest":{"properties":{"contact_id":{"type":"string","title":"Contact Id","description":"Site contact to notify; must belong to the event's site","examples":["2dd7154e-21dc-408f-ac25-b2918d591e29"]},"message":{"type":"string","maxLength":1600,"minLength":1,"title":"Message","description":"Message body. AI-drafted client-side; the operator owns the final text. Links are permitted — the 10DLC campaign declares them and link shortening is disabled per message, so a URL goes out exactly as typed.","examples":["Verdun Security: intrusion alert at your site. View: https://ops.verdun.ai/review/8f14e45f-ceea-467a-9f0b-2b0a1cbd7a71"]},"number":{"type":"string","enum":["primary","afterhours"],"title":"Number","description":"Which of the contact's phone numbers on file to text","default":"primary"},"attach_snapshot":{"type":"boolean","title":"Attach Snapshot","description":"Attach the event's alert-frame JPEG as MMS. 409 SNAPSHOT_UNAVAILABLE when this event has no snapshot (no clip key, a clip key that derives none, or the object is not readable in S3) — nothing is written and nothing is sent in that case. Absent or false behaves exactly as before.","default":false,"examples":[false]},"snapshot_offset_ms":{"anyOf":[{"type":"integer","maximum":600000.0,"minimum":0.0},{"type":"null"}],"title":"Snapshot Offset Ms","description":"WHICH moment of the clip to attach, in milliseconds from the start of the event's clip, as picked by the operator and cut earlier by `POST /events/{id}/snapshot-frame`. Rounded to a tenth of a second. Only meaningful with `attach_snapshot` (422 at the body level otherwise). Absent, `attach_snapshot` sends the ingestor's alert-frame poster as before. 409 SNAPSHOT_UNAVAILABLE when no frame exists at that offset.","examples":[7300]},"include_clip_link":{"type":"boolean","title":"Include Clip Link","description":"Append a login-free, expiring link to this event's clip. The URL is added by the SERVER to the end of the body; reserve share_link_reserved_chars (GET /telephony/config) characters for it. 503 when clip links are not configured on this deployment, 409 when the event has no clip.","default":false}},"type":"object","required":["contact_id","message"],"title":"NotifyOwnerRequest"},"NvrDeviceResponse":{"properties":{"device_id":{"type":"string","title":"Device Id","description":"Vendor device id (braces stripped): the Nx device UUID, the ONVIF profile token, or the Hikvision channel number (\"1\")."},"name":{"type":"string","title":"Name","description":"Device name as configured on the NVR.","examples":["Front Gate"]},"physical_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Physical Id","description":"Nx physical id (usually the MAC)."},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Camera model reported by the NVR.","examples":["IPC-HDW2431T"]},"vendor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor","description":"Camera vendor reported by the NVR.","examples":["Dahua"]},"mac":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mac","description":"Camera MAC address reported by the NVR."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Device status on the NVR (Hikvision: Online / Offline from the channel status, null when the NVR does not report it).","examples":["Online","Offline"]},"video_codec":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Codec","description":"Codec of the stream the relay will pull, when the NVR reports it. The ingest pipeline requires h264.","examples":["h264","h265"]},"stream_codec":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stream Codec","description":"Codec read from the relay stream itself (RTSP DESCRIBE), when the probe succeeded. More reliable than video_codec, which is the NVR's own metadata; prefer this when present.","examples":["h264","h265"]},"already_onboarded":{"type":"boolean","title":"Already Onboarded","description":"Whether this device is already a Verdun camera."},"onboarded_camera_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Onboarded Camera Id","description":"Existing Verdun camera UUID, when already onboarded."},"onboarded_camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Onboarded Camera Label","description":"Existing Verdun camera label, when already onboarded."}},"type":"object","required":["device_id","name","already_onboarded"],"title":"NvrDeviceResponse"},"NvrKind":{"type":"string","enum":["dw_spectrum","milestone_xprotect","hikvision"],"title":"NvrKind"},"NvrResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The NVR record ID."},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site this NVR belongs to."},"name":{"type":"string","title":"Name","description":"Display name for this NVR.","examples":["Warehouse DW Spectrum"]},"kind":{"$ref":"#/components/schemas/NvrKind","description":"NVR/VMS vendor."},"host":{"type":"string","title":"Host","description":"Hostname or IP of the NVR's media server (DW Spectrum / Nx), Open Network Bridge (Milestone XProtect), or ISAPI web server (Hikvision / LTS / HiLook).","examples":["64.183.7.186"]},"port":{"type":"string","title":"Port","description":"Control port Verdun connects to (7001 DW Spectrum / Nx media server, 580 Milestone Open Network Bridge ONVIF port, 80 Hikvision ISAPI HTTP port by default).","examples":["7001","580"]},"rtsp_port":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rtsp Port","description":"Hikvision / LTS / HiLook only: the RTSP port Verdun streams from (separate from `port`). Null for other NVR kinds.","examples":["554","8554"]},"username":{"type":"string","title":"Username","description":"NVR service-account username (password is never returned).","examples":["verdun-service"]},"api_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api Version","description":"Protocol negotiated at last login: the Nx REST API version (`v3` / `v2`) for DW Spectrum / Nx, `onvif` for Milestone XProtect, or `isapi` for Hikvision.","examples":["v3","onvif","isapi"]},"last_synced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Synced At","description":"Last successful device enumeration."}},"type":"object","required":["id","site_id","name","kind","host","port","username"],"title":"NvrResponse"},"OnShiftGroupModel":{"properties":{"group_id":{"type":"string","format":"uuid","title":"Group Id","description":"The coverage group ID."},"group_name":{"type":"string","title":"Group Name","description":"Display name.","examples":["Overnight High-Risk"]},"color":{"type":"string","title":"Color","description":"The group's display colour (hex).","examples":["#4DFFD2"]},"scheduled":{"items":{"$ref":"#/components/schemas/OnShiftOperatorModel"},"type":"array","title":"Scheduled","description":"Everyone seated on a shift of this group running now: most recently started shift first, stored seat order inside it. The primary is the first seat met, so a running shift without seats is skipped. Empty when nobody is scheduled."}},"type":"object","required":["group_id","group_name","color","scheduled"],"title":"OnShiftGroupModel"},"OnShiftOperatorModel":{"properties":{"operator_id":{"type":"string","format":"uuid","title":"Operator Id","description":"The operator ID."},"name":{"type":"string","title":"Name","description":"Display name.","examples":["Jamila Reyes"]},"primary":{"type":"boolean","title":"Primary","description":"Seat 0 of the most-recently-started shift running now on this site's coverage group - the operator who bears a miss."},"clocked_in":{"type":"boolean","title":"Clocked In","description":"Has an open time-clock punch right now."},"clocked_in_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Clocked In At","description":"When the open punch started (null when not clocked in)."}},"type":"object","required":["operator_id","name","primary","clocked_in"],"title":"OnShiftOperatorModel"},"OnboardIntegrationCamerasRequest":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The Verdun site the cameras are created under. Must belong to the client in the path."},"devices":{"items":{"$ref":"#/components/schemas/OnboardIntegrationDevice"},"type":"array","minItems":1,"title":"Devices","description":"Vendor devices to onboard."},"defaults":{"$ref":"#/components/schemas/OnboardIntegrationDefaults","description":"Settings applied to every onboarded camera."}},"type":"object","required":["site_id","devices"],"title":"OnboardIntegrationCamerasRequest","description":"Bulk-onboard selected vendor devices into one Verdun site.\n\nNOTE WHAT IS ABSENT: there is no `rtsp_url` field, and there never should\nbe. A bridge-fed camera's source address is derived server-side from the\ndeployment's bridge template — a client-supplied address would let the\ncaller point a monitored camera at a host of its choosing."},"OnboardIntegrationCamerasResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/OnboardIntegrationResult"},"type":"array","title":"Results","description":"Per-device outcome, in request order."},"created":{"type":"integer","title":"Created","description":"Number of cameras created."},"failed":{"type":"integer","title":"Failed","description":"Number of devices that failed."},"skipped":{"type":"integer","title":"Skipped","description":"Number of devices skipped because they were already onboarded."}},"type":"object","required":["results","created","failed","skipped"],"title":"OnboardIntegrationCamerasResponse","description":"PARTIAL SUCCESS IS THE CONTRACT — read `results`, not the status code.\n\nThe call commits per device, so a 200 can describe any mix of created,\nfailed and skipped. This mirrors the NVR bulk-onboard contract exactly."},"OnboardIntegrationDefaults":{"properties":{"ingest_mode":{"$ref":"#/components/schemas/CameraIngestMode","description":"Must be motion_clip: the HLS->RTSP bridge carries no metadata track, so a continuous cloud-VMS camera would never detect motion.","default":"motion_clip"},"motion_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Motion Config","description":"Extensible motion settings with bounded known keys (timings, thresholds, detector geometry/rate and RTSP buffer mode); see app/services/motion_config_validation.py. Unknown keys are preserved.","examples":[{"debounce_seconds":30,"post_roll_seconds":5,"pre_roll_seconds":5}]},"analytics_provider":{"$ref":"#/components/schemas/AnalyticsProvider","description":"Analytics vendor validating triggered clips before upload.","default":"none"},"analytics_config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Analytics Config","description":"Opaque per-vendor analytics settings interpreted by the ingestor."}},"type":"object","title":"OnboardIntegrationDefaults"},"OnboardIntegrationDevice":{"properties":{"device_id":{"type":"string","title":"Device Id","description":"The VENDOR's camera id, as returned by GET /parents/{parent_id}/integrations/{integration_id}/cameras.","examples":["3a1b0f4c-1d2e-4f5a-9b8c-7d6e5f4a3b2c"]},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Display label; defaults to the name the vendor reports for the device."},"camera_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id","description":"Verdun camera_id; defaults to a slug of the label, uniquified within the site."}},"type":"object","required":["device_id"],"title":"OnboardIntegrationDevice"},"OnboardIntegrationResult":{"properties":{"device_id":{"type":"string","title":"Device Id","description":"The vendor's camera id this result is for."},"status":{"type":"string","enum":["created","failed","skipped_already_onboarded"],"title":"Status","description":"What happened to this device."},"camera_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id","description":"The created camera's camera_id (slug), when created."},"camera_uuid":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Camera Uuid","description":"The created camera's UUID, when created."},"kvs_stream_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kvs Stream Name","description":"The created camera's KVS stream name, when created."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure detail, when failed. Sanitized — never carries the vendor API key."},"assigned_site":{"type":"boolean","title":"Assigned Site","description":"True when creating this camera also ASSIGNED its site to the connection (the site had no connection of this provider yet). False otherwise, including every non-created row.","default":false}},"type":"object","required":["device_id","status"],"title":"OnboardIntegrationResult","description":"One device's outcome.\n\nFIELD-FOR-FIELD IDENTICAL to nvrs' BulkOnboardResult, on purpose: the two\nonboarding flows are rendered by one component in ops-view, and a shape\nthat differed by a field name would make that component branch on which\nflow produced the row. Declared separately rather than imported so the NVR\nand cloud-VMS routers stay uncoupled — if the shapes ever have to diverge,\nthey can, without one flow's response model silently changing the other's."},"OnboardingMarksResponse":{"properties":{"onboarding_marks":{"additionalProperties":{"type":"string"},"type":"object","title":"Onboarding Marks","description":"The site's full onboarding marks after the write: mark key -> when it was set (ISO-8601 UTC). Who set a mark is never exposed.","examples":[{"first_night":"2026-10-13T08:00:00.000Z","skip:passcode":"2026-10-13T09:30:00.000Z"}]},"streaming_test_camera_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Streaming Test Camera Ids","description":"On streaming_test writes only: exact camera IDs covered by the saved test, or [] after clearing. Legacy marks without coverage return []."},"first_night_camera_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"First Night Camera Ids","description":"On first_night writes only: exact camera IDs covered by the saved review, or [] after clearing. Legacy marks without coverage return []."}},"type":"object","title":"OnboardingMarksResponse"},"OpenAlarmCounts":{"properties":{"scope_total":{"type":"integer","title":"Scope Total","description":"Open alarms in the requested scope created by `as_of`, counted when page 1 was read. Rows returned over the pass can exceed it by alarms that became visible mid-pass and fall short of it by alarms closed mid-pass."},"site_total":{"type":"integer","title":"Site Total","description":"Open alarms at the whole site, counted the same way as `scope_total`"},"older_than":{"additionalProperties":{"type":"integer"},"type":"object","title":"Older Than","description":"In scope: alarms queued at least N seconds before `as_of`, per `age_marks` value (exact, by queued_at)"},"oldest_queued_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Oldest Queued At","description":"In scope: the oldest `queued_at`, or null when none"},"by_camera":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"By Camera","description":"Whole-site open count per camera. PAGE 1 ONLY (null on later pages)"}},"type":"object","required":["scope_total","site_total","older_than"],"title":"OpenAlarmCounts"},"OpenAlarmItem":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"The alarm's event id: the only identity of an alarm"},"camera_id":{"type":"string","title":"Camera Id","description":"The camera that raised the alarm"},"camera_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Name","description":"The camera's label"},"zone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zone","description":"The camera zone the motion was in, when known"},"site_id":{"type":"string","title":"Site Id","description":"The site the camera belongs to"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Alarm time; the paging key with `event_id`"},"occurred_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Occurred At","description":"The ingestor-reported motion time, when sent"},"queued_at":{"type":"string","format":"date-time","title":"Queued At","description":"When the alarm entered the operator queue: the same instant the queue row carries"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"The AI summary of the clip, when one exists"},"claim":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Claim","description":"The queue row's `claim` payload (`operator_id`, `operator_name`, `claimed_at`, `expires_at`, `expires_in_seconds`) while someone holds the alarm, else null. Own vs foreign: compare `operator_id` with the caller's id."},"first_viewed":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"First Viewed","description":"Who first opened the alarm (`operator_id`, `operator_name`, `viewed_at`), else null"}},"type":"object","required":["event_id","camera_id","site_id","created_at","queued_at"],"title":"OpenAlarmItem"},"OpenAlarmsResponse":{"properties":{"status":{"type":"string","enum":["more","complete","truncated"],"title":"Status","description":"`more`: this page is fine and `next_cursor` continues the pass. `complete`: the last page; nothing after it in scope as of `snapshot.as_of`. `truncated`: the pass hit `limits.pass_ceiling`; the eligible rows counted in `omitted` will NOT be returned (the oldest are always the ones returned)."},"order":{"type":"string","const":"oldest_first","title":"Order","description":"Page order is `(created_at, id)` ascending; sort loaded rows by `(queued_at, id)`","default":"oldest_first"},"snapshot":{"$ref":"#/components/schemas/PagedSnapshot","description":"The pass's pinned snapshot instant and this page's read time"},"scope":{"$ref":"#/components/schemas/PagedScope","description":"What was read"},"items":{"items":{"$ref":"#/components/schemas/OpenAlarmItem"},"type":"array","title":"Items","description":"This page's open alarms, oldest first"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque; send it back with the same parameters. Null on the last page. An invalid cursor is 422 `CURSOR_INVALID`, an expired one 422 `CURSOR_EXPIRED`"},"counts":{"$ref":"#/components/schemas/OpenAlarmCounts","description":"Page-one observations: `scope_total`, `site_total`, `older_than` and `oldest_queued_at` repeat unchanged from page one on every page; `by_camera` is page one only (null afterwards)"},"limits":{"$ref":"#/components/schemas/PagedLimits","description":"The page and pass limits in force"},"omitted":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Omitted","description":"Set only when `truncated`: the eligible rows after the last returned key at this read (the page's own filters and keyset), at least this many when `omitted_capped`. A read-time count, not page 1's total minus the rows returned."},"omitted_capped":{"type":"boolean","title":"Omitted Capped","description":"True when the bounded remainder count hit its cap (10,000): `omitted` is then a lower bound. Always false unless `truncated`.","default":false}},"type":"object","required":["status","snapshot","scope","items","counts","limits"],"title":"OpenAlarmsResponse"},"OpenConversationRequest":{"properties":{"site_contact_id":{"type":"string","title":"Site Contact Id","description":"The site contact to open a thread with. Its site must be in your scope.","examples":["2dd7154e-21dc-408f-ac25-b2918d591e29"]},"number":{"type":"string","enum":["primary","afterhours"],"title":"Number","description":"Which of the contact's two numbers on file identifies the thread. Mirrors NotifyOwnerRequest.number; the server only ever texts numbers from the contact row.","default":"primary","examples":["primary"]}},"type":"object","required":["site_contact_id"],"title":"OpenConversationRequest","description":"Find-or-create the thread for a named contact.\n\nNO `event_id`. The brief had one, but a CONVERSATION stores no event — the\nevent link lives on MESSAGES (`sms_messages.event_metadata_id`), so\naccepting it here would publish a no-op field in the contract. The\nopen-from-review flow is this route, then\n`POST /conversations/{id}/messages` with `event_id`.\n\nTENANCY IS NOT TAKEN FROM THIS BODY. The contact is resolved, its site is\nchecked against the caller's scope, and the thread is opened with THAT\nsite's parent and site (round-0 review R0.9)."},"OperatorAccount":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique account id","examples":["<UUID of some sort>"]},"name":{"type":"string","title":"Name","description":"Name of the account","examples":["Northgate Logistics"]},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client (parent) this account belongs to","examples":["<UUID of the owning client>"]}},"type":"object","required":["id","name","parent_id"],"title":"OperatorAccount","description":"The account access grant on an ACCOUNT_MANAGER operator.\n\nCarried as a LIST on the operator payload (parity with `parents` and\n`sites`) even though the invariant is exactly one, so the response mirrors\nthe junction rather than inventing a nullable shape. Empty for every other\nrole.\n\nUnlike `sites`, this grant is not a snapshot of what the operator can see:\nit EXPANDS at scope-resolution time to whichever sites currently carry this\naccount_id, so the visible site list changes as sites are assigned to and\nremoved from the account, with no edit to the operator record."},"OperatorAlarmCaptureModel":{"properties":{"status":{"type":"string","title":"Status","description":"pending | claimed | captured | unavailable | failed","examples":["pending"]},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Why no clip, set only when status is unavailable or failed","examples":["buffer_expired"]}},"type":"object","required":["status"],"title":"OperatorAlarmCaptureModel"},"OperatorCoverageGroup":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique coverage group id","examples":["<UUID of some sort>"]},"name":{"type":"string","title":"Name","description":"Display name of the group","examples":["Overnight High-Risk"]},"color":{"type":"string","title":"Color","description":"Display color used in schedule grids and chips","examples":["#4DFFD2"]},"site_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Site Count","description":"Distinct live sites the group currently covers (parent grants expanded). Null on /auth/me, which omits the aggregate: present on the admin operator payloads.","examples":[42]}},"type":"object","required":["id","name","color"],"title":"OperatorCoverageGroup","description":"A coverage group the operator is a standing member of.\n\nTHE GROUP IS THE UNIT OF ACCESS (decision 2026-09-16): the operator's\ntenant scope is the union of every member group's grants plus their own\n`parents`/`sites` rows, which survive as an ADDITIVE exception list. A\nparent grant inside the group covers every current AND future site under\nthat client, so onboarding a site never touches the operator record.\n\nMEMBERSHIP CONFERS SCOPE, NEVER PERMISSION. It says which SITES the\noperator can see; their capabilities remain exactly their role's, and no\ngroup ever grants, widens or implies one. Render the chip accordingly.\n\nAccess is STANDING, not shift-bound: it holds off-shift too, so a manager\nreviewing last night's events sees the same sites they cover. Shift seating\nsays who is on the desk when and confers nothing by itself."},"OperatorDisposition":{"type":"string","enum":["False alarm - no human activity","Escalate Dispatch","Escalate Voice Down","Non-threatening human activity","Not monitored on this camera"],"title":"OperatorDisposition"},"OperatorInstructionVersion":{"properties":{"level":{"type":"string","enum":["account","site","camera"],"title":"Level","description":"Which level of the stack."},"id":{"type":"string","format":"uuid","title":"Id","description":"Id of the account, site or camera, as the GET returned it."},"updated_at":{"type":"string","format":"date-time","title":"Updated At","description":"The level's `updated_at` exactly as the GET returned it."}},"type":"object","required":["level","id","updated_at"],"title":"OperatorInstructionVersion"},"OperatorInstructionsLevel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Id of the account, site or camera."},"name":{"type":"string","title":"Name","description":"Display name of the level: the account name, the site name, or the camera label. Carried so the review panel can label each row without a second fetch."},"operator_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Instructions","description":"Instructions set at this level. Null means none set here."},"headline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Headline","description":"One-line headline shown under the alarm video (site and camera only; always null for the account)."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When this text or headline last changed (null: unchanged since tracking began). Echo it to `.../ack`."},"acknowledged":{"type":"boolean","title":"Acknowledged","description":"Caller has read the current text and headline: true when both are empty, `updated_at` is null, or they acked this version.","default":true}},"type":"object","required":["id","name"],"title":"OperatorInstructionsLevel","description":"One resolved level of the instruction stack, with its label for the UI."},"OperatorInstructionsResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Id of the account, site or camera that was written."},"operator_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Instructions","description":"The stored instructions after the write, trimmed. Null means no instruction is set at this level."}},"type":"object","required":["id"],"title":"OperatorInstructionsResponse","description":"The three operator-instruction writes answer the written value, nothing else.\n\nMINIMAL BY DECISION (Round-0 ruling Q4), and a visible divergence from the\n`PUT /sites/{id}/agent-config` route these are modelled on — that one returns\nthe full `EditSiteResponse`. Doing the same here would force\n`operator_instructions` into three large response models and, for sites, into\n`Sites.to_dict_complete()`: the serializer that feeds both SSE broadcast\nbuilders, the operator queue, history and `GET /events/{id}`. The whole\ndesign rests on that serializer never learning the field, so the response\nshape follows from it rather than the other way round.\n\nThe service layer returns the resource's display name alongside these two\nkeys — the router needs it for the activity-log summary — and pydantic drops\nit here (`extra=\"ignore\"`). That is deliberate, not an oversight, and is\npinned by a router test asserting the body carries exactly these two keys:\nthe name is audit material, not part of this contract."},"OperatorMissBoxModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The box's own id."},"event_metadata_id":{"type":"string","format":"uuid","title":"Event Metadata Id","description":"The event this box was drawn on. See the class docstring."},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The camera the event's clip came from. See the class docstring."},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"Audit snapshot of the camera's site at draw time."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"Audit snapshot of the camera's client (parent) at draw time."},"frame_timestamp_ms":{"type":"integer","title":"Frame Timestamp Ms","description":"Milliseconds into the clip"},"frame_capture_method":{"type":"string","enum":["rvfc","currentTime"],"title":"Frame Capture Method","description":"Which browser signal produced frame_timestamp_ms: 'rvfc' (requestVideoFrameCallback's metadata.mediaTime) or 'currentTime' (the requestAnimationFrame fallback)."},"x1":{"type":"number","title":"X1","description":"Left edge of the box, normalized 0-1."},"y1":{"type":"number","title":"Y1","description":"Top edge of the box, normalized 0-1."},"x2":{"type":"number","title":"X2","description":"Right edge of the box, normalized 0-1."},"y2":{"type":"number","title":"Y2","description":"Bottom edge of the box, normalized 0-1."},"class_label":{"type":"string","title":"Class Label","description":"Always 'person' in phase 1."},"label_source":{"type":"string","title":"Label Source","description":"Always 'operator_miss' -- how this box was made."},"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"Who drew it. Null if that operator account was later deleted."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the box was drawn."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When the box was last moved or resized. Null if never edited."}},"type":"object","required":["id","event_metadata_id","camera_id","site_id","parent_id","frame_timestamp_ms","frame_capture_method","x1","y1","x2","y2","class_label","label_source","created_at"],"title":"OperatorMissBoxModel","description":"The per-event API's shape. `event_metadata_id`/`camera_id` stay\nREQUIRED here even though the underlying column is nullable\n(`ON DELETE SET NULL`, PR #276 review) -- every box this model ever\nserializes was found by filtering `OperatorMissBoxes.event_metadata_id\n== event.id` against a live, loaded event, so a null value here can\nnever actually reach this model. The PERMANENT provenance\n(`source_event_id`/`source_camera_id`, which survive a hard delete) is\nnot exposed on this live per-event surface -- it exists for the offline\nexport, not this API."},"OperatorModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of operator","examples":["<Some UUID of some sort>"]},"first_name":{"type":"string","title":"First Name","description":"The first name of the operator","examples":["Joe"]},"last_name":{"type":"string","title":"Last Name","description":"The last name of the operator","examples":["Johnson"]},"email":{"type":"string","title":"Email","description":"The email address of the operator","examples":["JJ@example.com"]},"role":{"$ref":"#/components/schemas/OperatorRole","description":"The role of the operator","examples":["owner","operator"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"The time the operator was created"},"last_seen_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Seen At","description":"The time the operator was last seen"},"last_logged_in":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Logged In","description":"The time the operator last logged in"},"permissions":{"anyOf":[{"additionalProperties":{"type":"boolean"},"type":"object"},{"type":"null"}],"title":"Permissions","description":"Per-permission overrides ({OperatorPermission value: bool}); null means role defaults apply","examples":[{"manage_users":false,"view_alarm_queue":true}]},"is_primary_owner":{"type":"boolean","title":"Is Primary Owner","description":"The account anchor: cannot be removed or demoted, and only they may edit their own record. Not settable via the API.","default":false},"parents":{"items":{"$ref":"#/components/schemas/OperatorParent"},"type":"array","title":"Parents","description":"The parents of the operator"},"sites":{"items":{"$ref":"#/components/schemas/OperatorSite"},"type":"array","title":"Sites","description":"Per-site access grants (site-manager / site-scoped operators). Empty means access follows `parents` (all sites). Exactly one entry for a location_manager, whose `parents` entry is derived from it."},"accounts":{"items":{"$ref":"#/components/schemas/OperatorAccount"},"type":"array","title":"Accounts","description":"Account access grant (account managers). At most one entry. When present, `parents` and `sites` are empty: both are DERIVED from the account, and the operator sees whichever sites currently carry this account_id — including sites added later. Empty for every other role."},"coverage_groups":{"items":{"$ref":"#/components/schemas/OperatorCoverageGroup"},"type":"array","title":"Coverage Groups","description":"Standing coverage-group membership. The operator's site access is the UNION of these groups' grants and the `parents`/`sites` rows above, which are an ADDITIVE exception list ('also sees...'). SCOPE ONLY, NEVER PERMISSION: a member gains sites, never a capability. Empty for site_manager / account_manager / location_manager (structurally scoped, refused membership) and for owner / monitoring_manager (already unconfined, so a membership would be inert)."},"all_sites":{"type":"boolean","title":"All Sites","description":"EFFECTIVE all-sites access (operator / read_only only; false for every other role). When true the user sees every client and site, including future ones, and their coverage groups and parent/site rows are retained but inert. Set via PUT /operator/{id}/site-access.","default":false},"deactivated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deactivated At","description":"When this user was deactivated (POST /operator/{id}/deactivate), or null while active. A deactivated user cannot log in and holds no seats or coverage groups; their records are kept."}},"type":"object","required":["id","first_name","last_name","email","role","created_at","parents"],"title":"OperatorModel"},"OperatorParent":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique client id","examples":["<UUID of some sort>"]},"company_name":{"type":"string","title":"Company Name","description":"Name of the client","examples":["Joe"]},"type":{"$ref":"#/components/schemas/ParentType","description":"Type of the client","examples":["End User","Internal"]},"primary_contact_name":{"type":"string","title":"Primary Contact Name","description":"Name of the primary contact","examples":["Joe Johnson"]},"primary_contact_phone_number":{"type":"string","title":"Primary Contact Phone Number","description":"Phone number of the primary contact","examples":["1013217890"]},"primary_contact_email":{"type":"string","title":"Primary Contact Email","description":"Email of the primary contact","examples":["JJ@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Notes about the client","examples":["Super important notes"]}},"type":"object","required":["id","company_name","type","primary_contact_name","primary_contact_phone_number","primary_contact_email"],"title":"OperatorParent"},"OperatorPermission":{"type":"string","enum":["view_alarm_queue","review_label_alarms","escalate_voice_down","escalate_dispatch","notify_property_owner","manage_sites_cameras","configure_ignore_zones","configure_schedules","view_analytics","manage_users","billing_account_settings","configure_incident_routing"],"title":"OperatorPermission"},"OperatorRole":{"type":"string","enum":["owner","operator","site_manager","read_only","account_manager","monitoring_manager","location_manager","support"],"title":"OperatorRole"},"OperatorSite":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique site id","examples":["<UUID of some sort>"]},"name":{"type":"string","title":"Name","description":"Name of the site","examples":["Elm St Facility"]},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client (parent) this site belongs to","examples":["<UUID of the owning client>"]}},"type":"object","required":["id","name","parent_id"],"title":"OperatorSite","description":"A per-site access grant on an operator (site-manager / site-scoped\noperator). Present only when the operator's access is narrowed below the\nparent level; an empty list means access follows `parents` (all sites)."},"OptOutEventResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the opt-out event."},"webhook_id":{"type":"string","title":"Webhook Id","description":"Surge's signed webhook-id; the replay key."},"surge_contact_id":{"type":"string","title":"Surge Contact Id","description":"The Surge contact that opted out."},"event_ts":{"type":"string","format":"date-time","title":"Event Ts","description":"When the opt-out occurred, frozen at first insert."},"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number","description":"The number, once PROVEN to belong to the contact. NULL until then."},"resolved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Resolved At","description":"When it was reconciled."},"resolution_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolution Error","description":"Why the last resolution attempt proved nothing."},"attempt_count":{"type":"integer","title":"Attempt Count","description":"Resolution attempts so far."},"last_attempt_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Attempt At","description":"Most recent attempt."},"escalated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Escalated At","description":"When this was escalated for human attention."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"Row creation time."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"Row last-update time."}},"type":"object","required":["id","webhook_id","surge_contact_id","event_ts","attempt_count"],"title":"OptOutEventResponse","description":"One durable STOP event, for the owner's reconcile view.\n\nAn UNRESOLVED row here is why SMS may be refusing every send: we know\nsomeone opted out but not which number, so no destination can be proven\nunrelated to them."},"PagedLimits":{"properties":{"page_default":{"type":"integer","title":"Page Default","description":"Rows per page when `limit` is not sent"},"page_max":{"type":"integer","title":"Page Max","description":"Largest `limit` accepted"},"pass_ceiling":{"type":"integer","title":"Pass Ceiling","description":"Most rows one pass returns; beyond it the last page is `truncated` and `omitted` says how many eligible rows remained"}},"type":"object","required":["page_default","page_max","pass_ceiling"],"title":"PagedLimits"},"PagedReadErrorBody":{"properties":{"code":{"type":"string","title":"Code","description":"`PAGED_READ_UNAVAILABLE` (503), `CURSOR_INVALID` (422, an invalid cursor), `CURSOR_EXPIRED` (422, an expired cursor) or `INVALID_PARAMETER` (422)"},"message":{"type":"string","title":"Message","description":"A fixed, human-readable message; never database text"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"503 only: `deadline` or `database`"},"retry_after_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Retry After Ms","description":"503 only: how long to wait before retrying, in milliseconds"}},"type":"object","required":["code","message"],"title":"PagedReadErrorBody"},"PagedScope":{"properties":{"site_id":{"type":"string","title":"Site Id","description":"The site read"},"camera_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Camera Ids","description":"The cameras read, or null for every camera at the site"},"kind":{"type":"string","enum":["cameras","site"],"title":"Kind","description":"`cameras` when narrowed by `camera_id`, else `site`"}},"type":"object","required":["site_id","kind"],"title":"PagedScope"},"PagedSnapshot":{"properties":{"as_of":{"type":"string","format":"date-time","title":"As Of","description":"The snapshot instant, pinned on page 1 and identical on every page of the pass. Only rows created at or before it are returned; a row created later, or one that only becomes visible to the queue after it, is the push stream's and the next pass's (it may still appear if its key is ahead of the cursor). `complete` means complete AS OF this instant."},"server_time":{"type":"string","format":"date-time","title":"Server Time","description":"The database clock when this page was read. Age = server_time - queued_at, no workstation clock."}},"type":"object","required":["as_of","server_time"],"title":"PagedSnapshot"},"ParentModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique client id","examples":["<UUID of some sort>"]},"company_name":{"type":"string","title":"Company Name","description":"Name of the client","examples":["Joe"]},"type":{"$ref":"#/components/schemas/ParentType","description":"Type of the client","examples":["End User","Internal"]},"primary_contact_name":{"type":"string","title":"Primary Contact Name","description":"Name of the primary contact","examples":["Joe Johnson"]},"primary_contact_phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Primary Contact Phone Number","description":"Phone number of the primary contact. Absent for customer roles.","examples":["1013217890"]},"primary_contact_email":{"type":"string","title":"Primary Contact Email","description":"Email of the primary contact","examples":["JJ@example.com"]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Notes about the client","examples":["Super important notes"]}},"type":"object","required":["id","company_name","type","primary_contact_name","primary_contact_email"],"title":"ParentModel"},"ParentSiteDisarmStateModel":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site"},"site_name":{"type":"string","title":"Site Name","description":"Its name","examples":["Jerry Seiner Salt Lake"]},"disarm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Until","description":"When its cameras come back on. An early re-arm stamps this with the moment it ended rather than nulling it, so a non-null value does NOT mean 'disarmed' — read `disarm_active`."},"disarm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Set At","description":"When the most recent disarm window began."},"disarm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm By","description":"Who turned them off: 'operator:<uuid>', 'sms:<E.164>' for a customer text, or 'voice:<E.164>' for a customer call.","examples":["sms:+18015550142","voice:+18015550142"]},"disarm_active":{"type":"boolean","title":"Disarm Active","description":"Whether this site's cameras are off right now. The window predicate, resolved server-side; with ?active=1 every row is true.","default":false,"examples":[true,false]}},"type":"object","required":["site_id","site_name"],"title":"ParentSiteDisarmStateModel","description":"One site of this client and its live disarm state.\n\nFeeds the sites-list \"Disarmed until …\" chip and the \"Disarmed now\" filter.\nSite-granular rather than parent-granular because a site-narrowed operator\nmust only see the sites they hold."},"ParentType":{"type":"string","enum":["Monitoring Dealer","End User","Internal"],"title":"ParentType"},"PatchOperatorPermissionsRequest":{"properties":{"permissions":{"additionalProperties":{"type":"boolean"},"propertyNames":{"$ref":"#/components/schemas/OperatorPermission"},"type":"object","minProperties":1,"title":"Permissions","description":"Only the permission overrides to change. Omitted keys retain their current stored values. Concurrent patches are serialized; the last write to the same key wins."}},"additionalProperties":false,"type":"object","required":["permissions"],"title":"PatchOperatorPermissionsRequest"},"PendingCaptureResponse":{"properties":{"capture_id":{"type":"string","format":"uuid","title":"Capture Id","description":"Capture id."},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera to record."},"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"The alarm this belongs to."},"trigger_kind":{"type":"string","title":"Trigger Kind","description":"Which voice down this follows."},"ingest_mode":{"type":"string","title":"Ingest Mode","description":"motion_clip or continuous (bridge cameras are motion_clip)."},"window_start":{"type":"string","format":"date-time","title":"Window Start","description":"Authoritative start of the recording; never shifted."},"window_end":{"type":"string","format":"date-time","title":"Window End","description":"window_start + duration_seconds."},"duration_seconds":{"type":"integer","title":"Duration Seconds","description":"30 in v1."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"Claim-by deadline; after it the server marks the capture unavailable."},"s3_key":{"type":"string","title":"S3 Key","description":"Server-assigned upload key under clips/captures/."},"status":{"type":"string","title":"Status","description":"pending, or claimed with a lapsed lease (both claimable)."},"attempts":{"type":"integer","title":"Attempts","description":"Claims so far (cap 3)."}},"type":"object","required":["capture_id","camera_id","event_id","trigger_kind","ingest_mode","window_start","window_end","duration_seconds","expires_at","s3_key","status","attempts"],"title":"PendingCaptureResponse"},"PipelineTimeseriesPoint":{"properties":{"bucket_at":{"type":"string","format":"date-time","title":"Bucket At"},"motions_detected":{"type":"integer","title":"Motions Detected","default":0},"validator_rejected":{"type":"integer","title":"Validator Rejected","default":0},"validator_confirmed":{"type":"integer","title":"Validator Confirmed","default":0},"events_total":{"type":"integer","title":"Events Total","default":0},"agent_dismissed":{"type":"integer","title":"Agent Dismissed","description":"Events in this bucket automatically dismissed by agent mode (agent auto-dismiss decisions), never surfaced to the human queue","default":0},"gate_filtered":{"type":"integer","title":"Gate Filtered","description":"Events in this bucket closed before AI review by a pre-processing gate (gate_stop_auto_dismiss): DeepAlert finding no object of interest, the person confirmer (r50) not confirming DeepAlert's person, or the vehicle-flash confirmer matching a parked vehicle. DeepAlert is ~94% of it (prod, 24h to 2026-10-11). Most never reach the operator queue; an early-broadcast card is shown until the verdict closes it","default":0},"events_queued":{"type":"integer","title":"Events Queued","description":"Events created in this bucket that reached the operator queue: queue-visible (verdict landed, II-off, or early-broadcast), minus those the agent closed in their own eval (auto-dismiss, routing ignore, DeepAlert gate stop) before any operator saw them","default":0},"errors_total":{"type":"integer","title":"Errors Total","default":0}},"type":"object","required":["bucket_at"],"title":"PipelineTimeseriesPoint"},"PipelineTimeseriesSeries":{"properties":{"key":{"type":"string","title":"Key"},"label":{"type":"string","title":"Label"},"points":{"items":{"$ref":"#/components/schemas/PipelineTimeseriesPoint"},"type":"array","title":"Points"}},"type":"object","required":["key","label","points"],"title":"PipelineTimeseriesSeries"},"PostCameraConnectionTestRequest":{"properties":{"camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","maxItems":12,"minItems":1,"title":"Camera Ids","description":"Camera row ids. Any id the caller cannot see on the Camera Health tab makes the whole request a 404 and nothing is probed.","examples":[["55555555-5555-5555-5555-555555555555"]]}},"type":"object","required":["camera_ids"],"title":"PostCameraConnectionTestRequest","description":"Which cameras to try to connect to now.\n\nThe console sends one site's down cameras per call. Bounded so a single\nrequest stays inside its write timeout even when every camera hangs."},"PostCameraConnectionTestResponse":{"properties":{"tested_at":{"type":"string","format":"date-time","title":"Tested At","description":"Server time of the test; also the re-check stamp written on the cameras."},"recheck_requested":{"type":"integer","title":"Recheck Requested","description":"motion_clip cameras newly asked for a health sample (same cooldown as Re-check cameras). The ingestor answers within about a minute when it can reach the camera."},"results":{"items":{"$ref":"#/components/schemas/CameraConnectionResult"},"type":"array","title":"Results","description":"One per requested camera, in request order."}},"type":"object","required":["tested_at","recheck_requested","results"],"title":"PostCameraConnectionTestResponse"},"PostCameraDownNotifyRequest":{"properties":{"emails":{"items":{"$ref":"#/components/schemas/CameraDownEmailDraft"},"type":"array","maxItems":25,"minItems":1,"title":"Emails","description":"One entry per site; a site may appear once. At most 60 recipient sends in total across all entries."}},"type":"object","required":["emails"],"title":"PostCameraDownNotifyRequest","description":"`POST /analytics/camera-health/notify-down`: one e-mail per site."},"PostCameraDownNotifyResponse":{"properties":{"sent_at":{"type":"string","format":"date-time","title":"Sent At","description":"When the sends started."},"sites":{"items":{"$ref":"#/components/schemas/CameraDownSiteResult"},"type":"array","title":"Sites","description":"One per requested site, in request order."}},"type":"object","required":["sent_at","sites"],"title":"PostCameraDownNotifyResponse"},"PostCameraHealthRecheckRequest":{"properties":{"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id","description":"Re-check only this client's cameras. A client outside the caller's grants is a 403, never a silent no-op.","examples":["11111111-1111-1111-1111-111111111111"]},"site_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Id","description":"Re-check only this site's cameras. A site outside a site-narrowed caller's grants is a 403.","examples":["33333333-3333-3333-3333-333333333333"]}},"type":"object","title":"PostCameraHealthRecheckRequest","description":"Which slice of the Camera Health tab to re-check.\n\nThe same two filters GET /analytics/camera-health takes, with the same\nmeaning and the same scope checks: both omitted means every camera the\ncaller can see on the tab. UUID-typed so a malformed id is a 422 at the\nedge rather than a database error."},"PostCameraHealthRecheckResponse":{"properties":{"requested_at":{"type":"string","format":"date-time","title":"Requested At","description":"The server time stamped on every camera this call requested. Matches health_sample_requested_at on those rows in the next GET /analytics/camera-health."},"requested":{"type":"integer","title":"Requested","description":"motion_clip cameras asked for a sample by this call. The ingestor picks the request up on its next config poll (about 30 s) and posts one on-demand sample.","examples":[12]},"already_pending":{"type":"integer","title":"Already Pending","description":"motion_clip cameras already asked within the last cooldown_seconds. Left as they were: one sample per camera per cooldown window, however many times anyone clicks.","examples":[3]},"unsupported":{"type":"integer","title":"Unsupported","description":"In-scope cameras that cannot take an on-demand sample because they are not motion_clip (continuous ingest has no health sampler).","examples":[2]},"cooldown_seconds":{"type":"integer","title":"Cooldown Seconds","description":"The cooldown window, in seconds. Same value as recheck_cooldown_seconds on GET /analytics/camera-health.","examples":[120]}},"type":"object","required":["requested_at","requested","already_pending","unsupported","cooldown_seconds"],"title":"PostCameraHealthRecheckResponse","description":"What one \"Re-check cameras\" click did across the in-scope fleet.\n\nrequested + already_pending + unsupported is every in-scope camera."},"PostCameraHealthSampleRequest":{"properties":{"captured_at":{"type":"string","format":"date-time","title":"Captured At","description":"Worker clock at frame capture. Upsert key together with the camera id — re-posting the same captured_at updates the existing sample rather than duplicating it.","examples":["2026-08-22T12:00:00Z"]},"snapshot_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Snapshot Key","description":"S3 key of the captured JPEG under this camera's health-snapshots prefix. Null when no frame could be extracted this tick. A key outside health-snapshots/{camera_id}/ is rejected with 422.","examples":["health-snapshots/2b1d3f2e-6e78-4d9b-93d6-3c1f9e40a1d4/20260822T120000.123456Z.jpg"]},"sharpness":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Sharpness","description":"Normalized Laplacian variance, 0..1; higher is sharper. Null when no frame.","examples":[0.82]},"luminance":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Luminance","description":"Mean grayscale luminance, 0..1. Null when no frame.","examples":[0.47]},"frame_hash":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{16}$"},{"type":"null"}],"title":"Frame Hash","description":"64-bit dHash of the frame as 16 lowercase hex chars. Equality with the previous sample's hash is what advances identical_run.","examples":["9f1c3a7d20b45e68"]},"occlusion_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Occlusion Pct","description":"Percentage of the frame that is flat and unlike the reference image. Null when no frame OR no reference image is set — a missing measurement, not zero.","examples":[3.1]},"shift_pct":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"title":"Shift Pct","description":"How far the view has shifted from the reference image, 0..100. Null when no frame OR no reference image is set. Always sent; PTZ suppression happens server-side.","examples":[1.5]},"width":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Width","description":"Probed stream width in pixels.","examples":[1920]},"height":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Height","description":"Probed stream height in pixels.","examples":[1080]},"fps":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Fps","description":"Probed average frame rate.","examples":[15.0]},"bitrate_kbps":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Bitrate Kbps","description":"Ingest bitrate of the sampled segment, kbps.","examples":[2048]},"reconnects":{"type":"integer","minimum":0.0,"title":"Reconnects","description":"Runner restarts SINCE THE PREVIOUS SAMPLE, scheduled or on-demand — a per-interval delta, never a cumulative counter. The server sums these across every 24h sample (on-demand ones included) for the stream-flapping check, so an on-demand sample must reset the delta exactly as a scheduled one does, or restarts are counted twice.","default":0,"examples":[0]},"on_demand":{"type":"boolean","title":"On Demand","description":"True when this sample answers an operator's re-check request (health_sample_requested_at on the camera-config poll) rather than the hourly schedule. Stored on the row. On-demand samples are excluded from the frozen-frame run, whose threshold is calibrated on hourly spacing; every other check treats them as ordinary samples. Omitted means false, so an ingestor without re-check support posts scheduled samples unchanged.","default":false,"examples":[false]},"worker_id":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Worker Id","description":"Identifier of the ingest worker that produced the sample (ECS task metadata URI); truncated to 512 client-side."},"image_version":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Image Version","description":"Container image version of the ingest worker; truncated to 512 client-side."}},"type":"object","required":["captured_at"],"title":"PostCameraHealthSampleRequest","description":"One hourly camera-health sample from the ingest worker.\n\nEvery image field is optional because the sample is still posted when no\nframe could be extracted (snapshot_key=None, image metrics null) — the\nstream stats and the reconnect count remain useful precisely when the\nvideo does not. No extra=\"forbid\": nothing in this repo forbids extras,\nand doing so here would 422 an ingestor deployed ahead of a backend\nrollback."},"PostCameraHealthSampleResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera_health_samples row id (existing row's id on an upsert)."},"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"The camera the sample belongs to."},"captured_at":{"type":"string","format":"date-time","title":"Captured At","description":"Worker clock at frame capture, echoed back."}},"type":"object","required":["id","camera_id","captured_at"],"title":"PostCameraHealthSampleResponse","description":"Acknowledgement of one stored sample.\n\nCarries no derived state: the consecutive-identical-frame count is\ncomputed from the series on the health read, not maintained at ingest.\nThe ingest worker only checks that a body came back (`poster(...) is\nNone`), so this stays minimal on purpose."},"PostPipelineTelemetryRequest":{"properties":{"records":{"items":{"$ref":"#/components/schemas/CameraPipelineTelemetryRecord"},"type":"array","maxItems":200,"minItems":1,"title":"Records"}},"type":"object","required":["records"],"title":"PostPipelineTelemetryRequest"},"ProviderCameraResponse":{"properties":{"device_id":{"type":"string","title":"Device Id","description":"The provider's own device identifier.","examples":["fake-cam-h264-online"]},"name":{"type":"string","title":"Name","description":"Camera name as configured in the provider's console.","examples":["Front Gate"]},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Camera model reported by the provider.","examples":["CD52-E"]},"codec":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Codec","description":"Video codec the provider reports for this camera. The ingest pipeline requires h264; the onboarding UI greys out anything else.","examples":["h264","h265"]},"online":{"type":"boolean","title":"Online","description":"Whether the provider currently reports the camera as online. Offline cameras cannot be onboarded."},"site_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Id","description":"The provider's own identifier for the site this camera belongs to. The onboarding wizard groups the picker by this. Always a leaf site as the camera reports it, never a parent group node.","examples":["fake-site-primary"]},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site name as shown in the provider's console, for the group heading in the onboarding wizard.","examples":["Fake Dealership"]},"streamable":{"type":"boolean","title":"Streamable","description":"Whether the provider reports this camera as able to stream AT ALL — a different axis from `online`. False means an entitlement block (Turing: no Core AI licence), and the onboarding UI greys the row with that reason rather than an offline or codec one. Providers with no such concept always report true.","default":true},"licenses":{"items":{"type":"string"},"type":"array","title":"Licenses","description":"Entitlement names the provider reports for this camera. Informational only — `streamable` is the gate. Empty when the provider reports none or exposes no such field.","examples":[["Core AI"]]}},"type":"object","required":["device_id","name","online"],"title":"ProviderCameraResponse"},"PublicSharedClip":{"properties":{"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"The site's display name -- the recipient's own premises."},"camera_label":{"type":"string","title":"Camera Label","description":"The camera's display label. NEVER the stream key, the device id or anything RTSP."},"occurred_at":{"type":"string","format":"date-time","title":"Occurred At","description":"When the motion happened (the event's `occurred_at`, else its `created_at`), UTC."},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone","description":"The site's IANA timezone, so the page can render local time.","examples":["America/Los_Angeles"]},"clip_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clip Url","description":"Presigned GET for the clip, SHARE_LINK_CLIP_URL_TTL_SECONDS (default 900). Null when the presign failed; the page then says the clip is unavailable."},"poster_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Poster Url","description":"Presigned GET for the poster frame, same TTL. Null when no poster key derives from the clip key or the presign failed."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"When this link stops working, so the page can say so."},"brand_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand Name","description":"The monitoring dealer's customer-facing name, present ONLY when that dealer turned its branding on (white-label). Null means the page is Verdun-branded. A name the dealer chose to show its own customers, never the internal client record.","examples":["Weiser Security Services"]},"brand_logo_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand Logo Url","description":"Presigned GET for the dealer's logo (PNG), same TTL as the clip. Null when branding is off, no logo is saved, or the presign failed."}},"type":"object","required":["camera_label","occurred_at","expires_at"],"title":"PublicSharedClip","description":"The ONLY body `GET /public/share/{token}` ever returns on a live link.\n\nNINE KEYS, NO IDS OF ANY KIND. The holder of this link is an\nunauthenticated member of the public who happens to have been texted it,\nso the page reveals the minimum that makes the clip meaningful: whose\npremises, which camera, when, and the video. No contact name, no operator\nname, no AI rationale, no vendor string, no parent/client name (the\nopt-in white-label `brand_name` is the dealer's chosen display name, not\nthe client record), no other events, and nothing that could be replayed against another endpoint.\n\nIt lives beside the operator-facing share-link models on purpose: the\nwhole contract for this feature is one file, so a field added to the\npublic shape cannot be reviewed without the operator shapes in view."},"PurchaseDisarmNumberRequest":{"properties":{"area_code":{"anyOf":[{"type":"string","maxLength":3,"minLength":3,"pattern":"^[2-9][0-9]{2}$"},{"type":"null"}],"title":"Area Code","description":"Preferred NANP area code for the new number. Omit to let Surge choose. Ignored when an in-flight purchase is being reconciled.","examples":["801"]}},"type":"object","title":"PurchaseDisarmNumberRequest","description":"Buy (or reconcile) the dealer's one SMS line.\n\nRE-POSTING IS THE RECONCILE, deliberately, rather than a second endpoint:\na network failure during the purchase leaves the parent in `purchasing`\nwith no number, and the honest recovery is to ask Surge what we actually\nown before buying again. A separate /reconcile route would be a second\nowner-only mutating surface for what is naturally retry semantics on the\nroute that created the ambiguity."},"PushConfigResponse":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Whether this deployment can send Web Push"},"public_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Key","description":"VAPID public key; never the private key"}},"type":"object","required":["enabled","public_key"],"title":"PushConfigResponse"},"PushSubscriptionInput":{"properties":{"endpoint":{"type":"string","maxLength":2048,"minLength":1,"title":"Endpoint","description":"Browser push service endpoint"},"p256dh":{"type":"string","maxLength":128,"title":"P256Dh","description":"Base64url browser public key"},"auth":{"type":"string","maxLength":64,"title":"Auth","description":"Base64url browser auth secret"}},"type":"object","required":["endpoint","p256dh","auth"],"title":"PushSubscriptionInput"},"PushSubscriptionResponse":{"properties":{"registered":{"type":"boolean","title":"Registered","description":"Whether this browser is registered to this customer"}},"type":"object","required":["registered"],"title":"PushSubscriptionResponse"},"QueueOrder":{"properties":{"mode":{"type":"string","title":"Mode","description":"`oldest_first`: rank (aged >= focus_age_cap_s first, then the caller's own valid Focus, then the rest), then queued_at, then id. Severity does not sort.","examples":["oldest_first"]},"focus_age_cap_s":{"type":"integer","title":"Focus Age Cap S","description":"T: an eligible alarm queued at least this many seconds goes ahead of own-Focus preference.","examples":[45]}},"type":"object","required":["mode","focus_age_cap_s"],"title":"QueueOrder"},"ROIHitModel":{"properties":{"roi_id":{"type":"string","title":"Roi Id","description":"ROI ID"},"bbox":{"$ref":"#/components/schemas/BboxModel","description":"Bounding box"},"confidence":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Confidence","description":"Confidence score; null = the producer made no calibrated claim about this hit (ii InterpretationResultV1). Required-nullable: an omitted field is still a validation error."},"frame_index":{"type":"integer","title":"Frame Index","description":"Frame index"},"timestamp_ms":{"type":"integer","title":"Timestamp Ms","description":"Timestamp in milliseconds"},"source_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Provider","description":"Provider of the detection this ROI hit was derived from, e.g. 'deepalert' | 'gpt-4o-vision'. Null on rows written before ii emitted it."}},"type":"object","required":["roi_id","bbox","confidence","frame_index","timestamp_ms"],"title":"ROIHitModel","description":"One ROI-zone hit from ii's `InterpretationResultV1.roi_hits`.\n\n`confidence` carries the same required-nullable semantics as\n`DetectionModel.confidence` (see that model's docstring — it is the\ncanonical statement): ii copies it verbatim from the source detection, so\na promoted VLM box yields a hit with no calibrated claim either.\n\n`source_provider` is the explicit provenance ops-view renders on\n(round-0 ruling R2/A3). It is deliberately NOT inferred from a null\nconfidence: \"made no calibrated claim\" and \"VLM-derived\" are different\npredicates, and a future CV provider that abstains, or a VLM that\nself-scores, would silently mislabel the chip. Optional-with-default\nbecause ii's own field is additive-optional; these models are pydantic\ndefault `extra=\"ignore\"`, so it must be declared here or the key is\nsilently dropped off the wire."},"RaiseOperatorAlarmRequest":{"properties":{"incident_type":{"$ref":"#/components/schemas/IncidentType","description":"What the operator saw (an IncidentType name). Required: there is no AI verdict to supply it.","examples":["INTRUSION"]},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Note","description":"Optional free text, trimmed; empty becomes null. Stored as raised_note and shown beside the alarm, never in the AI-rationale slot.","examples":["Two people at the north gate, one carrying a crowbar"]},"idempotency_key":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Idempotency Key","description":"Optional client-minted key. A retry carrying the same key from the same operator returns the original alarm (existing=true) instead of raising another.","examples":["3f2b8e1c9d"]}},"additionalProperties":false,"type":"object","required":["incident_type"],"title":"RaiseOperatorAlarmRequest","description":"`POST /cameras/{camera_id}/operator-alarm`: an operator on the Live View\nwall raises an alarm by hand so dispatch can run on it."},"RaiseOperatorAlarmResponse":{"properties":{"event":{"$ref":"#/components/schemas/EventsModel","description":"The event information"},"camera":{"anyOf":[{"$ref":"#/components/schemas/CamerasModel"},{"type":"null"}],"description":"The camera information"},"site":{"anyOf":[{"$ref":"#/components/schemas/SitesModel"},{"type":"null"}],"description":"The site information"},"parent":{"anyOf":[{"$ref":"#/components/schemas/ParentModel"},{"type":"null"}],"description":"The parent information"},"operator_evaluation":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Operator Evaluation","description":"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":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Interpretation Result","description":"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":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Agent Action","description":"Newest agent action for this event (voice-down lifecycle states), if any"},"thread":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Thread","description":"Incident-thread summary when this event is part of a cross-camera thread; GET /threads/{id} returns the full timeline"},"extended_clip_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Extended Clip Url","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Poster Url","description":"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":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Continued Poster Url","description":"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":{"anyOf":[{"$ref":"#/components/schemas/OperatorAlarmCaptureModel"},{"type":"null"}],"description":"Operator-raised alarms only: the state of the alarm's own clip recording, {status, reason}. pending | claimed means the clip is still recording (re-poll GET /events/{event_id}, which sets event.clip_s3_url once it lands); captured means it landed; unavailable | failed means no clip will come. Null/absent on every other event, and on an operator-raised alarm for which no recording was requested."},"talk_down_available":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Talk Down Available","description":"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":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Talk Down Available As Of","description":"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":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ai Announcement Available","description":"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":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ai Announcement Available As Of","description":"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":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Voice Agent Call","description":"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":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ai Feedback Requested","description":"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."},"existing":{"type":"boolean","title":"Existing","description":"False: this call created the alarm (HTTP 201). True: the camera already had an open operator-raised alarm, or this was a replay of the same idempotency key, and this is that alarm (HTTP 200). Join it rather than raising another."}},"type":"object","required":["event","existing"],"title":"RaiseOperatorAlarmResponse","description":"The SAME bundle `GET /events/{event_id}` returns (site, contacts, parent,\ncamera, the event with its raised_* fields and operator_alarm_capture), so\nthe client can open the dispatch flow without a second fetch, plus whether\nthis call created the alarm."},"RearmInput":{"properties":{"disarm_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Disarm Id","description":"The currently displayed disarm identity this rearm is ending. Null only when the status showed active with no identity"}},"type":"object","title":"RearmInput"},"RecommendedAction":{"type":"string","enum":["false_alarm","non_threatening","escalate_voice_down","escalate_dispatch"],"title":"RecommendedAction"},"ReconcileOptOutRequest":{"properties":{"candidate_phone":{"type":"string","maxLength":50,"title":"Candidate Phone","description":"A CANDIDATE number for the opt-out, never an assertion. The server proves it belongs to the event's Surge contact with a live lookup before suppressing anything; an unprovable candidate resolves nothing and suppresses nothing.","examples":["(619) 555-0142"]}},"type":"object","required":["candidate_phone"],"title":"ReconcileOptOutRequest"},"ReconcileOptOutResponse":{"properties":{"outcome":{"type":"string","title":"Outcome","description":"resolved | already_resolved | unverified. `unverified` means the candidate number could NOT be proven to belong to this contact: nothing was suppressed, nothing was resolved, and SMS stays paused."},"event":{"$ref":"#/components/schemas/OptOutEventResponse","description":"The event's state after the attempt."}},"type":"object","required":["outcome","event"],"title":"ReconcileOptOutResponse"},"RefreshRequest":{"properties":{"refresh_token":{"type":"string","title":"Refresh Token","description":"Refresh token"},"user_id":{"type":"string","title":"User Id","description":"Email address"}},"type":"object","required":["refresh_token","user_id"],"title":"RefreshRequest"},"RefreshResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"New short-lived access token (JWT).","examples":["eyJhbGciOi...access2"]},"id_token":{"type":"string","title":"Id Token","description":"New identity token (JWT).","examples":["eyJhbGciOi...id2"]},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the new access token expires.","examples":[3600]}},"type":"object","required":["access_token","id_token","expires_in"],"title":"RefreshResponse"},"ReleaseDisarmNumberResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"The number was released at Surge and cleared from the client.","examples":[true]}},"type":"object","required":["success"],"title":"ReleaseDisarmNumberResponse"},"ReleaseHoldRequest":{"properties":{"reason":{"type":"string","enum":["submit","flag_off"],"title":"Reason","description":"`submit` (the holder finished: Close all) or `flag_off` (the console's best-effort label when it stops on the capability going false). Only the line's reason differs.","default":"submit"}},"additionalProperties":false,"type":"object","title":"ReleaseHoldRequest","description":"Optional body of `POST /focus/{id}/release`. Only a site hold reads it; Focus ignores it."},"RemovePushInput":{"properties":{"endpoint":{"type":"string","maxLength":2048,"title":"Endpoint","description":"This browser's registered endpoint"}},"type":"object","required":["endpoint"],"title":"RemovePushInput"},"RenameDemoCollectionRequest":{"properties":{"name":{"type":"string","maxLength":32,"minLength":1,"title":"Name","description":"Owner-defined Demo tab name. Case-insensitively unique; 'all' is reserved.","examples":["dealership"]}},"type":"object","required":["name"],"title":"RenameDemoCollectionRequest","description":"Same single-field name contract as create — a distinct model name keeps\nthe PATCH body legible in the generated OpenAPI schema."},"RenameDemoCollectionResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unchanged collection id — clip membership and the tab's console colour key off it"},"name":{"type":"string","title":"Name","description":"The tab's name after the rename","examples":["auto group"]},"previous_name":{"type":"string","title":"Previous Name","description":"The tab's name before the rename; equal to `name` when nothing changed","examples":["dealership"]},"changed":{"type":"boolean","title":"Changed","description":"False when the submitted name was already in place (no write, no Activity row)","examples":[true]},"event_ids":{"items":{"type":"string"},"type":"array","title":"Event Ids","description":"Demo events filed under the tab, oldest membership first — unaffected by a rename"},"created_by_operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By Operator Id","description":"Operator who created the tab, if still on file"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When the tab was created"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When the tab was last modified"}},"type":"object","required":["id","name","previous_name","changed","event_ids"],"title":"RenameDemoCollectionResponse","description":"The renamed Demo tab, shaped like the create response so the console can\nreplace the row it already holds, plus what the rename actually did."},"ReorderSiteContactsRequest":{"properties":{"contact_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Contact Ids","description":"Every contact id on the site, first-to-call first.","examples":[["3fa85f64-5717-4562-b3fc-2c963f66afa6"]]}},"type":"object","required":["contact_ids"],"title":"ReorderSiteContactsRequest","description":"The site's whole contact list, in the order operators should call it.\n\nEXACTLY the site's current contacts, each once: a list naming a contact\nthat is gone (or was added since the console loaded) is refused rather\nthan half-applied, so two people reordering at once cannot silently drop\neach other's rows to the bottom."},"ReorderSiteContactsResponse":{"properties":{"contacts":{"items":{"$ref":"#/components/schemas/SiteContactResponseModel"},"type":"array","title":"Contacts","description":"The site's contacts in their new call order."}},"type":"object","required":["contacts"],"title":"ReorderSiteContactsResponse"},"ReportLineRequest":{"properties":{"outcome":{"anyOf":[{"type":"string","enum":["officers_sent","already_aware","report_taken","declined","call_back"]},{"type":"null"}],"title":"Outcome","description":"What the dispatcher said, from the closed list. null clears the line (the call is then left out of the customer's daily report).","examples":["officers_sent"]},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference","description":"The dispatcher's reference number, if one was given. Needs an outcome.","examples":["26-118342"]}},"type":"object","title":"ReportLineRequest"},"ReportLineResponse":{"properties":{"id":{"type":"string","title":"Id","description":"dispatch_call id."},"report_outcome":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Report Outcome","description":"The operator-confirmed outcome; null = no line."},"report_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Report Reference","description":"Operator-confirmed reference number."},"ai_outcome":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Outcome","description":"The AI's suggestion (never sent to a customer by itself)."},"ai_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Reference","description":"AI-suggested reference number, if any."}},"type":"object","required":["id"],"title":"ReportLineResponse"},"Reporting":{"properties":{"emails":{"type":"string","maxLength":2000,"title":"Emails","description":"Extra nightly-report recipients, comma separated.","default":""},"send_local_time":{"type":"string","maxLength":10,"title":"Send Local Time","description":"Nightly report time, site-local HH:MM.","default":""},"start_date":{"type":"string","maxLength":20,"title":"Start Date","description":"Requested monitoring start date.","default":""},"current_provider":{"type":"string","maxLength":200,"title":"Current Provider","description":"Current monitoring company, if switching.","default":""},"cutover_date":{"type":"string","maxLength":20,"title":"Cutover Date","description":"When the current provider's service ends.","default":""},"parallel_run":{"type":"boolean","title":"Parallel Run","description":"Run alongside the current provider before switching.","default":false},"notes":{"type":"string","maxLength":5000,"title":"Notes","description":"Anything else.","default":""}},"additionalProperties":false,"type":"object","title":"Reporting"},"Reservation":{"properties":{"origin":{"type":"string","enum":["focus","story"],"title":"Origin","description":"`story` for a site hold. A reservation from an older api has no `origin`: it is a Focus.","default":"focus"},"focus_id":{"type":"string","title":"Focus Id","description":"The focus generation or site hold that reserved this alarm."},"operator_id":{"type":"string","title":"Operator Id","description":"The focuser or holder."},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Their display name; null if the operator no longer exists."},"reserved_at":{"type":"string","title":"Reserved At","description":"When the alarm entered the queue and was reserved (ISO 8601, UTC)."},"elapsed_seconds":{"type":"integer","title":"Elapsed Seconds","description":"Server-computed now - reserved_at, for the age display; the unreviewed-age cue applies to reserved alarms exactly as to free ones."},"engaged":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Engaged","description":"Story only: the current holder has opened this alarm."},"open":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Open","description":"Story only: the holder's page is open (no deadline while it is)."},"return_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return At","description":"Story only: when the unattended hold returns its alarms (ISO 8601, UTC); null while `open`. After a clean leave, about 30 s after it unless an earlier idle or new-alert deadline or a live call changes it."},"now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Now","description":"Story only: the server clock `return_at` is measured against."},"serious_unopened":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Serious Unopened","description":"Story only: serious alarms the holder has not opened (visibility only)."},"oldest_serious_queued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oldest Serious Queued At","description":"Story only: when the oldest of those entered the queue."}},"type":"object","required":["focus_id","operator_id","reserved_at","elapsed_seconds"],"title":"Reservation","description":"An alarm reserved for a focuser or held by a site hold. A reservation is NOT a claim: it has no TTL and lives under the claim."},"ReviewCallAuditRequest":{"properties":{"confirm":{"type":"boolean","title":"Confirm","description":"True confirms the peer's overturn (it then costs a human decider 1000 points and earns the peer 1000, or 250 when the decider was the AI agent, which loses nothing); false rejects it."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Optional note for the decider, up to 2000 characters after stripping. Blank reads as absent."}},"additionalProperties":false,"type":"object","required":["confirm"],"title":"ReviewCallAuditRequest"},"RotateIntegrationCredentialsRequest":{"properties":{"api_key":{"type":"string","format":"password","title":"Api Key","description":"Replacement provider API key. WRITE-ONLY, and validated against the provider before the stored secret is overwritten — a rejected key leaves the existing credentials untouched.","writeOnly":true,"examples":["<API_KEY>"]},"org_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Id","description":"Replacement organization id. Omit to keep the stored one (the same omitted-means-unchanged contract as the NVR edit password), so rotating a key alone cannot silently drop the org id.","examples":["ceb8ff18-0000-0000-0000-000000000000"]},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username","description":"AvertX only: a replacement sign-in email. Omit to keep the stored one (omitted-means-unchanged, like org_id). Ignored by Verkada and Turing.","examples":["support@verdun.ai"]}},"type":"object","required":["api_key"],"title":"RotateIntegrationCredentialsRequest"},"RuleCallAuditDisputeRequest":{"properties":{"reverse":{"type":"boolean","title":"Reverse","description":"True reverses the overturn (the operator gets the points back); false lets it stand."}},"additionalProperties":false,"type":"object","required":["reverse"],"title":"RuleCallAuditDisputeRequest"},"SameHours":{"properties":{"mode":{"type":"string","const":"same","title":"Mode","description":"The same hours on every watched day."},"all_day":{"type":"boolean","title":"All Day","description":"Watched around the clock on the chosen days."},"start":{"type":"string","maxLength":10,"title":"Start","description":"Start time, 24-hour HH:MM (unless all_day).","default":""},"end":{"type":"string","maxLength":10,"title":"End","description":"End time, 24-hour HH:MM; earlier than start means overnight.","default":""},"days":{"items":{"type":"string","enum":["Mon","Tue","Wed","Thu","Fri","Sat","Sun"]},"type":"array","maxItems":7,"minItems":1,"title":"Days","description":"Weekdays watched, each at most once."}},"additionalProperties":false,"type":"object","required":["mode","all_day","days"],"title":"SameHours"},"SaveConcessionRequest":{"properties":{"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client the concession was given to."},"title":{"type":"string","maxLength":200,"minLength":1,"title":"Title","description":"One line saying what was given.","examples":["First month free"]},"details":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Details","description":"Anything else worth knowing. Blank or null clears it.","examples":["Agreed on the 10/14 call in exchange for a 2-year term."]},"starts_on":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Starts On","description":"When it starts, if dated.","examples":["2026-11-01"]},"ends_on":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Ends On","description":"When it ends, if it does. On or after starts_on.","examples":["2027-10-31"]}},"type":"object","required":["parent_id","title"],"title":"SaveConcessionRequest","description":"Create or fully replace a concession. Every field is sent on each save."},"ScheduleModel":{"properties":{"grid":{"items":{"items":{"type":"string","enum":["monitored","heightened","off"]},"type":"array"},"type":"array","title":"Grid","description":"7 rows (Monday-Sunday) of 24 hourly monitor states."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form operator notes about the schedule.","examples":["Cleaning crew Tue/Thu until 23:00"]},"id":{"type":"string","format":"uuid","title":"Id","description":"The schedule ID."},"site_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Id","description":"The site this schedule belongs to (null for camera overrides)."},"camera_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Camera Id","description":"The camera this schedule overrides (null for site schedules)."},"resolution_minutes":{"type":"integer","title":"Resolution Minutes","description":"Authoritative grain of this schedule: 60 when the 7x24 grid above is the source of truth, 15 when it is the coarsened projection of a quarter-hour schedule (GET /sites|cameras/{id}/monitoring-schedule). Additive-optional: safe to ignore. Editing a 15 schedule through THIS hourly endpoint discards its sub-hour detail.","default":60,"examples":[15,60]}},"type":"object","required":["grid","id"],"title":"ScheduleModel"},"SendMessageRequest":{"properties":{"body":{"type":"string","maxLength":1600,"minLength":1,"title":"Body","description":"Message body. Links are permitted — the 10DLC campaign declares them and link shortening is disabled per message, so a URL goes out exactly as typed (see NotifyOwnerRequest.message).","examples":["We have an operator reviewing the camera now."]},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Optional incident this message is about. When given, the message ALSO appears on that event's communications timeline; the event must be in your scope AND belong to this thread's site.","examples":["8f14e45f-ceea-467a-9f0b-2b0a1cbd7a71"]},"attach_snapshot":{"type":"boolean","title":"Attach Snapshot","description":"Attach the event's alert-frame JPEG as MMS. REQUIRES `event_id` — there is no snapshot without an incident to take it from, and `attach_snapshot` without one is a 422 at the body level. 409 SNAPSHOT_UNAVAILABLE when the event has no snapshot (no clip key, a clip key that derives none, or the object is not readable in S3); nothing is written and nothing is sent in that case. Absent or false behaves exactly as before.","default":false,"examples":[false]},"snapshot_offset_ms":{"anyOf":[{"type":"integer","maximum":600000.0,"minimum":0.0},{"type":"null"}],"title":"Snapshot Offset Ms","description":"WHICH moment of the clip to attach, in milliseconds from the start of the event's clip, as picked by the operator and cut earlier by `POST /events/{id}/snapshot-frame`. Rounded to a tenth of a second. Only meaningful with `attach_snapshot` (422 at the body level otherwise). Absent, `attach_snapshot` sends the ingestor's alert-frame poster as before. 409 SNAPSHOT_UNAVAILABLE when no frame exists at that offset.","examples":[7300]},"include_clip_link":{"type":"boolean","title":"Include Clip Link","description":"Append a login-free, expiring link to this event's clip. The URL is added by the SERVER to the end of the body; reserve share_link_reserved_chars (GET /telephony/config) characters for it. REQUIRES event_id — a clip link with no incident behind it is a 422.","default":false}},"type":"object","required":["body"],"title":"SendMessageRequest"},"SendSiteSummaryRequest":{"properties":{"mode":{"type":"string","enum":["test","resend"],"title":"Mode","description":"'test' sends only to the calling operator's own e-mail (audited with contact_id: null); 'resend' sends to every site contact with a usable address and no opt-out.","examples":["test"]}},"type":"object","required":["mode"],"title":"SendSiteSummaryRequest"},"SetBillingRateRequest":{"properties":{"rate_cents":{"anyOf":[{"type":"integer","maximum":10000000.0,"minimum":0.0},{"type":"null"}],"title":"Rate Cents","description":"Per-camera monthly rate in whole cents (4500 = $45.00), a JSON integer. On a client it is the base rate; on an account, site or camera it overrides the level above. null clears it, so the level inherits again.","examples":[4500,null]}},"type":"object","required":["rate_cents"],"title":"SetBillingRateRequest"},"SetCameraGroupMembersRequest":{"properties":{"camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Camera Ids","description":"The complete desired member set. Every camera must be a live camera at this camera group's own site (409 otherwise); a camera may belong to several camera groups at once."}},"type":"object","title":"SetCameraGroupMembersRequest","description":"Replace the camera group's membership with this exact set.\n\nReplace-set, not per-camera add/remove: the picker submits a set, and\nrewriting the whole set in one transaction is what makes the change atomic\n— two concurrent PUTs would otherwise interleave into the UNION of both\nsets, leaving a camera someone just removed still silenced by this group's\nschedule while the UI shows it removed.\n\nAn empty list empties the camera group. If the group has a schedule, that\nschedule then governs nobody; the group itself survives."},"SetCameraHealthReferenceRequest":{"properties":{"sample_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Sample Id","description":"Health sample whose captured frame becomes the reference image. Omit or send null to use the camera's most recent sample that actually has a frame. A sample belonging to another camera, or one with no frame, is rejected with 400."}},"type":"object","title":"SetCameraHealthReferenceRequest"},"SetCameraHealthReferenceResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera id."},"reference_set_at":{"type":"string","format":"date-time","title":"Reference Set At","description":"When this reference image was adopted."},"reference_url":{"type":"string","title":"Reference Url","description":"Presigned GET URL for the reference image."},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds the presigned reference_url stays valid, counted from this response.","examples":[3600]}},"type":"object","required":["id","reference_set_at","reference_url","expires_in"],"title":"SetCameraHealthReferenceResponse"},"SetClaimActingRequest":{"properties":{"action":{"type":"string","enum":["voice_down","dispatch"],"title":"Action","description":"What the holder opened: the Voice Down panel, or Escalate - dispatch."},"generation":{"type":"string","format":"uuid","title":"Generation","description":"`claim.generation` the console holds. A stale one is rejected 409 `claim_changed`."},"action_token":{"type":"string","maxLength":64,"minLength":1,"title":"Action Token","description":"A client nonce minted once per panel open (a UUID is fine); echo it on cancel."}},"additionalProperties":false,"type":"object","required":["action","generation","action_token"],"title":"SetClaimActingRequest","description":"Body of `POST /events/{id}/claim/acting`."},"SetContractedArrRequest":{"properties":{"contracted_arr_cents":{"anyOf":[{"type":"integer","maximum":10000000000.0,"minimum":0.0},{"type":"null"}],"title":"Contracted Arr Cents","description":"The annual amount the client contracted for, in whole cents (12000000 = $120,000), a JSON integer. null clears it.","examples":[12000000,null]}},"type":"object","required":["contracted_arr_cents"],"title":"SetContractedArrRequest"},"SetDisarmPasscodeRequest":{"properties":{"passcode":{"anyOf":[{"type":"string","maxLength":100,"format":"password","writeOnly":true},{"type":"null"}],"title":"Passcode","description":"The code to set, e.g. 'Blue Tiger 7'. Letters, numbers and spaces; case, spaces and dashes are ignored when it is texted or spoken. Omit for six random digits.","examples":["******"]}},"type":"object","title":"SetDisarmPasscodeRequest","description":"POST /sites/{id}/disarm-passcode's optional body.\n\nNo body, or `passcode` null, mints six random digits exactly as before. A\nstring is a code the customer CHOSE (owner decision 2026-10-07); the rules\nlive in `disarm_passcode.validate_custom_code` and a refusal is a 422 whose\ndetail is a sentence the console shows as-is."},"SetOnboardingMarkRequest":{"properties":{"camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","minItems":1,"title":"Camera Ids","description":"For streaming_test or first_night: the exact camera row IDs covered by the connection test or first-night review. Other marks do not use a body."}},"type":"object","required":["camera_ids"],"title":"SetOnboardingMarkRequest"},"SetOperatorCoverageGroupsRequest":{"properties":{"group_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Group Ids","description":"The coverage groups this operator is a standing member of. Replace-all: groups omitted here are removed, and their sites stop resolving at the operator's next scope resolution."}},"type":"object","required":["group_ids"],"title":"SetOperatorCoverageGroupsRequest","description":"Replace-all: this operator's coverage-group membership becomes exactly\nthis list. The user-side mirror of\n`PUT /scheduling/coverage-groups/{id}/members`.\n\nSending `[]` removes the operator from every group, leaving only their own\n`parent_ids`/`site_ids` rows, which are an ADDITIVE exception list.\n\nMEMBERSHIP CONFERS SCOPE, NEVER PERMISSION: it widens which SITES the\noperator can see, never what they may do. Their capabilities remain exactly\ntheir role's."},"SetOperatorSiteAccessRequest":{"properties":{"all_sites":{"type":"boolean","title":"All Sites","description":"true = sees every client and site, including ones onboarded later. false = sees only what their coverage groups and exceptions grant."}},"type":"object","required":["all_sites"],"title":"SetOperatorSiteAccessRequest","description":"PUT /operator/{id}/site-access: switch a user between \"all sites\" and\n\"restricted\" (coverage groups + per-user exceptions).\n\nSCOPE, NEVER PERMISSION: it changes which sites the user sees, never what\nthey may do. Switching is lossless: their groups and exceptions are kept\nwhile \"all sites\" is on and apply again the moment it is switched off."},"SetSiteAgentModeRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"True arms every ii-enabled camera at the site (agent mode); False sets all cameras to shadow.","examples":[true,false]}},"type":"object","required":["enabled"],"title":"SetSiteAgentModeRequest","description":"Bulk arm/shadow every camera at a site in one call (site-admin-only), instead\nof toggling agent_mode camera by camera. Arming only touches cameras whose\nincident intelligence is on; ii-off cameras cannot be armed and are left in\nshadow and reported back, rather than failing the whole operation."},"SetSiteAgentModeResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site that was flipped."},"enabled":{"type":"boolean","title":"Enabled","description":"The requested target mode (True = armed)."},"total":{"type":"integer","title":"Total","description":"Cameras at the site.","examples":[23]},"updated":{"type":"integer","title":"Updated","description":"Cameras whose agent_mode actually changed.","examples":[21]},"skipped_ii_off":{"type":"integer","title":"Skipped Ii Off","description":"Cameras that could not be armed because incident intelligence is off (arming only).","examples":[2]},"cameras":{"items":{"$ref":"#/components/schemas/SiteAgentModeCameraResult"},"type":"array","title":"Cameras","description":"Per-camera outcome, so the client can reflect the new armed state on every row."}},"type":"object","required":["site_id","enabled","total","updated","skipped_ii_off","cameras"],"title":"SetSiteAgentModeResponse"},"SetSiteBillableRequest":{"properties":{"billable":{"type":"boolean","title":"Billable","description":"Whether this site's cameras count toward MRR. Sites are billable by default.","examples":[false]}},"type":"object","required":["billable"],"title":"SetSiteBillableRequest"},"SetSiteHeightenedGroundedClearResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site that was updated."},"site_name":{"type":"string","title":"Site Name","description":"The site's name.","examples":["Harbor Warehouse"]},"heightened_grounded_clear":{"type":"boolean","title":"Heightened Grounded Clear","description":"The site's setting after this request.","examples":[true]},"changed":{"type":"boolean","title":"Changed","description":"False when the site already had this value (nothing was written).","examples":[true]}},"type":"object","required":["site_id","site_name","heightened_grounded_clear","changed"],"title":"SetSiteHeightenedGroundedClearResponse"},"SetSiteLiveViewOnlyResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site that was updated."},"site_name":{"type":"string","title":"Site Name","description":"The site's name.","examples":["Harbor Warehouse"]},"live_view_only":{"type":"boolean","title":"Live View Only","description":"The site's Live View only flag after this request.","examples":[true]},"camera_count":{"type":"integer","title":"Camera Count","description":"Live cameras at the site, all of which the site flag covers.","examples":[6]},"changed":{"type":"boolean","title":"Changed","description":"False when the site already had this value (nothing was written).","examples":[true]}},"type":"object","required":["site_id","site_name","live_view_only","camera_count","changed"],"title":"SetSiteLiveViewOnlyResponse"},"SetSpeakerCamerasRequest":{"properties":{"mappings":{"items":{"$ref":"#/components/schemas/SpeakerCameraMapping"},"type":"array","title":"Mappings","description":"The complete desired mapping set. Every camera must belong to the speaker's own site."}},"type":"object","title":"SetSpeakerCamerasRequest","description":"Replace the speaker's camera mappings with this exact set.\n\nReplace-set, not per-pair add/remove: the picker submits a set, and\nrewriting the whole set in one transaction makes priority reordering\natomic. An empty list unmaps the speaker entirely — which immediately\nreturns every affected camera to fail-closed behavior.\n\nARMING IS RE-DECIDED ON EVERY SUBMISSION. Each mapping's\n`agent_trigger_enabled` is written from THIS request, so a mapping sent\nwithout the field lands disarmed (human-triggered only) regardless of what\nthe row it replaces said — it is never inherited. A picker that round-trips\nmappings must send the flag back or it will disarm the agent on every\ncamera it re-submits, which is the safe direction and the reason this is\nstated here rather than left to be discovered."},"SetSpeakerChecklistRequest":{"properties":{"items":{"additionalProperties":{"type":"boolean"},"propertyNames":{"$ref":"#/components/schemas/SpeakerChecklistItem"},"type":"object","title":"Items","description":"The checklist items to set, keyed by item id. Omitted items are left exactly as they are.","examples":[{"audio_direction":true,"firewall":false}]}},"type":"object","required":["items"],"title":"SetSpeakerChecklistRequest","description":"PARTIAL BY DESIGN: send only the items that changed.\n\nMerge semantics, not full replacement, and that is the whole reason this is\nits own route rather than three more fields on `PUT /speakers/{id}`. That\nbody is a full replace that re-defaults every omitted field, so checklist\nstate living there would be one forgotten key away from an unrelated edit\nwiping the record of a device someone drove to a site to configure.\n\n`true` confirms an item, `false` un-confirms it. `done_at` and the\nconfirming operator are stamped SERVER-SIDE — a client cannot backdate an\nattestation about a safety-relevant device configuration.\n\nTHE KEYS ARE A BACKEND ENUM, so an unknown one is a 422 rather than a silent\nwrite under a key nothing reads. `default_account` is refused too: it is\nderived from the device's own `GetSIPAccounts` answer, and a hand-typed tick\nfor it would be strictly worse evidence than the one the device gives."},"Severity":{"type":"string","enum":["LOW","MEDIUM","HIGH","CRITICAL"],"title":"Severity"},"ShareLinkBrief":{"properties":{"id":{"type":"string","title":"Id","description":"The share link id. Pass it to the revoke route."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When the link stops working, whatever else happens."},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"Set once an operator killed it. Null means live (if unexpired)."},"first_viewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Viewed At","description":"When the recipient first opened it. Null means nobody has yet -- which is the single most useful fact on the row."},"view_count":{"type":"integer","title":"View Count","description":"How many times the page was served. No IP and no device is recorded."}},"type":"object","required":["id","view_count"],"title":"ShareLinkBrief","description":"The share link minted with ONE send, as it rides that send's timeline\nrow.\n\nA deliberate SUBSET of `ShareLinkRecord`: enough to render \"viewed\" /\n\"expires in 2 days\" / \"revoked\" and offer a Revoke button, and nothing\nmore. Absent (or null) on every row that carries no link -- voice, e-mail,\nmanually-logged and inbound rows always do.\n\nNO TOKEN AND NO HASH, here or anywhere else on an operator surface. The\nplaintext URL is already in the message body this row carries; a second\ncopy in a structured field would invite a client to render it as a\nclickable control, which is not what an operator's timeline is for."},"ShareLinkRecord":{"properties":{"id":{"type":"string","title":"Id","description":"The share link id."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When it was minted."},"created_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Name","description":"The operator who sent the message that minted it. Operator-only; never customer-facing."},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At","description":"When it stops working."},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"When an operator killed it. Write-once: a second revoke returns this value unchanged rather than rewriting the audit."},"first_viewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Viewed At","description":"First time the page was served. Null means never opened."},"last_viewed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Viewed At","description":"Most recent time the page was served."},"view_count":{"type":"integer","title":"View Count","description":"How many times the page was served. Counters and timestamps only -- no IP, no user agent: there is no consent basis for tracking a property owner's device."},"notification_log_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notification Log Id","description":"The incident-timeline row this link was minted with."},"sms_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sms Message Id","description":"The thread message this link was minted with."}},"type":"object","required":["id","view_count"],"title":"ShareLinkRecord","description":"One share link on `GET /events/{event_id}/share-links`.\n\nOPERATOR-ONLY; NEVER CUSTOMER-FACING (round-0 review O-6). `created_by_name`\nis a MONITORING OPERATOR's name, and the monitoring centre's roster is not\na customer's business -- the same rule the site-summary fact sheet enforces\nby never loading the column. If this list is ever widened to a\ncustomer-side role, that field goes first."},"ShiftBlockModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The shift ID. Stable across move and resize."},"group_id":{"type":"string","format":"uuid","title":"Group Id","description":"The coverage group being scheduled."},"shift_date":{"type":"string","format":"date","title":"Shift Date","description":"Center-local calendar date the shift STARTS on.","examples":["2026-08-24"]},"start_minute":{"type":"integer","title":"Start Minute","description":"Minutes after local midnight of shift_date (0-1439, multiple of 15).","examples":[1080]},"end_minute":{"type":"integer","title":"End Minute","description":"Minutes after local midnight of shift_date. Exceeds 1440 for an overnight shift (1560 = 02:00 the next day); at most start_minute + 1440.","examples":[1560]},"start_time":{"type":"string","title":"Start Time","description":"start_minute as center-local HH:MM.","examples":["18:00"]},"end_time":{"type":"string","title":"End Time","description":"end_minute as center-local HH:MM.","examples":["02:00"]},"end_day_offset":{"type":"integer","title":"End Day Offset","description":"0 when the shift ends on shift_date, 1 when it runs past midnight.","examples":[1]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes about the shift."},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"Optional per-shift display color (hex). Null = inherit the coverage group's color.","examples":["#4DFFD2"]},"seats":{"items":{"$ref":"#/components/schemas/ShiftSeatModel"},"type":"array","title":"Seats","description":"Seated operators in seat order - index 0 is the PRIMARY. May be EMPTY: an unassigned block is a real product state (it reads as a coverage gap), not an error."}},"type":"object","required":["id","group_id","shift_date","start_minute","end_minute","start_time","end_time","end_day_offset","seats"],"title":"ShiftBlockModel","description":"One shift block: a center-local wall-clock range with an ordered roster."},"ShiftBlockResponse":{"properties":{"block":{"$ref":"#/components/schemas/ShiftBlockModel","description":"The created or updated shift block."}},"type":"object","required":["block"],"title":"ShiftBlockResponse"},"ShiftCellModel":{"properties":{"group_id":{"type":"string","format":"uuid","title":"Group Id","description":"The coverage group this cell belongs to."},"shift_date":{"type":"string","format":"date","title":"Shift Date","description":"Center-local calendar date of the cell.","examples":["2026-07-30"]},"hour":{"type":"integer","maximum":23.0,"minimum":0.0,"title":"Hour","description":"Center-local hour of day (0-23).","examples":[14]},"operator_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Operator Ids","description":"Seated operators in seat order - index 0 is the PRIMARY, who bears a miss for this hour."}},"type":"object","required":["group_id","shift_date","hour","operator_ids"],"title":"ShiftCellModel"},"ShiftSeatModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The seat's own ID. Stable across reseats the operator survives - the calendar-invite work keys an iCalendar UID on it."},"operator_id":{"type":"string","format":"uuid","title":"Operator Id","description":"The seated operator."},"seat_order":{"type":"integer","title":"Seat Order","description":"Position in the seat list, contiguous from 0. Seat 0 is the shift's PRIMARY, who bears a miss during the shift.","examples":[0]}},"type":"object","required":["id","operator_id","seat_order"],"title":"ShiftSeatModel"},"ShowAnchor":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"The alarm the request is anchored on"},"camera_id":{"type":"string","title":"Camera Id","description":"The anchor's camera, always included in the scope"},"camera_name":{"type":"string","title":"Camera Name","description":"The anchor camera's label"},"site_id":{"type":"string","title":"Site Id","description":"The site the scene is read from"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"The anchor's alarm time"}},"type":"object","required":["event_id","camera_id","camera_name","site_id","created_at"],"title":"ShowAnchor"},"ShowCounts":{"properties":{"total":{"type":"integer","title":"Total","description":"Every clip in the window created by `as_of`, counted when page 1 was read (rows returned over the pass can differ by clips that became visible or closed mid-pass)"},"by_state":{"additionalProperties":{"type":"integer"},"type":"object","title":"By State","description":"Counts for open, reviewed, filtered and agent_closed"},"by_camera":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"By Camera","description":"PAGE 1 ONLY (null on later pages)"}},"type":"object","required":["total","by_state"],"title":"ShowCounts"},"ShowItem":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"The clip's event id: the only identity of an alarm"},"camera_id":{"type":"string","title":"Camera Id","description":"The camera that recorded the clip"},"camera_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Name","description":"The camera's label"},"zone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zone","description":"The camera zone the motion was in, when known"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"Clip time; the paging key with `event_id`"},"occurred_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Occurred At","description":"The ingestor-reported motion time, when sent"},"queued_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Queued At","description":"Null for a clip that never entered the queue (stopped by a gate)"},"offset_seconds":{"type":"number","title":"Offset Seconds","description":"created_at minus the anchor's, in seconds"},"is_anchor":{"type":"boolean","title":"Is Anchor","description":"True for the alarm the request is anchored on"},"state":{"type":"string","enum":["open","reviewed","filtered","agent_closed"],"title":"State","description":"`open` (in the queue), `reviewed` (an operator closed it), `filtered` (stopped before AI review by a gate), `agent_closed`. The state at the moment this page ran."},"disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disposition","description":"The closing disposition; null while open"},"closed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Closed At","description":"When it was closed; null while open"},"closed_by_operator_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Closed By Operator Id","description":"The closing operator, for a `reviewed` clip"},"closed_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Closed By Name","description":"The closing operator's name, for a `reviewed` clip"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"The AI summary of the clip, when one exists"},"claim":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Claim","description":"The queue row's `claim` payload (`operator_id`, `operator_name`, `claimed_at`, `expires_at`, `expires_in_seconds`) while someone holds the alarm, else null. Own vs foreign: compare `operator_id` with the caller's id. Open rows only."},"media":{"$ref":"#/components/schemas/ShowMedia","description":"References to the clip and picture, never the media itself"}},"type":"object","required":["event_id","camera_id","created_at","offset_seconds","is_anchor","state","media"],"title":"ShowItem"},"ShowMedia":{"properties":{"clip_available":{"type":"boolean","title":"Clip Available","description":"False while the clip is not yet attached (an operator-raised alarm)"},"clip_url_path":{"type":"string","title":"Clip Url Path","description":"GET this for a fresh presigned clip URL; never fetched by this read"},"snapshot_path":{"type":"string","title":"Snapshot Path","description":"GET this for the alarm's picture; never fetched by this read"}},"type":"object","required":["clip_available","clip_url_path","snapshot_path"],"title":"ShowMedia"},"ShowWindow":{"properties":{"start":{"type":"string","format":"date-time","title":"Start","description":"Window start (on `created_at`), inclusive"},"end":{"type":"string","format":"date-time","title":"End","description":"Window end (on `created_at`), inclusive"},"before_seconds":{"type":"integer","title":"Before Seconds","description":"Seconds before the anchor (or before the snapshot when until=now)"},"after_seconds":{"type":"integer","title":"After Seconds","description":"Seconds after the anchor (ignored when until=now)"},"until":{"type":"string","enum":["anchor","now"],"title":"Until","description":"`anchor`: window around the alarm. `now`: up to the snapshot"}},"type":"object","required":["start","end","before_seconds","after_seconds","until"],"title":"ShowWindow"},"SipAccountOption":{"properties":{"id":{"type":"string","title":"Id","description":"The account IDENTIFIER — what the device wants when placing a call. Not its display name.","examples":["sip_account_1"]},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"The account's descriptive name, as typed into the device's own UI.","examples":["Gate horn"]},"is_default":{"type":"boolean","title":"Is Default","description":"Whether this is the device's default account. Only one can be."},"enabled":{"type":"boolean","title":"Enabled","description":"Whether the account is enabled on the device."}},"type":"object","required":["id","is_default","enabled"],"title":"SipAccountOption","description":"One SIP account, as the DEVICE describes it — projected to four fields.\n\n**THE DEVICE'S RAW ANSWER CARRIES ACCOUNT PASSWORDS** (the Call Service\nAPI's `SIPAccount` structure documents `Password`, `UserId` and\n`Registrar`), so the projection happens inside `AxisSpeakerClient.\nlist_sip_accounts` and the raw body never leaves it. This model is the\nsecond statement of the same boundary, not the first."},"SipTestDeviceError":{"properties":{"http_status":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Http Status","description":"The HTTP status the device answered with.","examples":[400]},"body":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Body","description":"A bounded excerpt of the device's error body.","examples":["{\"error\":{\"code\":400,\"message\":\"Invalid SIPAccountId 'Gate horn'\"}}"]}},"type":"object","title":"SipTestDeviceError","description":"The device's own refusal, quoted.\n\nTHE SINGLE MOST USEFUL SENTENCE IN AN INSTALL. AXIS answers a refused\n`axcall:Call` with an HTTP 400 whose body names the fault — `Invalid\nSIPAccountId 'Gate horn'` is literally the mistake that cost a full day on\nsite — and until this shipped the body was dropped and the console showed\nnothing but a failure. Bounded at 200 characters; headers are never quoted,\nand the body cannot carry device credentials (they exist only in the auth\nheader)."},"SipTestDeviceState":{"properties":{"state":{"type":"string","title":"State","description":"The state the device reported, verbatim.","examples":["Calling","Active","Terminated"]},"at":{"type":"string","title":"At","description":"When we observed it, ISO-8601 UTC."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"What the device said about the transition, when it said anything."}},"type":"object","required":["state","at"],"title":"SipTestDeviceState","description":"One entry in the device's own state timeline.\n\nTHE TIMELINE IS THE POINT OF THE FEATURE. `Calling → Terminated` with no\n`Active` is a different install fault from `Active` with nothing audible —\nthe first is the network or the SIP account, the second is Audio direction —\nand the console can only say which if the states are recorded in order."},"Site":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Site name as it should appear on reports."},"address":{"type":"string","maxLength":300,"minLength":1,"title":"Address","description":"Street address."},"city":{"type":"string","maxLength":100,"minLength":1,"title":"City","description":"City."},"state":{"type":"string","maxLength":50,"minLength":1,"title":"State","description":"State."},"zip_code":{"type":"string","maxLength":20,"minLength":1,"title":"Zip Code","description":"ZIP code."},"hours":{"oneOf":[{"$ref":"#/components/schemas/SameHours"},{"$ref":"#/components/schemas/ByDayHours"}],"title":"Hours","description":"When the site should be watched.","discriminator":{"propertyName":"mode","mapping":{"by_day":"#/components/schemas/ByDayHours","same":"#/components/schemas/SameHours"}}},"connection":{"$ref":"#/components/schemas/Connection","description":"How to reach the recorder or cameras."},"details":{"$ref":"#/components/schemas/SiteDetails","description":"Optional details that help monitoring from night one."}},"additionalProperties":false,"type":"object","required":["name","address","city","state","zip_code","hours","connection","details"],"title":"Site"},"SiteAgentModeCameraResult":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Camera row id."},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Human label.","examples":["NW Exterior Multi - East"]},"agent_mode":{"type":"boolean","title":"Agent Mode","description":"The camera's agent_mode after this call."},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Whether incident intelligence is on for this camera."},"skipped_ii_off":{"type":"boolean","title":"Skipped Ii Off","description":"True when arming was requested but the camera has ii disabled, so it stayed in shadow.","default":false}},"type":"object","required":["id","agent_mode","ii_enabled"],"title":"SiteAgentModeCameraResult"},"SiteCameraOperatorInstructionsResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Id of the account, site or camera that was written."},"operator_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Instructions","description":"The stored instructions after the write, trimmed. Null means no instruction is set at this level."},"headline":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Headline","description":"The stored headline after the write; null when unset."}},"type":"object","required":["id"],"title":"SiteCameraOperatorInstructionsResponse","description":"Site and camera writes also answer the stored headline."},"SiteCameraPausesResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site."},"timezone":{"type":"string","title":"Timezone","description":"The site's IANA zone.","examples":["America/Chicago"]},"as_of":{"type":"string","format":"date-time","title":"As Of","description":"The server instant everything here was resolved at."},"limits":{"$ref":"#/components/schemas/CameraPauseLimitsModel","description":"The pause limits and where this site stands."},"cameras":{"items":{"$ref":"#/components/schemas/CameraPauseCameraModel"},"type":"array","title":"Cameras","description":"Every live camera at the site, in label order."},"changed_camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Changed Camera Ids","description":"On a pause or resume: the cameras this request actually changed."},"resumed":{"items":{"$ref":"#/components/schemas/CameraResumedModel"},"type":"array","title":"Resumed","description":"On a resume: what each resumed camera is doing now."},"deduplicated":{"type":"boolean","title":"Deduplicated","description":"True when this was a retry of an earlier pause and nothing new was written.","default":false}},"type":"object","required":["site_id","timezone","as_of","limits","cameras"],"title":"SiteCameraPausesResponse","description":"`GET /sites/{id}/camera-pauses` and the result of a pause or resume."},"SiteContactNotifyMethod":{"type":"string","enum":["call","text","email"],"title":"SiteContactNotifyMethod"},"SiteContactResponseModel":{"properties":{"first_name":{"type":"string","title":"First Name","description":"The first name of the site contact.","examples":["1231231234"]},"last_name":{"type":"string","title":"Last Name","description":"The last name of the site contact.","examples":["1231231234"]},"role":{"type":"string","title":"Role","description":"The role of the site contact.","examples":["Security Manager"]},"primary_phone_number":{"type":"string","title":"Primary Phone Number","description":"The primary phone number of the site contact.","examples":["1231231234"]},"afterhours_phone_number":{"type":"string","title":"Afterhours Phone Number","description":"The afterhours phone number of the site contact.","examples":["1231231234"]},"email":{"type":"string","title":"Email","description":"The email of the site contact.","examples":["JJ@example.com"]},"notify_method":{"$ref":"#/components/schemas/SiteContactNotifyMethod","description":"The notify method of the site contact.","examples":["email"]},"availability":{"anyOf":[{"items":{"$ref":"#/components/schemas/ContactAvailabilityWindow"},"type":"array","maxItems":14},{"type":"null"}],"title":"Availability","description":"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.","examples":[[{"days":[0,1,2,3,4],"end":"17:00","start":"08:00"}]]},"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the site contact."},"daily_summary_opt_out":{"type":"boolean","title":"Daily Summary Opt Out","description":"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.","default":false,"examples":[false]}},"type":"object","required":["first_name","last_name","role","primary_phone_number","afterhours_phone_number","email","notify_method","id"],"title":"SiteContactResponseModel","description":"RESPONSE-side contact: the request fields plus the required id.\n\nEvery response embedding site contacts (SitesModel and the site CRUD\nresponses) must use this, not SiteContactsModel — a response without the\nid strips it from the payload, and ops-view substitutes synthetic\n\"api-contact-N\" ids that notify-owner then rejects."},"SiteContactsModel":{"properties":{"first_name":{"type":"string","title":"First Name","description":"The first name of the site contact.","examples":["1231231234"]},"last_name":{"type":"string","title":"Last Name","description":"The last name of the site contact.","examples":["1231231234"]},"role":{"type":"string","title":"Role","description":"The role of the site contact.","examples":["Security Manager"]},"primary_phone_number":{"type":"string","title":"Primary Phone Number","description":"The primary phone number of the site contact.","examples":["1231231234"]},"afterhours_phone_number":{"type":"string","title":"Afterhours Phone Number","description":"The afterhours phone number of the site contact.","examples":["1231231234"]},"email":{"type":"string","title":"Email","description":"The email of the site contact.","examples":["JJ@example.com"]},"notify_method":{"$ref":"#/components/schemas/SiteContactNotifyMethod","description":"The notify method of the site contact.","examples":["email"]},"availability":{"anyOf":[{"items":{"$ref":"#/components/schemas/ContactAvailabilityWindow"},"type":"array","maxItems":14},{"type":"null"}],"title":"Availability","description":"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.","examples":[[{"days":[0,1,2,3,4],"end":"17:00","start":"08:00"}]]}},"type":"object","required":["first_name","last_name","role","primary_phone_number","afterhours_phone_number","email","notify_method"],"title":"SiteContactsModel"},"SiteDetails":{"properties":{"property_type":{"type":"string","maxLength":100,"title":"Property Type","description":"Site type (platform SiteType value).","default":""},"holidays":{"items":{"type":"string"},"type":"array","maxItems":20,"title":"Holidays","description":"Holiday catalog keys to watch around the clock."},"concerns":{"items":{"type":"string"},"type":"array","maxItems":20,"title":"Concerns","description":"What the customer is most worried about."},"response":{"type":"string","maxLength":40,"title":"Response","description":"Preferred response when someone shouldn't be there.","default":""},"normal_activity":{"type":"string","maxLength":4000,"title":"Normal Activity","description":"Normal activity operators should ignore.","default":""},"camera_notes":{"type":"string","maxLength":4000,"title":"Camera Notes","description":"Cameras needing special handling.","default":""},"pause_code":{"type":"string","maxLength":10,"title":"Pause Code","description":"Wants a staff pause code: yes, no, or unsure.","default":""},"speakers":{"type":"string","maxLength":10,"title":"Speakers","description":"Speakers on site: yes, no, or unsure.","default":""},"speaker_list":{"items":{"$ref":"#/components/schemas/Speaker"},"type":"array","maxItems":100,"title":"Speaker List","description":"Speaker details when speakers is yes."},"guard":{"type":"string","maxLength":10,"title":"Guard","description":"Guard on site: yes, no, or unsure.","default":""},"guard_company":{"type":"string","maxLength":200,"title":"Guard Company","description":"Guard company, when guard is yes.","default":""},"guard_phone":{"type":"string","maxLength":50,"title":"Guard Phone","description":"Guard dispatch phone, when guard is yes.","default":""},"guard_notes":{"type":"string","maxLength":2000,"title":"Guard Notes","description":"Guard uniform and patrol times.","default":""},"police_direct_dispatch_line":{"type":"string","maxLength":50,"title":"Police Direct Dispatch Line","description":"Direct police dispatch line, if the customer has one.","default":""},"police_notes":{"type":"string","maxLength":2000,"title":"Police Notes","description":"Alarm permit number or other police notes.","default":""}},"additionalProperties":false,"type":"object","title":"SiteDetails"},"SiteDisarmModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique id of the record"},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site whose cameras this concerns"},"kind":{"type":"string","title":"Kind","description":"'disarm' = the cameras were deliberately OFF (a customer or an operator turned them off). 'arm' = an operator held them ON for a bounded TEST while the schedule had them off. Arm rows always carry source 'console' and a null phone_number.","default":"disarm","examples":["disarm","arm"]},"source":{"type":"string","title":"Source","description":"'sms' (the customer texted the dealer line), 'voice' (the customer called it and gave the passcode to the voice agent), 'console' (an operator) or 'app' (the customer's mobile app).","examples":["sms","voice","console","app"]},"auth_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth Method","description":"What proved the caller on a text or voice disarm: 'passcode' (the site code was given) or 'trusted_number' (the caller's number is on the site's trusted list and no code was needed). Null for console and app rows and for history written before trusted numbers existed.","examples":["passcode","trusted_number"]},"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number","description":"The phone that texted or called, for 'sms' and 'voice' rows; null for console rows.","examples":["+18015550142"]},"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"The operator who disarmed, for console rows; null once they are deleted."},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Operator name snapshotted at the time, so history stays readable.","examples":["Sonny Tai"]},"minutes":{"type":"integer","title":"Minutes","description":"Minutes granted, after clamping to the site's cap.","examples":[90]},"starts_at":{"type":"string","format":"date-time","title":"Starts At","description":"When the cameras stopped alerting"},"ends_at":{"type":"string","format":"date-time","title":"Ends At","description":"When they were scheduled to come back on"},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ended At","description":"When something ENDED this period early. Null past ends_at means it simply ran its course — expiry is implicit and there is no sweep."},"end_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Reason","description":"Why it ended early; null when it expired on its own. The reason names the channel that ENDED the window, not the one that opened it: a call can end a window a text opened, and vice versa. The arm half adds two: 'ended_console' (an operator ended a test early) and 'disarmed' (a disarm from any channel superseded the test — off wins).","examples":["rearm_sms","rearm_voice","rearm_console","rearm_app","replaced","ended_console","disarmed"]},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the record was written"}},"type":"object","required":["id","site_id","source","minutes","starts_at","ends_at","created_at"],"title":"SiteDisarmModel","description":"One period during which a site's cameras were deliberately off — or,\nsince 0104, deliberately ON outside the schedule for an install test.\n\nOne table, two kinds: `kind` is the discriminator and every other field\nmeans exactly the same thing for both."},"SiteDisarmPasscodeResponse":{"properties":{"passcode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Passcode","description":"The passcode (six digits, or a chosen code as typed), or null when the site has none or has one minted before codes were kept readable (reset it to view).","examples":["******"]},"viewable":{"type":"boolean","title":"Viewable","description":"True when `passcode` is present.","examples":[true]},"has_disarm_passcode":{"type":"boolean","title":"Has Disarm Passcode","description":"Whether the site has a working passcode at all, viewable or not.","examples":[true]},"disarm_passcode_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Passcode Hint","description":"Last two characters, present whenever a code exists.","examples":["92"]}},"type":"object","required":["viewable","has_disarm_passcode"],"title":"SiteDisarmPasscodeResponse","description":"GET /sites/{id}/disarm-passcode: the site's current code, for any viewer\nthe site is in scope for (owner decision 2026-10-03)."},"SiteEarlyDisarmResponse":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site."},"timezone":{"type":"string","title":"Timezone","description":"The site's IANA zone.","examples":["America/Chicago"]},"window_minutes":{"type":"integer","title":"Window Minutes","description":"How far ahead of a scheduled disarm one may disarm early.","examples":[60]},"horizon_minutes":{"type":"integer","title":"Horizon Minutes","description":"How far ahead scheduled_off_at is looked for.","examples":[180]},"as_of":{"type":"string","format":"date-time","title":"As Of","description":"The server instant everything here was resolved at."},"site_disarm_active":{"type":"boolean","title":"Site Disarm Active","description":"A whole-site disarm (text, call, app or console) is live, so nothing is eligible."},"site_arm_active":{"type":"boolean","title":"Site Arm Active","description":"A test arm is live at the site, so nothing is eligible.","default":false},"cameras":{"items":{"$ref":"#/components/schemas/EarlyDisarmCameraModel"},"type":"array","title":"Cameras","description":"Every live camera at the site, in label order."},"changed_camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Changed Camera Ids","description":"On a disarm or re-arm: the cameras this request actually changed."}},"type":"object","required":["site_id","timezone","window_minutes","horizon_minutes","as_of","site_disarm_active","cameras"],"title":"SiteEarlyDisarmResponse","description":"`GET /sites/{id}/early-disarm` and the result of a disarm or re-arm."},"SiteHoldCapability":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"True when the site hold (SITE_HOLD_ENABLED and its co-flags) is on for this api; the story routes 404 otherwise.","examples":[false]},"heartbeat_s":{"type":"integer","title":"Heartbeat S","description":"Seconds between the open story page's heartbeats (`POST /focus/{id}/heartbeat`).","examples":[10]},"open_s":{"type":"integer","title":"Open S","description":"A hold counts as open while not left and last seen within this many seconds.","examples":[30]},"idle_s":{"type":"integer","title":"Idle S","description":"A hold whose page is no longer open stays valid at most this many seconds after it was last seen (the backstop for a lost heartbeat). A clean leave ends it sooner, about 30 s after the leave.","examples":[120]},"new_alert_s":{"type":"integer","title":"New Alert S","description":"A hold whose page is closed stays valid at most this many seconds after a new alarm is reserved for it.","examples":[120]}},"type":"object","required":["enabled","heartbeat_s","open_s","idle_s","new_alert_s"],"title":"SiteHoldCapability"},"SiteHolidaysResponse":{"properties":{"holidays":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Holidays","description":"The stored list, verbatim and in stored order. Each entry is either {'rule': <catalog key>} or {'date': 'YYYY-MM-DD', 'label': str}.","examples":[[{"rule":"thanksgiving"},{"date":"2026-12-24","label":"Christmas Eve half day"}]]},"catalog":{"items":{"$ref":"#/components/schemas/HolidayCatalogEntry"},"type":"array","title":"Catalog","description":"Every recurring holiday on offer, in render order, independent of what this site has configured."},"upcoming":{"items":{"$ref":"#/components/schemas/UpcomingHolidayEntry"},"type":"array","title":"Upcoming","description":"This site's configured closures resolved over the next 366 site-local days, ascending, one row per date."}},"type":"object","required":["holidays","catalog","upcoming"],"title":"SiteHolidaysResponse","description":"GET and PUT /sites/{id}/holidays both return this.\n\nOn a listed site-local calendar day every schedule cell that would read\n\"off\" reads \"monitored\" instead, and `monitoring_source.source` reports\n\"holiday\" for the cameras that actually flipped."},"SiteIncidentCounts":{"properties":{"site_id":{"type":"string","title":"Site Id","description":"Site UUID."},"count":{"type":"integer","title":"Count","description":"Total AI detections at this site in the window.","examples":[120]},"by_type":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Type","description":"Per-incident-type counts at this site.","examples":[{"INTRUSION":12,"NORMAL":80}]}},"type":"object","required":["site_id","count","by_type"],"title":"SiteIncidentCounts"},"SiteIntegrationOption":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Connection ID used to assign this connection to the site."},"provider":{"$ref":"#/components/schemas/IntegrationProvider","description":"Cloud-VMS provider for this connection."},"label":{"type":"string","title":"Label","description":"Connection display label used to choose between provider accounts."}},"type":"object","required":["id","provider","label"],"title":"SiteIntegrationOption"},"SiteIntegrationResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The integration record ID."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client this connection belongs to. The credential is client-level; WHICH SITES it serves is `site_ids`."},"provider":{"$ref":"#/components/schemas/IntegrationProvider","description":"Which cloud VMS this connection targets.","examples":["verkada","turing"]},"label":{"type":"string","title":"Label","description":"Operator-facing name for this connection.","examples":["Northgate Command"]},"external_org_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Org Id","description":"Provider organization id (non-secret), for display.","examples":["ceb8ff18-0000-0000-0000-000000000000"]},"command_short_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Command Short Name","description":"The vendor console's organization short name (the subdomain in https://<short-name>.command.verkada.com/...). Owner-entered, never fetched from the provider, and used only to build an operator's deep link. A Verkada concept: providers whose console has no such deep link leave it null.","examples":["northgate-security"]},"status":{"$ref":"#/components/schemas/SiteIntegrationStatus","description":"Result of the most recent connection test.","examples":["active"]},"last_verified_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Verified At","description":"When the connection last tested successfully."},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error","description":"Most recent connection-test failure, sanitized and truncated. Never contains a credential.","examples":["The provider rejected the supplied API key."]},"site_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Site Ids","description":"The live sites this connection is assigned to, ordered by id. Empty means UNASSIGNED: the connection is listed and manageable but no site uses it yet. Absent from payloads of older servers (read as empty)."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the connection was created."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When the connection was last modified."}},"type":"object","required":["id","parent_id","provider","label","status","created_at"],"title":"SiteIntegrationResponse","description":"One cloud-VMS connection.\n\nThere is deliberately NO api_key field and NO secret_arn field. The key\nnever leaves Secrets Manager, and the ARN is an internal handle with no\nclient-facing use (the same omission as NvrResponse / the speaker\nresponses). Because FastAPI serializes through this model, a field that\nsomehow appeared in the service dict would be dropped here rather than\nreaching a client — a second layer under to_dict_complete's omission, and\nthe property the router leak test pins."},"SiteIntegrationStatus":{"type":"string","enum":["active","error"],"title":"SiteIntegrationStatus"},"SitePoliceCall":{"properties":{"dispatch_call_id":{"type":"string","title":"Dispatch Call Id","description":"The dispatch_call row id."},"event_id":{"type":"string","title":"Event Id","description":"The OTHER alarm the call was about."},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"The other alarm's camera; null if the camera is gone."},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Snapshot of who placed the call (survives operator deletion)."},"target_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Label","description":"Snapshot of who was dialled, e.g. the precinct."},"channel":{"type":"string","title":"Channel","description":"How the call was placed: phone_bridge or browser_call."},"status":{"type":"string","title":"Status","description":"Raw Twilio status of the operator leg."},"dial_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dial Status","description":"Outcome of the police leg once the dial ended; null until then."},"dial_duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Dial Duration Seconds","description":"Length of the police leg in seconds; null until the dial ends."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the call was placed."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"Last status change from the provider; null if none yet."},"active":{"type":"boolean","title":"Active","description":"The call may still be in progress (inside the live window)."},"same_operator":{"type":"boolean","title":"Same Operator","description":"The caller placed this call."}},"type":"object","required":["dispatch_call_id","event_id","channel","status","created_at","active","same_operator"],"title":"SitePoliceCall","description":"One police call on ANOTHER alarm at the same site.\n\nDeliberately carries no phone numbers (no operator_number, to_number or\nfrom_number): this is shown to other operators, and callback numbers are\npersonal."},"SitePoliceCallsResponse":{"properties":{"window_minutes":{"type":"integer","title":"Window Minutes","description":"How far back finished calls are reported."},"calls":{"items":{"$ref":"#/components/schemas/SitePoliceCall"},"type":"array","title":"Calls","description":"Police calls on other alarms at this site, newest first, at most 10."}},"type":"object","required":["window_minutes","calls"],"title":"SitePoliceCallsResponse"},"SitePriority":{"type":"string","enum":["critical","high","medium"],"title":"SitePriority"},"SiteSummaryEvent":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Contributing record UUID; an event can have multiple records. Opaque for an Archived record shown to a Parent Manager."},"event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Event Id","description":"Event to open in the authorized review page; null if a camera move put the event outside current scope, or for an Archived event shown to a Parent Manager."},"occurred_at":{"type":"string","format":"date-time","title":"Occurred At","description":"Time counted by this metric, not necessarily event arrival."},"camera":{"type":"string","title":"Camera","description":"Current camera label."},"outcome":{"type":"string","title":"Outcome","description":"Customer-safe outcome label, without operator identity or notes."}},"type":"object","required":["id","event_id","occurred_at","camera","outcome"],"title":"SiteSummaryEvent"},"SiteSummaryListItem":{"properties":{"site_id":{"type":"string","title":"Site Id","description":"Site UUID.","examples":["3f1e0d2c-1111-4222-8333-444455556666"]},"site_name":{"type":"string","title":"Site Name","description":"Site name as it is TODAY (live, not the summary's snapshot) — this is where the row renders in the tab's grouping.","examples":["Northgate Yard"]},"parent_id":{"type":"string","title":"Parent Id","description":"Parent (dealer) UUID, live.","examples":["a1b2c3d4-1111-4222-8333-444455556666"]},"parent_name":{"type":"string","title":"Parent Name","description":"Parent company name, live.","examples":["Pioneer Security"]},"account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id","description":"Account UUID, live; null for a site held directly under the parent.","examples":[null]},"account_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Name","description":"Account name, live; null for a direct site.","examples":["Jerry Seiner Group"]},"account_id_at_generation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id At Generation","description":"Account UUID SNAPSHOTTED on the summary row at generation. Group historical rows by this; group today's site by `account_id`. Null on a live row, a stub, or a summary generated while the site was held directly under its parent.","examples":[null]},"account_name_at_generation":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Name At Generation","description":"Account name as it was at generation, from the stored fact sheet — the only place it survives an account rename or retirement.","examples":["Jerry Seiner Group"]},"local_date":{"type":"string","title":"Local Date","description":"YYYY-MM-DD, site-local. The requested date for a stored row or a merged stub; today's site-local date for a live row.","examples":["2026-09-05"]},"stored":{"type":"boolean","title":"Stored","description":"True when this row is a persisted summary. False for both the live view and a merged stub.","examples":[true]},"live":{"type":"boolean","title":"Live","description":"True for a 'today so far' row computed on read and never persisted.","default":false,"examples":[false]},"merged_into":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merged Into","description":"Set only on a stub: the local_date of the summary whose stretched window swallowed the requested date. The tab renders 'included in <that date>'s summary'.","examples":[null]},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Summary UUID; null on a live row and on a merged stub, neither of which is addressable.","examples":["9f8e7d6c-1111-4222-8333-444455556666"]},"narrative_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Narrative Source","description":"'llm' or 'template' on a stored row; null otherwise. A live row has no narrative at all.","examples":["llm"]},"generated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Generated At","description":"When the stored summary finished generating (UTC); null on a live row or a stub."},"counts":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Counts","description":"The sheet's ten counts. Present on stored and live rows, null on a stub. Typed loosely because the counts block is the versioned fact-sheet contract, not this endpoint's own shape.","examples":[{"cameras":12,"motion":143}]},"escalations":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Escalations","description":"Escalation entries. Populated ONLY on a live row (which has no detail endpoint); always empty on a stored row, where GET /site-summaries/{id} serves them. Projected for a customer-side reader.","examples":[[]]},"escalation_count":{"type":"integer","title":"Escalation Count","description":"How many escalations the row has, on both stored and live rows.","default":0,"examples":[2]},"outage_count":{"type":"integer","title":"Outage Count","description":"How many outages the row has. ALWAYS 0 on a live row — the outage scan is skipped there, so read this as 'not measured' rather than 'none' whenever `live` is true.","default":0,"examples":[1]},"partial":{"type":"boolean","title":"Partial","description":"True when the stored sheet was written from a failed fact build; the card should say so rather than imply a quiet night.","default":false,"examples":[false]},"email_enabled":{"type":"boolean","title":"Email Enabled","description":"Whether the site is on the automatic daily e-mail RIGHT NOW (its current settings, or the default when it has none) - on every reading, stubs included. Not a record of whether this stored day was sent; the sends endpoint answers that.","default":false,"examples":[true]}},"type":"object","required":["site_id","site_name","parent_id","parent_name","local_date","stored"],"title":"SiteSummaryListItem"},"SiteSummaryRecipient":{"properties":{"contact_id":{"type":"string","title":"Contact Id","description":"Site contact UUID.","examples":["7a6b5c4d-1111-4222-8333-444455556666"]},"name":{"type":"string","title":"Name","description":"Contact's full name as stored on the site contact.","examples":["Dana Whitfield"]},"email":{"type":"string","title":"Email","description":"Contact's e-mail, whitespace-trimmed; may be empty when none is recorded.","examples":["dana@example.com"]},"receives":{"type":"boolean","title":"Receives","description":"Whether a send would actually mail this contact: a usable address AND no opt-out.","examples":[true]},"reason":{"type":"string","title":"Reason","description":"Why not, or 'ok': 'ok' | 'no_email' | 'opted_out'. An unusable address wins over an opt-out.","examples":["ok"]},"has_login":{"type":"boolean","title":"Has Login","description":"A customer-side login exists with this e-mail whose grant covers the site — an account manager through the site's account, or a location manager through the site itself — i.e. the 'View event' links will work for them. Never gates delivery.","examples":[false]},"opted_out":{"type":"boolean","title":"Opted Out","description":"The contact's daily-summary opt-out flag, independent of whether the address is usable.","examples":[false]}},"type":"object","required":["contact_id","name","email","receives","reason","has_login","opted_out"],"title":"SiteSummaryRecipient","description":"One site contact, as the send would treat them.\n\nCarries no operator identity: `has_login` is a boolean about the CONTACT\n(does a customer-side login exist for this address), never a pointer to\nthe account that matched."},"SiteSummarySendItem":{"properties":{"id":{"type":"string","title":"Id","description":"Send-attempt UUID.","examples":["1f2e3d4c-1111-4222-8333-444455556666"]},"triggered_by":{"type":"string","title":"Triggered By","description":"'schedule' (the digest loop), 'test' or 'resend' (an owner/site-manager action). Only the latter two consume the hourly budget.","examples":["schedule"]},"sent_at":{"type":"string","format":"date-time","title":"Sent At","description":"When the attempt was made (UTC)."},"status":{"type":"string","title":"Status","description":"'pending' (reserved, outcome not yet known) | 'sent' | 'partial' | 'failed' | 'skipped_quiet' | 'skipped_no_recipients' | 'skipped_disabled'.","examples":["sent"]},"recipients":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Recipients","description":"Per-recipient outcome: {contact_id, email, status, error}. contact_id is null for a test send, whose recipient is the caller's own address. An account manager sees {contact_id, email, status} only, and only for entries that have a contact_id.","examples":[[{"email":"dana@example.com","status":"sent"}]]},"ses_message_ids":{"items":{"type":"string"},"type":"array","title":"Ses Message Ids","description":"SES message ids, one per accepted recipient, for tracing a delivery with the provider. Always empty for an account manager.","examples":[[]]},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure detail for the attempt as a whole; null on success and on every skip. An account manager sees a flat 'delivery failed' instead of the provider text.","examples":[null]}},"type":"object","required":["id","triggered_by","sent_at","status","recipients","ses_message_ids"],"title":"SiteSummarySendItem"},"SiteType":{"type":"string","enum":["Commercial","Car Dealership","Construction","Residential","Logistics","Education","Healthcare","Utilities","Other"],"title":"SiteType"},"SitesModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the site"},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"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":{"type":"string","title":"Name","description":"Name of the site","examples":["San Diego Sewing Company - 1"]},"address":{"type":"string","title":"Address","description":"Address of the site","examples":["1234 Hairy Cherry Lane"]},"city":{"type":"string","title":"City","description":"City of the site","examples":["Shanghai"]},"state":{"type":"string","title":"State","description":"State of the site","examples":["CA"]},"zip_code":{"type":"string","title":"Zip Code","description":"Zip code of the site","examples":["78912"]},"type":{"$ref":"#/components/schemas/SiteType","description":"Type of the site","examples":["Education"]},"priority_tier":{"$ref":"#/components/schemas/SitePriority","description":"Priority of the site","examples":["high"]},"monitoring_hours":{"$ref":"#/components/schemas/MonitoringHours","description":"Monitoring hours of the site","examples":["Nights"]},"timezone":{"type":"string","title":"Timezone","description":"IANA timezone the site's monitoring schedule is evaluated in","examples":["America/Los_Angeles"]},"police_nearest_precinct_name":{"type":"string","title":"Police Nearest Precinct Name","description":"Police nearest precinct name","examples":["SD Police hehe"]},"police_non_emergency_line":{"type":"string","title":"Police Non Emergency Line","description":"Police non-emergency line","examples":["1231231234"]},"police_direct_dispatch_line":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Direct Dispatch Line","description":"Police direct dispatch line","examples":["32133"]},"police_jurisdiction_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Police Jurisdiction Notes","description":"Police jurisdiction notes","examples":["This is the police"]},"alarm_license_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alarm License Number","description":"Dealer alarm license number","examples":["127.001463"]},"alarm_license_issuer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Alarm License Issuer","description":"Who issued the alarm license","examples":["Illinois"]},"mobile_guard_contract":{"type":"boolean","title":"Mobile Guard Contract","description":"Mobile guard contract available?","examples":[true,false]},"guard_company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Company","description":"Mobile guard company name."},"guard_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Unit","description":"Mobile guard unit or contact identifier."},"guard_contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Guard Contact","description":"Mobile guard dispatch phone."},"agent_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Context","description":"Site-wide scene context composed into the LLM prompt for every camera at this site.","examples":["Gated storage facility; groundskeeper drives a white pickup weekday mornings."]},"agent_thread_window_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Agent Thread Window Minutes","description":"Incident-thread join window for this site's agent cameras, in minutes; null = global default.","examples":[5]},"heightened_exceptions":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Heightened Exceptions","description":"Owner-curated heightened-monitoring exception categories; null = none.","examples":[[{"description":"Navy uniforms with badge","label":"Uniformed security"}]]},"holidays":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Holidays","description":"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.","examples":[[{"rule":"thanksgiving"},{"date":"2026-12-24","label":"Christmas Eve half day"}]]},"stage":{"anyOf":[{"type":"string","enum":["live","test"]},{"type":"null"}],"title":"Stage","description":"'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.","examples":["test","live"]},"closed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Closed","description":"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.","examples":[false]},"onboarding_marks":{"additionalProperties":{"type":"string"},"type":"object","title":"Onboarding Marks","description":"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.","examples":[{"first_night":"2026-10-13T08:00:00.000Z"}]},"map_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Mode","description":"Map-tab mode: 'geo' (geographic pin) or 'floorplan' (uploaded plan); null = not configured.","examples":["geo"]},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude of the site pin (geo mode).","examples":[42.0442]},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude of the site pin (geo mode).","examples":[-87.9816]},"floorplan_width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Floorplan Width","description":"Natural pixel width of the uploaded floor plan.","examples":[1200]},"floorplan_height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Floorplan Height","description":"Natural pixel height of the uploaded floor plan.","examples":[800]},"has_floorplan":{"type":"boolean","title":"Has Floorplan","description":"Whether a floor-plan image is uploaded (fetch it via GET /sites/{id}/floorplan-url).","default":false,"examples":[true,false]},"disarm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Until","description":"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.","examples":["2026-09-05T21:15:00Z"]},"disarm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Disarm Set At","description":"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.","examples":["2026-09-05T19:45:00Z"]},"disarm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm By","description":"Who turned the cameras off: 'sms:<E.164>' for a customer text, 'voice:<E.164>' for a customer call, 'operator:<uuid>' from the console.","examples":["sms:+18015550142","voice:+18015550142","operator:6f1c8f2a-..."]},"disarm_active":{"type":"boolean","title":"Disarm Active","description":"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.","default":false,"examples":[true,false]},"arm_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Until","description":"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.","examples":["2026-09-18T04:30:00Z"]},"arm_set_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Arm Set At","description":"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.","examples":["2026-09-18T04:00:00Z"]},"arm_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Arm By","description":"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.","examples":["operator:6f1c8f2a-..."]},"arm_active":{"type":"boolean","title":"Arm Active","description":"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.","default":false,"examples":[true,false]},"disarm_max_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Disarm Max Minutes","description":"The longest a single disarm may last at this site (1-120).","examples":[120]},"live_view_only":{"type":"boolean","title":"Live View Only","description":"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.","default":false,"examples":[false]},"has_disarm_passcode":{"type":"boolean","title":"Has Disarm Passcode","description":"Whether this site has a disarm passcode. Read the code through the dedicated disarm-passcode endpoint.","default":false,"examples":[true,false]},"disarm_passcode_hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Passcode Hint","description":"Last two characters of the stored passcode, for rendering it as '****92'.","examples":["92"]},"disarm_passcode_revision":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disarm Passcode Revision","description":"Opaque revision that changes whenever the passcode is minted or reset, including when its hint is unchanged."},"monitoring_now":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Now","description":"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.","examples":["monitored","off_schedule","disarmed","unscheduled","live_view_only"]},"monitoring_next_change_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Monitoring Next Change At","description":"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.","examples":["2026-09-08T04:30:00Z"]},"contacts":{"items":{"$ref":"#/components/schemas/SiteContactResponseModel"},"type":"array","title":"Contacts","description":"List of site contacts"}},"type":"object","required":["id","name","address","city","state","zip_code","type","priority_tier","monitoring_hours","timezone","police_nearest_precinct_name","police_non_emergency_line","mobile_guard_contract","contacts"],"title":"SitesModel"},"SmsConsentQueueItem":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the consent record."},"phone_number":{"type":"string","title":"Phone Number","description":"Canonical E.164 number the consent covers."},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Name given at consent time, if any."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Email given at consent time, if any."},"organization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization","description":"Organization given at consent time."},"consent_version":{"type":"string","title":"Consent Version","description":"Server-owned key for the exact text agreed to."},"consented_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Consented At","description":"When consent actually occurred. NULL for a Rail A enrollment that is still waiting for its YES reply."},"source":{"type":"string","title":"Source","description":"How the consent was obtained."},"status":{"type":"string","title":"Status","description":"pending_confirmation | pending_review | linked | dismissed | revoked | unverifiable. Only `linked` with a bound contact authorizes a send."},"site_contact_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Contact Id","description":"Bound contact, if any."},"site_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Id","description":"Bound site, if any."},"contact_slot":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Slot","description":"primary | afterhours."},"evidence_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evidence Ref","description":"What proves it — the inbound YES message id for Rail A."},"linked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Linked At","description":"When it was bound to a contact."},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"When a STOP superseded it."},"dismissed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Dismissed At","description":"When it was dismissed or expired."},"confirmation_sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmation Sent At","description":"When the Rail A confirmation text was sent. One, ever."},"confirmation_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Error","description":"Last confirmation send failure, if any."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"Row creation time."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"Row last-update time."},"linkable":{"type":"boolean","title":"Linkable","description":"False for `unverifiable` rows, which are surfaced so an operator can see them but can never be linked."}},"type":"object","required":["id","phone_number","consent_version","source","status","linkable"],"title":"SmsConsentQueueItem"},"SmsConsentQueueResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/SmsConsentQueueItem"},"type":"array","title":"Items","description":"This page of the review queue."},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Opaque keyset cursor for the next page, or null at the end. Keyset rather than offset because the public form inserts continuously."}},"type":"object","required":["items"],"title":"SmsConsentQueueResponse"},"SmsConsentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Unique identifier of the consent record."},"phone_number":{"type":"string","title":"Phone Number","description":"Canonical E.164 number the consent covers."},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Name given at consent time, if any."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Email given at consent time, if any."},"organization":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization","description":"Organization given at consent time."},"consent_version":{"type":"string","title":"Consent Version","description":"Server-owned key for the exact text agreed to."},"consented_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Consented At","description":"When consent actually occurred. NULL for a Rail A enrollment that is still waiting for its YES reply."},"source":{"type":"string","title":"Source","description":"How the consent was obtained."},"status":{"type":"string","title":"Status","description":"pending_confirmation | pending_review | linked | dismissed | revoked | unverifiable. Only `linked` with a bound contact authorizes a send."},"site_contact_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Contact Id","description":"Bound contact, if any."},"site_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Id","description":"Bound site, if any."},"contact_slot":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contact Slot","description":"primary | afterhours."},"evidence_ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evidence Ref","description":"What proves it — the inbound YES message id for Rail A."},"linked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Linked At","description":"When it was bound to a contact."},"revoked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Revoked At","description":"When a STOP superseded it."},"dismissed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Dismissed At","description":"When it was dismissed or expired."},"confirmation_sent_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Confirmation Sent At","description":"When the Rail A confirmation text was sent. One, ever."},"confirmation_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confirmation Error","description":"Last confirmation send failure, if any."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"Row creation time."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"Row last-update time."}},"type":"object","required":["id","phone_number","consent_version","source","status"],"title":"SmsConsentResponse","description":"One consent row as the operator console sees it.\n\nDeliberately NOT the whole row: `source_ip`, `user_agent`, `phone_raw` and\nthe raw consent text are evidence for a compliance request, not console\nfurniture, and an endpoint that returns them invites them into a UI (and a\nbrowser cache) they have no business being in."},"SnapshotFrameRequest":{"properties":{"offset_ms":{"type":"integer","maximum":600000.0,"minimum":0.0,"title":"Offset Ms","description":"Milliseconds from the start of the event's clip (the fast clip that `clip_url` plays, NOT the extended pre-roll). Rounded to a tenth of a second server-side; the response says which offset was used. Send the same value back as `snapshot_offset_ms` on the text.","examples":[7300]}},"type":"object","required":["offset_ms"],"title":"SnapshotFrameRequest"},"SnapshotFrameResponse":{"properties":{"offset_ms":{"type":"integer","title":"Offset Ms","description":"The offset actually used, after rounding to a tenth of a second"},"url":{"type":"string","title":"Url","description":"Presigned preview of the frame. A bearer: never persist it, never log it. The text sends `snapshot_offset_ms`, never this."},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until `url` expires"}},"type":"object","required":["offset_ms","url","expires_in"],"title":"SnapshotFrameResponse"},"Speaker":{"properties":{"name":{"type":"string","maxLength":200,"title":"Name","description":"Speaker name or location.","default":""},"host":{"type":"string","maxLength":255,"title":"Host","description":"Speaker IP or hostname.","default":""},"port":{"type":"string","maxLength":10,"title":"Port","description":"Speaker port (usually 443).","default":""},"username":{"type":"string","maxLength":200,"title":"Username","description":"Speaker login.","default":""},"password":{"type":"string","maxLength":500,"format":"password","title":"Password","description":"Speaker password; stored in Secrets Manager, never emailed.","default":"","writeOnly":true},"model":{"type":"string","maxLength":100,"title":"Model","description":"Speaker model, e.g. Axis C1310-E.","default":""},"covers":{"type":"string","maxLength":500,"title":"Covers","description":"Cameras this speaker covers.","default":""}},"additionalProperties":false,"type":"object","title":"Speaker"},"SpeakerBrand":{"type":"string","enum":["axis"],"title":"SpeakerBrand"},"SpeakerCameraMapping":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Camera whose field of view this speaker covers."},"priority":{"type":"integer","minimum":0.0,"title":"Priority","description":"Failover order for THIS camera: 0 is the primary, lower wins. Duplicates are allowed and break ties deterministically by mapping age.","default":0,"examples":[0]},"agent_trigger_enabled":{"type":"boolean","title":"Agent Trigger Enabled","description":"Arm the AI agent to fire this speaker automatically for this camera. Default false = human-triggered only: operators can Talk Down, the agent's voice-down resolves nothing and fails closed exactly as an unmapped camera. Replace-set semantics: omitting this re-decides it as FALSE, it never inherits the previous row's value.","default":false,"examples":[false]}},"type":"object","required":["camera_id"],"title":"SpeakerCameraMapping"},"SpeakerCameraMappingResponse":{"properties":{"camera_id":{"type":"string","format":"uuid","title":"Camera Id","description":"Mapped camera."},"camera_label":{"type":"string","title":"Camera Label","description":"The camera's display name.","examples":["21 - SE PTZ"]},"priority":{"type":"integer","title":"Priority","description":"Failover order for that camera: 0 is the primary.","examples":[0]},"agent_trigger_enabled":{"type":"boolean","title":"Agent Trigger Enabled","description":"Whether the AI agent is armed to fire this speaker automatically for that camera. False (the default) is human-triggered only: operators can Talk Down, the agent's voice-down fails closed exactly as on an unmapped camera, and the policy ledger reports voice-down as unavailable there.","examples":[false]}},"type":"object","required":["camera_id","camera_label","priority","agent_trigger_enabled"],"title":"SpeakerCameraMappingResponse"},"SpeakerChecklistItem":{"type":"string","enum":["sip_enabled","default_account","codec_priority","audio_direction","nat_traversal","call_handling","firewall"],"title":"SpeakerChecklistItem","description":"The device-configuration steps an owner works before a horn can carry\nthe AI talk-down. Copy lives in ops-view; THE KEYS LIVE HERE.\n\nAn enum rather than free-form strings because the checklist is stored as a\nJSONB map keyed by these values: a copy change in the console must never be\nable to orphan an attestation under a key nothing reads any more, and an\nunknown key is a 422 rather than a silent write. Each value names the\nconfiguration fact, never the menu path — the path is console copy and will\nchange with AXIS OS; the fact will not.\n\nEvery item except DEFAULT_ACCOUNT is owner-attested. DEFAULT_ACCOUNT is the\none tick the DEVICE can prove, from the same `GetSIPAccounts` the account\ndropdown already reads (round-0 review, OQ-4), so it is computed\nserver-side and refused as a write."},"SpeakerChecklistResponse":{"properties":{"speaker_id":{"type":"string","format":"uuid","title":"Speaker Id","description":"The speaker this checklist belongs to."},"speaker_label":{"type":"string","title":"Speaker Label","description":"The speaker's display name.","examples":["North horn — gate"]},"onboarding_checklist":{"additionalProperties":true,"type":"object","title":"Onboarding Checklist","description":"The whole stored envelope: `{\"version\": 1, \"items\": {...}}`."},"confirmed_count":{"type":"integer","title":"Confirmed Count","description":"How many of the attestable items the owner has confirmed.","examples":[5]},"total_count":{"type":"integer","title":"Total Count","description":"How many attestable items there are. Excludes the items the device answers for itself.","examples":[6]}},"type":"object","required":["speaker_id","speaker_label","onboarding_checklist","confirmed_count","total_count"],"title":"SpeakerChecklistResponse","description":"A speaker's device-setup checklist after a merge write.\n\nThe envelope is `{\"version\", \"items\"}`. `items` holds the owner's own\nattestations, each stamped server-side with `done_at` and a snapshot of who\nconfirmed it; an absent key means \"not confirmed\".\n\nTHE DEVICE'S OWN TICK IS NOT IN HERE, deliberately: it is computed from the\nlive `GET /speakers/{id}/sip-accounts` read and returned there, so there is\nno stored copy to go stale or to be forged. `confirmed_count` and\n`total_count` are therefore over the ATTESTABLE items only — a fully set-up\nspeaker reads N of N rather than N-1 of N."},"SpeakerDiscoveryResponse":{"properties":{"host":{"type":"string","title":"Host","description":"The host probed, in canonical form.","examples":["72.234.89.250"]},"probed_at":{"type":"string","format":"date-time","title":"Probed At","description":"When the probe started."},"credentials":{"type":"string","enum":["stored","supplied","none"],"title":"Credentials","description":"Which credentials were tried: the speaker's stored ones, the ones in the request, or none."},"results":{"items":{"$ref":"#/components/schemas/SpeakerDiscoveryResult"},"type":"array","title":"Results","description":"One row per port: speakers first, then other answers, then closed/timed-out ports; by port within each group."}},"type":"object","required":["host","probed_at","credentials","results"],"title":"SpeakerDiscoveryResponse","description":"Every candidate port on one host, speakers first. Nothing was written."},"SpeakerDiscoveryResult":{"properties":{"port":{"type":"integer","title":"Port","description":"The TCP port probed.","examples":[8384]},"scheme":{"anyOf":[{"type":"string","enum":["https","http"]},{"type":"null"}],"title":"Scheme","description":"The scheme that got an HTTP answer (HTTPS is tried first). Null when nothing answered."},"status":{"type":"string","enum":["speaker","other","closed","timeout"],"title":"Status","description":"speaker: an Axis device answered. other: something answered over HTTP but not an Axis device API (e.g. a router page). closed: no HTTP answer on either scheme. timeout: the probe did not finish inside the overall time budget."},"product":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Product","description":"Product name the device reported (read without credentials).","examples":["AXIS C1310-E Network Horn Speaker"]},"serial":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Serial","description":"Serial number the device reported. Two rows with different serials are two physical units.","examples":["B8A44FE7F06F"]},"firmware":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Firmware","description":"Firmware version the device reported.","examples":["11.11.205"]},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note","description":"Short detail when the answer was not a plain Axis identity, e.g. a web page's title. Never response headers.","examples":["Router Login"]},"auth":{"type":"string","enum":["ok","rejected","error","not_tried"],"title":"Auth","description":"Whether the credentials worked on THIS device: ok, rejected (401), error (could not tell), or not_tried (no credentials, or not a speaker)."},"clips":{"anyOf":[{"items":{"$ref":"#/components/schemas/DiscoveredClip"},"type":"array"},{"type":"null"}],"title":"Clips","description":"Clips on the device, when the credentials worked. Null otherwise."}},"type":"object","required":["port","status","auth"],"title":"SpeakerDiscoveryResult","description":"What answered on one port."},"SpeakerListItemResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The speaker record ID."},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site this speaker belongs to."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client this speaker belongs to."},"label":{"type":"string","title":"Label","description":"Display name.","examples":["SE Horn — dumpster"]},"brand":{"$ref":"#/components/schemas/SpeakerBrand","description":"Speaker vendor."},"model":{"anyOf":[{"$ref":"#/components/schemas/SpeakerModel"},{"type":"null"}],"description":"Speaker model."},"host":{"type":"string","title":"Host","description":"IP address of the speaker (IPv4 or IPv6 literal), in canonical form. Hostnames are not supported in v1.","examples":["167.20.111.104","2606:4700::1111"]},"port":{"type":"string","title":"Port","description":"HTTPS port.","examples":["443"]},"username":{"type":"string","title":"Username","description":"Device account username (the password is never returned).","examples":["root"]},"transport":{"$ref":"#/components/schemas/SpeakerTransport","description":"How api-backend reaches the device."},"clip_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clip Id","description":"Id of the pre-loaded deterrent clip.","examples":["1"]},"clip_params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Clip Params","description":"Playback parameters.","examples":[{"volume":80}]},"enabled":{"type":"boolean","title":"Enabled","description":"Whether delivery and the health poll consider this speaker."},"sip_enabled":{"type":"boolean","title":"Sip Enabled","description":"Whether the AI voice agent may place a SIP call to this speaker.","default":false},"sip_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sip Account Id","description":"Which of the device's SIP accounts the agent's call is placed from; null means the device default.","examples":["sip_account_1"]},"mic_enabled":{"type":"boolean","title":"Mic Enabled","description":"Whether this unit's microphone is live, which decides whether the agent waits for a reply.","default":false},"onboarding_checklist":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Onboarding Checklist","description":"The device-setup checklist: the owner's own attestations under `items`, each stamped server-side. An absent key means not confirmed. The one item the device answers for itself is NOT stored here — read it live from `GET /speakers/{id}/sip-accounts`.","examples":[{"items":{"audio_direction":{"done_at":"2026-09-11T18:20:44Z","operator_id":"..."}},"version":1}]},"agent_armed_camera_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Agent Armed Camera Count","description":"How many of this speaker's camera mappings are ARMED for the AI agent. Zero means nothing will ever announce here, however the device is configured.","examples":[2]},"last_sip_test":{"anyOf":[{"$ref":"#/components/schemas/LastSipTest"},{"type":"null"}],"description":"The newest SIP test call on this speaker, or null if it has never been tested."},"map_x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x."},"map_y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y."},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude of the speaker pin."},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude of the speaker pin."},"map_location_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose.","examples":["SE corner — by the dumpster"]},"map_heading":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Heading","description":"Direction a horn faces, degrees clockwise from map-up; null = not aimed (always null for an omnidirectional ceiling unit).","examples":[180]},"healthy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Healthy","description":"Last health-poll result."},"last_healthcheck_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Healthcheck At","description":"When the speaker was last polled."},"health_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Health Error","description":"Why the last health poll failed: unreachable (no HTTP answer), auth_rejected (the device answered 401 to the stored credentials), not_a_speaker (something answered, but not an Axis device API - e.g. a router page), clip_missing (the configured clip id is not on the device), or error. Null when healthy or never checked.","examples":["auth_rejected"]},"device_serial":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Serial","description":"Serial number of the device last seen at this host:port, read without credentials. Compare it across speakers to tell two units behind one IP apart. Last known value; a failed poll keeps it.","examples":["B8A44FA89513"]},"device_product":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Product","description":"Product name of the device last seen at this host:port (ProdFullName, else ProdNbr). Last known value.","examples":["AXIS C1310-E Network Horn Speaker"]},"device_firmware":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Firmware","description":"Firmware version of the device last seen at this host:port. Last known value.","examples":["11.11.192"]},"device_identity_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Device Identity At","description":"When the device identity above was last read successfully."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When the speaker was registered."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When the speaker was last edited."},"cameras":{"items":{"$ref":"#/components/schemas/SpeakerCameraMappingResponse"},"type":"array","title":"Cameras","description":"Cameras this speaker covers, in failover order. Empty means the speaker is registered but not mapped to any camera, so nothing can announce on it."}},"type":"object","required":["id","site_id","parent_id","label","brand","host","port","username","transport","enabled","cameras"],"title":"SpeakerListItemResponse","description":"A speaker as the site list returns it: the record plus the cameras it\ncovers, so the owner's Speakers surface can show coverage at a glance\ninstead of opening each speaker's mapping editor in turn.\n\n`cameras` is the same view `GET /speakers/{id}/cameras` gives for one\nspeaker - live cameras only, in failover order - and it is REQUIRED here\nrather than defaulted: a list item that lost its mappings would otherwise\nvalidate as \"covers nothing\", which is a claim, not an absence. Only the\nlist carries it; the create and edit responses stay `SpeakerResponse`."},"SpeakerMappingsResponse":{"properties":{"speaker_id":{"type":"string","format":"uuid","title":"Speaker Id","description":"The speaker."},"mappings":{"items":{"$ref":"#/components/schemas/SpeakerCameraMappingResponse"},"type":"array","title":"Mappings","description":"Cameras this speaker covers, by priority."}},"type":"object","required":["speaker_id","mappings"],"title":"SpeakerMappingsResponse","description":"This speaker's camera mappings, as they stand after the request.\n\nARMING IS RE-DECIDED ON EVERY REPLACE-SET PUT AND IS NEVER INHERITED. The\nPUT rewrites the whole set, so each `agent_trigger_enabled` here is what\nTHAT request asked for: a mapping submitted without the field comes back\nfalse (human-triggered only) regardless of what the row it replaced said.\nRead this payload as the authoritative new state — do not assume an omitted\nfield left the previous value alone, because it did not."},"SpeakerModel":{"type":"string","enum":["axis_c1310_e","axis_c1210_e","axis_c1310_e_mk2"],"title":"SpeakerModel"},"SpeakerResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The speaker record ID."},"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site this speaker belongs to."},"parent_id":{"type":"string","format":"uuid","title":"Parent Id","description":"The client this speaker belongs to."},"label":{"type":"string","title":"Label","description":"Display name.","examples":["SE Horn — dumpster"]},"brand":{"$ref":"#/components/schemas/SpeakerBrand","description":"Speaker vendor."},"model":{"anyOf":[{"$ref":"#/components/schemas/SpeakerModel"},{"type":"null"}],"description":"Speaker model."},"host":{"type":"string","title":"Host","description":"IP address of the speaker (IPv4 or IPv6 literal), in canonical form. Hostnames are not supported in v1.","examples":["167.20.111.104","2606:4700::1111"]},"port":{"type":"string","title":"Port","description":"HTTPS port.","examples":["443"]},"username":{"type":"string","title":"Username","description":"Device account username (the password is never returned).","examples":["root"]},"transport":{"$ref":"#/components/schemas/SpeakerTransport","description":"How api-backend reaches the device."},"clip_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clip Id","description":"Id of the pre-loaded deterrent clip.","examples":["1"]},"clip_params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Clip Params","description":"Playback parameters.","examples":[{"volume":80}]},"enabled":{"type":"boolean","title":"Enabled","description":"Whether delivery and the health poll consider this speaker."},"sip_enabled":{"type":"boolean","title":"Sip Enabled","description":"Whether the AI voice agent may place a SIP call to this speaker.","default":false},"sip_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sip Account Id","description":"Which of the device's SIP accounts the agent's call is placed from; null means the device default.","examples":["sip_account_1"]},"mic_enabled":{"type":"boolean","title":"Mic Enabled","description":"Whether this unit's microphone is live, which decides whether the agent waits for a reply.","default":false},"onboarding_checklist":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Onboarding Checklist","description":"The device-setup checklist: the owner's own attestations under `items`, each stamped server-side. An absent key means not confirmed. The one item the device answers for itself is NOT stored here — read it live from `GET /speakers/{id}/sip-accounts`.","examples":[{"items":{"audio_direction":{"done_at":"2026-09-11T18:20:44Z","operator_id":"..."}},"version":1}]},"agent_armed_camera_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Agent Armed Camera Count","description":"How many of this speaker's camera mappings are ARMED for the AI agent. Zero means nothing will ever announce here, however the device is configured.","examples":[2]},"last_sip_test":{"anyOf":[{"$ref":"#/components/schemas/LastSipTest"},{"type":"null"}],"description":"The newest SIP test call on this speaker, or null if it has never been tested."},"map_x":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x."},"map_y":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y."},"map_lat":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude of the speaker pin."},"map_lng":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude of the speaker pin."},"map_location_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose.","examples":["SE corner — by the dumpster"]},"map_heading":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Map Heading","description":"Direction a horn faces, degrees clockwise from map-up; null = not aimed (always null for an omnidirectional ceiling unit).","examples":[180]},"healthy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Healthy","description":"Last health-poll result."},"last_healthcheck_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Healthcheck At","description":"When the speaker was last polled."},"health_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Health Error","description":"Why the last health poll failed: unreachable (no HTTP answer), auth_rejected (the device answered 401 to the stored credentials), not_a_speaker (something answered, but not an Axis device API - e.g. a router page), clip_missing (the configured clip id is not on the device), or error. Null when healthy or never checked.","examples":["auth_rejected"]},"device_serial":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Serial","description":"Serial number of the device last seen at this host:port, read without credentials. Compare it across speakers to tell two units behind one IP apart. Last known value; a failed poll keeps it.","examples":["B8A44FA89513"]},"device_product":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Product","description":"Product name of the device last seen at this host:port (ProdFullName, else ProdNbr). Last known value.","examples":["AXIS C1310-E Network Horn Speaker"]},"device_firmware":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Firmware","description":"Firmware version of the device last seen at this host:port. Last known value.","examples":["11.11.192"]},"device_identity_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Device Identity At","description":"When the device identity above was last read successfully."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When the speaker was registered."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When the speaker was last edited."}},"type":"object","required":["id","site_id","parent_id","label","brand","host","port","username","transport","enabled"],"title":"SpeakerResponse","description":"A speaker record. Deliberately carries NO credential material: not the\npassword (which never leaves Secrets Manager) and not the credentials_arn\n(an internal handle nothing client-side has a use for) — the same shape as\nNvrResponse."},"SpeakerSipAccountsResponse":{"properties":{"speaker_id":{"type":"string","format":"uuid","title":"Speaker Id","description":"The speaker whose device was read."},"accounts":{"items":{"$ref":"#/components/schemas/SipAccountOption"},"type":"array","title":"Accounts","description":"Every SIP account the device reported."},"read_at":{"type":"string","format":"date-time","title":"Read At","description":"When the device answered."},"derived_checklist":{"additionalProperties":true,"type":"object","title":"Derived Checklist","description":"The checklist items the DEVICE answers, computed from this read and stored nowhere. Today that is one: whether a default account exists. `confirmed` is always present — a device that stopped reporting a default must show the tick gone, and an absent key would read as 'we did not ask'. The owner's own attestations live on the speaker record, under `onboarding_checklist`.","examples":[{"default_account":{"confirmed":true,"read_at":"2026-09-11T18:40:02Z","source":"GetSIPAccounts"}}]}},"type":"object","required":["speaker_id","accounts","read_at"],"title":"SpeakerSipAccountsResponse","description":"The SIP accounts a speaker's device reports, read live.\n\nThis is what turns the console's SIP-account field from a string somebody\ntranscribes into a dropdown the device answers for. Blank stays the\nRECOMMENDED state — a single peer-to-peer account is what the device default\nmeans, and the call is placed without an account id at all."},"SpeakerSipTestResponse":{"properties":{"call_id":{"type":"string","format":"uuid","title":"Call Id","description":"The call record for this test."},"speaker_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Speaker Id","description":"The speaker that was tested."},"speaker_label":{"type":"string","title":"Speaker Label","description":"The speaker's display name.","examples":["North horn — gate"]},"status":{"type":"string","title":"Status","description":"placing | connected | ended | failed.","examples":["connected"]},"passed":{"type":"boolean","title":"Passed","description":"True only when the device and the voice platform both reported this call live."},"summary":{"type":"string","title":"Summary","description":"One sentence a person can act on."},"device_call_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Call Id","description":"The device's own CallId, once it answered the dial.","examples":["1"]},"device_states":{"items":{"$ref":"#/components/schemas/SipTestDeviceState"},"type":"array","title":"Device States","description":"Every state the device reported, in order."},"provider_call_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Call Id","description":"The voice platform's call id.","examples":["call_85b9"]},"provider_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Status","description":"The platform's own call status, verbatim.","examples":["ongoing"]},"provider_disconnection_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Disconnection Reason","description":"Why the platform says the call ended.","examples":["agent_hangup"]},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms","description":"The platform's measured call duration.","examples":[13400]},"provider_unreadable":{"type":"boolean","title":"Provider Unreadable","description":"True when the voice platform could not be read at all. NOT the same as `passed: false` with a status: it means we do not know what the platform saw, which is a different thing to do about it than a call the platform never received.","default":false},"signals_agreed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Signals Agreed At","description":"When the device and the platform first agreed the call was live. A HIGH-WATER MARK: once set it stays, because a call that has since ended still passed."},"auth_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth Mode","description":"Which auth mode the device accepted.","examples":["digest"]},"playback_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Playback Note","description":"Set when the device could not confirm it had finished a clip and the owner went ahead anyway."},"device_error":{"anyOf":[{"$ref":"#/components/schemas/SipTestDeviceError"},{"type":"null"}],"description":"Present only when the device refused the call."},"sip_account_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sip Account Id","description":"Which SIP account the call was placed from; null means the device default.","examples":["sip_account_1"]},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At","description":"When the call was placed."},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ended At","description":"When the call ended, if it has."},"end_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Reason","description":"Why the call ended.","examples":["max_duration"]},"max_seconds":{"type":"integer","title":"Max Seconds","description":"The test call's duration cap — the watchdog hangs it up at this.","examples":[30]},"result_version":{"type":"integer","title":"Result Version","description":"Shape version of the stored result this was rendered from. 0 means a row written before the stamp existed.","default":0,"examples":[1]}},"type":"object","required":["call_id","speaker_label","status","passed","summary","max_seconds"],"title":"SpeakerSipTestResponse","description":"The result of one SIP test call.\n\nA 200 MEANS \"THE HORN IS ON A CALL\", NOT \"THE TEST IS OVER\": the request\nreturns at connect and deliberately leaves the call up so the owner hears\nit. Poll `GET /speakers/{id}/sip-test` to watch it end.\n\nA `DELETE` on the same path hangs the call up early — the owner's release\nfor a horn that is stuck, and the only one a test row has (the event-scoped\nhang-up cannot address it).\n\n`passed` is the two-signal check, minus the reservation: the device reported\nthe call `Active` AND the voice platform reported the same call live. Either\nalone proves half of it — the platform connecting proves an agent is\nspeaking into a call, the device being Active proves the horn is on a call,\nand it takes both to prove the agent is speaking OUT OF the horn."},"SpeakerTestResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"True only when the device confirmed playback."},"speaker_id":{"type":"string","format":"uuid","title":"Speaker Id","description":"The speaker that played."},"speaker_label":{"type":"string","title":"Speaker Label","description":"The speaker's display name.","examples":["SE Horn — dumpster"]},"clip_id":{"type":"string","title":"Clip Id","description":"Clip that was played.","examples":["1"]},"auth_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth Mode","description":"Which auth mode the device accepted.","examples":["digest"]}},"type":"object","required":["success","speaker_id","speaker_label","clip_id"],"title":"SpeakerTestResponse","description":"Result of an owner-initiated clip test. Success means the device\nCONFIRMED playback — a 2xx with an AXIS error body is a failure, not a\nsuccess with a warning."},"SpeakerTransport":{"type":"string","enum":["direct_https","direct_http","nvr_proxy"],"title":"SpeakerTransport"},"StartFocusRequest":{"properties":{"site_id":{"type":"string","format":"uuid","title":"Site Id","description":"The site to focus on."},"scope":{"oneOf":[{"$ref":"#/components/schemas/FocusScopeSite"},{"$ref":"#/components/schemas/FocusScopeCamera"}],"title":"Scope","description":"`{\"kind\": \"site\"}` for the whole site or `{\"kind\": \"camera\", \"camera_id\": ...}` for one camera. The scope is frozen at start.","discriminator":{"propertyName":"kind","mapping":{"camera":"#/components/schemas/FocusScopeCamera","site":"#/components/schemas/FocusScopeSite"}}},"origin":{"type":"string","enum":["focus","story"],"title":"Origin","description":"`focus` (the default, today's Focus) or `story`: a site hold, which owns the whole site while its story is open. A site hold takes `{\"kind\": \"site\"}` only.","default":"focus"}},"additionalProperties":false,"type":"object","required":["site_id","scope"],"title":"StartFocusRequest","description":"Body of `POST /focus`."},"StartLiveViewResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera ID."},"live_view_until":{"type":"string","format":"date-time","title":"Live View Until","description":"The ingestor pushes to KVS until this deadline; keepalive POSTs extend it.","examples":["2026-07-03T21:15:00+00:00"]}},"type":"object","required":["id","live_view_until"],"title":"StartLiveViewResponse"},"StartVoiceSessionRequest":{"properties":{"event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Event Id","description":"The alarm this talk is about. Must be in the caller's scope. The speaker must share the alarm camera's current site and parent; a camera-speaker mapping is not required for human Live Talk."},"camera_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Camera Id","description":"Optional; must equal the alarm's camera. Requires event_id."}},"additionalProperties":false,"type":"object","title":"StartVoiceSessionRequest","description":"OPTIONAL body for POST /speakers/{id}/talk-sessions.\n\nAbsent (or empty) = today's behaviour: a session addressed to a place, with\nno alarm. With `event_id` the session is tied to that alarm, which is what\nlets the Escalations timeline show it and the after-voice-down recording\nattach to it."},"StartVoiceSessionResponse":{"properties":{"session_id":{"type":"string","format":"uuid","title":"Session Id","description":"The session that now holds this speaker."},"speaker_id":{"type":"string","format":"uuid","title":"Speaker Id","description":"The speaker being talked through."},"speaker_label":{"type":"string","title":"Speaker Label","description":"The speaker's display name.","examples":["SE Horn - dumpster"]},"ws_url":{"type":"string","title":"Ws Url","description":"Path to open the audio WebSocket on. Present the ticket as `?ticket=`.","examples":["/speakers/talk/2b0f.../stream"]},"ticket":{"type":"string","title":"Ticket","description":"Single-use, short-lived credential for the audio socket. The browser WebSocket API cannot send an Authorization header, so this stands in for the bearer token. Shown once."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"After this the ticket is dead and the session is reaped."},"max_seconds":{"type":"integer","title":"Max Seconds","description":"Hard cap on the session. The server stops it regardless of the client.","examples":[120]},"idle_seconds":{"type":"integer","title":"Idle Seconds","description":"Silence for this long ends the session - an open mic never dangles.","examples":[10]}},"type":"object","required":["session_id","speaker_id","speaker_label","ws_url","ticket","expires_at","max_seconds","idle_seconds"],"title":"StartVoiceSessionResponse","description":"A claimed speaker and the credentials to open the audio socket.\n\n`ticket` is the ONLY time the plaintext ticket exists anywhere - it is\nstored on the row as a SHA-256 digest and can never be re-read. A client\nthat loses it must start a new session."},"StopVoiceSessionResponse":{"properties":{"session_id":{"type":"string","format":"uuid","title":"Session Id","description":"The session that was stopped, or was already stopped."},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ended At","description":"When the session closed. Null while a stop is still in progress - the row is only stamped once the device stream is actually shut."},"end_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Reason","description":"Why it stopped. Exhaustive set - see VoiceSessionEndReason.","examples":["ended_by_operator"]},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms","description":"Time the stream was live."},"bytes_sent":{"type":"integer","title":"Bytes Sent","description":"Bytes accepted by the device socket. NOT a delivery confirmation: transmit.cgi answers only at stream end, so no mid-stream acknowledgement exists to verify.","default":0}},"type":"object","required":["session_id"],"title":"StopVoiceSessionResponse","description":"What a stopped session cost. No audio is retained - only these facts."},"StoryHoldRecord":{"properties":{"origin":{"type":"string","const":"story","title":"Origin","description":"Always `story`."},"id":{"type":"string","title":"Id","description":"The hold id (a site Take over creates a successor with a new id in the same lineage)."},"lineage_id":{"type":"string","title":"Lineage Id","description":"Shared by a hold and its Take over successors."},"site_id":{"type":"string","title":"Site Id","description":"The site the hold is on."},"scope":{"$ref":"#/components/schemas/FocusScopeOut","description":"Always the whole site."},"operator_id":{"type":"string","title":"Operator Id","description":"The holder."},"operator_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Operator Name","description":"Their display name; null if the operator no longer exists."},"started_at":{"type":"string","title":"Started At","description":"ISO 8601, UTC."},"last_seen_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen At","description":"The holder's last heartbeat (ISO 8601, UTC)."},"left_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Left At","description":"Set by the holder's best-effort leave signal, cleared by a heartbeat inside the grace. After a clean leave the hold returns its alarms about 30 s after this instant (a fixed server constant), or sooner on its idle or new-alert deadline."},"open":{"type":"boolean","title":"Open","description":"True while the holder's page is open: no clean leave and a heartbeat within `open_s`. While true there is no deadline of any kind."},"return_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Return At","description":"When the unattended hold returns its alarms to the queue (ISO 8601, UTC); null while `open`. The earliest of the idle deadline, the new-alert deadline and (after a clean leave) 30 s after `left_at`, pushed later by a live call. A display, never a decision."},"now":{"type":"string","title":"Now","description":"The server clock this record was built against: compute `return_at - now` once and tick it locally."},"serious_unopened":{"type":"integer","title":"Serious Unopened","description":"Serious alarms the current holder has not opened (visibility only).","default":0},"oldest_serious_queued_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oldest Serious Queued At","description":"When the oldest of those entered the queue (ISO 8601, UTC)."},"open_seconds_total":{"type":"integer","title":"Open Seconds Total","description":"Seconds the holder's page was open during the hold (each interval since the last heartbeat, capped at `open_s`, settled at the next heartbeat, a leave or the release).","default":0},"idle_s":{"type":"integer","title":"Idle S","description":"Frozen at start: seconds since the last heartbeat before an unattended hold returns. The backstop for a page that vanished without a clean leave; after a clean leave the hold returns about 30 s later instead (a fixed server constant, not frozen)."},"new_alert_s":{"type":"integer","title":"New Alert S","description":"Frozen at start: seconds after a new alert before an unattended hold returns."},"open_s":{"type":"integer","title":"Open S","description":"Frozen at start: seconds since the last heartbeat within which the page still counts as open."},"state":{"type":"string","enum":["active","released"],"title":"State","description":"`active` while it holds the site."},"released_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Released At","description":"ISO 8601, UTC; null while active."},"release_reason":{"anyOf":[{"type":"string","enum":["idle_2min","new_alert_2min","holder_gone","submit","taken_over","flag_off","scope_lost","disabled"]},{"type":"null"}],"title":"Release Reason","description":"Why it ended; null while active. `idle_2min` is the unattended return after a clean leave (about 30 s after it, or the idle deadline if that came first; the name is historical, not a measured 2 minutes); `holder_gone` is the idle deadline with no clean leave."}},"type":"object","required":["origin","id","lineage_id","site_id","scope","operator_id","started_at","open","now","idle_s","new_alert_s","open_s","state"],"title":"StoryHoldRecord","description":"One site hold (`origin = \"story\"`, Site Story step 3): one operator owns the site while its\nstory is open. `id` is the hold; `lineage_id` is shared by a Take over's successor, which is how\nthe held alarms stay held for the taker with no gap."},"StreamGrantResponse":{"properties":{"url":{"type":"string","title":"Url","description":"Short-lived HLS playlist URL for this camera. CONTAINS A CREDENTIAL (the provider's streaming token, as a query parameter) — internal callers only. Never log it and never surface it to an operator.","examples":["https://api.verkada.com/stream/cameras/v1/footage/stream/stream.m3u8?org_id=...&camera_id=...&jwt=..."]},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"UTC instant at which the embedded credential expires. The caller must obtain a fresh grant before this; the value is the token's real expiry, so a grant issued from a cached token reports the time remaining on that token rather than a full TTL.","examples":["2026-09-02T18:31:07Z"]},"transport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transport","description":"How the bridge pulls. Absent = `url` (ffmpeg opens `url`). `avertx_osp`: `url` is the recorder's AvertX relay base URL and the bridge speaks the recorder's OSP WebSocket using `cookies` and `stream_token`.","examples":["avertx_osp"]},"cookies":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Cookies","description":"AvertX only. The recorder relay session cookies. A LIVE CREDENTIAL, exactly like `url` for Verkada: never log it."},"stream_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stream Token","description":"AvertX only. The per-camera OSP stream token (StartStream authToken). A LIVE CREDENTIAL."},"stream_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stream Name","description":"AvertX only. The recorder stream to request (its logicalName); `low` is the 720x480 H.264 sub-stream.","examples":["low"]},"camera_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Id","description":"AvertX only. The recorder-side camera UUID (the OSP cameraId); the second half of the device id."}},"type":"object","required":["url","expires_at"],"title":"StreamGrantResponse","description":"A short-lived authorization for the internal HLS->RTSP bridge.\n\nTHE BODY CARRIES A LIVE CREDENTIAL. `url` embeds the provider's streaming\ntoken as a query parameter (so does every segment URL inside the playlist\nit returns), which is the entire purpose of the endpoint. It is served only\non the internal shared-API-key route, is never logged, never written to a\nrow, and never recorded in an activity-log entry."},"SubjectDescriptorRequest":{"properties":{"kind":{"type":"string","enum":["person","vehicle"],"title":"Kind"},"confidence_band":{"$ref":"#/components/schemas/ConfidenceBand"},"attributes":{"items":{"type":"string"},"type":"array","maxItems":12,"title":"Attributes"},"description":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Description"}},"type":"object","required":["kind","confidence_band"],"title":"SubjectDescriptorRequest"},"Submitter":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Full name of the person submitting."},"company":{"type":"string","maxLength":200,"minLength":1,"title":"Company","description":"Their company (customer or dealer)."},"email":{"type":"string","maxLength":320,"format":"email","title":"Email","description":"Reply-To for the internal email; receives the receipt."},"phone":{"type":"string","maxLength":50,"minLength":1,"title":"Phone","description":"Phone number to reach the submitter."}},"additionalProperties":false,"type":"object","required":["name","company","email","phone"],"title":"Submitter"},"SuggestedDisposition":{"type":"string","enum":["ESCALATE","MONITOR","DISMISS","NEEDS_REVIEW"],"title":"SuggestedDisposition"},"SupportOnCallPerson":{"properties":{"operator_id":{"type":"string","format":"uuid","title":"Operator Id","description":"The person on call."},"operator_name":{"type":"string","title":"Operator Name","description":"Their display name.","examples":["Dana Reyes"]}},"type":"object","required":["operator_id","operator_name"],"title":"SupportOnCallPerson"},"SupportRosterEntry":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The operator ID."},"name":{"type":"string","title":"Name","description":"Display name.","examples":["Dana Reyes"]},"role":{"$ref":"#/components/schemas/OperatorRole","description":"support or owner.","examples":["support"]}},"type":"object","required":["id","name","role"],"title":"SupportRosterEntry"},"SupportShiftModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The support shift ID. Stable across move and resize."},"shift_date":{"type":"string","format":"date","title":"Shift Date","description":"Center-local calendar date the shift STARTS on.","examples":["2026-10-14"]},"start_minute":{"type":"integer","title":"Start Minute","description":"Minutes after local midnight of shift_date (0-1439, multiple of 15).","examples":[540]},"end_minute":{"type":"integer","title":"End Minute","description":"Minutes after local midnight of shift_date. Exceeds 1440 for an overnight shift; at most start_minute + 1440.","examples":[1020]},"end_day_offset":{"type":"integer","title":"End Day Offset","description":"0 when the shift ends on shift_date, 1 when it runs past midnight.","examples":[0]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form notes about the shift."},"seats":{"items":{"$ref":"#/components/schemas/SupportShiftSeatModel"},"type":"array","title":"Seats","description":"On-call list in seat order - index 0 is the PRIMARY. May be EMPTY: an unassigned block reads as a gap in the rotation."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At","description":"When the shift was created."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When the shift was last changed."}},"type":"object","required":["id","shift_date","start_minute","end_minute","end_day_offset","seats"],"title":"SupportShiftModel","description":"One support on-call block: a center-local wall-clock range with an\nordered on-call list."},"SupportShiftResponse":{"properties":{"shift":{"$ref":"#/components/schemas/SupportShiftModel","description":"The created or updated support shift."}},"type":"object","required":["shift"],"title":"SupportShiftResponse"},"SupportShiftSeatModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The seat's own ID."},"shift_id":{"type":"string","format":"uuid","title":"Shift Id","description":"The support shift this seat belongs to."},"operator_id":{"type":"string","format":"uuid","title":"Operator Id","description":"The person on call in this seat."},"operator_name":{"type":"string","title":"Operator Name","description":"Display name of the seated person, so a read-only viewer needs no GET /operator/ call to render the rotation.","examples":["Dana Reyes"]},"seat_order":{"type":"integer","title":"Seat Order","description":"Position on call, contiguous from 0: 0 is the PRIMARY, 1 the backup, 2+ further backups.","examples":[0]}},"type":"object","required":["id","shift_id","operator_id","operator_name","seat_order"],"title":"SupportShiftSeatModel"},"SystemHealthCameraAccept":{"properties":{"window_days":{"type":"integer","title":"Window Days","description":"Length of the per-camera window, in days.","examples":[7]},"cameras":{"type":"integer","title":"Cameras","description":"Live ii_gated cameras with at least one clip in the window.","examples":[65]},"fleet_accept_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fleet Accept Pct","description":"Fleet-wide accept rate over the window. Null when the fleet had no clips.","examples":[68.5]},"band":{"$ref":"#/components/schemas/SystemHealthCameraAcceptBand","description":"The fleet interquartile band the outlier rule is measured against."},"band_rule":{"type":"string","title":"Band Rule","description":"The exact outlier rule the server applied, published so the UI labels what it draws instead of restating a rule that could drift.","examples":["fleet p25/p75 ± 1.5 × IQR over cameras with total >= min_volume"]},"min_volume":{"type":"integer","title":"Min Volume","description":"Minimum clips in the window for a camera to be eligible for the band or to be flagged.","examples":[50]},"outliers":{"items":{"$ref":"#/components/schemas/SystemHealthCameraRow"},"type":"array","title":"Outliers","description":"Eligible cameras outside the band, busiest first."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Caveats that belong on the number wherever it is rendered."}},"type":"object","required":["window_days","cameras","band","band_rule","min_volume"],"title":"SystemHealthCameraAccept","description":"M5 — per-camera accept-rate outliers. Per-sensor vendor drift is real:\none camera can move days before the fleet does."},"SystemHealthCameraAcceptBand":{"properties":{"p25":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P25","description":"Fleet 25th percentile accept rate across eligible cameras. Null when no camera cleared the volume floor.","examples":[61.0]},"p75":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P75","description":"Fleet 75th percentile accept rate across eligible cameras. Null when no camera cleared the volume floor.","examples":[74.0]},"eligible_cameras":{"type":"integer","title":"Eligible Cameras","description":"Cameras that cleared the volume floor and so contributed to the band.","examples":[58]},"min_cameras":{"type":"integer","title":"Min Cameras","description":"How many eligible cameras the band needs before any outlier is reported at all.","examples":[4]}},"type":"object","required":["eligible_cameras","min_cameras"],"title":"SystemHealthCameraAcceptBand"},"SystemHealthCameraRow":{"properties":{"camera_id":{"type":"string","title":"Camera Id","description":"Camera id."},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Operator-facing camera name.","examples":["18 - SW Exterior Quad - SW"]},"site_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Name","description":"Site the camera belongs to."},"total":{"type":"integer","title":"Total","description":"ii_gated-arm clips for this camera in the window.","examples":[412]},"accept_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Accept Pct","description":"Share of those clips the gate did NOT stop.","examples":[99.3]},"fail_open":{"type":"integer","title":"Fail Open","description":"Of those clips, how many were read blind.","examples":[2]},"direction":{"type":"string","title":"Direction","description":"Which side of the fleet band this camera sits on: 'high' (accepting far more than the fleet) or 'low' (stopping far more).","examples":["high"]}},"type":"object","required":["camera_id","total","fail_open","direction"],"title":"SystemHealthCameraRow","description":"One camera's gate outcome mix, shaped like the per-camera rows on\n/analytics/agent-agreement."},"SystemHealthDailyBand":{"properties":{"p50":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P50","description":"Median of the daily values in the baseline window. Null when no day had data.","examples":[0.8]},"p90":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"P90","description":"90th percentile of the daily values. Null when no day had data.","examples":[2.1]},"days":{"type":"integer","title":"Days","description":"How many days contributed a value.","examples":[7]}},"type":"object","required":["days"],"title":"SystemHealthDailyBand","description":"A rolling daily baseline the current window is read against. Percentiles\nare over one point per day, so `days` is how much history the band actually\nhad — a freshly deployed fleet reports a band of 1 or 2 days and the UI\nshould say so rather than draw a confident envelope."},"SystemHealthFailOpen":{"properties":{"window_1h":{"$ref":"#/components/schemas/SystemHealthRatio","description":"Rolling last hour, cameras currently in the ii_gated arm."},"window_24h":{"$ref":"#/components/schemas/SystemHealthRatio","description":"Rolling last 24 hours, cameras currently in the ii_gated arm."},"legacy_validator_24h":{"$ref":"#/components/schemas/SystemHealthRatio","description":"The same question for the LEGACY arm, from the ingest worker's 5-minute validator counters. Restricted to cameras that are not ii_gated — ii_gated cameras run a no-op validator whose confirms would dilute the ratio toward zero."},"band_7d_pct":{"$ref":"#/components/schemas/SystemHealthDailyBand","description":"Daily ii_gated fail-open percentage over the last 7 days."},"failsafe_sentinels_24h":{"type":"integer","title":"Failsafe Sentinels 24H","description":"Clips in the last 24h that NO AI verdict ever arrived for, surfaced to a human by the fail-safe sweep. They are excluded from every ratio on this page — they are not pipeline results — so a persistent nonzero value is its own signal that evals are stalling. Read it beside fail_open: one counts clips read blind, the other clips never read at all.","examples":[0]},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Caveats that belong on the number wherever it is rendered."}},"type":"object","required":["window_1h","window_24h","legacy_validator_24h","band_7d_pct","failsafe_sentinels_24h"],"title":"SystemHealthFailOpen","description":"M1 — how many clips the pipeline read blind because the detection vendor\ncould not answer. The brownout metric."},"SystemHealthGate":{"properties":{"window_24h":{"$ref":"#/components/schemas/SystemHealthGateWindow","description":"Rolling last 24 hours."},"band_7d_stop_pct":{"$ref":"#/components/schemas/SystemHealthDailyBand","description":"Daily gate-stop percentage over the last 7 days."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Caveats that belong on the number wherever it is rendered."}},"type":"object","required":["window_24h","band_7d_stop_pct"],"title":"SystemHealthGate","description":"M2 + M7 — how much the object gate is stopping, and what the vision\npromotions cost."},"SystemHealthGateWindow":{"properties":{"stopped":{"type":"integer","title":"Stopped","description":"Clips the object gate ended before any full interpretation ran.","examples":[310]},"total":{"type":"integer","title":"Total","description":"ii_gated-arm clips in the window.","examples":[990]},"stop_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Stop Pct","description":"stopped as a percentage of total. Null when the window is empty.","examples":[31.3]},"fire_screens":{"type":"integer","title":"Fire Screens","description":"Gate stops whose vision fire screen actually completed (an errored screen stamps no prompt version, so this counts calls made, not attempted).","examples":[118]},"fire_screen_cost_usd":{"type":"number","title":"Fire Screen Cost Usd","description":"Summed cost of those completed fire screens, in USD.","examples":[4.91]}},"type":"object","required":["stopped","total","fire_screens","fire_screen_cost_usd"],"title":"SystemHealthGateWindow"},"SystemHealthHeightened":{"properties":{"window_24h":{"$ref":"#/components/schemas/SystemHealthHeightenedWindow","description":"Rolling last 24 hours, windowed on decision time."},"band_7d_benign_pct":{"$ref":"#/components/schemas/SystemHealthDailyBand","description":"Daily benign percentage over the last 7 days."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Caveats that belong on the number wherever it is rendered."}},"type":"object","required":["window_24h","band_7d_benign_pct"],"title":"SystemHealthHeightened","description":"M4 — the benign-heightened flood: what the AI escalates during\nheightened windows, and how much of it a human then calls nothing."},"SystemHealthHeightenedWindow":{"properties":{"heightened_evals":{"type":"integer","title":"Heightened Evals","description":"Live policy decisions taken inside a heightened monitoring window.","examples":[420]},"ai_escalated":{"type":"integer","title":"Ai Escalated","description":"Of those, how many the AI escalated (the leading signal — available immediately).","examples":[356]},"ai_escalate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ai Escalate Pct","description":"ai_escalated as a percentage of heightened_evals. Null when the window is empty.","examples":[84.8]},"operator_graded":{"type":"integer","title":"Operator Graded","description":"Of the AI-escalated set, how many a human has dispositioned so far. This is the denominator of benign_pct, and the coverage behind it: events still in the queue are excluded, so review lag cannot move the percentage.","examples":[190]},"operator_false_alarm":{"type":"integer","title":"Operator False Alarm","description":"Of the graded set, how many the operator called a false alarm. 'Non-threatening human activity' is graded but is deliberately NOT counted here — it is a different human judgment.","examples":[155]},"benign_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Benign Pct","description":"operator_false_alarm as a percentage of operator_graded. Null when nothing has been graded yet.","examples":[81.6]}},"type":"object","required":["heightened_evals","ai_escalated","operator_graded","operator_false_alarm"],"title":"SystemHealthHeightenedWindow"},"SystemHealthPolicy":{"properties":{"window_24h":{"$ref":"#/components/schemas/SystemHealthPolicyWindow","description":"Rolling last 24 hours."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Caveats that belong on the number wherever it is rendered."}},"type":"object","required":["window_24h"],"title":"SystemHealthPolicy","description":"The policy-compute fail-open — a different silent fallback from the\ndetection one in `fail_open`, and the same class of problem: invisible\nuntil somebody runs SQL by hand."},"SystemHealthPolicyWindow":{"properties":{"version_mix":{"additionalProperties":{"type":"integer"},"type":"object","title":"Version Mix","description":"Live ledger rows by policy version — which policy actually decided, in fact rather than by flag state.","examples":[{"boundary-live-v2":980,"preboundary-v2":24}]},"fallback_nonzero":{"type":"integer","title":"Fallback Nonzero","description":"Live rows where the boundary-flip policy compute failed and silently fell back to the legacy backstop. MUST trend to zero while the flip is on.","examples":[24]}},"type":"object","required":["fallback_nonzero"],"title":"SystemHealthPolicyWindow"},"SystemHealthQueue":{"properties":{"depth":{"type":"integer","title":"Depth","description":"Alarms waiting for a human, counted with the SAME queue-visibility predicate the operator console uses: AI-evaluated OR early-broadcast (surfaced before the verdict landed), and not operator-evaluated, not expired, not demo. An early-broadcast alarm is on an operator's screen waiting for a decision, so it belongs in the backlog this number reports.","examples":[41]},"age_p95_minutes":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Age P95 Minutes","description":"95th percentile age of those alarms, in minutes. Null when the queue is empty.","examples":[122.0]},"oldest_unreviewed_minutes":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Oldest Unreviewed Minutes","description":"Age of the oldest waiting alarm, in minutes. Null when the queue is empty.","examples":[611.0]},"expiry_horizon_hours":{"type":"integer","title":"Expiry Horizon Hours","description":"How long an unreviewed alarm survives before the hourly sweep expires it out of the queue — regardless of severity.","examples":[24]},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Caveats that belong on the number wherever it is rendered."}},"type":"object","required":["depth","expiry_horizon_hours"],"title":"SystemHealthQueue","description":"M3 — the human review queue, point-in-time (no window: it is a depth and\nan age, not a rate)."},"SystemHealthRatio":{"properties":{"fail_open":{"type":"integer","title":"Fail Open","description":"Clips read without a usable detector verdict in the window.","examples":[12]},"total":{"type":"integer","title":"Total","description":"Clips in the window this ratio is over.","examples":[1004]},"pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pct","description":"fail_open as a percentage of total. Null when the window is empty (an empty window is normal, not zero percent).","examples":[1.2]}},"type":"object","required":["fail_open","total"],"title":"SystemHealthRatio","description":"A count over a total, with the percentage the server computed — never\nrecompute it client-side, and never render a null pct as 0%."},"SystemHealthSecondLook":{"properties":{"window_24h":{"$ref":"#/components/schemas/SystemHealthSecondLookWindow","description":"Rolling last 24 hours."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Caveats that belong on the number wherever it is rendered."}},"type":"object","required":["window_24h"],"title":"SystemHealthSecondLook","description":"M6 — the stage-2a shadow's own audit trail. Load-bearing while the\nshadow soaks; demote it once stage-2a is enforced or abandoned."},"SystemHealthSecondLookWindow":{"properties":{"low_band_rows":{"type":"integer","title":"Low Band Rows","description":"Low-confidence-band interpretations in the window — the population stage-2a triggers on.","examples":[130]},"traces":{"type":"integer","title":"Traces","description":"Of those, how many carry a stage-2a shadow trace.","examples":[126]},"trace_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Trace Pct","description":"traces as a percentage of low_band_rows. Null when the window is empty.","examples":[96.9]},"status_mix":{"additionalProperties":{"type":"integer"},"type":"object","title":"Status Mix","description":"Trace counts by status: 'ran' (the re-check completed) or 'skipped'.","examples":[{"ran":96,"skipped":30}]},"skipped_reasons":{"additionalProperties":{"type":"integer"},"type":"object","title":"Skipped Reasons","description":"Skipped traces by their reason, e.g. not_gated, detector_failopen, zone_parity_unavailable, no_novel_frames, timeout, detector_timeout, detector_error, redecode_error, redaction_error, internal_error.","examples":[{"not_gated":8,"timeout":22}]},"decision_mix":{"additionalProperties":{"type":"integer"},"type":"object","title":"Decision Mix","description":"What stage-2a WOULD have concluded: 'would_conclude_benign' or 'defer_to_2b'. Shadow only — nothing acts on it.","examples":[{"defer_to_2b":65,"would_conclude_benign":61}]}},"type":"object","required":["low_band_rows","traces"],"title":"SystemHealthSecondLookWindow"},"SystemLogCategory":{"type":"string","enum":["ingest","ai_pipeline","config","queue","infra"],"title":"SystemLogCategory"},"SystemLogEntryModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of the system log entry"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When this entry was first written"},"last_seen_at":{"type":"string","format":"date-time","title":"Last Seen At","description":"When this entry last recurred (equals created_at unless coalesced)"},"occurrences":{"type":"integer","title":"Occurrences","description":"How many times this entry recurred within its coalescing windows","examples":[1,777]},"level":{"$ref":"#/components/schemas/SystemLogLevel","description":"Severity of the entry","examples":["warning"]},"category":{"$ref":"#/components/schemas/SystemLogCategory","description":"Pipeline area the entry concerns","examples":["ai_pipeline"]},"camera_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Camera Id","description":"Camera the entry concerns; null for system-wide entries or once that camera is deleted"},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Camera label at the time of the entry (snapshot)","examples":["Front Porch"]},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id","description":"Most recent event this entry refers to, when one exists"},"message":{"type":"string","title":"Message","description":"Human-readable description of what happened","examples":["Event recorded while incident intelligence is off — it will not appear in the review queue"]},"details":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Details","description":"Structured context for the entry"}},"type":"object","required":["id","created_at","last_seen_at","occurrences","level","category","message"],"title":"SystemLogEntryModel"},"SystemLogLevel":{"type":"string","enum":["info","warning","error"],"title":"SystemLogLevel"},"TakeOverClaimRequest":{"properties":{"expected_holder_id":{"type":"string","format":"uuid","title":"Expected Holder Id","description":"`operator_id` of the claim the console showed. The take-over moves the claim only if this operator still holds it."},"expected_claimed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expected Claimed At","description":"`claimed_at` of the claim the console showed (ISO 8601, echoed from the claim payload). Must equal the live claim's `claimed_at` (to the millisecond), so a renewal between the console's read and the click answers 409 `claim_changed` and the operator reconfirms against the fresh holder. Omit it only in the Focus reserved shape (together with `expected_focus_id`), to take over an alarm RESERVED for another operator's focus that has no live claim: there is nothing to echo, and it is ignored. In the reserved shape a live claim that appeared meanwhile is 409 `claim_changed` carrying that claim, a reservation that is gone or the caller's own is 409 `claim_changed` with `claim` null, and Focus being off (`FOCUS_ENABLED` off) is 404. A body that names neither `expected_claimed_at` nor `expected_focus_id` is 422 (like any schema-invalid body), except that with Focus on an alarm reserved for another operator's focus with no live claim answers 409 `claim_changed` with the current `reservation`."},"expected_focus_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Expected Focus Id","description":"Claim-lock R5 (Focus). The `reservation.focus_id` the console showed. Required to take over an alarm that is reserved for another operator's focus and has no live claim (together with `expected_holder_id` = the focuser); otherwise the answer is 409 `claim_changed` with the current `reservation`. When `expected_claimed_at` is omitted and a live claim exists, it is not compared: the answer is 409 `claim_changed` carrying that claim."}},"additionalProperties":false,"type":"object","required":["expected_holder_id"],"title":"TakeOverClaimRequest","description":"Body of `POST /events/{id}/claim/take-over`: the claim the caller SAW."},"TakeOverHoldRequest":{"properties":{"expected_operator_id":{"type":"string","format":"uuid","title":"Expected Operator Id","description":"The holder the console showed (`operator_id` of the record)."},"expected_focus_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Expected Focus Id","description":"The hold the console showed; must equal the path id when present."}},"additionalProperties":false,"type":"object","required":["expected_operator_id"],"title":"TakeOverHoldRequest","description":"Body of `POST /focus/{id}/take-over`: the hold the console showed. If it is no longer that\nholder's valid hold, nothing moves and the answer is 409 `claim_changed` with the current hold."},"TalkSpeakerResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The speaker to open a live voice session on."},"label":{"type":"string","title":"Label","description":"Display name, as the owner registered it.","examples":["SE Horn - dumpster"]},"map_location_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing prose for where this speaker physically is. The field an operator actually chooses by when a site has several.","examples":["SE corner - by the dumpster"]},"enabled":{"type":"boolean","title":"Enabled","description":"Whether delivery and the health poll consider this speaker. A disabled speaker is returned rather than hidden so the UI can show it as unavailable - a horn that silently vanishes from the list reads as a broken console."},"healthy":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Healthy","description":"Has a health poll confirmed this speaker? The column defaults to false, so a never-polled speaker reads false rather than null - this does NOT distinguish 'polled and failed' from 'never polled', and UI copy must not imply that it does. Advisory in either case: a healthy speaker can still refuse the stream and an unhealthy one may work, so this greys a choice rather than removing it."},"health_state":{"type":"string","enum":["healthy","unhealthy","unknown"],"title":"Health State","description":"'unhealthy' = the last health check failed; 'healthy' = it passed; 'unknown' = never checked, disabled (the poll skips a disabled speaker, so its stored verdict is stale) or no verdict. Unknown is not down: do not warn on it.","default":"unknown"},"unhealthy_since":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Unhealthy Since","description":"The time of the first recorded failed check in this episode; the device may have failed earlier. Only set while health_state is 'unhealthy'. Null while unhealthy means down since before this was tracked."},"camera_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Camera Ids","description":"The live (non-retired), same-site cameras this speaker covers. Use camera_priorities, not positions in this list, to compare speakers for a camera. Empty means no camera mappings; it can still be talked through from the review panel's site picker."},"camera_priorities":{"additionalProperties":{"type":"integer"},"propertyNames":{"format":"uuid"},"type":"object","title":"Camera Priorities","description":"Camera ID to this speaker's configured priority for that camera. Lower wins (0 is primary). Keys match camera_ids; list positions cannot express relative priority between different speakers."}},"type":"object","required":["id","label","enabled"],"title":"TalkSpeakerResponse","description":"One speaker an operator may open a live voice session on.\n\n**THIS MODEL IS A DISCLOSURE BOUNDARY, AND THAT IS ITS ONLY REASON FOR\nEXISTING.** `SpeakerResponse` already describes a speaker, and this could\nhave reused it in one line. It must not: that model carries `host`, `port`,\n`username` and `transport`, so returning it here would hand every operator\nwith ESCALATE_VOICE_DOWN a customer site's internal IP addresses and the\ndevice account username - a network map of a client's property, published\nto a role that has no configuration duties at all.\n\nSo the fields below are an ALLOW-LIST, not a convenient subset, and each one\nis here because the picker cannot work without it:\n\n- `id` is what `POST /speakers/{speaker_id}/talk-sessions` needs.\n- `label` and `map_location_label` are what a human picks by, mid-incident.\n- `enabled` and `healthy` let the UI show a known-bad horn as unavailable\n  instead of letting someone choose it blind and hear nothing.\n- `health_state` and `unhealthy_since` are DERIVED verdicts about whether\n  the device has been answering its health checks, added so the Voice down\n  panel can warn before an operator announces through a horn that has not\n  answered. `healthy` alone cannot do that: it defaults to false, so it\n  cannot tell \"polled and failed\" from \"never polled\", and a warning keyed\n  on it would fire on every new speaker. Both say nothing about HOW the\n  device is reached (no address, port, login or failure code), and an\n  operator can already read the vaguer `healthy`, so this is a refinement\n  of an existing disclosure, not a new one. `health_error` is deliberately\n  NOT here: it appears only on `/camera-health/speakers`, never on\n  talk-speakers.\n- `camera_ids` lets Live View put Talk on exactly the tiles whose camera\n  this speaker covers. Ids of cameras the operator already watches - a\n  place, not a wire.\n- `camera_priorities` identifies the preferred speaker for each camera;\n  it is mapping configuration, not device connection information.\n\n**Adding a field here is a security decision, not a convenience.** Anything\nthat describes how api-backend REACHES the device - host, port, transport,\ncredentials_arn, username - is out, permanently: an operator addresses a\nplace, and never needs to know the wire. If a future need seems to require\none, that need is almost certainly a different endpoint under a different\nguard."},"ThreadEventItem":{"properties":{"event":{"additionalProperties":true,"type":"object","title":"Event","description":"The member event (incl. thread_id, occurred_at)"},"camera":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Camera","description":"Compact camera ref: {id, camera_label}"},"interpretation_summary":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Interpretation Summary","description":"Compact eval summary (incident_type, severity, confidence_band, recommended_action, needs_human_review); null while the eval is pending"},"agent_action":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Agent Action","description":"Newest agent action for this member (incl. config_snapshot)"},"operator_evaluation":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Operator Evaluation","description":"Disposition on this member, if any (actor='agent' marks an auto-dismissal)"}},"type":"object","required":["event"],"title":"ThreadEventItem"},"TimePunchModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The punch ID."},"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"The operator who punched (null after account deletion; the name snapshot below survives)."},"operator_name":{"type":"string","title":"Operator Name","description":"Operator display name at punch time.","examples":["M. Delgado"]},"clock_in_at":{"type":"string","format":"date-time","title":"Clock In At","description":"When the shift started."},"clock_out_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Clock Out At","description":"When the shift ended (null while still on shift)."}},"type":"object","required":["id","operator_name","clock_in_at"],"title":"TimePunchModel"},"TimeclockStatusResponse":{"properties":{"punch":{"anyOf":[{"$ref":"#/components/schemas/TimePunchModel"},{"type":"null"}],"description":"The caller's open punch, or null when they are not clocked in."}},"type":"object","title":"TimeclockStatusResponse"},"TrainingCountModel":{"properties":{"incident_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Incident Type","description":"AI incident type this bucket counts (by_incident_type rows)"},"prompt_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Version","description":"Prompt version this bucket counts (by_prompt_version rows)"},"count":{"type":"integer","title":"Count","description":"Samples in this bucket"}},"type":"object","required":["count"],"title":"TrainingCountModel"},"TrainingRerunModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of the rerun row"},"sample_id":{"type":"string","format":"uuid","title":"Sample Id","description":"Training sample this rerun replayed"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the rerun was executed"},"requested_by":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Requested By","description":"Owner who requested the rerun"},"prompt_profile":{"type":"string","title":"Prompt Profile","description":"Prompt profile the clip was replayed against","examples":["production","demo"]},"status":{"type":"string","title":"Status","description":"Whether the AI pipeline returned a result; failures are persisted too","examples":["succeeded","failed"]},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Failure detail when status is 'failed'"},"rerun_incident_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rerun Incident Type","description":"Incident type from the rerun result"},"rerun_severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rerun Severity","description":"Severity from the rerun result"},"rerun_confidence_band":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rerun Confidence Band","description":"Confidence band from the rerun result"},"rerun_recommended_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rerun Recommended Action","description":"Operator-aligned recommendation from the rerun result"},"rerun_rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rerun Rationale","description":"Model rationale from the rerun result"},"llm_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Provider","description":"Provider that served the rerun"},"llm_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Model","description":"Model that served the rerun"},"llm_prompt_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Prompt Version","description":"Prompt version the rerun ran under"},"pipeline_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pipeline Version","description":"Pipeline version the rerun ran under"},"latency_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Latency Ms","description":"Wall-clock rerun latency in milliseconds"},"cost_usd":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Cost Usd","description":"Inference cost reported by the pipeline"},"matches_original":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Matches Original","description":"Rerun recommendation vs the sample's original AI recommendation; null when either side carries none"},"matches_operator":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Matches Operator","description":"Rerun recommendation vs the human's crosswalked expected action; null when either side carries none"}},"type":"object","required":["id","sample_id","created_at","prompt_profile","status"],"title":"TrainingRerunModel"},"TrainingSampleDetailModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of the training sample"},"event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Event Id","description":"Drill-back to the live event; null once that event is deleted"},"source_event_id":{"type":"string","format":"uuid","title":"Source Event Id","description":"Permanent provenance key: the event this sample snapshotted"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the sample was captured"},"occurred_at":{"type":"string","format":"date-time","title":"Occurred At","description":"Motion time (ingestor-reported, falling back to event creation)"},"evaluated_at":{"type":"string","format":"date-time","title":"Evaluated At","description":"When the disposition was written"},"ai_incident_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Incident Type","description":"AI incident classification snapshot"},"ai_severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Severity","description":"AI severity snapshot"},"ai_confidence_band":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Confidence Band","description":"AI self-reported confidence band snapshot"},"ai_recommended_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Recommended Action","description":"AI operator-aligned recommendation snapshot; null when the eval carried none"},"operator_disposition":{"type":"string","title":"Operator Disposition","description":"Enum member name (stable across display-label renames)","examples":["FALSE_ALARM","ESCALATE_DISPATCH"]},"expected_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expected Action","description":"The operator disposition crosswalked into the recommendation's 4-class space"},"is_disagreement":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Disagreement","description":"Three-state: true/false when AI and operator are comparable; null when the AI carried no recommendation"},"actor":{"type":"string","title":"Actor","description":"Who authored the disposition; only 'operator' rows are ground truth","examples":["operator","agent"]},"llm_prompt_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Prompt Version","description":"Prompt version the original eval ran under"},"llm_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Model","description":"Model that served the original eval"},"clip_copy_status":{"type":"string","title":"Clip Copy Status","description":"Whether the clip was durably copied into the training prefix","examples":["copied","failed","source_missing"]},"kvs_stream_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kvs Stream Name","description":"Source camera stream"},"zone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zone","description":"Camera zone label"},"is_demo":{"type":"boolean","title":"Is Demo","description":"Sample originated from an owner-uploaded demo clip"},"rerun_count":{"type":"integer","title":"Rerun Count","description":"Number of rerun experiments recorded for this sample"},"capture_version":{"type":"string","title":"Capture Version","description":"Version of the capture logic that wrote this snapshot"},"original_clip_s3_key":{"type":"string","title":"Original Clip S3 Key","description":"S3 key of the source clip at capture time"},"training_clip_s3_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Training Clip S3 Key","description":"S3 key of the durable corpus copy; null when the copy failed"},"clip_copy_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Clip Copy Error","description":"Copy failure detail, when any"},"ai_suggested_disposition":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Suggested Disposition","description":"AI suggested disposition snapshot"},"ai_needs_human_review":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ai Needs Human Review","description":"AI ambiguity flag snapshot"},"ai_rationale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Rationale","description":"AI rationale snapshot"},"llm_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Provider","description":"Provider that served the original eval"},"detection_provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Provider","description":"CV detection provider snapshot"},"detection_model_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detection Model Version","description":"CV detection model version snapshot"},"pipeline_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pipeline Version","description":"Pipeline version the original eval ran under"},"roi_config_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Roi Config Version","description":"ROI config version snapshot"},"schema_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Schema Version","description":"Interpretation schema version snapshot"},"ai_total_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Ai Total Ms","description":"Original eval end-to-end latency in milliseconds"},"ai_cost_usd":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Ai Cost Usd","description":"Original eval inference cost"},"ai_artifacts":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Ai Artifacts","description":"{debug, detections, keyframes, roi_hits} snapshotted from the interpretation's child tables"},"operator_notes":{"type":"string","title":"Operator Notes","description":"Disposition notes (agent rows carry the agent's rationale)"},"operator_closeout":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Operator Closeout","description":"Structured close-out snapshot {schema_version, observed, actions[], outcome, outcome_note}; null for rows captured before the feature, false-alarm fast-path rows that submitted none, and agent rows","examples":[{"actions":["reviewed_clip","viewed_live"],"observed":"Two people on foot crossing the west lot","outcome":"subject_left","schema_version":1}]},"operator_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Operator Id","description":"Human who dispositioned; null for agent rows"},"camera_context":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Context","description":"The composed SITE/CAMERA context snapshot re-runs replay against"},"site_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Timezone","description":"Site timezone at capture time"},"event_local_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Local Time","description":"Event time in the site's local timezone (ISO)"},"camera_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Camera Id","description":"Camera the event came from (denormalized)"},"site_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Site Id","description":"Site the event came from (denormalized)"},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id","description":"Client the event came from (denormalized)"},"event_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Source","description":"Ingest source label from the event"},"motion_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Motion Score","description":"Ingestor motion score from the event"},"event_summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Summary","description":"Event summary text at capture time"},"thread_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Thread Id","description":"Cross-camera incident thread, when any"},"reruns":{"items":{"$ref":"#/components/schemas/TrainingRerunModel"},"type":"array","title":"Reruns","description":"Replay experiments against this sample, newest first"}},"type":"object","required":["id","source_event_id","created_at","occurred_at","evaluated_at","operator_disposition","actor","clip_copy_status","is_demo","rerun_count","capture_version","original_clip_s3_key","operator_notes","reruns"],"title":"TrainingSampleDetailModel"},"TrainingSampleListItemModel":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"UUID of the training sample"},"event_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Event Id","description":"Drill-back to the live event; null once that event is deleted"},"source_event_id":{"type":"string","format":"uuid","title":"Source Event Id","description":"Permanent provenance key: the event this sample snapshotted"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the sample was captured"},"occurred_at":{"type":"string","format":"date-time","title":"Occurred At","description":"Motion time (ingestor-reported, falling back to event creation)"},"evaluated_at":{"type":"string","format":"date-time","title":"Evaluated At","description":"When the disposition was written"},"ai_incident_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Incident Type","description":"AI incident classification snapshot"},"ai_severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Severity","description":"AI severity snapshot"},"ai_confidence_band":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Confidence Band","description":"AI self-reported confidence band snapshot"},"ai_recommended_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai Recommended Action","description":"AI operator-aligned recommendation snapshot; null when the eval carried none"},"operator_disposition":{"type":"string","title":"Operator Disposition","description":"Enum member name (stable across display-label renames)","examples":["FALSE_ALARM","ESCALATE_DISPATCH"]},"expected_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expected Action","description":"The operator disposition crosswalked into the recommendation's 4-class space"},"is_disagreement":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Disagreement","description":"Three-state: true/false when AI and operator are comparable; null when the AI carried no recommendation"},"actor":{"type":"string","title":"Actor","description":"Who authored the disposition; only 'operator' rows are ground truth","examples":["operator","agent"]},"llm_prompt_version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Prompt Version","description":"Prompt version the original eval ran under"},"llm_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Llm Model","description":"Model that served the original eval"},"clip_copy_status":{"type":"string","title":"Clip Copy Status","description":"Whether the clip was durably copied into the training prefix","examples":["copied","failed","source_missing"]},"kvs_stream_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Kvs Stream Name","description":"Source camera stream"},"zone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zone","description":"Camera zone label"},"is_demo":{"type":"boolean","title":"Is Demo","description":"Sample originated from an owner-uploaded demo clip"},"rerun_count":{"type":"integer","title":"Rerun Count","description":"Number of rerun experiments recorded for this sample"}},"type":"object","required":["id","source_event_id","created_at","occurred_at","evaluated_at","operator_disposition","actor","clip_copy_status","is_demo","rerun_count"],"title":"TrainingSampleListItemModel"},"TranscriptSegment":{"properties":{"role":{"type":"string","title":"Role","description":"verdun | other | caller","examples":["caller"]},"start_ms":{"type":"integer","title":"Start Ms","description":"Segment start offset in milliseconds.","examples":[900]},"end_ms":{"type":"integer","title":"End Ms","description":"Segment end offset in milliseconds.","examples":[8400]},"text":{"type":"string","title":"Text","description":"Transcribed speech.","examples":["Please call me back about the alarm."]}},"type":"object","required":["role","start_ms","end_ms","text"],"title":"TranscriptSegment"},"UndoCallAuditResponse":{"properties":{"event_id":{"type":"string","format":"uuid","title":"Event Id","description":"The alarm whose audit ruling was taken back."},"undone":{"type":"boolean","const":true,"title":"Undone","description":"Always true: the ruling is gone and the call is back in Needs audit.","default":true}},"type":"object","required":["event_id"],"title":"UndoCallAuditResponse"},"UndoOperatorEvalResponse":{"properties":{"event_id":{"type":"string","title":"Event Id","description":"The event whose close was taken back"},"reopened":{"type":"boolean","title":"Reopened","description":"Always true on 200: the evaluation is gone, the alarm is back in the queue and claimed by the caller (unless their role never holds claims)."}},"type":"object","required":["event_id","reopened"],"title":"UndoOperatorEvalResponse"},"UnreadCountResponse":{"properties":{"count":{"type":"integer","title":"Count","description":"Total unread messages across OPEN threads in your scope."}},"type":"object","required":["count"],"title":"UnreadCountResponse"},"UpcomingHolidayEntry":{"properties":{"date":{"type":"string","title":"Date","description":"The observed site-local closure date, 'YYYY-MM-DD'.","examples":["2026-11-26"]},"label":{"type":"string","title":"Label","description":"Catalog label for a rule entry, or the operator's own label for a one-off.","examples":["Thanksgiving Day"]},"rule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rule","description":"Catalog key when this row came from a recurring rule; null for a one-off date.","examples":["thanksgiving"]},"actual_date":{"type":"string","title":"Actual Date","description":"The unshifted calendar date. Equal to `date` for one-offs and for anything that did not shift.","examples":["2026-11-26"]}},"type":"object","required":["date","label","actual_date"],"title":"UpcomingHolidayEntry","description":"One closure this site is actually configured for, inside the next year."},"UpdateAgentAuditSamplingRequest":{"properties":{"false_alarm_bp":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":0.0},{"type":"null"}],"title":"False Alarm Bp","description":"Share of the agent's False alarm closes put up for audit, in basis points (100 = 1%, 0 = none). Null reverts to the server default.","examples":[100]},"non_threatening_bp":{"anyOf":[{"type":"integer","maximum":10000.0,"minimum":0.0},{"type":"null"}],"title":"Non Threatening Bp","description":"Share of the agent's Non-threatening human activity closes put up for audit, in basis points (10 = 0.1%). Null reverts to the server default.","examples":[10]}},"type":"object","required":["false_alarm_bp","non_threatening_bp"],"title":"UpdateAgentAuditSamplingRequest","description":"Body for PUT /settings/agent-audit-sampling. Both fields REQUIRED but\nnullable: null reverts that disposition to the server default, and an\nomitted field is a 422 rather than an accidental reset."},"UpdateAgentConfigRequest":{"properties":{"agent_mode":{"type":"boolean","title":"Agent Mode","description":"true = act on AI recommendations (auto-dismiss / voice-down / escalate-upgrade); false = shadow mode, every clip waits for human review. Requires ii_enabled."},"agent_observation_minutes":{"type":"integer","maximum":120.0,"minimum":1.0,"title":"Agent Observation Minutes","description":"After an agent voice-down, how long to watch this camera for further threatening activity before the alert auto-dismisses.","default":10},"rag_context":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Rag Context","description":"Free-form scene context injected into the LLM classification prompt for this camera.","examples":["Cleaning crew expected 19:00-21:00 nightly; classify as non-threatening human activity."]}},"type":"object","required":["agent_mode"],"title":"UpdateAgentConfigRequest","description":"Site-admin camera automation settings (PUT /cameras/{id}/agent-config).\n\nThese fields are grouped behind one site-admin-gated endpoint because they all\nsteer automated dispositions: agent_mode arms them, the observation window\nbounds the voice-down state machine, and rag_context is operator-authored text\ninjected into the LLM prompt (in agent mode a poisoned context could\nauto-dismiss a real intrusion, so editing it is a safety control, not\nroutine camera config).\n\nThe per-camera classifier selection MOVED OUT of this model in 0079: it is\nnow the three-state `incident_routing` map behind\nPUT /cameras/{id}/incident-routing, which is gated on the\nconfigure_incident_routing PERMISSION rather than on the owner role, so it\ncannot ride along on this site-admin-only request. Request models here do not\nforbid extras, so a not-yet-deployed ops-view still gets 200s across the\nAmplify gap — its `agent_incident_types` field is simply ignored."},"UpdateAiFeedbackCaptureEnabledRequest":{"properties":{"ai_feedback_capture_enabled":{"type":"boolean","title":"Ai Feedback Capture Enabled","description":"Whether the operator AI-feedback panel is live. Dark by default (false) until an owner turns it on. Writes ONLY this column — never touches monitoring_center_timezone or slow_triage_target_seconds, so a concurrent change to either on another connection is structurally impossible to lose here.","examples":[true]}},"type":"object","required":["ai_feedback_capture_enabled"],"title":"UpdateAiFeedbackCaptureEnabledRequest","description":"Body for PUT /settings/ai-feedback-capture-enabled — the SOLE write\npath for org_settings.ai_feedback_capture_enabled (round-1 design's\ncorrection to round-0 §5: the general whole-record PUT /settings/ is\ntimezone-only by design, exactly the reason slow_triage_target_seconds\nalso gets its own dedicated endpoint rather than living on that one).\nREQUIRED, same reasoning as UpdateSlowTriageTargetRequest: this endpoint\nexists for exactly one purpose, so there is no \"leave unchanged\" case."},"UpdateCameraHealthConfigRequest":{"properties":{"ptz":{"type":"boolean","title":"Ptz","description":"Whether this camera is pan/tilt/zoom. When true the 'camera moved' health issue is suppressed, because a changed view is what the hardware is for. Does not reach the ingestor.","examples":[true]}},"type":"object","required":["ptz"],"title":"UpdateCameraHealthConfigRequest"},"UpdateCameraHealthConfigResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The camera id."},"ptz":{"type":"boolean","title":"Ptz","description":"The stored pan/tilt/zoom flag after the update."}},"type":"object","required":["id","ptz"],"title":"UpdateCameraHealthConfigResponse"},"UpdateCameraHealthRequest":{"properties":{"healthy":{"type":"boolean","title":"Healthy","description":"Health status","examples":[true,false]}},"type":"object","required":["healthy"],"title":"UpdateCameraHealthRequest"},"UpdateCameraHealthResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Camera UUID","examples":["2b1d3f2e-6e78-4d9b-93d6-3c1f9e40a1d4"]},"healthy":{"type":"boolean","title":"Healthy","description":"Whether the ingestor marked the camera healthy in this update","examples":[true]},"last_healthcheck_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Healthcheck At","description":"Timestamp (UTC) of the last health check, if any","examples":["2026-05-07T20:51:53.762+00:00"]}},"type":"object","required":["id","healthy"],"title":"UpdateCameraHealthResponse"},"UpdateCameraMapPositionRequest":{"properties":{"map_x":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x (0 = left edge, 1 = right edge). Null clears the floor-plan placement.","examples":[0.42]},"map_y":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y (0 = top edge, 1 = bottom edge). Null clears the floor-plan placement.","examples":[0.63]},"map_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude. Null clears the geographic placement.","examples":[42.0443]},"map_lng":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude. Null clears the geographic placement.","examples":[-87.9825]},"map_location_label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose shown on queue rows and mini-maps. Null clears it.","examples":["NE corner — loading dock"]},"map_heading":{"anyOf":[{"type":"number","exclusiveMaximum":360.0,"minimum":0.0},{"type":"null"}],"title":"Map Heading","description":"Direction the device faces, in degrees clockwise from map-up (true north on a geographic map, the top edge of a floor plan). Null clears the aim; omit to preserve it.","examples":[225]}},"type":"object","title":"UpdateCameraMapPositionRequest","description":"Camera placement on the site map (Sites → Map tab).\n\nCarries whichever coordinate space the site's map_mode uses: normalized\n0..1 floor-plan (map_x, map_y) or geographic (map_lat, map_lng). Explicit\nnulls clear that space's placement (remove the pin); the other space is\nALWAYS preserved server-side, so switching a site's map mode never loses\nplacements — callers send only the pair they are editing."},"UpdateCameraZonesRequest":{"properties":{"zones":{"items":{"$ref":"#/components/schemas/IgnoreZoneInput"},"type":"array","title":"Zones","description":"Full set of ignore zones for the camera. Replaces any existing zones."},"expected_revision":{"anyOf":[{"type":"string","maxLength":64,"minLength":64},{"type":"null"}],"title":"Expected Revision","description":"Revision returned by GET zones (it covers the zone rows and the camera's display rotation). When supplied, replacement is atomic and returns 409 if the zones or the rotation changed. Omit only for legacy clients; a camera with a non-zero display rotation returns 409 when it is omitted."}},"type":"object","required":["zones"],"title":"UpdateCameraZonesRequest"},"UpdateCameraZonesResponse":{"properties":{"zones":{"items":{"$ref":"#/components/schemas/IgnoreZoneModel"},"type":"array","title":"Zones","description":"The persisted ignore zones after the replace."},"revision":{"type":"string","title":"Revision","description":"Opaque revision of the persisted zones for the next conditional replacement."},"display_rotation":{"type":"integer","title":"Display Rotation","description":"The camera's display rotation (degrees CCW, 0 or 180) the saved zone points are expressed against: percent of the frame in the rotated (upright) space.","default":0}},"type":"object","required":["zones","revision"],"title":"UpdateCameraZonesResponse"},"UpdateCoverageGrantsRequest":{"properties":{"parent_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Parent Ids","description":"Parents granted wholesale - each covers every current AND future site under it, so onboarding never touches coverage."},"site_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Site Ids","description":"Individually granted sites (exceptions outside the parent grants)."}},"type":"object","required":["parent_ids","site_ids"],"title":"UpdateCoverageGrantsRequest","description":"Replace-all with MOVE semantics: the group's grants become exactly\nthese lists, and any requested parent/site currently owned by ANOTHER\ngroup is stolen from it in the same transaction (membership is\nexclusive). The one 409: requesting a site that another group covers\nvia a PARENT grant - move the parent, not the site."},"UpdateCoverageGroupRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":120,"minLength":1},{"type":"null"}],"title":"Name","description":"New display name (omit to keep the current one).","examples":["Retail & Commercial"]},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"New display color (omit to keep the current one).","examples":["#60a5fa"]}},"type":"object","title":"UpdateCoverageGroupRequest"},"UpdateCoverageGroupResponse":{"properties":{"group":{"$ref":"#/components/schemas/CoverageGroupModel","description":"The updated coverage group."}},"type":"object","required":["group"],"title":"UpdateCoverageGroupResponse"},"UpdateCoverageMembersRequest":{"properties":{"operator_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Operator Ids","description":"The operators who are standing members of this group. Replace-all: operators omitted here lose the group's site access at their next scope resolution (on an open SSE stream, within 30 seconds)."}},"type":"object","required":["operator_ids"],"title":"UpdateCoverageMembersRequest","description":"Replace-all: the group's members become exactly this list.\n\nSending `[]` clears the group, which is also how a group is prepared for\ndeletion (deleting a group with members is refused with a 409 -- a revoked\ngrant must not vanish silently).\n\nMEMBERSHIP CONFERS SCOPE, NEVER PERMISSION. A member sees the sites this\ngroup's grants cover, off-shift included; their capabilities stay exactly\ntheir role's. Structurally scoped roles (site_manager, account_manager,\nlocation_manager) and\nunconfined ones (owner / monitoring_manager) are refused with a 409 -- the first because their\nscope is derived and not editable as a grant, the second because their\nscope is unrestricted and the row would be permanently inert."},"UpdateDeepAlertDefaultThresholdRequest":{"properties":{"value":{"anyOf":[{"type":"integer"},{"type":"number"}],"title":"Value","description":"The new fleet default DeepAlert confidence floor, 0 < value <= 100 (lower = more sensitive). Applies to every ii_gated camera with no valid per-camera override. A real JSON number: a boolean, a string or null is rejected.","examples":[25]},"expected_revision":{"type":"integer","minimum":0.0,"title":"Expected Revision","description":"The fleet revision the caller last read. A different current revision answers 409 with the current value and revision, even when the requested value equals the current one.","examples":[0]},"reason":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Reason","description":"Free text recorded with the change; optional.","examples":["Per the recorded flip procedure"]}},"type":"object","required":["value","expected_revision"],"title":"UpdateDeepAlertDefaultThresholdRequest","description":"Body for PUT /settings/deepalert-default-confidence-threshold (owner\nonly), the SOLE write path for the DeepAlert fleet default.\n\nValidated on the RAW JSON types, before any coercion can erase them: the\nvalue must be a real JSON number (StrictInt | StrictFloat rejects a bool,\na numeric string and null) that is finite and 0 < v <= 100, and\nexpected_revision a real integer >= 0 (a bool, string, float or negative is\nrefused). A stale revision is a 409 from the service, not a 422 here."},"UpdateDeepAlertDefaultThresholdResponse":{"properties":{"value":{"type":"number","title":"Value","description":"The fleet default DeepAlert confidence floor now in force.","examples":[25.0]},"revision":{"type":"integer","title":"Revision","description":"The fleet revision now in force. Moves by exactly one per change; an equal-value PUT at the current revision is a no-op and returns the existing one.","examples":[1]},"changed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Changed At","description":"When the CURRENT revision was recorded (the DB clock inside the change's transaction). Descriptive only: rows and events are assigned to an epoch by revision, never by this time. Null only if no change row exists for the revision."}},"type":"object","required":["value","revision"],"title":"UpdateDeepAlertDefaultThresholdResponse"},"UpdateDeepalertThresholdRequest":{"properties":{"confidence_threshold":{"anyOf":[{"type":"number"},{"type":"integer"},{"type":"null"}],"title":"Confidence Threshold","description":"DeepAlert confidence floor, 0 < v <= 100 (lower = more sensitive); null = default.","examples":[25]}},"type":"object","required":["confidence_threshold"],"title":"UpdateDeepalertThresholdRequest","description":"PUT /cameras/{id}/deepalert-threshold (owner only). Required; null clears."},"UpdateDetectionModeRequest":{"properties":{"detection_mode":{"anyOf":[{"type":"string","enum":["legacy","ii_gated"]},{"type":"null"}],"title":"Detection Mode","description":"'ii_gated' delegates the DeepAlert object-of-interest gate to incident intelligence for this camera's clips; 'legacy' keeps today's upstream ingestion-side DeepAlert gating. null is accepted and means exactly 'legacy' (it is how an unflipped camera reads). Required — omitting the field is a 422, not a no-op.","examples":["ii_gated"]}},"type":"object","required":["detection_mode"],"title":"UpdateDetectionModeRequest","description":"Site-admin DeepAlert routing switch (PUT /cameras/{id}/detection-mode).\n\nKept off the general camera edit payload for the same reason agent-config\nis: this is a safety control, not routine camera config. \"ii_gated\" moves\nthe DeepAlert object-of-interest gate out of ingestion-server and into\nincident-intelligence for this camera's clips, and a wrong value here\nleaves a camera with NO object detection anywhere (see the guards in\nCamerasService.update_detection_mode and the DB CHECK\nck_cameras_ii_gated_requires_ii).\n\nWHY Literal HERE, when detection_mode is a plain str everywhere else.\nThe accept-then-inspect widening on the wire — interpretation_results\n.terminal_stage, gate_outcome.reason, and detection_mode on\nCreateEventRequest — exists so INBOUND MACHINE traffic from\nincident-intelligence / ingestion-server can never 422 and lose a row\n(0059_camera_detection_mode's docstring; gate_outcome_model.py). This\nfield is the opposite trust boundary: a human-driven config write where an\nunrecognized value must fail loudly. Storing e.g. \"ii-gated\" would read as\nNOT ii_gated at every consumer (the CHECK's IS DISTINCT FROM 'ii_gated',\nEventsService.create_event, PipelineTelemetryService._state_for,\ncamera_health_derivations.derive_issues) and leave the camera silently\nlegacy while the UI reported otherwise. A closed set here and an open set\nthere is not an inconsistency — it is the trust boundary being different."},"UpdateDetectionModeResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"Camera row id."},"camera_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Camera Label","description":"Human label.","examples":["18 - SW Exterior Quad - North"]},"detection_mode":{"type":"string","title":"Detection Mode","description":"The camera's detection mode after this call: 'legacy' | 'ii_gated'. Never null.","examples":["ii_gated"]},"previous_detection_mode":{"type":"string","title":"Previous Detection Mode","description":"The canonical mode before this call, same vocabulary. Equal to detection_mode on a no-op.","examples":["legacy"]},"changed":{"type":"boolean","title":"Changed","description":"False when the requested mode already matched (no write, no health-signal clear, nothing journaled)."},"ii_enabled":{"type":"boolean","title":"Ii Enabled","description":"Whether incident intelligence is on for this camera (ii_gated requires it)."},"ii_deepalert_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ii Deepalert State","description":"II-side DeepAlert health observation after this call; null after any real mode change (cleared on flip).","examples":["ok"]},"ii_deepalert_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ii Deepalert At","description":"Ordering key / observation date for ii_deepalert_state; null after any real mode change."}},"type":"object","required":["id","detection_mode","previous_detection_mode","changed","ii_enabled"],"title":"UpdateDetectionModeResponse","description":"Outcome of PUT /cameras/{id}/detection-mode.\n\nDeliberately NOT EditCameraResponse (which PUT /cameras/{id}/agent-config\nreturns): that model carries no detection_mode field, so reusing it would\nreturn a body that cannot echo the value this endpoint just wrote. The\nshape follows SetSiteAgentModeResponse instead — a small purpose-built\nmodel reporting what actually happened, including what did not change."},"UpdateDisarmSettingsRequest":{"properties":{"max_minutes":{"type":"integer","maximum":120.0,"minimum":1.0,"title":"Max Minutes","description":"The longest a single disarm may last at this site. Both the console and the texted 'how many minutes?' answer are clamped to it. Mirrored by a CHECK constraint on the column.","examples":[60,120]}},"type":"object","required":["max_minutes"],"title":"UpdateDisarmSettingsRequest"},"UpdateDisarmTrustedRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"True to let this site's trusted numbers skip the passcode by text and by phone; false to switch that off (the list is kept, inert).","examples":[true]},"acknowledged_risk":{"type":"boolean","title":"Acknowledged Risk","description":"Must be true when `enabled` is true, or the request is refused with a 422. Caller ID on a phone call can be faked, so enabling is a warned act: the console shows the warning and sends this flag, and the server enforces it so the warning cannot be skipped by calling the API directly. Ignored when switching OFF.","default":false,"examples":[true]}},"type":"object","required":["enabled"],"title":"UpdateDisarmTrustedRequest","description":"PUT /sites/{id}/disarm-trusted: turn trusted numbers on or off for a site."},"UpdateDisplayRotationRequest":{"properties":{"display_rotation":{"type":"integer","enum":[0,180],"title":"Display Rotation","description":"Degrees counter-clockwise to rotate the recorded frame for display. 0 = as recorded, 180 = mounted upside down. 90/270 are not enabled yet. Only motion_clip cameras may be set non-zero.","examples":[180]}},"type":"object","required":["display_rotation"],"title":"UpdateDisplayRotationRequest","description":"PUT /cameras/{id}/display-rotation (0190). Owner only for now.\n\nFor a camera mounted upside down: ingestion stamps each clip it remuxes\nwith a display-matrix tag (no re-encode) so every reader shows it upright.\nChanging the value also rotates every stored mask/annotation zone of the\ncamera in the same transaction, so zones stay on the same physical spot."},"UpdateExternalTalkdownRequest":{"properties":{"status":{"type":"string","enum":["connected","ended","failed"],"title":"Status","description":"The state the extension observed. `connected` = the vendor console reported the call up (the horn is live). `ended` and `failed` are terminal.","examples":["connected"]},"fail_reason":{"anyOf":[{"$ref":"#/components/schemas/ExternalTalkdownFailReason"},{"type":"null"}],"description":"Why the talk-down never happened. REQUIRED when status is `failed`, and refused otherwise.","examples":["timeout"]},"detail":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Detail","description":"Short attempt trail — extension version, last observed console state. At most 200 characters, letters/digits/spaces and `. , ; : _ ( ) / -` only, no unbroken run over 32 characters, and never a URL. Anything token-shaped is refused rather than stored.","examples":["extension 0.3.1; last observed state: connecting"]}},"additionalProperties":false,"type":"object","required":["status"],"title":"UpdateExternalTalkdownRequest","description":"Move one talk-down row along its state machine.\n\n`requested` IS NOT AN ACCEPTED TARGET, and that is a 422 rather than a 409:\nit is not a transition anyone could legitimately attempt, it is a malformed\nrequest. The three that are accepted are the only ones the extension can\never observe.\n\nTHERE IS NO `connected_at`, `ended_at` OR `duration_ms` FIELD. All three are\nwritten by the service from its own clock; see the service's state machine\nfor which transition writes which. Unknown keys are rejected rather than\nignored (`extra=\"forbid\"`), so a client that sends one of those names learns\nit was not accepted instead of believing the value landed."},"UpdateHeightenedExceptionsRequest":{"properties":{"exceptions":{"items":{"$ref":"#/components/schemas/HeightenedExceptionModel"},"type":"array","maxItems":20,"title":"Exceptions","description":"Full replacement exception list (at most 20 entries; labels unique case-insensitively)."}},"type":"object","required":["exceptions"],"title":"UpdateHeightenedExceptionsRequest","description":"Site-admin heightened-monitoring exceptions (PUT /sites/{id}/heightened-exceptions).\n\nDuring \"heightened\" schedule windows all human/vehicle activity escalates\nfor human review UNLESS the AI cites a clear match against one of these\ncategories. The list steers automated non-escalation — the same reason\nagent_context is site-admin-only — so it sits behind the same gate. An absolute\nwrite: the stored list is replaced wholesale; empty = no exceptions."},"UpdateIncidentRoutingRequest":{"properties":{"incident_routing":{"additionalProperties":{"$ref":"#/components/schemas/IncidentRoutingMode"},"propertyNames":{"$ref":"#/components/schemas/IncidentType"},"type":"object","title":"Incident Routing","description":"Incident types to route on this camera (or on every camera at this site), each to 'ignore', 'humans' or 'agent'. PATCH semantics: omitted types keep their current value; {} is a no-op. UNKNOWN is rejected - an unevaluated clip always goes to a human.","examples":[{"FIRE":"ignore","LOITERING":"agent","NORMAL":"humans"}]}},"type":"object","required":["incident_routing"],"title":"UpdateIncidentRoutingRequest","description":"Per-camera / per-site incident routing\n(PUT /cameras/{id}/incident-routing, PUT /sites/{id}/incident-routing).\n\nONE three-state control per incident type: `ignore` drops the verdict\nentirely (recorded as an agent-authored dismissal so it shows in dismissed\nhistory and analytics — never queued, never broadcast, never notified),\n`humans` sends it to the operator queue, and `agent` lets the agent act on\nit, still bounded by the global auto-dismiss safety allowlist, which\nrouting may only narrow.\n\nONE model for both routes on purpose: a site-level apply is the same patch,\nfanned out over every live camera at the site, so two models could only\never diverge.\n\nPATCH SEMANTICS, deliberately: omitted incident types keep their current\nvalue and `{}` is a committed no-op. An absolute write would let a client\nthat knows only part of the taxonomy silently reset every type it has never\nheard of — the same rollout trap `agent_incident_types`' omitted-means-\nunchanged guard was written for.\n\n`UNKNOWN` IS NOT ROUTABLE and is rejected here (422). It is the failsafe\nsweep's sentinel — the AI never returned a verdict — so it always goes to a\nhuman. Owner decision 4: not configurable."},"UpdateIntegrationRequest":{"properties":{"command_short_name":{"anyOf":[{"type":"string","maxLength":63,"pattern":"^[a-z0-9-]{1,63}$"},{"type":"null"}],"title":"Command Short Name","description":"The vendor console's organization short name (the subdomain in https://<short-name>.command.verkada.com/...). Owner-entered and never fetched from the provider. Lowercase letters, digits and hyphens only. Send null or an empty string to clear it. REQUIRED in this body: an omitted field is a 422, so an empty PATCH cannot silently clear a stored value.","examples":["northgate-security"]}},"additionalProperties":false,"type":"object","required":["command_short_name"],"title":"UpdateIntegrationRequest","description":"The ONE editable display field on a cloud-VMS connection.\n\nDeliberately a one-field model rather than a general edit body. This route\nexists because the short name has to be settable on connections that ALREADY\nEXIST in production, and the narrowest possible request is what keeps it\nfrom becoming a second way to change a credential: there is no `api_key`\nhere (that is `PUT .../credentials`, which validates against the provider\nbefore overwriting the stored secret), no `org_id`, no `label`, and no\n`status` / `last_error` / `last_verified_at` — those three are written only\nby a real connection test and must never be settable by a client.\n\nUNKNOWN KEYS ARE REJECTED rather than ignored, which is what makes the\nnarrowness above ENFORCED rather than incidental. An owner who sends\n`api_key` or `status` here has misunderstood which route they are on, and\nsilently dropping the field would let them believe the value landed on a\ncredential record. (Most request models in this repo ignore extras; this\none is a deliberate exception, for the same reason the talk-down bodies\nare.)\n\nTHE FIELD IS REQUIRED HERE (round-3 G1), unlike on create. It is the only\nfield in the body, so an omitted one carried no information a client could\nhave meant: `PATCH {}` cleared a short name the owner had set and answered\n200. Clearing is still expressible — `null` and `\"\"` both do it — but it\nnow has to be asked for."},"UpdateLiveViewOnlyRequest":{"properties":{"live_view_only":{"type":"boolean","title":"Live View Only","description":"true = Live View only (no AI, no alarms, whatever the schedule says); false = monitored by its schedule again.","examples":[true]}},"type":"object","required":["live_view_only"],"title":"UpdateLiveViewOnlyRequest","description":"PUT /cameras/{id}/live-view-only (0177). Ops-lead only.\n\nOn: the camera is connected for Live View and nothing else. Its monitoring\nstate reads \"off\" at every instant, so the ingestor withholds its runner\n(no clips, nothing sent to incident intelligence, no alarms) until an\noperator opens it in Live View."},"UpdateMonitoringScheduleRequest":{"properties":{"slots":{"items":{"items":{"type":"string","enum":["monitored","heightened","off"]},"type":"array"},"type":"array","title":"Slots","description":"7 rows (Monday-Sunday) of 96 quarter-hour monitor states."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form operator notes about the schedule.","examples":["Contractor window Thu 14:15-15:00"]}},"type":"object","required":["slots"],"title":"UpdateMonitoringScheduleRequest","description":"Replace a site's or camera's whole week at 15-minute grain.\n\nSaving through this endpoint makes the schedule authoritative at quarter-\nhour grain (`resolution_minutes` becomes 15) and rewrites the legacy 7x24\nview as its coarsened projection in the same transaction."},"UpdateMonitoringScheduleResponse":{"properties":{"schedule":{"$ref":"#/components/schemas/MonitoringScheduleModel","description":"The persisted schedule at 15-minute grain."}},"type":"object","required":["schedule"],"title":"UpdateMonitoringScheduleResponse"},"UpdateOperatorInstructionsRequest":{"properties":{"operator_instructions":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Operator Instructions","description":"Free-text instructions shown to the HUMAN monitoring operator on every alarm at this level, and again at the escalation step. NEVER enters an AI prompt — that is what the agent context fields are for. Leading and trailing whitespace is trimmed BEFORE the length check, so the 2000-character limit measures the value that will actually be stored and matches the editor's character counter exactly. Empty or whitespace-only is stored as null (= no instruction), as is an explicit null or an omitted field; there is no patch semantic."}},"type":"object","title":"UpdateOperatorInstructionsRequest","description":"Body for the three owner-only operator-instruction writes.\n\nONE MODEL FOR ACCOUNT, SITE AND CAMERA. The field, the cap, the trim rule\nand the null semantics are identical at all three levels by decision, and\nops-view mirrors them in one editor component — see the package docstring\nfor why this lives under `shared/` rather than in three copies.\n\nABSOLUTE WRITE, NO PATCH SEMANTIC. Omitting the field is identical to\nsending `null` and means CLEAR, matching how `agent_context` is written.\n\nNO `examples=` ON THE FIELD, DELIBERATELY. Real instruction text is\nafter-hours phone numbers and premise procedure; an example in the published\nOpenAPI would be a plausible-looking one sitting in every generated client\nand doc page, and \"sample\" premise procedure is exactly the sort of thing\nthat gets pasted in and acted on. The description carries the contract\ninstead."},"UpdateOperatorMissBoxRequest":{"properties":{"x1":{"type":"number","maximum":1.0,"minimum":0.0,"title":"X1","description":"Left edge of the box, normalized 0-1. Strict: a JSON boolean or a numeric string is rejected, not coerced.","examples":[0.41]},"y1":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Y1","description":"Top edge of the box, normalized 0-1. Strict: a JSON boolean or a numeric string is rejected, not coerced.","examples":[0.55]},"x2":{"type":"number","maximum":1.0,"minimum":0.0,"title":"X2","description":"Right edge of the box, normalized 0-1. Must be greater than x1 (checked below). Strict: a JSON boolean or a numeric string is rejected, not coerced.","examples":[0.48]},"y2":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Y2","description":"Bottom edge of the box, normalized 0-1. Must be greater than y1 (checked below). Strict: a JSON boolean or a numeric string is rejected, not coerced.","examples":[0.71]}},"type":"object","required":["x1","y1","x2","y2"],"title":"UpdateOperatorMissBoxRequest","description":"Move/resize an existing box. Phase 1 scope: bbox only -- the frame it's\npinned to is not editable here (delete and redraw for a different frame).\n\nEVERY FIELD IS `strict=True` (PR #276 review, Greptile P2) -- see\n`CreateOperatorMissBoxRequest`'s docstring for why: without it a JSON\nboolean or numeric string is silently coerced into a different\nannotation than the one submitted."},"UpdateOrgSettingsRequest":{"properties":{"monitoring_center_timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Monitoring Center Timezone","description":"IANA timezone the monitoring center's operators work in; shown as the 'Center' clock beside each site's local time. Explicit null clears it (site-local only); the field cannot be omitted.","examples":["America/New_York"]}},"type":"object","required":["monitoring_center_timezone"],"title":"UpdateOrgSettingsRequest","description":"TIMEZONE-ONLY. slow_triage_target_seconds deliberately has NO field\nhere — it used to (optional, omit-means-unchanged), but that let a stale\nwhole-record PUT (e.g. an out-of-date ops-view timezone-editor payload\nthat still carried an old target value) revert a target set concurrently\nvia the dedicated PUT /settings/slow-triage-target. Removed entirely\n(Copilot re-review on PR #175, mirror of the P1-2 hazard the dedicated\nendpoint closes in the other direction) rather than hardened further, so\n\"PUT /settings/ is timezone-only\" is an enforced contract, not just\ndocumentation. See OrgSettingsService.set_slow_triage_target for the\nsole write path; GET /settings/ still reads the column."},"UpdateScheduleRequest":{"properties":{"grid":{"items":{"items":{"type":"string","enum":["monitored","heightened","off"]},"type":"array"},"type":"array","title":"Grid","description":"7 rows (Monday-Sunday) of 24 hourly monitor states."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-form operator notes about the schedule.","examples":["Cleaning crew Tue/Thu until 23:00"]}},"type":"object","required":["grid"],"title":"UpdateScheduleRequest"},"UpdateScheduleResponse":{"properties":{"schedule":{"$ref":"#/components/schemas/ScheduleModel","description":"The persisted schedule."}},"type":"object","required":["schedule"],"title":"UpdateScheduleResponse"},"UpdateShiftBlockRequest":{"properties":{"shift_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Shift Date","description":"New center-local start date (omit to keep)."},"start_minute":{"anyOf":[{"type":"integer","exclusiveMaximum":1440.0,"minimum":0.0},{"type":"null"}],"title":"Start Minute","description":"New start, minutes after local midnight; multiple of 15."},"end_minute":{"anyOf":[{"type":"integer","maximum":2880.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"End Minute","description":"New end, minutes after local midnight of shift_date; multiple of 15."},"operator_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Operator Ids","description":"Replacement seat list in seat order - index 0 is the PRIMARY. Omit to keep the current roster; pass [] to clear every seat."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"New notes. OMIT the key to keep the stored value; send an explicit null to clear it. (Omitted and null are distinguished by the field's presence in the request body, not by its value.)"},"color":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Color","description":"New per-shift display color (hex). OMIT the key to keep the stored value; send an explicit null to reset the shift to its coverage group's color. (Omitted and null are distinguished by the field's presence in the request body, not by its value.)","examples":["#4DFFD2"]}},"type":"object","title":"UpdateShiftBlockRequest","description":"Move, resize, reseat or annotate an existing shift block.\n\nEvery field is optional; omitted fields keep their stored value. The row is\nupdated IN PLACE - `id` never changes across a move or a resize, because the\ncalendar-invite work keys an iCalendar UID on it and a churned id reads as\ncancel-and-reinvite in every operator's calendar.\n\nPassing an empty `operator_ids` CLEARS the seats and keeps the shift (an\nunassigned block reads as a coverage gap). Deleting is DELETE.\n\n`notes` and `color` are nullable, so OMITTING the key and sending an explicit\nnull mean different things: omit to keep the stored value, send null to clear\nit (for `color`, null resets the shift to its coverage group's color)."},"UpdateShiftCellRequest":{"properties":{"group_id":{"type":"string","format":"uuid","title":"Group Id","description":"The coverage group being scheduled."},"shift_date":{"type":"string","format":"date","title":"Shift Date","description":"Center-local calendar date of the cell.","examples":["2026-07-30"]},"hour":{"type":"integer","maximum":23.0,"minimum":0.0,"title":"Hour","description":"Center-local hour of day (0-23).","examples":[14]},"operator_ids":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Operator Ids","description":"Seated operators in seat order - index 0 is the PRIMARY, who bears a miss for this hour. An empty list clears the cell."}},"type":"object","required":["group_id","shift_date","hour","operator_ids"],"title":"UpdateShiftCellRequest","description":"Replace the seats of one (group, date, hour) cell."},"UpdateShiftCellResponse":{"properties":{"cell":{"anyOf":[{"$ref":"#/components/schemas/ShiftCellModel"},{"type":"null"}],"description":"The cell after the update, or null when the update cleared it."}},"type":"object","title":"UpdateShiftCellResponse"},"UpdateSiteAgentConfigRequest":{"properties":{"agent_context":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Agent Context","description":"Site-wide scene context injected (with each camera's own context) into the LLM classification prompt. Null = none.","examples":["Gated storage facility; groundskeeper drives a white pickup weekday mornings."]},"agent_thread_window_minutes":{"anyOf":[{"type":"integer","maximum":60.0,"minimum":1.0},{"type":"null"}],"title":"Agent Thread Window Minutes","description":"Incident-thread join window for this site's agent cameras, in minutes. Null = the global default (5)."}},"type":"object","title":"UpdateSiteAgentConfigRequest","description":"Site-level agent settings (PUT /sites/{id}/agent-config).\n\nagent_context is composed with each camera's rag_context into the LLM\nprompt (\"=== SITE CONTEXT ===\" section) — like camera rag_context it\nsteers automated agent dispositions, so it's a safety control behind a\nrole gate: owner, monitoring_manager, and site_manager may write both\nfields within their assigned sites. Both fields are absolute writes:\nnull clears context or restores the global thread-window default.\n\nThe site-level incident-type overrides MOVED OUT of this model in 0079:\nthey are now the three-state patch behind\nPUT /sites/{id}/incident-routing, gated on the configure_incident_routing\nPERMISSION rather than on the owner role. Request models here do not forbid\nextras, so a not-yet-deployed ops-view still gets 200s across the Amplify\ngap — its `agent_incident_type_overrides` field is simply ignored."},"UpdateSiteCameraOperatorInstructionsRequest":{"properties":{"operator_instructions":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Operator Instructions","description":"Free-text instructions shown to the HUMAN monitoring operator on every alarm at this level, and again at the escalation step. NEVER enters an AI prompt — that is what the agent context fields are for. Leading and trailing whitespace is trimmed BEFORE the length check, so the 2000-character limit measures the value that will actually be stored and matches the editor's character counter exactly. Empty or whitespace-only is stored as null (= no instruction), as is an explicit null or an omitted field; there is no patch semantic."},"headline":{"anyOf":[{"type":"string","maxLength":140},{"type":"null"}],"title":"Headline","description":"One line shown to the operator under the alarm video. Trimmed before the 140-character check; empty or null CLEARS it. UNLIKE the text, OMITTING the key leaves the stored headline unchanged."}},"type":"object","title":"UpdateSiteCameraOperatorInstructionsRequest","description":"Site and camera writes also take the headline. The ACCOUNT write uses the base\nmodel, which has no such field, so a `headline` sent there is ignored."},"UpdateSiteClosedRequest":{"properties":{"closed":{"type":"boolean","title":"Closed","description":"True = closed site.","examples":[true]}},"type":"object","required":["closed"],"title":"UpdateSiteClosedRequest","description":"Mark a site closed or reopen it (the console's CLOSED badge). A display\nflag only: nothing but the console reads it."},"UpdateSiteHeightenedGroundedClearRequest":{"properties":{"heightened_grounded_clear":{"type":"boolean","title":"Heightened Grounded Clear","description":"true = the agent may auto-close qualifying heightened False alarms at this site; false (the default) = this grounded-clear path is off and the existing heightened rules apply.","examples":[true]}},"type":"object","required":["heightened_grounded_clear"],"title":"UpdateSiteHeightenedGroundedClearRequest","description":"PUT /sites/{id}/heightened-grounded-clear. Ops-lead only; off is the stop control."},"UpdateSiteHolidaysRequest":{"properties":{"holidays":{"items":{"anyOf":[{"$ref":"#/components/schemas/HolidayRuleEntry"},{"$ref":"#/components/schemas/HolidayDateEntry"}]},"type":"array","maxItems":40,"title":"Holidays","description":"Full replacement closure list (at most 40 entries; no duplicate rule, no duplicate date)."}},"type":"object","required":["holidays"],"title":"UpdateSiteHolidaysRequest","description":"The site's closure calendar (PUT /sites/{id}/holidays).\n\nOn a listed SITE-LOCAL calendar day every schedule cell that would read\n\"off\" reads \"monitored\" instead: the premises are closed, so nobody\nlegitimate is there. A holiday only ever ADDS monitoring — it never\ndowngrades a heightened window, and a live text-to-disarm still beats it.\n\nAn ABSOLUTE WRITE: the stored list is replaced wholesale, mirroring\n`UpdateHeightenedExceptionsRequest`. An empty list clears the calendar.\n\nEACH ENTRY IS EXACTLY ONE OF THE TWO SHAPES. `extra=\"forbid\"` on both\nmembers is what enforces that: an entry carrying both `rule` and `date`, or\nany unknown key, fails BOTH arms of the union rather than being silently\ncoerced into one of them."},"UpdateSiteLiveViewOnlyRequest":{"properties":{"live_view_only":{"type":"boolean","title":"Live View Only","description":"true = every camera at this site is Live View only (no AI, no alarms); false = cameras follow their schedules again (except cameras that are Live View only on their own).","examples":[true]}},"type":"object","required":["live_view_only"],"title":"UpdateSiteLiveViewOnlyRequest","description":"PUT /sites/{id}/live-view-only (0177). Ops-lead only.\n\nOn: every camera at the site is connected for Live View and nothing else\n(monitoring state \"off\" at every instant, beating the schedule, a\ntext-to-disarm and a test arm). Each camera's own flag is untouched, so\nturning the site back off restores exactly the cameras that were not Live\nView only on their own."},"UpdateSiteMapRequest":{"properties":{"map_mode":{"type":"string","enum":["geo","floorplan"],"title":"Map Mode","description":"How the site is visualized for camera placement: a geographic map or an uploaded floor plan.","examples":["geo"]},"map_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude of the site pin (geo mode). OMIT the field to preserve the stored pin (a mode toggle sends map_mode alone); send it explicitly as null to CLEAR the pin.","examples":[42.0442]},"map_lng":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude of the site pin (geo mode). OMIT the field to preserve the stored pin (a mode toggle sends map_mode alone); send it explicitly as null to CLEAR the pin.","examples":[-87.9816]}},"type":"object","required":["map_mode"],"title":"UpdateSiteMapRequest","description":"Sites → Map tab settings: which map the site uses and, for geo mode,\nwhere the site pin sits. Floor-plan uploads go through the separate\nmultipart POST /sites/{site_id}/floorplan endpoint."},"UpdateSiteStageRequest":{"properties":{"stage":{"type":"string","enum":["live","test"],"title":"Stage","description":"'test' or 'live'.","examples":["live"]}},"type":"object","required":["stage"],"title":"UpdateSiteStageRequest","description":"Flip a site between 'test' and 'live' without a full site edit (the\nconsole's \"Mark as live site\" menu item). A display label only: nothing but\nthe console's TEST badge reads it."},"UpdateSiteSummaryInclusionsRequest":{"properties":{"event_id":{"type":"string","maxLength":64,"minLength":1,"title":"Event Id","description":"Event id to include or exclude. Must appear in this summary's escalations or reviewed_non_escalated list, else 404.","examples":["9f8e7d6c-1111-4222-8333-444455556666"]},"included":{"type":"boolean","title":"Included","description":"True ticks the event INTO the customer e-mail; false writes an explicit exclusion rather than deleting the key, so 'never considered' and 'deliberately removed' stay distinguishable.","examples":[true]}},"type":"object","required":["event_id","included"],"title":"UpdateSiteSummaryInclusionsRequest"},"UpdateSiteSummaryInclusionsResponse":{"properties":{"id":{"type":"string","title":"Id","description":"Summary UUID.","examples":["9f8e7d6c-1111-4222-8333-444455556666"]},"site_id":{"type":"string","title":"Site Id","description":"Site UUID this summary belongs to.","examples":["3f1e0d2c-1111-4222-8333-444455556666"]},"inclusions":{"items":{"type":"string"},"type":"array","title":"Inclusions","description":"Event ids currently ticked INTO the customer e-mail, in insertion order. Escalations are always sent and are not listed here unless explicitly ticked.","examples":[["9f8e7d6c-1111-4222-8333-444455556666"]]}},"type":"object","required":["id","site_id","inclusions"],"title":"UpdateSiteSummaryInclusionsResponse"},"UpdateSiteSummarySettingsRequest":{"properties":{"email_enabled":{"type":"boolean","title":"Email Enabled","description":"Send the daily e-mail for this site at all.","examples":[true]},"send_local_time":{"type":"string","pattern":"^([01]\\d|2[0-3]):([0-5]\\d)$","title":"Send Local Time","description":"24h HH:MM wall clock in the SITE's timezone, no seconds and no offset. Before noon closes last night's window; noon or later closes today's.","examples":["07:00"]},"send_on_quiet_days":{"type":"boolean","title":"Send On Quiet Days","description":"Send even when nothing happened.","examples":[true]},"min_outage_minutes":{"type":"integer","maximum":1440.0,"minimum":1.0,"title":"Min Outage Minutes","description":"Shortest camera outage worth reporting, in minutes (1..1440).","examples":[10]},"sections":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Sections","description":"Which e-mail sections render, by the four keys narrative/stats/escalations/outages. A missing key means ON; an unknown key is ignored.","examples":[{"escalations":true,"narrative":true,"outages":false,"stats":true}]}},"type":"object","required":["email_enabled","send_local_time","send_on_quiet_days","min_outage_minutes"],"title":"UpdateSiteSummarySettingsRequest"},"UpdateSiteTimezoneRequest":{"properties":{"timezone":{"type":"string","title":"Timezone","description":"IANA timezone the site's monitoring schedule is evaluated in.","examples":["America/Los_Angeles"]}},"type":"object","required":["timezone"],"title":"UpdateSiteTimezoneRequest"},"UpdateSiteTimezoneResponse":{"properties":{"timezone":{"type":"string","title":"Timezone","description":"The site's saved IANA schedule timezone.","examples":["America/Los_Angeles"]}},"type":"object","required":["timezone"],"title":"UpdateSiteTimezoneResponse"},"UpdateSlowTriageTargetRequest":{"properties":{"slow_triage_target_seconds":{"type":"integer","maximum":600.0,"minimum":30.0,"title":"Slow Triage Target Seconds","description":"Operator Performance 'slow triage' target in seconds (event-created -> disposition) to set. Writes ONLY this column — never touches monitoring_center_timezone, so a concurrent timezone change made by another client is structurally impossible to lose here (unlike the whole-record PUT /settings/, which re-sends whatever timezone value the caller last read and can revert a concurrent change).","examples":[90]}},"type":"object","required":["slow_triage_target_seconds"],"title":"UpdateSlowTriageTargetRequest","description":"Body for PUT /settings/slow-triage-target — the SOLE write path for\nslow_triage_target_seconds (UpdateOrgSettingsRequest, the whole-record\nPUT /settings/'s body, no longer declares this field at all — see that\nmodel's docstring for why). The field here is REQUIRED: this endpoint\nexists for exactly one purpose, setting a concrete triage target, so\nthere is no \"leave unchanged\" case to support — the caller either sets a\nvalue or doesn't call this endpoint at all."},"UpdateSpeakerMapPositionRequest":{"properties":{"map_x":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Map X","description":"Normalized floor-plan x (0 = left edge, 1 = right edge). Null clears the floor-plan placement.","examples":[0.42]},"map_y":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Map Y","description":"Normalized floor-plan y (0 = top edge, 1 = bottom edge). Null clears the floor-plan placement.","examples":[0.63]},"map_lat":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Map Lat","description":"Geographic latitude. Null clears the geographic placement.","examples":[42.0443]},"map_lng":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Map Lng","description":"Geographic longitude. Null clears the geographic placement.","examples":[-87.9825]},"map_location_label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Map Location Label","description":"Operator-facing location prose shown on queue rows and mini-maps. Null clears it.","examples":["NE corner — loading dock"]},"map_heading":{"anyOf":[{"type":"number","exclusiveMaximum":360.0,"minimum":0.0},{"type":"null"}],"title":"Map Heading","description":"Direction the device faces, in degrees clockwise from map-up (true north on a geographic map, the top edge of a floor plan). Null clears the aim; omit to preserve it.","examples":[225]}},"type":"object","title":"UpdateSpeakerMapPositionRequest","description":"Speaker placement on the site map (Sites → Map tab).\n\nTHE SAME CONTRACT AS THE CAMERA ONE, by inheritance rather than by copy:\na speaker pin sits on the same map as a camera pin, in the same two\ncoordinate spaces, with the same pair rule (send both to place, both null\nto clear, omit both to preserve) and the same heading semantics. A\nsubclass rather than a reuse of the camera class by name so the OpenAPI\nschema and the 422 messages say \"speaker\" where a client would look for\nit, and so the two can diverge later (a dispersion angle, say) without a\ncontract change on the camera side."},"UpdateStaleAlarmAlertRequest":{"properties":{"stale_alarm_alert_minutes":{"anyOf":[{"type":"integer","maximum":1440.0,"minimum":0.0},{"type":"null"}],"title":"Stale Alarm Alert Minutes","description":"Post to Slack when an alarm has waited in the queue this many minutes. 0 turns the alert off; null reverts to the server default.","examples":[5]}},"type":"object","required":["stale_alarm_alert_minutes"],"title":"UpdateStaleAlarmAlertRequest","description":"Body for PUT /settings/stale-alarm-alert. REQUIRED but nullable: null\nclears the Admin setting so the server default applies again, and an\nomitted field is a 422 rather than an accidental clear."},"UpdateSupportShiftRequest":{"properties":{"shift_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Shift Date","description":"New center-local start date (omit to keep)."},"start_minute":{"anyOf":[{"type":"integer","exclusiveMaximum":1440.0,"minimum":0.0},{"type":"null"}],"title":"Start Minute","description":"New start, minutes after local midnight; multiple of 15."},"end_minute":{"anyOf":[{"type":"integer","maximum":2880.0,"exclusiveMinimum":0.0},{"type":"null"}],"title":"End Minute","description":"New end, minutes after local midnight of shift_date; multiple of 15."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"New notes. OMIT the key to keep the stored value; send an explicit null to clear it."},"operator_ids":{"anyOf":[{"items":{"type":"string","format":"uuid"},"type":"array"},{"type":"null"}],"title":"Operator Ids","description":"Replacement on-call list in order - index 0 is the PRIMARY. Omit to keep the current roster; pass [] to clear every seat. Each must be a support or owner user (409 ineligible_operator otherwise)."}},"type":"object","title":"UpdateSupportShiftRequest","description":"Move, resize, reseat or annotate a support shift, IN PLACE.\n\nEvery field is optional; omitted fields keep their stored value. The merged\nrange is re-checked (a 400 if one end crosses the other or the shift would\nexceed 24 hours).\n\n`operator_ids` REPLACES the roster in the order given (index 0 = primary);\n`[]` clears every seat and keeps the shift. `notes`: omit the key to keep\nthe stored value, send an explicit null to clear it."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError","description":"One request-validation error. The submitted value (`input`) is deliberately never included — it may be a credential."},"VoiceAgentHangupResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"True when the hang-up was requested and recorded. Read device_confirmed to learn whether the speaker has actually stopped."},"speaker_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Label","description":"The speaker the agent was speaking through; null if that speaker has since been retired.","examples":["SE Horn — dumpster"]},"end_reason":{"type":"string","title":"End Reason","description":"Always operator_hangup on this route.","examples":["operator_hangup"]},"announced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Announced At","description":"When the announcement was confirmed delivered, if it ever was. Null means nothing was confirmed, and the observation was cancelled so the event goes back to the human queue; a value means the deterrent did reach the site and the observation window stands."},"device_confirmed":{"type":"boolean","title":"Device Confirmed","description":"True when the speaker confirmed it hung the SIP call up. False means the request was recorded but the device did not answer: the call may still be live, the announcement row stays open, and the watchdog keeps retrying — show \"hanging up…\" rather than \"ended\"."}},"type":"object","required":["success","end_reason","device_confirmed"],"title":"VoiceAgentHangupResponse","description":"An operator took the incident back from the AI voice agent.\n\nALWAYS 200, even if the device refused to hang up: a 502 would only invite\nthe operator to press it again, and the voice platform's own duration cap\nends the call regardless. A second press is 409 (\"already ended\"), never\n404 — \"already ended\" and \"there was never a call\" are different facts.\n\n**200 IS NOT A PROMISE OF SILENCE.** When the device does not confirm the\nhang-up the agent may still be talking on that horn, so the call row stays\nOPEN (which is what keeps the speaker reserved against an operator\nmicrophone or a second agent call) and the watchdog retries until the\ndevice confirms, the platform reports the call ended, or its duration cap\nhas certainly ended it. `device_confirmed` is how the review page tells the\ntwo apart."},"VoiceAgentStartResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"True when a call was placed."},"speaker_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Speaker Id","description":"The speaker the AI is dialling through."},"speaker_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Speaker Label","description":"That speaker's label.","examples":["SE Horn - dumpster"]},"agent_action_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Action Id","description":"The operator-authored voice_down reservation (actor=operator) the call belongs to."},"voice_agent_call":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Voice Agent Call","description":"The placed call's current public status, including its local id and created_at. Lets the console show status and Hang up without another successful request; contains no provider identifiers or transcript."}},"type":"object","required":["success"],"title":"VoiceAgentStartResponse","description":"An operator started the AI voice announcement on this event's camera.\n\n200 MEANS PLACED, NOT ANNOUNCED. The speaker has been told to dial the\nvoice platform; whether the AI is actually speaking out of the horn is the\ntwo-signal `announced_at` on the event's `voice_agent_call`, exactly as for\nthe agent's own call. 409 SPEAKER_NOT_MAPPED when no SIP-enabled speaker is\nmapped to the camera; 409 when an AI announcement is already on the speaker\nfor this incident; 502 when the speaker or the platform refused."},"VoiceDownResponse":{"properties":{"success":{"type":"boolean","title":"Success","description":"True only when a speaker confirmed the announcement."},"speaker_id":{"type":"string","format":"uuid","title":"Speaker Id","description":"The speaker that played."},"speaker_label":{"type":"string","title":"Speaker Label","description":"The speaker's display name.","examples":["SE Horn — dumpster"]},"agent_action_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Agent Action Id","description":"Audit row (agent_actions) recording this announcement, actor=operator."},"announced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Announced At","description":"When the announcement was confirmed."}},"type":"object","required":["success","speaker_id","speaker_label"],"title":"VoiceDownResponse","description":"A confirmed manual (operator) announcement. A failure never reaches this\nmodel — it raises, and the attempt is still audited."},"VoiceSessionAudioUrlResponse":{"properties":{"audio_url":{"type":"string","title":"Audio Url","description":"Presigned GET link to the WAV recording."},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds the link stays valid.","examples":[3600]}},"type":"object","required":["audio_url","expires_in"],"title":"VoiceSessionAudioUrlResponse","description":"A short-lived link to the recorded audio of an alarm-scoped Live Talk\nsession. Internal only: served to ops leads, never to customers."},"VoiceTokenResponse":{"properties":{"token":{"type":"string","title":"Token","description":"Twilio Voice access token (JWT) for the browser softphone. The outgoing grant is bound to the server's TwiML App — it cannot dial arbitrary numbers. An incoming grant is added only when incoming_allowed.","examples":["eyJhbGciOi...voice"]},"identity":{"type":"string","title":"Identity","description":"The token's client identity: the operator's UUID in 32-char hex form (Twilio identities allow only alphanumerics and underscores).","examples":["3fa85f6457174562b3fc2c963f66afa6"]},"ttl_seconds":{"type":"integer","title":"Ttl Seconds","description":"Seconds until the token expires; the SDK's tokenWillExpire event is the refresh hook.","examples":[3600]},"incoming_allowed":{"type":"boolean","title":"Incoming Allowed","description":"True when this token also carries the incoming grant (INBOUND_CALLS_ENABLED and the caller is central staff with a calling permission). The console registers a Device to receive calls only when this is true.","default":false,"examples":[true]}},"type":"object","required":["token","identity","ttl_seconds"],"title":"VoiceTokenResponse"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"},"APIKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"}}}}