---
title: "Update Site Holidays"
method: PUT
path: "/sites/{site_id}/holidays"
tags: ["sites"]
---

# Update Site Holidays

`PUT /sites/{site_id}/holidays`

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

## Path parameters

- `site_id` string, required

## Request body

- UpdateSiteHolidaysRequest: The site's closure calendar (PUT /sites/{id}/holidays). On a listed SITE-LOCAL calendar day every schedule cell that would read "off" reads "monitored" instead: the premises are closed, so nobody legitimate is there. A holiday only ever ADDS monitoring — it never downgrades a heightened window, and a live text-to-disarm still beats it. An ABSOLUTE WRITE: the stored list is replaced wholesale, mirroring `UpdateHeightenedExceptionsRequest`. An empty list clears the calendar. EACH ENTRY IS EXACTLY ONE OF THE TWO SHAPES. `extra="forbid"` on both members is what enforces that: an entry carrying both `rule` and `date`, or any unknown key, fails BOTH arms of the union rather than being silently coerced into one of them.
  - `holidays` union[], required: Full replacement closure list (at most 40 entries; no duplicate rule, no duplicate date).
    - union
      - HolidayRuleEntry: A recurring named closure, by catalog key.
        - `rule` string, required: Catalog key, from GET /sites/{id}/holidays -> catalog[].rule.
      - HolidayDateEntry: A one-off closure on an exact site-local calendar date.
        - `date` string, date, required: 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.
        - `label` string, required: 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.

## Response `200`

Successful Response

- SiteHolidaysResponse: GET and PUT /sites/{id}/holidays both return this. On a listed site-local calendar day every schedule cell that would read "off" reads "monitored" instead, and `monitoring_source.source` reports "holiday" for the cameras that actually flipped.
  - `holidays` object[], required: The stored list, verbatim and in stored order. Each entry is either {'rule': <catalog key>} or {'date': 'YYYY-MM-DD', 'label': str}.
  - `catalog` HolidayCatalogEntry[], required: Every recurring holiday on offer, in render order, independent of what this site has configured.
    - `rule` string, required: Catalog key. Stable forever — a client stores this, never the label.
    - `label` string, required: Display name for this rule.
    - `next_date` string, required: 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.
    - `actual_date` string, required: 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.
  - `upcoming` UpcomingHolidayEntry[], required: This site's configured closures resolved over the next 366 site-local days, ascending, one row per date.
    - `date` string, required: The observed site-local closure date, 'YYYY-MM-DD'.
    - `label` string, required: Catalog label for a rule entry, or the operator's own label for a one-off.
    - `rule` string, nullable: Catalog key when this row came from a recurring rule; null for a one-off date.
    - `actual_date` string, required: The unshifted calendar date. Equal to `date` for one-offs and for anything that did not shift.

## 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/35a2a6ebedda?raw)
