---
title: "Choose the voicemail script for one number (enabling is refused)"
method: PATCH
path: "/voice/bridge/voicemail"
tags: ["voice"]
---

# Choose the voicemail script for one number (enabling is refused)

`PATCH /voice/bridge/voicemail`

## Request

- Base URL: `https://corebee.ai/api/v1`
- URL: `https://corebee.ai/api/v1/voice/bridge/voicemail`
- Auth: HTTP bearer

Set the voicemail script for one of your DIDs, and/or explicitly turn its per-number opt-in off. Requires the voice:bridge scope.

EXACTLY THREE FIELDS ARE ACCEPTED: from_number, enabled, script_id. from_number is always required; send at least one of the other two or the request is a 400 that says there is nothing to change. 

⛔ THE WORDING IS NOT A STRING YOU SEND, AND SENDING ONE IS A 400. `message`, `script_body`, `scriptBody`, `voicemail_message`, `body`, `text` and `content` are all refused by name with an explanation, and any other unrecognised key is refused too, never a 200 that quietly discards it. The reason is not stylistic: the message is a VERSION-PINNED script chosen by id, because every send has to record which exact words were spoken and for how long that wording was in force. A free-text column makes that unanswerable the first time somebody edits a row, and the retention obligation it exists to satisfy becomes decorative. If you came here from "the content is settable via the API", this is the sentence that redirects you: GET this endpoint, read available_script_ids, and send one of them as script_id. 

Fields this endpoint RETURNS but does not read (voicemail_enabled, connection_status, enabling_blocked, enabling_blocked_reason, runtime_flag, available_script_ids) are ignored if you send them back, so a GET-edit-PATCH round trip works. 

The standing owner decision recorded in source is that the consumer never hears synthetic audio, so PATCH with "enabled": true answers 409 for EVERY organization, EVERY number and EVERY key. Nothing you can send makes it a 200; reversing it is a code change, not configuration. The 409 is decided BEFORE any database work and before the ownership check, because it is a property of the product decision rather than of your row, which also means an enable attempt cannot be used to probe which DIDs exist. "enabled": false is ACCEPTED and written: turning it off is never the dangerous direction, so an idempotent configuration script does not have to special-case this endpoint. 

THIS FEATURE IS GATED OFF AND SETTING THE FLAG DOES NOT TURN IT ON. Four independent conditions must all hold before any voicemail is left, and they are AND-ed: (1) the source constant OWNER_DECISION_CONSUMER_MAY_HEAR_SYNTHETIC_AUDIO in lib/voice/voicemail/gate.ts, which is false today and is a code change to alter; (2) the runtime flag ASKOTTO_CONSUMER_VOICEMAIL, which ships unset; (3) a per-DID opt-in, which is the consumer_voicemail_enabled column this endpoint reads and writes and which is false or NULL on every row; (4) an admissible answering-machine-detection configuration, which the account does not currently have, the premium AMD config the bridge dials with sets greeting_duration_millis equal to total_analysis_time_millis, so the "a greeting this long is a machine" rule cannot fire before analysis stops. Condition 4 is a property of the dialing configuration, not of anything you can send here. DO NOT READ THIS ENDPOINT AS "voicemail happens once the flag is set": with the flag set and a number opted in, the first and fourth conditions still hold it closed, and no voicemail is left.

AN UNKNOWN script_id IS A 400 NAMING THE KNOWN IDS, never a silent fallback to the default, a typo must not leave one of your numbers configured to speak words you did not choose at your customer. script_id: null clears the choice. 

from_number must be an E.164 Telnyx DID owned by your organization, else 403. 

A 409 CAN ALSO MEAN THE COLUMNS ARE NOT IN THE DATABASE YET. The migration behind this feature is applied by hand, so a deploy can reach production ahead of it; the message names the migration and says NOTHING WAS WRITTEN. Retry after the operator applies it. GET, by contrast, keeps answering 200 in that state.

## Request body

- VoicemailUpdate: EXACTLY three fields are read, and any other key is a 400 that names it. from_number is always required and at least one of enabled / script_id must be present, or the request is a 400 saying there is nothing to change. ⛔ THERE IS NO MESSAGE-TEXT FIELD. message, script_body, scriptBody, voicemail_message, body, text and content are each refused by name: the wording is a version-pinned script chosen by script_id, because every send must record which exact words were spoken and for how long they were in force.
  - `from_number` string, required: E.164 DID owned by your organization. 403 if it is not.
  - `enabled` boolean: true is REFUSED with 409 for every organization and every number, see the path description. false is accepted and written, because turning it off is never the dangerous direction. A non-boolean is a 400, never coerced.
  - `script_id` 'consumer_no_answer_v1' | 'null', nullable: One of the ids from available_script_ids, or null to clear the choice. An id the registry does not hold is a 400 listing the known ids, never a silent fallback to the default.

## Response `200`

Updated

- object
  - `data` VoicemailResult, required: What the number now holds, re-read from the row the write returned. A 200 here means the script choice was STORED; it does not mean a voicemail will ever be left.
    - `from_number` string
    - `voicemail_enabled` boolean: The per-DID opt-in as stored after this write. NULL and false are both reported false.
    - `script_id` 'consumer_no_answer_v1' | 'null', nullable
    - `enabling_blocked` boolean: Repeated on the write response, not only on the read, so a partner who only ever PATCHes still sees that the feature is held closed.
    - `enabling_blocked_reason` string: The same paragraph GET returns, naming both blockers.

## Other responses

- `400`: Validation error
- `401`: Invalid or missing credential
- `403`: Forbidden (missing scope, or resource not owned by the credential org)
- `409`: Conflict (code conflict), one of two causes. EITHER the request tried to enable consumer voicemail, which crosses a standing owner decision and is refused for every organization, do NOT retry, nothing about your account changes it. OR the columns behind the feature are not in this database yet (the migration is applied by hand); nothing was written and the request succeeds once the operator applies it. The message distinguishes them.
- `429`: Rate limit exceeded (code rate_limit_exceeded). The ceiling is 100 requests per minute per API key. The response carries Retry-After (seconds) plus X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset, and repeats the seconds in the body as retry_after.
- `500`: Error

---

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