---
title: "Update Detection Mode"
method: PUT
path: "/cameras/{camera_id}/detection-mode"
tags: ["cameras"]
---

# Update Detection Mode

`PUT /cameras/{camera_id}/detection-mode`

## Request

- Base URL: `https://api.verdun.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.verdun.ai/cameras/{camera_id}/detection-mode`
- Auth: HTTP bearer

## Path parameters

- `camera_id` string, required

## Request body

- UpdateDetectionModeRequest: Site-admin DeepAlert routing switch (PUT /cameras/{id}/detection-mode). Kept off the general camera edit payload for the same reason agent-config is: this is a safety control, not routine camera config. "ii_gated" moves the DeepAlert object-of-interest gate out of ingestion-server and into incident-intelligence for this camera's clips, and a wrong value here leaves a camera with NO object detection anywhere (see the guards in CamerasService.update_detection_mode and the DB CHECK ck_cameras_ii_gated_requires_ii). WHY Literal HERE, when detection_mode is a plain str everywhere else. The accept-then-inspect widening on the wire — interpretation_results .terminal_stage, gate_outcome.reason, and detection_mode on CreateEventRequest — exists so INBOUND MACHINE traffic from incident-intelligence / ingestion-server can never 422 and lose a row (0059_camera_detection_mode's docstring; gate_outcome_model.py). This field is the opposite trust boundary: a human-driven config write where an unrecognized value must fail loudly. Storing e.g. "ii-gated" would read as NOT ii_gated at every consumer (the CHECK's IS DISTINCT FROM 'ii_gated', EventsService.create_event, PipelineTelemetryService._state_for, camera_health_derivations.derive_issues) and leave the camera silently legacy while the UI reported otherwise. A closed set here and an open set there is not an inconsistency — it is the trust boundary being different.
  - `detection_mode` 'legacy' | 'ii_gated', nullable, required: '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.

## Response `200`

Successful Response

- UpdateDetectionModeResponse: Outcome of PUT /cameras/{id}/detection-mode. Deliberately NOT EditCameraResponse (which PUT /cameras/{id}/agent-config returns): that model carries no detection_mode field, so reusing it would return a body that cannot echo the value this endpoint just wrote. The shape follows SetSiteAgentModeResponse instead — a small purpose-built model reporting what actually happened, including what did not change.
  - `id` string, uuid, required: Camera row id.
  - `camera_label` string, nullable: Human label.
  - `detection_mode` string, required: The camera's detection mode after this call: 'legacy' | 'ii_gated'. Never null.
  - `previous_detection_mode` string, required: The canonical mode before this call, same vocabulary. Equal to detection_mode on a no-op.
  - `changed` boolean, required: False when the requested mode already matched (no write, no health-signal clear, nothing journaled).
  - `ii_enabled` boolean, required: Whether incident intelligence is on for this camera (ii_gated requires it).
  - `ii_deepalert_state` string, nullable: II-side DeepAlert health observation after this call; null after any real mode change (cleared on flip).
  - `ii_deepalert_at` string, date-time, nullable: Ordering key / observation date for ii_deepalert_state; null after any real mode change.

## Other responses

- `422`: Validation Error

---

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