---
title: "Run Speaker Sip Test"
method: POST
path: "/speakers/{speaker_id}/sip-test"
tags: ["speakers"]
---

# Run Speaker Sip Test

`POST /speakers/{speaker_id}/sip-test`

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

Place a REAL AI call on this speaker to prove the SIP path works.

SITE-ADMIN ONLY, like the rest of this router and for the same reason: it
hands out real-world sound at a customer site. During an install it is often
an ESCALATE_VOICE_DOWN operator on the phone with the technician — widening
it to them was considered and declined (round-0 review, OQ-5), and that
refusal is unchanged: the 2026-09-15 widening admitted the monitoring
manager, who runs the floor, not the operator on the call.

IT ANNOUNCES NOTHING AND CANNOT. The call row has no reservation, so the
only writer of `announced_at` has nothing to write on; the provider webhook
drops the call as not-a-talk-down; and the test agent is published with no
custom functions, so it cannot escalate, dismiss, or record an outcome.

503 until `RETELL_SIPTEST_AGENT_ID` is set (or when it equals the talk-down
agent's id). 409 for a speaker tested inside the last minute, for a site
that has used its daily allowance, and for a horn somebody else is already
talking on. **A DEVICE REFUSAL IS A 200, NOT A 502**: the device's own error
body ("Invalid SIPAccountId 'Gate horn'") is the answer the owner pressed the
button for, so it comes back in `device_error` with `passed: false` rather
than as an error carrying nothing. A 502 here means the device could not be
reached at all.

**A 200 MEANS THE HORN IS ON A CALL RIGHT NOW**, not that the test is over:
the route returns at connect so the owner hears it, and the watchdog hangs
it up at the cap. Poll the GET below to watch it end.

## Path parameters

- `speaker_id` string, required

## Response `200`

Successful Response

- SpeakerSipTestResponse: The result of one SIP test call. A 200 MEANS "THE HORN IS ON A CALL", NOT "THE TEST IS OVER": the request returns at connect and deliberately leaves the call up so the owner hears it. Poll `GET /speakers/{id}/sip-test` to watch it end. A `DELETE` on the same path hangs the call up early — the owner's release for a horn that is stuck, and the only one a test row has (the event-scoped hang-up cannot address it). `passed` is the two-signal check, minus the reservation: the device reported the call `Active` AND the voice platform reported the same call live. Either alone proves half of it — the platform connecting proves an agent is speaking into a call, the device being Active proves the horn is on a call, and it takes both to prove the agent is speaking OUT OF the horn.
  - `call_id` string, uuid, required: The call record for this test.
  - `speaker_id` string, uuid, nullable: The speaker that was tested.
  - `speaker_label` string, required: The speaker's display name.
  - `status` string, required: placing | connected | ended | failed.
  - `passed` boolean, required: True only when the device and the voice platform both reported this call live.
  - `summary` string, required: One sentence a person can act on.
  - `device_call_id` string, nullable: The device's own CallId, once it answered the dial.
  - `device_states` SipTestDeviceState[]: Every state the device reported, in order.
    - `state` string, required: The state the device reported, verbatim.
    - `at` string, required: When we observed it, ISO-8601 UTC.
    - `reason` string, nullable: What the device said about the transition, when it said anything.
  - `provider_call_id` string, nullable: The voice platform's call id.
  - `provider_status` string, nullable: The platform's own call status, verbatim.
  - `provider_disconnection_reason` string, nullable: Why the platform says the call ended.
  - `duration_ms` integer, nullable: The platform's measured call duration.
  - `provider_unreadable` boolean: True when the voice platform could not be read at all. NOT the same as `passed: false` with a status: it means we do not know what the platform saw, which is a different thing to do about it than a call the platform never received.
  - `signals_agreed_at` string, date-time, nullable: When the device and the platform first agreed the call was live. A HIGH-WATER MARK: once set it stays, because a call that has since ended still passed.
  - `auth_mode` string, nullable: Which auth mode the device accepted.
  - `playback_note` string, nullable: Set when the device could not confirm it had finished a clip and the owner went ahead anyway.
  - `device_error` SipTestDeviceError: The device's own refusal, quoted. THE SINGLE MOST USEFUL SENTENCE IN AN INSTALL. AXIS answers a refused `axcall:Call` with an HTTP 400 whose body names the fault — `Invalid SIPAccountId 'Gate horn'` is literally the mistake that cost a full day on site — and until this shipped the body was dropped and the console showed nothing but a failure. Bounded at 200 characters; headers are never quoted, and the body cannot carry device credentials (they exist only in the auth header).
    - `http_status` integer, nullable: The HTTP status the device answered with.
    - `body` string, nullable: A bounded excerpt of the device's error body.
  - `sip_account_id` string, nullable: Which SIP account the call was placed from; null means the device default.
  - `started_at` string, date-time, nullable: When the call was placed.
  - `ended_at` string, date-time, nullable: When the call ended, if it has.
  - `end_reason` string, nullable: Why the call ended.
  - `max_seconds` integer, required: The test call's duration cap — the watchdog hangs it up at this.
  - `result_version` integer: Shape version of the stored result this was rendered from. 0 means a row written before the stamp existed.

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