---
title: "Post Camera Health Sample"
method: POST
path: "/cameras/{camera_id}/health-samples"
tags: ["cameras"]
---

# Post Camera Health Sample

`POST /cameras/{camera_id}/health-samples`

## 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}/health-samples`
- Auth: API key in header `X-API-Key`

## Path parameters

- `camera_id` string, required

## Request body

- PostCameraHealthSampleRequest: One hourly camera-health sample from the ingest worker. Every image field is optional because the sample is still posted when no frame could be extracted (snapshot_key=None, image metrics null) — the stream stats and the reconnect count remain useful precisely when the video does not. No extra="forbid": nothing in this repo forbids extras, and doing so here would 422 an ingestor deployed ahead of a backend rollback.
  - `captured_at` string, date-time, required: 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.
  - `snapshot_key` string, nullable: 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.
  - `sharpness` number, nullable: Normalized Laplacian variance, 0..1; higher is sharper. Null when no frame.
  - `luminance` number, nullable: Mean grayscale luminance, 0..1. Null when no frame.
  - `frame_hash` string, nullable: 64-bit dHash of the frame as 16 lowercase hex chars. Equality with the previous sample's hash is what advances identical_run.
  - `occlusion_pct` number, nullable: 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.
  - `shift_pct` number, nullable: 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.
  - `width` integer, nullable: Probed stream width in pixels.
  - `height` integer, nullable: Probed stream height in pixels.
  - `fps` number, nullable: Probed average frame rate.
  - `bitrate_kbps` integer, nullable: Ingest bitrate of the sampled segment, kbps.
  - `reconnects` integer: 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.
  - `on_demand` boolean: 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.
  - `worker_id` string, nullable: Identifier of the ingest worker that produced the sample (ECS task metadata URI); truncated to 512 client-side.
  - `image_version` string, nullable: Container image version of the ingest worker; truncated to 512 client-side.

## Response `200`

Successful Response

- PostCameraHealthSampleResponse: Acknowledgement of one stored sample. Carries no derived state: the consecutive-identical-frame count is computed from the series on the health read, not maintained at ingest. The ingest worker only checks that a body came back (`poster(...) is None`), so this stays minimal on purpose.
  - `id` string, uuid, required: The camera_health_samples row id (existing row's id on an upsert).
  - `camera_id` string, uuid, required: The camera the sample belongs to.
  - `captured_at` string, date-time, required: Worker clock at frame capture, echoed back.

## 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)
