For AI agents: markdown of this page — /docs-content-en/ai/audio.md documentation index — /llms.txt
Speech recognition
Convert audio to text via Whisper Large v3 Turbo on Bitrix24 infrastructure — no BYOK required. Transcription counts against your Bitrix24 account's AI quota by audio duration: within your plan's quota nothing is charged to the balance. Beyond the quota, usage is charged to the Bitrix24 account's money balance at the model's base price.
Scope: vibe:ai
Transcribe audio
POST /v1/audio/transcriptions
Converts an audio file to text via Whisper Large v3 Turbo — no BYOK required. The transcription counts toward the Bitrix24 account's AI quota by audio duration: within the plan's quota nothing is charged to the balance, above the quota the usage is charged to the account's money balance at the model's base price. Accepts the file via multipart/form-data. The request and response format is compatible with POST /v1/audio/transcriptions from the OpenAI API.
The response comes in raw OpenAI format.
There is no
{success, data}wrapper used by the other Vibecode endpoints —/v1/deals,/v1/tasksand others.This is done for compatibility with the OpenAI SDK. If you have a single client that checks
if (!response.success), add an exception for the AI Router.
Request fields (form-data)
| Field | Type | Req. | Default | Description |
|---|---|---|---|---|
file |
file | yes | — | Audio file. Supported: mp3, mp4, mpeg, mpga, m4a, wav, webm, flac, ogg. Maximum size — 25 MB |
model |
string | no | deepdml/faster-whisper-large-v3-turbo-ct2 |
Whisper model ID. The bitrix/ prefix is optional and is stripped automatically |
language |
string | no | auto-detection | Language code per ISO 639 (2-3 letters): ru, en, de, fr, zh, etc. Specifying the language speeds up recognition |
prompt |
string | no | — | Context hint: the conversation topic, style, correct spelling of terms. Up to 2000 characters, the model uses the last ~224 tokens |
hotwords |
string | no | — | Special words and terms separated by commas — improve recognition accuracy for rare names and brands. Up to 500 characters |
temperature |
number | no | 0 |
Decoder temperature from 0 to 1. 0 — deterministic result, higher — more variability. Out-of-range values are rejected |
vad_filter |
boolean | no | — | true enables the VAD filter: the model cuts out silence before recognition — fewer hallucinations on recordings with pauses |
timestamp_granularities[] |
string | no | segment |
Timestamp granularity: word or segment (the field can be repeated). Only with response_format=verbose_json. With word, each segment is augmented with a words array carrying the timing and probability of each word |
response_format |
string | no | json |
Result format: json, text, srt, vtt, verbose_json |
Examples
curl — personal key
curl -X POST https://vibecode.bitrix24.com/v1/audio/transcriptions \
-H "X-Api-Key: YOUR_API_KEY" \
-F "file=@call-recording.mp3" \
-F "language=en" \
-F "response_format=json"
curl — OAuth application
curl -X POST https://vibecode.bitrix24.com/v1/audio/transcriptions \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
-F "file=@call-recording.mp3" \
-F "language=en" \
-F "response_format=json"
JavaScript — personal key
const formData = new FormData()
formData.append('file', audioFile) // File or Blob object
formData.append('language', 'en')
formData.append('response_format', 'json')
const res = await fetch('https://vibecode.bitrix24.com/v1/audio/transcriptions', {
method: 'POST',
headers: { 'X-Api-Key': 'YOUR_API_KEY' },
body: formData,
})
const result = await res.json()
console.log('Recognized text:', result.text)
JavaScript — OAuth application
const formData = new FormData()
formData.append('file', audioFile)
formData.append('language', 'en')
const res = await fetch('https://vibecode.bitrix24.com/v1/audio/transcriptions', {
method: 'POST',
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
body: formData,
})
const result = await res.json()
console.log('Text:', result.text)
Response fields
The response structure depends on response_format. The json format (default) is the most compact, verbose_json — with timings.
`response_format: json`
| Field | Type | Description |
|---|---|---|
text |
string | The full recognized text |
`response_format: text`
The response is a plain string with the recognized text, without a JSON wrapper.
`response_format: verbose_json`
| Field | Type | Description |
|---|---|---|
task |
string | Task type: transcribe |
language |
string | Code of the detected or specified language |
duration |
number | Audio duration in seconds |
text |
string | The full recognized text |
usage |
object | null | Model usage metadata. null if the model does not return it |
words |
array | Word-level timings at the whole-response level. Empty array if word-level timings were not requested |
segments |
array | Segments with timings and recognition metadata |
segments[].id |
number | Segment ordinal number |
segments[].seek |
number | Whisper's internal recognition-window offset |
segments[].start |
number | Segment start in seconds |
segments[].end |
number | Segment end in seconds |
segments[].text |
string | Segment text |
segments[].tokens |
array | Model tokens for the segment text (integers) |
segments[].temperature |
number | Decoding temperature applied by the model |
segments[].avg_logprob |
number | Average log probability of the segment tokens — a confidence metric |
segments[].compression_ratio |
number | Compression ratio of the segment text |
segments[].no_speech_prob |
number | Probability that the segment contains no speech |
segments[].words |
array | Word-level timings inside the segment. Empty array if not requested |
segments[].emotion |
string | null | Detected emotion of the segment. null if not detected |
`response_format: srt` / `vtt`
The response is subtitles in SubRip Text or WebVTT format.
Response example
response_format: json:
{
"text": "Hello, this is Acme LLC. We want a CRM for 50 users, budget up to 500 thousand per month."
}
response_format: verbose_json:
{
"task": "transcribe",
"language": "en",
"duration": 8.42,
"text": "Hello, this is Acme LLC. We want a CRM for 50 users, budget up to 500 thousand per month.",
"usage": null,
"words": [],
"segments": [
{
"id": 0,
"seek": 0,
"start": 0.0,
"end": 3.2,
"text": "Hello, this is Acme LLC.",
"tokens": [50365, 2425, 11, 341, 307],
"temperature": 0.0,
"avg_logprob": -0.38,
"compression_ratio": 1.12,
"no_speech_prob": 0.02,
"words": [],
"emotion": null
},
{
"id": 1,
"seek": 320,
"start": 3.2,
"end": 8.42,
"text": "We want a CRM for 50 users, budget up to 500 thousand per month.",
"tokens": [50414, 1003, 13767, 295, 1500],
"temperature": 0.0,
"avg_logprob": -0.41,
"compression_ratio": 1.20,
"no_speech_prob": 0.01,
"words": [],
"emotion": null
}
]
}
Error response example
400 no_file — the file field was not provided:
{
"error": {
"message": "Audio file is required. Send as multipart/form-data with field \"file\".",
"type": "invalid_request_error",
"code": "no_file"
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 400 | no_file |
The file field was not provided in multipart/form-data |
| 400 | empty_file |
The file field was provided, but the file is empty (0 bytes). Common cause: curl -F "file=path" without the @ prefix — curl sends the path string instead of the file contents |
| 400 | invalid_prompt |
The prompt field is longer than 2000 characters |
| 400 | invalid_hotwords |
The hotwords field is longer than 500 characters |
| 400 | invalid_temperature |
The temperature field is not a number or is outside the 0..1 range |
| 400 | invalid_vad_filter |
The vad_filter field is neither true nor false |
| 400 | invalid_timestamp_granularities |
The value is not word/segment, or the response format is not verbose_json |
| 400 | invalid_language |
The language code does not match the ISO 639 format (2-3 letters) |
| 402 | ai_credentials_not_configured |
The Bitrix provider is not configured on the platform |
| 402 | insufficient_balance |
The PREPAY account is past its overdraft — checked BEFORE the recognition call (PLATFORM/PORTAL keys only, BYOK is free and not checked). A fully frozen account is rejected earlier with ACCOUNT_FROZEN |
| 402 | ai_quota_exhausted |
The portal's monthly AI quota is exhausted — see "Known specifics" below |
| 403 | scope_missing |
The API key is missing the vibe:ai scope |
| 502 | ai_provider_unavailable |
The Whisper service returned an error or is unavailable |
| 503 | ai_provider_timeout |
Whisper did not respond within 15 minutes — the file is too long or the service is overloaded. For recordings longer than ~30 minutes, split them into parts |
| 401 | MISSING_API_KEY |
The X-Api-Key header was not provided |
| 429 | ai_congested |
The AI cluster pool is overloaded. The request was not executed, nothing was charged, retry it per the Retry-After header. The response carries the X-AI-Admission: shed header, not X-RateLimit-Scope |
| 429 | ai_pacing_limited |
A daily or weekly pacing window is exceeded. This is not quota exhaustion — repeat the request after the time in the Retry-After header. See Pacing (day/week smoothing) |
The full list of common API errors — Errors.
Known specifics
Hints improve accuracy on rare terms. The prompt field sets the context — the conversation topic, style, correct spelling of terms. The hotwords field lists comma-separated special words the decoder gives higher priority to. The difference on the phrase "discussing the NeuralDeep integration with Kimi and the RAG approach":
| Request | Result |
|---|---|
| Without hints | "…the neural-dip integration with Key Me and the rug approach for search…" |
With hotwords |
"…the NeuralDeep integration with Kimi and the RAG approach for search…" |
Within the quota — no charges, above the quota — the model's base price. Whisper runs on Bitrix24 infrastructure. Transcription is priced by audio duration and counts toward the Bitrix24 account's monthly AI quota. As long as usage stays within the plan's quota, nothing is charged to the account balance. Above the quota, usage is charged to the account's money balance at the model's base catalog price. On accounts with quota control enabled, a request may return 402 ai_quota_exhausted when the monthly limit is exhausted. The reason field distinguishes three cases: breaker — the hourly over-quota spending breaker fired, wallet_empty — the quota is exhausted and the account balance has no funds, wallet_off — over-quota spend is not available for the account. The resetAt field is the moment when requests will start passing again; for wallet_off it may be absent. In the wallet_empty case the response may additionally carry a hint string and a topupUrl link to top up the balance — both fields appear when enforced quota control and the top-up hint are enabled on the platform, so read them as optional. The hint field in this response is a string.
Size limit — 25 MB. For long recordings, split the file into parts up to 25 MB and stitch the results on the client side. One hour of mp3 at 128 kbit/s is about 60 MB — you will have to cut it.
Request timeout — 15 minutes. Transcription takes roughly 5-15% of the audio length. A 5-minute file (5-7 MB mp3) transcribes in 15-45 seconds. On a timeout, 503 ai_provider_timeout is returned.