---
title: "Update Site Agent Config"
method: PUT
path: "/sites/{site_id}/agent-config"
tags: ["sites"]
---

# Update Site Agent Config

`PUT /sites/{site_id}/agent-config`

## 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/sites/{site_id}/agent-config`
- Auth: HTTP bearer

## Path parameters

- `site_id` string, required

## Request body

- UpdateSiteAgentConfigRequest: Site-level agent settings (PUT /sites/{id}/agent-config). agent_context is composed with each camera's rag_context into the LLM prompt ("=== SITE CONTEXT ===" section) — like camera rag_context it steers automated agent dispositions, so it's a safety control behind a role gate: owner, monitoring_manager, and site_manager may write both fields within their assigned sites. Both fields are absolute writes: null clears context or restores the global thread-window default. The site-level incident-type overrides MOVED OUT of this model in 0079: they are now the three-state patch behind PUT /sites/{id}/incident-routing, gated on the configure_incident_routing PERMISSION rather than on the owner role. Request models here do not forbid extras, so a not-yet-deployed ops-view still gets 200s across the Amplify gap — its `agent_incident_type_overrides` field is simply ignored.
  - `agent_context` string, nullable: Site-wide scene context injected (with each camera's own context) into the LLM classification prompt. Null = none.
  - `agent_thread_window_minutes` integer, nullable: Incident-thread join window for this site's agent cameras, in minutes. Null = the global default (5).

## Response `200`

Successful Response

- EditSiteResponse
  - `stage` 'live' | 'test', nullable: The stored site stage, a display label only.
  - `closed` boolean, nullable: Closed site: any human activity warrants an immediate police call. A display flag only (the console badges it); nothing else branches on it. Set via PUT /sites/{id}/closed.
  - `onboarding_marks` object: Onboarding checklist marks: mark key -> when it was set (ISO-8601 UTC). Who set a mark is never exposed. Empty when none.
  - `id` string, uuid, required: Unique identifier of the site
  - `account_id` string, uuid, nullable: Account (the client's customer) this site is grouped under; null = directly under the client. This is the ONLY expression of account membership -- group sites by it client-side.
  - `name` string, required: Name of the site
  - `address` string, required: Address of the site
  - `city` string, required: City of the site
  - `state` string, required: State of the site
  - `zip_code` string, required: Zip code of the site
  - `type` 'Commercial' | 'Car Dealership' | 'Construction' | 'Residential' | 'Logistics' | 'Education' | 'Healthcare' | 'Utilities' | 'Other', required
  - `priority_tier` 'critical' | 'high' | 'medium', required
  - `monitoring_hours` 'Nights' | 'All Day' | 'Weekends' | 'Custom', required
  - `timezone` string, required: IANA timezone the site's monitoring schedule is evaluated in
  - `police_nearest_precinct_name` string, required: Police nearest precinct name
  - `police_non_emergency_line` string, required: Police non-emergency line
  - `police_direct_dispatch_line` string, nullable: Police direct dispatch line
  - `police_jurisdiction_notes` string, nullable: Police jurisdiction notes
  - `alarm_license_number` string, nullable: Dealer alarm license number
  - `alarm_license_issuer` string, nullable: Who issued the alarm license
  - `mobile_guard_contract` boolean, required: Mobile guard contract available?
  - `guard_company` string, nullable: Mobile guard company name.
  - `guard_unit` string, nullable: Mobile guard unit or contact identifier.
  - `guard_contact` string, nullable: Mobile guard dispatch phone.
  - `agent_context` string, nullable: Site-wide scene context composed into the LLM prompt for every camera at this site.
  - `agent_thread_window_minutes` integer, nullable: Incident-thread join window for this site's agent cameras, in minutes; null = global default.
  - `heightened_exceptions` object[], nullable: Owner-curated heightened-monitoring exception categories; null = none.
  - `holidays` object[], nullable: Per-site closure calendar: on each listed site-local day every schedule cell that would read 'off' reads 'monitored' instead. Entries are {'rule': <catalog key>} or {'date': 'YYYY-MM-DD', 'label': str}. Managed through GET/PUT /sites/{id}/holidays; null on a payload built before the column existed.
  - `map_mode` string, nullable: Map-tab mode: 'geo' (geographic pin) or 'floorplan' (uploaded plan); null = not configured.
  - `map_lat` number, nullable: Geographic latitude of the site pin (geo mode).
  - `map_lng` number, nullable: Geographic longitude of the site pin (geo mode).
  - `floorplan_width` integer, nullable: Natural pixel width of the uploaded floor plan.
  - `floorplan_height` integer, nullable: Natural pixel height of the uploaded floor plan.
  - `has_floorplan` boolean: Whether a floor-plan image is uploaded (fetch it via GET /sites/{id}/floorplan-url).
  - `disarm_until` string, date-time, nullable: End of the current or most recent disarm window. NOT a null-means-armed flag: an early re-arm sets this to the re-arm instant, so it is non-null for every site that has ever been disarmed; read disarm_active for the live state. Expiry is implicit -- nothing sweeps it.
  - `disarm_set_at` string, date-time, nullable: When the current disarm window began. The resolver's predicate is the window between this and disarm_until, so a historical read is never repainted by a disarm set later.
  - `disarm_by` string, nullable: Who turned the cameras off: 'sms:<E.164>' for a customer text, 'voice:<E.164>' for a customer call, 'operator:<uuid>' from the console.
  - `disarm_active` boolean: Whether this site's cameras are off RIGHT NOW because of a text-to-disarm. Resolved server-side from the same window predicate the ingest gate uses -- do NOT recompute it from disarm_until, which stays set at the moment an early re-arm ended the window and so is non-null for every site that has ever been disarmed.
  - `arm_until` string, date-time, nullable: End of the current or most recent TEST ARM window, during which an operator held the cameras on outside the schedule. NOT a null-means-off flag: ending a test early sets this to that instant, so it is non-null for every site ever tested; read arm_active for the live state.
  - `arm_set_at` string, date-time, nullable: When the current test-arm window began. The resolver's predicate is the window between this and arm_until, so a historical read is never repainted by a test started later.
  - `arm_by` string, nullable: Who started the test: always 'operator:<uuid>'. The console is the only channel into a test arm, so unlike disarm_by this never carries a customer's phone number.
  - `arm_active` boolean: Whether this site's cameras are on FOR A TEST right now. Resolved server-side from the same window predicate the ingest gate uses -- do NOT recompute it from arm_until. A live disarm beats a live test arm, and starting a disarm ends the test outright.
  - `disarm_max_minutes` integer, nullable: The longest a single disarm may last at this site (1-120).
  - `live_view_only` boolean: Live View only: every camera at this site is connected for Live View with no AI and no alarms (monitoring state always off), whatever the schedule, disarm or test arm say.
  - `has_disarm_passcode` boolean: Whether this site has a disarm passcode. Read the code through the dedicated disarm-passcode endpoint.
  - `disarm_passcode_hint` string, nullable: Last two characters of the stored passcode, for rendering it as '****92'.
  - `disarm_passcode_revision` string, nullable: Opaque revision that changes whenever the passcode is minted or reset, including when its hint is unchanged.
  - `monitoring_now` string, nullable: Live View only (site flag or every live camera individually flagged) reads 'live_view_only' and overrides both arm and disarm windows. Otherwise: Whether this site's cameras are being monitored RIGHT NOW, resolved server-side: 'disarmed' (a live text/call/console disarm window - it beats every schedule), 'monitored' (at least one live camera resolves monitored or heightened now), 'off_schedule' (no camera is monitored now, but the schedule turns one on within 7 days - see monitoring_next_change_at), or 'unscheduled' (no monitored hours anywhere in the week). Derived, never stored. Null on payloads that embed a site incidentally (an event); ask a site endpoint. Do NOT derive this from the schedule grid client-side.
  - `monitoring_next_change_at` string, date-time, nullable: The next instant this site's monitored/off state flips BY SCHEDULE ALONE - the disarm window is deliberately ignored, because the console already has disarm_until. Null when nothing changes within 7 days and for an 'unscheduled' site. Aligned to a 15-minute boundary in the site's own timezone.
  - `contacts` SiteContactResponseModel[], required: List of site contacts
    - `first_name` string, required: The first name of the site contact.
    - `last_name` string, required: The last name of the site contact.
    - `role` string, required: The role of the site contact.
    - `primary_phone_number` string, required: The primary phone number of the site contact.
    - `afterhours_phone_number` string, required: The afterhours phone number of the site contact.
    - `email` string, required: The email of the site contact.
    - `notify_method` 'call' | 'text' | 'email', required
    - `availability` ContactAvailabilityWindow[], nullable: Weekly windows, in the site's timezone, when this contact is the one to reach. The review panel pre-selects the first contact on the list whose window covers the current site-local time. Null or empty means no schedule: the contact is eligible at any time and is the fallback when no scheduled contact is on. On edit, omitting the key keeps the stored schedule; null or [] clears it.
      - `days` integer[], required: Days the window starts on, Monday-first: 0 = Monday ... 6 = Sunday.
      - `start` string, required: Start time, HH:MM on a 24-hour clock, site-local.
      - `end` string, required: End time, HH:MM on a 24-hour clock, site-local; '24:00' means midnight. An end earlier than start wraps past midnight into the next day.
    - `id` string, uuid, required: Unique identifier of the site contact.
    - `daily_summary_opt_out` boolean: True when this contact has opted out of the daily site summary e-mail (set by the unsubscribe link, not by the console). Does not affect alerts about the property.

## Other responses

- `422`: Validation Error

## Changes

- **2026-11-02** `35a2a6ebedda`: 2 info
  - added the optional property `alarm_license_issuer` to the response with the `200` status
  - added the optional property `alarm_license_number` to the response with the `200` status

[Operation history](https://skmtc.dev/verdun/apis/verdun-api/changes/sites/:site_id/agent-config/put.md)

---

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