---
title: "Create Speaker"
method: POST
path: "/sites/{site_id}/speakers"
tags: ["speakers"]
---

# Create Speaker

`POST /sites/{site_id}/speakers`

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

## Path parameters

- `site_id` string, required

## Request body

- CreateSpeakerRequest: Register a network speaker at a site. The password is write-only: it goes straight to Secrets Manager and is never stored in a column or returned by any endpoint.
  - `label` string, required: Display name for this speaker.
  - `brand` 'axis'
  - `model` 'axis_c1310_e' | 'axis_c1210_e' | 'axis_c1310_e_mk2'
  - `host` string, required: 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.
  - `port` string: Device web port: the HTTPS port for direct_https (typically 443), the HTTP port for direct_http (typically 80).
  - `username` string, required: Device account username.
  - `password` string, required: Device account password. Stored in Secrets Manager only, never returned.
  - `transport` 'direct_https' | 'direct_http' | 'nvr_proxy'
  - `clip_id` string, nullable: Id of the pre-loaded deterrent clip on the device. Without it the speaker cannot announce.
  - `clip_params` object, nullable: Playback parameters (volume, repeat). Only known VAPIX keys are sent to the device.
  - `enabled` boolean: Kill switch: a disabled speaker is skipped by delivery and by the health poll.
  - `sip_enabled` boolean: 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.
  - `sip_account_id` string, nullable: Which of the device's SIP accounts to place the call from. Omit for the device default.
  - `mic_enabled` boolean: 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.
  - `map_x` number, nullable: Normalized floor-plan x (0 = left edge, 1 = right edge).
  - `map_y` number, nullable: Normalized floor-plan y (0 = top edge, 1 = bottom edge).
  - `map_lat` number, nullable: Geographic latitude of the speaker pin.
  - `map_lng` number, nullable: Geographic longitude of the speaker pin.
  - `map_location_label` string, nullable: Operator-facing location prose.

## Response `200`

Successful Response

- SpeakerResponse: A speaker record. Deliberately carries NO credential material: not the password (which never leaves Secrets Manager) and not the credentials_arn (an internal handle nothing client-side has a use for) — the same shape as NvrResponse.
  - `id` string, uuid, required: The speaker record ID.
  - `site_id` string, uuid, required: The site this speaker belongs to.
  - `parent_id` string, uuid, required: The client this speaker belongs to.
  - `label` string, required: Display name.
  - `brand` 'axis', required
  - `model` 'axis_c1310_e' | 'axis_c1210_e' | 'axis_c1310_e_mk2'
  - `host` string, required: IP address of the speaker (IPv4 or IPv6 literal), in canonical form. Hostnames are not supported in v1.
  - `port` string, required: HTTPS port.
  - `username` string, required: Device account username (the password is never returned).
  - `transport` 'direct_https' | 'direct_http' | 'nvr_proxy', required
  - `clip_id` string, nullable: Id of the pre-loaded deterrent clip.
  - `clip_params` object, nullable: Playback parameters.
  - `enabled` boolean, required: Whether delivery and the health poll consider this speaker.
  - `sip_enabled` boolean: Whether the AI voice agent may place a SIP call to this speaker.
  - `sip_account_id` string, nullable: Which of the device's SIP accounts the agent's call is placed from; null means the device default.
  - `mic_enabled` boolean: Whether this unit's microphone is live, which decides whether the agent waits for a reply.
  - `onboarding_checklist` object, nullable: 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`.
  - `agent_armed_camera_count` integer, nullable: 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.
  - `last_sip_test` LastSipTest: The headline of the newest SIP test on a speaker. `passed` is the two-signal check — the device reported the call Active AND the voice platform reported the same call live. The full timeline lives on `GET /speakers/{id}/sip-test`; this is what a list row needs to say whether a horn is armed.
    - `at` string, date-time, nullable: When the test was placed.
    - `passed` boolean, required: True only when both signals agreed.
    - `call_id` string, uuid, required: The call record, for the detail view.
    - `summary` string, required: One sentence a person can act on.
  - `map_x` number, nullable: Normalized floor-plan x.
  - `map_y` number, nullable: Normalized floor-plan y.
  - `map_lat` number, nullable: Geographic latitude of the speaker pin.
  - `map_lng` number, nullable: Geographic longitude of the speaker pin.
  - `map_location_label` string, nullable: Operator-facing location prose.
  - `map_heading` number, nullable: Direction a horn faces, degrees clockwise from map-up; null = not aimed (always null for an omnidirectional ceiling unit).
  - `healthy` boolean, nullable: Last health-poll result.
  - `last_healthcheck_at` string, date-time, nullable: When the speaker was last polled.
  - `health_error` string, nullable: 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.
  - `device_serial` string, nullable: 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.
  - `device_product` string, nullable: Product name of the device last seen at this host:port (ProdFullName, else ProdNbr). Last known value.
  - `device_firmware` string, nullable: Firmware version of the device last seen at this host:port. Last known value.
  - `device_identity_at` string, date-time, nullable: When the device identity above was last read successfully.
  - `created_at` string, date-time, nullable: When the speaker was registered.
  - `updated_at` string, date-time, nullable: When the speaker was last edited.

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