# Как повысить точность (/accuracy) Несколько простых способов повысить качество транскрибации от Nexara API. ## Качество аудио [#качество-аудио] Чем чище исходный звук, тем точнее результат. Записывайте на качественный микрофон, ближе к говорящему, в тихом помещении. Отключайте нейросетевое шумоподавление и «улучшение» голоса на устройстве. Такая обработка искажает речь и **ухудшает** распознавание. Модели нужен естественный, необработанный звук. Нейросетевые фильтры шума и «умное» улучшение звука вредят точности сильнее, чем сам шум. Отправляйте аудио как есть, без предварительной обработки. ## Указывайте число говорящих при диаризации [#указывайте-число-говорящих-при-диаризации] Если вы заранее знаете, сколько человек в записи, передайте `num_speakers` при диаризации (`task=diarize`). Это избавляет модель от необходимости угадывать количество говорящих и делает разделение точнее. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="meeting.mp3", task="diarize", num_speakers=2, ) ``` ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "meeting.mp3", task: "diarize", num_speakers: 2, }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@meeting.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F "num_speakers=2" ``` Указывайте `num_speakers`, только если уверены в числе говорящих. Если не уверены — не передавайте параметр, и модель определит их количество автоматически. ## Передавайте `diarization_setting` [#передавайте-diarization_setting] `diarization_setting` — это настройка для модели диаризации, у нее есть два пресета — `general` и `telephonic`. `telephonic` лучше подходит для телефонных звонков, и в случаях, когда спикеры часто перебивают друг друга. Для всех остальных случаев `general` показывает более высокую точность. По умолчанию стоит `general`. # Асинхронное распознавание (/async-transcription) Асинхронный режим подходит для длинных записей и пакетной обработки: запрос не держит HTTP-соединение открытым всё время обработки. Вместо этого он сразу возвращает `job_id`, а готовый результат вы забираете отдельным запросом. Подробнее о разнице режимов — на странице [синхронная и асинхронная обработка](/sync-async). ## Шаг 1. Поставить задачу [#шаг-1-поставить-задачу] Отправьте аудио на `POST /v1/audio/transcriptions/async`. Запрос принимает те же параметры, что и синхронный (можно передать локальный `file` или ссылку `url`), но возвращается сразу, не дожидаясь обработки. В официальной библиотеке [`nexara`](/python-sdk) задачу ставит `create_job()` — он возвращает объект `Job` сразу, не дожидаясь обработки. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY job = client.transcriptions.create_job(file="long-audio.mp3") print(job.job_id, job.status) # 5f1c2e3a-... in_progress ``` В официальной библиотеке [`nexara-sdk`](/typescript-sdk) задачу ставит `createJob()` — он возвращает объект `Job` сразу, не дожидаясь обработки. ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const job = await client.transcriptions.createJob({ file: "long-audio.mp3" }); console.log(job.job_id, job.status); // 5f1c2e3a-... in_progress ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions/async \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@long-audio.mp3" \ -F "model=whisper-1" ``` ```python import requests with open("long-audio.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions/async", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={"model": "whisper-1"}, ) job_id = response.json()["job_id"] print(job_id) ``` Node.js 18+: ```javascript import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("long-audio.mp3")]), "long-audio.mp3"); form.append("model", "whisper-1"); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions/async", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const { job_id } = await response.json(); console.log(job_id); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("long-audio.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "long-audio.mp3") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions/async", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions/async") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("long-audio.mp3")], ["model", "whisper-1"], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)["job_id"] ``` В ответ приходит идентификатор задачи и её статус: ```json { "job_id": "5f1c2e3a-...", "status": "in_progress", "created_at": "2026-07-13T10:00:00+00:00" } ``` Сохраните `job_id` — по нему вы будете забирать результат. ## Шаг 2. Опросить статус [#шаг-2-опросить-статус] Периодически запрашивайте `GET /v1/audio/transcriptions/async/{job_id}`, пока задача не завершится. Обычно опрос не нужен: `job.wait()` сделает его за вас (см. [полный пример](#полный-пример)). Отдельный запрос статуса — в том числе из другого процесса — выглядит так: ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY job = client.transcriptions.retrieve_job("5f1c2e3a-...") print(job.status) # in_progress / complete / error print(job.result) # None, пока задача не завершена ``` Обычно опрос не нужен: `job.wait()` сделает его за вас (см. [полный пример](#полный-пример)). Отдельный запрос статуса — в том числе из другого процесса — выглядит так: ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const job = await client.transcriptions.retrieveJob("5f1c2e3a-..."); console.log(job.status); // in_progress / complete / error console.log(job.result); // null, пока задача не завершена ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions/async/5f1c2e3a-... \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" ``` ```python import requests job_id = "5f1c2e3a-..." response = requests.get( f"https://api.nexara.ru/v1/audio/transcriptions/async/{job_id}", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, ) print(response.json()) ``` ```javascript const jobId = "5f1c2e3a-..."; const response = await fetch( `https://api.nexara.ru/v1/audio/transcriptions/async/${jobId}`, { headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" } }, ); console.log(await response.json()); ``` ```go package main import ( "fmt" "io" "net/http" ) func main() { jobID := "5f1c2e3a-..." req, _ := http.NewRequest("GET", "https://api.nexara.ru/v1/audio/transcriptions/async/"+jobID, nil) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" job_id = "5f1c2e3a-..." uri = URI("https://api.nexara.ru/v1/audio/transcriptions/async/#{job_id}") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts response.body ``` Пока задача выполняется, поле `result` пустое: ```json { "job_id": "5f1c2e3a-...", "status": "in_progress", "created_at": "2026-07-13T10:00:00+00:00", "completed_at": null, "result": null, "error": null } ``` После завершения в поле `result` появляется готовая расшифровка: ```json { "job_id": "5f1c2e3a-...", "status": "complete", "created_at": "2026-07-13T10:00:00+00:00", "completed_at": "2026-07-13T10:01:12+00:00", "result": { "text": "Привет! Это пример транскрибации." }, "error": null } ``` Поле `result` может содержать любой из [форматов ответа](/response-formats) — в зависимости от параметра `response_format`, переданного при постановке задачи. ### Статусы задачи [#статусы-задачи] * `in_progress`: задача поставлена в очередь или обрабатывается. Поле `result` пустое; * `complete`: обработка завершена успешно. Результат находится в поле `result`, а время завершения — в `completed_at`; * `error`: обработка завершилась ошибкой. Описание причины находится в поле `error`. ## Полный пример [#полный-пример] Постановка задачи и опрос до готового результата в одном скрипте: `job.wait()` сам опрашивает статус с разумным интервалом и возвращает готовый результат. Если задача завершилась ошибкой, он бросает `JobFailedError` — такая задача не тарифицируется, и её можно бесплатно отправить заново. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY job = client.transcriptions.create_job(file="long-audio.mp3") result = job.wait() # таймаут по умолчанию — 1800 секунд print(result.text) ``` `job.wait()` сам опрашивает статус с разумным интервалом и возвращает готовый результат. Если задача завершилась ошибкой, он бросает `JobFailedError` — такая задача не тарифицируется, и её можно бесплатно отправить заново. ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const job = await client.transcriptions.createJob({ file: "long-audio.mp3" }); const result = await job.wait(); // таймаут по умолчанию — 1_800_000 мс console.log((result as { text: string }).text); ``` ```python import time import requests headers = {"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"} # 1. Поставить задачу with open("long-audio.mp3", "rb") as audio_file: job = requests.post( "https://api.nexara.ru/v1/audio/transcriptions/async", headers=headers, files={"file": audio_file}, data={"model": "whisper-1"}, ).json() job_id = job["job_id"] # 2. Опрашивать статус, пока задача не завершится while True: status = requests.get( f"https://api.nexara.ru/v1/audio/transcriptions/async/{job_id}", headers=headers, ).json() if status["status"] == "complete": print(status["result"]["text"]) break if status["status"] == "error": raise RuntimeError(status["error"]) time.sleep(3) ``` ## Что учитывать [#что-учитывать] * **Хранение результата.** После завершения результат хранится на сервере **12 часов**, а затем удаляется безвозвратно. * **Интервал опроса.** Опрашивайте статус с разумным интервалом (например, раз в несколько секунд), чтобы вы не уперлись в лимит частоты запросов. * **Одновременные задачи.** На один API-ключ может выполняться не более **200** задач одновременно. Дождитесь завершения текущих, прежде чем ставить новые. Подробнее — на странице [лимитов](/limits). * **Частота запросов.** Постановка задач и опрос статуса ограничены **10 запросами в секунду**. Подробнее — на странице [лимитов](/limits). * **Форматы и размер.** Как и в синхронном режиме, принимается большинство [аудио- и видеоформатов](/supported-files), а максимальный размер файла — **3 ГБ**. * **Хранение ключа.** Не вставляйте API-ключ прямо в код. Храните его в переменной окружения (например, `export NEXARA_API_KEY="nx-..."`) и ссылайтесь на неё. Подробнее — на странице [аутентификации](/authentication#curl). # Аутентификация (/authentication) Все запросы к Nexara API аутентифицируются с помощью **API-ключа**, который передаётся в заголовке `Authorization` по схеме `Bearer`. ## API-ключ [#api-ключ] API-ключ выглядит так: ``` nx-XXXXXXXXXXXXXXXXXXXXXXXX ``` Ключ начинается с префикса `nx-`, за которым следуют 24 случайных символа (латинские буквы в верхнем и нижнем регистре и цифры). API-ключ подобен паролю от аккаунта. Он даёт полный доступ к вашему аккаунту и позволяет списывать средства с вашего баланса. Никогда не публикуйте ключи в клиентском коде, репозиториях или сообщениях. Храните его в переменных окружения или в защищённом хранилище секретов. Если думаете, что Ваш ключ утек в сеть, немедленно замените его в [личном кабинете](https://app.nexara.ru). ## Получение ключа [#получение-ключа] API-ключи создаются и управляются в личном кабинете — [app.nexara.ru](https://app.nexara.ru). Там же можно создавать новые ключи, задавать им имена и удалять (отзывать) ненужные. ## Использование ключа [#использование-ключа] Передавайте ключ в HTTP-заголовке `Authorization` в формате `Bearer <ваш_ключ>`: ``` Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX ``` ### cURL [#curl] ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@audio.mp3" \ -F "model=whisper-1" ``` Чтобы не подставлять ключ в каждый запрос, сохраните его в переменную окружения `NEXARA_API_KEY`, а затем ссылайтесь на неё как `$NEXARA_API_KEY`: ```bash export NEXARA_API_KEY="nx-XXXXXXXXXXXXXXXXXXXXXXXX" curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer $NEXARA_API_KEY" \ -F "file=@audio.mp3" \ -F "model=whisper-1" ``` Команда `export` действует только в текущей сессии терминала. Чтобы переменная сохранялась между сессиями, добавьте строку `export NEXARA_API_KEY="nx-..."` в файл конфигурации вашей оболочки (например, `~/.bashrc` или `~/.zshrc`). ### Python SDK [#python-sdk] Официальная библиотека [`nexara`](/python-sdk) добавляет заголовок сама. Если ключ не передан явно, он читается из переменной окружения `NEXARA_API_KEY`: ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY ``` Если ключ не найден ни в аргументе, ни в окружении, клиент сразу бросит понятную ошибку — до отправки запроса. ### TypeScript SDK [#typescript-sdk] Официальная библиотека [`nexara-sdk`](/typescript-sdk) добавляет заголовок сама. Если ключ не передан явно полем `apiKey`, он читается из переменной окружения `NEXARA_API_KEY`: ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY ``` Если ключ не найден ни в поле `apiKey`, ни в окружении, конструктор сразу бросит понятную ошибку — до отправки запроса. ### Python (OpenAI SDK) [#python-openai-sdk] Nexara API совместим с OpenAI SDK — достаточно указать `base_url` и передать API-ключ Nexara. Обратите внимание, что в таком случае `Bearer` добавлять к ключу не нужно. ```python from openai import OpenAI client = OpenAI( api_key="nx-XXXXXXXXXXXXXXXXXXXXXXXX", base_url="https://api.nexara.ru/v1", ) with open("audio.mp3", "rb") as audio_file: transcript = client.audio.transcriptions.create( model="whisper-1", file=audio_file, ) print(transcript.text) ``` Не храните ключ прямо в коде. Считывайте его из переменной окружения, например `os.environ["NEXARA_API_KEY"]`. ## Ошибки аутентификации [#ошибки-аутентификации] Если ключ отсутствует или недействителен, API возвращает статус **403 Forbidden** с описанием в поле `detail`: | Сообщение | Причина | Как исправить | | -------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | `No token specified` | Заголовок `Authorization` отсутствует, пуст или имеет неверный формат (ожидается `Bearer <ключ>`). | Добавьте заголовок `Authorization: Bearer <ваш_ключ>`. | | `Invalid token` | Ключ не существует или был отозван (удалён). | Проверьте правильность ключа или создайте новый в [личном кабинете](https://app.nexara.ru). | Пример тела ответа: ```json { "detail": "Invalid token" } ``` Заголовок обязательно должен быть в формате `Bearer <ключ>`. Значение без префикса `Bearer`, лишние пробелы или `Bearer ` без самого ключа будут восприняты как отсутствие токена и приведут к ошибке `No token specified`. ## Отзыв ключа [#отзыв-ключа] Если ключ скомпрометирован, удалите его в [личном кабинете](https://app.nexara.ru). Удалённый ключ немедленно перестаёт работать — все последующие запросы с ним будут отклонены с ошибкой `Invalid token`. При необходимости создайте новый ключ на замену. # Диаризация (/diarization) **Диаризация** — это разделение аудио по говорящим: API не только распознает речь, но и определяет, какой фрагмент речи кому принадлежит. Каждому сегменту присваивается метка говорящего (например, `speaker_0`, `speaker_1`), либо его роль в диалоге. ## Как включить [#как-включить] Передайте параметр `task=diarize` в запросе на `POST /v1/audio/transcriptions`. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="meeting.mp3", task="diarize", ) for segment in result.segments: print(f"{segment.speaker}: {segment.text}") ``` ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "meeting.mp3", task: "diarize", }); for (const segment of result.segments) { console.log(`${segment.speaker}: ${segment.text}`); } ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@meeting.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F "response_format=json" ``` Минимальная длительность аудио для диаризации — **3 секунды**. Более короткие записи выдают ошибку `400`. ## Результат [#результат] В ответе каждый сегмент содержит метку говорящего в поле `speaker`: ```json { "task": "diarize", "language": "ru", "duration": 12.4, "text": "Здравствуйте! Чем могу помочь? Хочу оформить возврат.", "segments": [ { "start": 0.0, "end": 2.1, "text": "Здравствуйте! Чем могу помочь?", "speaker": "speaker_0" }, { "start": 2.5, "end": 5.0, "text": "Хочу оформить возврат.", "speaker": "speaker_1" } ] } ``` Метки говорящих сохраняются во всех форматах вывода. Например, при `response_format=text` результат выглядит так: ``` Speaker 0: Здравствуйте! Чем могу помочь? Speaker 1: Хочу оформить возврат. ``` Диаризация также работает с форматами `srt`, `vtt` и `verbose_json`. Вы можете передать `timestamp_granularities=['word']`, чтобы получить точные временные метки слов для каждого спикера. Более подробно об этом написано на странице про [временные метки](/timestamps). ## Полезные параметры [#полезные-параметры] * `num_speakers`: если вы заранее знаете, сколько человек в записи, передайте это значение — это повысит качество диаризации. Этот параметр не гарантирует `num_speakers` спикеров в ответе. По умолчанию число определяется автоматически. * `diarization_setting`: настройка для модели диаризации - `general` или `telephonic`. `telephonic` лучше подходит для телефонных звонков, и в случаях, когда спикеры часто перебивают друг друга. Для всех остальных случаев `general` показывает более высокую точность. По умолчанию стоит `general`. ## Многоканальное аудио [#многоканальное-аудио] Если передавать `num_speakers=2`, то включается проверка похожести каналов. Если каналы разные, значит один спикер будет извлечен из одного канала, а другой — из другого. Данная настройка обеспечивает максимальное качество диаризации, но для этого нужно, чтобы телефония поддерживала режим записи звонка в два канала. ## Разметка ролей (role tagging) [#разметка-ролей-role-tagging] По умолчанию говорящие обозначаются безликими метками `speaker_0`, `speaker_1`. **Разметка ролей** заменяет их на понятные роли — например, `Клиент` и `Агент`. Разметка ролей включается параметром `roles` и доступна **только вместе с диаризацией** (`task=diarize`). Поддерживаются три режима: `roles=auto` — модель сама определяет и назначает подходящие роли на основе содержания разговора. JSON-массив строк, например `["Клиент", "Агент"]` — вы отправляете набор ролей, а модель распределяет их между говорящими. JSON-объект `{"Клиент": "Клиент, который обратился в поддержку", "agent": "Оператор поддержки"}` — фиксированные роли с описаниями, которые помогают модели точнее их распознать. Если модель определила больше говорящих, чем было передано в списке, остальные спикеры будут помечены как неизвестные. Например, если в записи говорило 4 человека, а передано только 2 человека в списке, остальные будут помечены как `unknown_1` и `unknown_2`. Пример запроса с автоматической разметкой ролей: ```python result = client.transcriptions.create( file="call.mp3", task="diarize", roles="auto", ) ``` ```typescript const result = await client.transcriptions.create({ file: "call.mp3", task: "diarize", roles: "auto", }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F "roles=auto" ``` Пример с фиксированными ролями: В [Python SDK](/python-sdk) роли передаются обычным списком или словарём — сериализация в JSON выполняется автоматически. ```python result = client.transcriptions.create( file="call.mp3", task="diarize", roles=["Клиент", "Агент"], ) ``` В [TypeScript SDK](/typescript-sdk) роли передаются обычным массивом или объектом — сериализация в JSON выполняется автоматически. ```typescript const result = await client.transcriptions.create({ file: "call.mp3", task: "diarize", roles: ["Клиент", "Агент"], }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F 'roles=["Клиент", "Агент"]' ``` В результате метки `speaker_N` заменяются на назначенные роли: ```json { "task": "diarize", "segments": [ { "start": 0.0, "end": 2.1, "text": "Здравствуйте! Чем могу помочь?", "speaker": "Агент" }, { "start": 2.5, "end": 5.0, "text": "Хочу оформить возврат.", "speaker": "Клиент" } ] } ``` ### Ограничения [#ограничения] * В одном запросе можно задать не более **10** ролей. * Каждое название роли — непустая строка длиной не более **64** символов, роли не должны повторяться. * Описание роли (в режиме с описаниями) не должно превышать **500** символов. Разметка ролей тарифицируется дополнительно к стоимости диаризации. Если разметку не удалось применить (например, в записи не оказалось говорящих), надбавка не списывается, а в ответе остаются исходные метки `speaker_N`. Стоимость услуги смотрите на странице [тарифов](/pricing). # Распознавание эмоций (/emotions) **Распознавание эмоций** оценивает, с какой эмоцией произнесён каждый фрагмент речи. К сегментам диаризации добавляется объект `emotion` с меткой (`angry`, `sad`, `neutral`, `positive`), уверенностью модели и распределением вероятностей по всем меткам. Это удобно для аналитики звонков: видно не только *что* сказал клиент, но и *как* — например, в какой момент разговора он начал злиться. Эмоцию определяет сама модель распознавания речи `nexara-ru` — отдельного запроса и отдельной обработки не требуется, всё приходит в одном ответе. ## Что нужно для работы [#что-нужно-для-работы] Эмоции считаются по сегментам речи, а сегменты появляются только при диаризации, поэтому у параметра три условия: | Условие | Значение | | ------------- | ------------------------- | | Режим | `task=diarize` | | Модель | `model=nexara-ru` | | Формат ответа | `json` или `verbose_json` | Форматы `text`, `srt` и `vtt` не подойдут: в них нет структуры, куда положить объект `emotion`. Любую другую комбинацию API отклоняет с ошибкой `400`, а [Python](/python-sdk) и [TypeScript](/typescript-sdk) SDK — ещё до отправки файла, чтобы запрос не тарифицировался впустую. ## Как включить [#как-включить] Передайте `emotions=true` в запросе на `POST /v1/audio/transcriptions`. Замените `nx-XXXXXXXXXXXXXXXXXXXXXXXX` на свой [API-ключ](/authentication), а `call.mp3` — на путь к вашему файлу. Официальная библиотека [`nexara`](/python-sdk) (`pip install nexara`): ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="call.mp3", model="nexara-ru", task="diarize", emotions=True, ) for segment in result.segments: if segment.emotion: print(f"{segment.speaker} [{segment.emotion.label}]: {segment.text}") else: print(f"{segment.speaker}: {segment.text}") ``` Официальная библиотека [`nexara-sdk`](/typescript-sdk) (`npm install nexara-sdk`): ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "call.mp3", model: "nexara-ru", task: "diarize", emotions: true, }); for (const segment of result.segments) { if (segment.emotion) { console.log(`${segment.speaker} [${segment.emotion.label}]: ${segment.text}`); } else { console.log(`${segment.speaker}: ${segment.text}`); } } ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=nexara-ru" \ -F "task=diarize" \ -F "emotions=true" ``` ```python import requests with open("call.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={ "model": "nexara-ru", "task": "diarize", "emotions": "true", }, ) for segment in response.json()["segments"]: emotion = segment.get("emotion") label = emotion["label"] if emotion else "—" print(f"{segment['speaker']} [{label}]: {segment['text']}") ``` Node.js 18+: ```javascript import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("call.mp3")]), "call.mp3"); form.append("model", "nexara-ru"); form.append("task", "diarize"); form.append("emotions", "true"); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const result = await response.json(); for (const segment of result.segments) { console.log(`${segment.speaker} [${segment.emotion?.label ?? "—"}]: ${segment.text}`); } ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("call.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "call.mp3") io.Copy(part, file) writer.WriteField("model", "nexara-ru") writer.WriteField("task", "diarize") writer.WriteField("emotions", "true") writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("call.mp3")], ["model", "nexara-ru"], ["task", "diarize"], ["emotions", "true"], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end JSON.parse(response.body)["segments"].each do |segment| label = segment["emotion"] ? segment["emotion"]["label"] : "—" puts "#{segment["speaker"]} [#{label}]: #{segment["text"]}" end ``` ## Результат [#результат] У каждого оценённого сегмента появляется объект `emotion`: ```json { "task": "diarize", "language": "ru", "duration": 12.4, "text": "Здравствуйте, чем могу помочь? Я третий раз звоню по одному и тому же вопросу!", "segments": [ { "start": 0.0, "end": 3.21, "text": "Здравствуйте, чем могу помочь?", "speaker": "speaker_0", "emotion": { "label": "neutral", "confidence": 0.87, "probs": { "angry": 0.03, "sad": 0.04, "neutral": 0.87, "positive": 0.06 } } }, { "start": 3.6, "end": 8.9, "text": "Я третий раз звоню по одному и тому же вопросу!", "speaker": "speaker_1", "emotion": { "label": "angry", "confidence": 0.91, "probs": { "angry": 0.91, "sad": 0.05, "neutral": 0.03, "positive": 0.01 } } } ] } ``` ### Метки [#метки] | Метка | Значение | | ---------- | ------------------------------------ | | `angry` | Злость, раздражение | | `sad` | Грусть, расстроенность | | `neutral` | Нейтральная, спокойная речь | | `positive` | Позитив, радость, доброжелательность | ### Поля объекта [#поля-объекта] * **`label`** — выбранная метка эмоции. * **`confidence`** — уверенность модели в этой метке, от 0 до 1. * **`probs`** — вероятности по всем меткам. Полезно, когда важна не только победившая эмоция: например, `angry` c `confidence` 0.45 и `neutral` 0.4 рядом — это скорее «лёгкое раздражение», чем настоящая злость. ## Ограничения [#ограничения] * **Эмоция приходит не для каждого сегмента.** Если модель не смогла оценить фрагмент (слишком короткий, тишина, шум), поля `emotion` в сегменте просто не будет. Всегда проверяйте его наличие, а не обращайтесь к нему напрямую. * **У слов эмоции нет.** Она считается по сегменту целиком, поэтому в массиве `words` объекта `emotion` не будет даже при `timestamp_granularities=["word"]`. * **Только `nexara-ru` и только диаризация.** С `whisper-1` или с `task=transcribe` параметр вернёт ошибку `400`. * **Только JSON-форматы ответа.** Для `text`, `srt` и `vtt` параметр вернёт ошибку `400`. ## Эмоции в LLM-анализе [#эмоции-в-llm-анализе] Если в том же запросе передан `prompt`, метки эмоций уходят в языковую модель вместе с расшифровкой — анализ тональности опирается не только на слова, но и на то, как они сказаны. Модель видит примерно такой текст: ```text Оператор [neutral]: Служба поддержки, здравствуйте. Клиент [angry]: Я третий раз звоню по одному и тому же вопросу! ``` Ничего дополнительно включать не нужно: достаточно передать `emotions=true` и `prompt` в одном запросе. Подробнее про промпты и схемы — на странице [LLM-анализ](/llm-usage). Что важно знать: * **В модель уходит только метка.** `confidence` и `probs` остаются в ответе API, но в промпт не передаются. * **Метка акустическая.** Она получена из тона голоса, а не из слов, поэтому может расходиться со смыслом сказанного — вежливая фраза, произнесённая раздражённо, получит метку `angry`. Модель об этом предупреждена отдельно. Без `emotions=true` промпт не меняется: расшифровка уходит в модель ровно в том же виде, что и раньше. ## Длинные записи [#длинные-записи] Для длинных файлов параметр работает так же в [асинхронном режиме](/async-transcription) — передайте `emotions=true` при постановке задачи, и эмоции придут в готовом результате. Распознавание эмоций тарифицируется дополнительно к стоимости диаризации. Если ни один сегмент не удалось оценить, надбавка не списывается. Стоимость услуги смотрите на странице [тарифов](/pricing). # Ошибки (/errors) При возникновении ошибки Nexara API возвращает соответствующий HTTP-код состояния и тело ответа в формате JSON с описанием проблемы в поле `detail`: ```json { "detail": "Недостаточно средств на аккаунте." } ``` Ниже перечислены все коды ошибок, которые может вернуть API, с указанием причин и способов их устранения. ## 400 — Неверный запрос (Bad Request) [#400--неверный-запрос-bad-request] Самая частая категория ошибок. Возникает при некорректных параметрах запроса. Проверка выполняется до обработки файла, поэтому такие ошибки возвращаются быстро. Возможные причины: * **Файл и ссылка одновременно** — указаны и `file`, и `url`. Нужно передать что-то одно. * `Неверный запрос: укажите либо аудиофайл, либо ссылку, но не оба одновременно.` * **Не указан ни файл, ни ссылка** — не передан ни `file`, ни `url`. * `Неверный запрос: укажите либо аудиофайл, либо ссылку.` * **Некорректная ссылка** — значение `url` не прошло валидацию. * **Неподдерживаемая задача** — параметр `task` не входит в список поддерживаемых. * `Задача '...' не поддерживается. Вот список поддерживаемых задач: ...` * **Неподдерживаемый формат ответа** — параметр `response_format` не входит в список поддерживаемых. * `Формат ответа '...' не поддерживается. Вот список поддерживаемых форматов: ...` * **Неподдерживаемая модель** — параметр `model` не входит в список поддерживаемых. * `Модель '...' не поддерживается. Поддерживаемые модели: ...` * **Неподдерживаемый словарь** — параметр `dictionary` не входит в список поддерживаемых. * `Словарь '...' не поддерживается. Поддерживаемые словари: ...` * **Некорректные параметры генерации:** * `temperature должна быть в диапазоне от 0.1 до 1.` * `repetition_penalty должен быть в диапазоне от 0.1 до 2.` * `length_penalty должен быть в диапазоне от 0.1 до 2.` * **Неподдерживаемый язык** — параметр `language` не входит в список поддерживаемых. * `Язык '...' не поддерживается. Список доступных языков - https://docs.nexara.ru/docs/languages.` * **Файл слишком большой** — размер файла превышает максимально допустимый. * `Неверный запрос: файл слишком большой. Максимальный размер - ... байт.` * **Слишком короткое аудио:** * `Минимальная длина аудио - 0.3 секунды.` * `Минимальная длина аудио при диаризации - 3 секунды.` (для `task=diarize`) * **Некорректная гранулярность таймкодов** — значение `timestamp_granularities` не поддерживается. * `Гранулярность '...' не поддерживается. Поддерживаемые: ...` * `Гранулярность 'sentence' доступна только для задачи 'transcribe'.` * **Ошибки тегирования ролей (`roles`):** * `Тегирование ролей (roles) доступно только с диаризацией (task=diarize).` * `roles должен быть 'auto', JSON-списком строк или JSON-объектом {роль: описание}.` * `Список ролей не может быть пустым.` / `Объект ролей не может быть пустым.` * `Слишком много ролей. Максимум — ...` * `Каждая роль должна быть непустой строкой не длиннее ... символов.` * `Роли не должны повторяться.` * `Описание роли должно быть строкой.` / `Описание роли не должно превышать ... символов.` * **Ошибка обработки файла или ссылки:** * `Error while processing the file metadata.` * `Ошибка обработки ссылки на файл: ...` **Как исправить:** внимательно проверьте параметры запроса и сообщение в поле `detail` — оно указывает на конкретную проблему. ## 402 — Требуется оплата (Payment Required) [#402--требуется-оплата-payment-required] Недостаточно средств на балансе для выполнения запроса. Стоимость рассчитывается заранее по длительности аудио (с учётом надбавок за фильтр мата, тегирование ролей и LLM-обработку). Запрос отклоняется, если предполагаемая стоимость превышает баланс плюс лимит овердрафта. * `Недостаточно средств на аккаунте.` **Как исправить:** пополните баланс аккаунта. ## 403 — Доступ запрещён (Forbidden) [#403--доступ-запрещён-forbidden] Проблема с аутентификацией по API-ключу. Это основной механизм авторизации для эндпоинтов транскрибации. * `No token specified` — не передан API-ключ (заголовок `Authorization`). * `Invalid token` — передан несуществующий или недействительный API-ключ. **Как исправить:** убедитесь, что вы передаёте корректный API-ключ в заголовке `Authorization`. ## 404 — Не найдено (Not Found) [#404--не-найдено-not-found] Запрашиваемый ресурс не существует. * `Model not found` — запрошена несуществующая модель. * `API key not found` — API-ключ не найден. * `Job not found` — асинхронная задача с указанным `job_id` не найдена. ## 408 — Тайм-аут запроса (Request Timeout) [#408--тайм-аут-запроса-request-timeout] Превышено время анализа медиафайла (`ffprobe`). * `Media analysis timed out.` — при обработке загруженного файла. * `Media analysis timed out (ffprobe). URL might be too slow or unresponsive.` — при обработке по ссылке (медленный или недоступный `url`). **Как исправить:** проверьте доступность и скорость отдачи файла по ссылке, либо загрузите файл напрямую. ## 413 — LLM-обработка не успела завершиться (Payload Too Large) [#413--llm-обработка-не-успела-завершиться-payload-too-large] Несмотря на название HTTP-кода, эта ошибка **не связана с размером файла**. Она возникает только на синхронном эндпоинте `/v1/audio/transcriptions`, когда в запросе указан `prompt` (LLM-обработка), а аудио длинное: сама транскрибация успешно завершилась, но пост-обработка языковой моделью не уложилась в синхронный лимит времени. * `The synchronous LLM request timed out. Use async mode for long audio.` **Как исправить:** не повторяйте тот же запрос синхронно — он снова упрётся в тайм-аут. Отправьте идентичный запрос через [асинхронный режим](/async-transcription) (`/v1/audio/transcriptions/async`), где на шаг LLM отводится значительно больший тайм-аут. ## 415 — Неподдерживаемый тип файла (Unsupported Media Type) [#415--неподдерживаемый-тип-файла-unsupported-media-type] Формат файла или его MIME-тип не поддерживается. Полный список поддерживаемых форматов — на странице [Поддерживаемые файлы](/supported-files). **Как исправить:** конвертируйте файл в один из поддерживаемых форматов и убедитесь, что указан правильный `Content-Type`. ## 429 — Слишком много запросов (Too Many Requests) [#429--слишком-много-запросов-too-many-requests] Превышены лимиты частоты или конкурентности. * Превышен лимит частоты запросов (rate limit). * `Too many concurrent async jobs. Maximum is ... Please wait for existing jobs to complete before submitting new ones.` — превышено число одновременных асинхронных задач. **Как исправить:** снизьте частоту запросов или дождитесь завершения текущих асинхронных задач перед отправкой новых. ## 500 — Внутренняя ошибка сервера (Internal Server Error) [#500--внутренняя-ошибка-сервера-internal-server-error] Непредвиденная ошибка на стороне сервера. * `Internal Server Error` — общая ошибка обработки. * `Error retrieving user information.` — не удалось получить данные пользователя. * `Ошибка при получении LLM-саммари.` — сбой при генерации LLM-обработки (при этом запрос не тарифицируется). **Как исправить:** повторите запрос позже. Если ошибка повторяется — обратитесь в [Поддержку](https://t.me/RND_RandoM). ## 502 — Ошибка LLM-провайдера (Bad Gateway) [#502--ошибка-llm-провайдера-bad-gateway] Редкая ошибка: при LLM-обработке провайдер языковой модели вернул пустой или некорректный ответ. Это обычная серверная ошибка — в отличие от `413`, где транскрибация уже готова и достаточно повторить запрос в асинхронном режиме. **Как исправить:** повторите запрос позже или обратитесь в [Поддержку](https://t.me/RND_RandoM). ## Обработка ошибок в SDK [#обработка-ошибок-в-sdk] В официальных библиотеках ([Python](/python-sdk) и [TypeScript](/typescript-sdk)) каждому коду соответствует типизированное исключение — `BadRequestError` (400), `InsufficientBalanceError` (402), `AuthenticationError` (403), `NotFoundError` (404), `SyncLLMTimeoutError` (413), `RateLimitError` (429), `InternalServerError` (500), `BadGatewayError` (502). Сообщение сервера доступно в поле `detail`, а ошибки `429` и сетевые сбои повторяются автоматически. Поймав `SyncLLMTimeoutError`, повторно отправьте тот же запрос через `create_job()` / `createJob()` — синхронный повтор снова упрётся в тайм-аут. ```python from nexara import Nexara, InsufficientBalanceError client = Nexara() try: result = client.transcriptions.create(file="audio.mp3") except InsufficientBalanceError as e: # 402 print("Пополните баланс:", e.detail) ``` Подробнее — в разделе [ошибки Python SDK](/python-sdk#ошибки). ```typescript import { Nexara, InsufficientBalanceError } from "nexara-sdk"; const client = new Nexara(); try { await client.transcriptions.create({ file: "audio.mp3" }); } catch (e) { if (e instanceof InsufficientBalanceError) { console.log("Пополните баланс:", e.detail); // 402 } else { throw e; } } ``` Подробнее — в разделе [ошибки TypeScript SDK](/typescript-sdk#ошибки). Если вы столкнулись с ошибкой, причину которой не удаётся определить по сообщению, напишите в [Поддержку](https://t.me/RND_RandoM) — приложите текст ошибки и, по возможности, идентификатор запроса. # Файл по ссылке (/file-url) Вместо загрузки файла с диска можно передать прямую ссылку на аудио, и Nexara API скачает файл и вернёт текст из аудио. Вместо поля `file` передаётся поле `url` в том же запросе `POST /v1/audio/transcriptions`, а результат приходит в ответе. ## Транскрибация [#транскрибация] Ниже представлен один и тот же запрос на разных языках программирования. Замените `nx-XXXXXXXXXXXXXXXXXXXXXXXX` на свой [API-ключ](/authentication), а `https://example.com/audio.mp3` — на прямую ссылку к вашему файлу. Официальная библиотека [`nexara`](/python-sdk) (`pip install nexara`): ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create(url="https://example.com/audio.mp3") print(result.text) ``` Официальная библиотека [`nexara-sdk`](/typescript-sdk) (`npm install nexara-sdk`): ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ url: "https://example.com/audio.mp3" }); console.log(result.text); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "url=https://example.com/audio.mp3" \ -F "model=whisper-1" ``` ```python import requests response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, data={ "url": "https://example.com/audio.mp3", "model": "whisper-1", }, ) print(response.json()["text"]) ``` Node.js 18+: ```javascript const form = new FormData(); form.append("url", "https://example.com/audio.mp3"); form.append("model", "whisper-1"); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const result = await response.json(); console.log(result.text); ``` ```go package main import ( "fmt" "io" "net/http" "net/url" "strings" ) func main() { form := url.Values{} form.Set("url", "https://example.com/audio.mp3") form.Set("model", "whisper-1") req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", strings.NewReader(form.Encode())) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form_data( "url" => "https://example.com/audio.mp3", "model" => "whisper-1", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)["text"] ``` ## Результат [#результат] По умолчанию (`response_format=json`) в ответе возвращается распознанный текст: ```json { "text": "Привет! Это пример транскрибации." } ``` Формат ответа можно менять параметром `response_format` — `json` (по умолчанию), `text`, `verbose_json`, `srt` или `vtt`. Подробнее — на странице [форматы ответа](/response-formats). ## Что учитывать [#что-учитывать] * **Ссылка должна быть публичной.** По ссылке должен скачиваться сам аудиофайл, без авторизации и редиректов на страницу. Если ссылка недоступна или отдаётся слишком медленно, запрос завершится ошибкой. В качестве ссылок лучше всего подходит pre-signed ссылка на S3 бакет. **Ссылки на YouTube, TikTok, VK и другие соцсети не поддерживаются.** * **Форматы файлов.** Принимается большинство существующих аудио- и видеоформатов. Полный список — на странице [поддерживаемых файлов](/supported-files). * **Размер файла.** Максимальный размер одного файла — **3 ГБ**. Подробнее — на странице [лимитов](/limits). * **Частота запросов.** Синхронный эндпоинт ограничен **10 запросами в секунду**. Подробнее читайте здесь — [Лимиты](/limits#%D1%87%D0%B0%D1%81%D1%82%D0%BE%D1%82%D0%B0-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2). * **Хранение ключа.** Не вставляйте API-ключ прямо в код. Храните его в переменной окружения (например, `export NEXARA_API_KEY="nx-..."`) и ссылайтесь на неё. Подробнее — на странице [аутентификации](/authentication#curl). Для длинных записей вместо синхронного запроса используйте [асинхронный режим](/async-transcription). # Поддерживаемые языки (/languages) Nexara API поддерживает автоматическое определение языка и транскрибацию для широкого спектра языков. Вы также можете явно указать язык, используя параметр `language` в ваших API-запросах. Для получения дополнительной информации обратитесь к [документации API](/api-reference/create_transcription_audio_transcriptions_post). Ниже приведен полный список поддерживаемых языков с их кодами. ## Полный список поддерживаемых языков [#полный-список-поддерживаемых-языков] | Код языка | Название языка | | --------- | --------------------- | | af | африкаанс | | am | амхарский | | ar | арабский | | as | ассамский | | az | азербайджанский | | ba | башкирский | | be | белорусский | | bg | болгарский | | bn | бенгальский | | bo | тибетский | | br | бретонский | | bs | боснийский | | ca | каталанский | | cs | чешский | | cy | валлийский | | da | датский | | de | немецкий | | el | греческий | | en | английский | | es | испанский | | et | эстонский | | eu | баскский | | fa | персидский | | fi | финский | | fo | фарерский | | fr | французский | | gl | галисийский | | gu | гуджарати | | ha | хауса | | haw | гавайский | | he | иврит | | hi | хинди | | hr | хорватский | | ht | гаитянский креольский | | hu | венгерский | | hy | армянский | | id | индонезийский | | is | исландский | | it | итальянский | | ja | японский | | jw | яванский | | ka | грузинский | | kk | казахский | | km | кхмерский | | kn | каннада | | ko | корейский | | la | латинский | | lb | люксембургский | | ln | лингала | | lo | лаосский | | lt | литовский | | lv | латышский | | mg | малагасийский | | mi | маори | | mk | македонский | | ml | малаялам | | mn | монгольский | | mr | маратхи | | ms | малайский | | mt | мальтийский | | my | бирманский | | ne | непальский | | nl | нидерландский | | nn | нюнорск | | no | норвежский | | oc | окситанский | | pa | панджаби | | pl | польский | | ps | пушту | | pt | португальский | | ro | румынский | | ru | русский | | sa | санскрит | | sd | синдхи | | si | сингальский | | sk | словацкий | | sl | словенский | | sn | шона | | so | сомалийский | | sq | албанский | | sr | сербский | | su | сунданский | | sv | шведский | | sw | суахили | | ta | тамильский | | te | телугу | | tg | таджикский | | th | тайский | | tk | туркменский | | tl | тагальский | | tr | турецкий | | tt | татарский | | uk | украинский | | ur | урду | | uz | узбекский | | vi | вьетнамский | | yi | идиш | | yo | йоруба | | yue | кантонский | | zh | китайский | Используйте эти коды языков, когда вам нужно явно указать язык для транскрибации с помощью параметра `language`. Если параметр не указан, API попытается определить язык автоматически. # Лимиты (/limits) На этой странице собраны все ограничения Nexara API. Их стоит учитывать при интеграции, чтобы запросы не отклонялись. ## Файлы и аудио [#файлы-и-аудио] Файл больше 3 ГБ будет отклонён с ошибкой `400`. Ограничение действует как на загрузку файлом, так и на обработку по ссылке через параметр `url`. Принимаются только аудио- и видеоформаты из списка на странице [Поддерживаемые файлы](/supported-files). Неподдерживаемый формат вернёт ошибку `415`. Аудио короче 0.3 секунды отклоняется с ошибкой `400`. Для задачи `task=diarize` минимальная длительность аудио составляет 3 секунды. ## Частота запросов [#частота-запросов] Эндпоинты транскрибации (`/audio/transcriptions` и `/audio/transcriptions/async`) ограничены частотой **10 запросов в секунду**. При превышении возвращается ошибка `429`. Одновременно может выполняться не более **200** асинхронных задач (`IN_PROGRESS`) на один API-ключ. Дождитесь завершения текущих задач перед отправкой новых, иначе вам вернется ошибка `429`. Если у Вас большие объемы аудио и Вам нужны увеличенные лимиты на частоту запросов или на максимальный размер файла, напишите нам в [поддержку](https://t.me/RND_RandoM). ## Аккаунт и ключи [#аккаунт-и-ключи] Один аккаунт может иметь максимум **50** активных API-ключей. Попытка создать больше вернёт ошибку `400`. Удалите неиспользуемые ключи в [личном кабинете](https://app.nexara.ru). Минимальная сумма пополнения баланса составляет 200 рублей. ## Тегирование ролей [#тегирование-ролей] Ограничения параметра `roles` (доступен только с диаризацией, `task=diarize`): В одном запросе можно задать не более **10** ролей. Каждое название роли должно быть непустой строкой длиной не более **64** символов. Описание роли (при передаче объектом `{роль: описание}`) не должно превышать **500** символов. При превышении любого из лимитов API возвращает соответствующий код ошибки с описанием причины в поле `detail`. Подробнее — на странице [ошибок](/errors). # LLM анализ (/llm-usage) Помимо транскрибации, Nexara API может сразу проанализировать её языковой моделью. Вы передаёте инструкцию в параметре `prompt`, и модель выполняет её над текстом расшифровки — например, делает краткое резюме, выделяет ключевые пункты, определяет тему или извлекает нужные поля. Анализ включается, как только вы передаёте `prompt`. Возможны два режима: Только `prompt` — модель возвращает ответ в свободной форме (текстом). `prompt` + `json_schema` — модель возвращает JSON строго по заданной вами схеме. Когда включён анализ, ответ содержит и расшифровку, и результат работы модели: ```json { "transcription": { "task": "transcribe", "text": "...", "segments": [ ... ] }, "llm_output": "..." } ``` Поле `transcription` — это полная расшифровка (в формате `verbose_json`), а `llm_output` — результат анализа: строка в свободном режиме или JSON-объект при использовании схемы. `json_schema` учитывается только вместе с `prompt`. Отдельно, без `prompt`, анализ не запускается. На длинном аудио LLM-обработка может не уложиться в лимит времени синхронного запроса — тогда `/v1/audio/transcriptions` вернёт ошибку [`413`](/errors): транскрибация готова, но анализ не завершился. Повторять запрос синхронно бесполезно — используйте [асинхронный режим](/async-transcription), где на шаг LLM отводится значительно больший тайм-аут. ## Свободный анализ [#свободный-анализ] Передайте `prompt` с инструкцией. Замените `nx-XXXXXXXXXXXXXXXXXXXXXXXX` на свой [API-ключ](/authentication), а `call.mp3` — на путь к вашему файлу. Официальная библиотека [`nexara`](/python-sdk) (`pip install nexara`): ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="call.mp3", prompt="Сделай краткое резюме разговора в трёх предложениях.", ) print(result.llm_output) # ответ модели print(result.transcription.text) # расшифровка, из которой он получен ``` Официальная библиотека [`nexara-sdk`](/typescript-sdk) (`npm install nexara-sdk`): ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "call.mp3", prompt: "Сделай краткое резюме разговора в трёх предложениях.", }); console.log(result.llm_output); // ответ модели console.log(result.transcription.text); // расшифровка, из которой он получен ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=whisper-1" \ -F "prompt=Сделай краткое резюме разговора в трёх предложениях." ``` ```python import requests with open("call.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={ "model": "whisper-1", "prompt": "Сделай краткое резюме разговора в трёх предложениях.", }, ) print(response.json()["llm_output"]) ``` Node.js 18+: ```javascript import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("call.mp3")]), "call.mp3"); form.append("model", "whisper-1"); form.append("prompt", "Сделай краткое резюме разговора в трёх предложениях."); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const result = await response.json(); console.log(result.llm_output); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("call.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "call.mp3") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.WriteField("prompt", "Сделай краткое резюме разговора в трёх предложениях.") writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("call.mp3")], ["model", "whisper-1"], ["prompt", "Сделай краткое резюме разговора в трёх предложениях."], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)["llm_output"] ``` В свободном режиме `llm_output` — это строка с ответом модели: ```json { "transcription": { "task": "transcribe", "text": "...", "segments": [ ... ] }, "llm_output": "Клиент обратился с вопросом о возврате товара. Оператор объяснил условия и оформил заявку. Стороны договорились о звонке на следующий день." } ``` ## Структурированный вывод [#структурированный-вывод] Чтобы получить не текст, а данные с предсказуемой структурой, добавьте `json_schema` — [JSON Schema](https://json-schema.org/), описывающую форму ответа. Модель вернёт JSON, который строго ей соответствует, — такой ответ удобно сразу разбирать в коде без парсинга свободного текста. ### Как устроена схема [#как-устроена-схема] `json_schema` — это обычная JSON Schema (черновик 2020-12). Обычно это объект (`"type": "object"`) с описанием полей в `properties`. Основные возможности: * **Типы полей** — `string`, `number`, `integer`, `boolean`, `array`, `object`. * **Обязательные поля** — перечислите их в массиве `required`, чтобы модель всегда возвращала эти ключи. * **Фиксированный набор значений** — `enum` ограничивает поле списком допустимых вариантов (удобно для категорий и тональности). * **Списки** — `"type": "array"` с `items`, описывающим тип элементов. * **Вложенные объекты** — поле само может быть `"type": "object"` со своими `properties`. Например, схема для анализа звонка в поддержку: ```json { "type": "object", "properties": { "topic": { "type": "string", "description": "Краткая тема обращения" }, "sentiment": { "type": "string", "enum": ["положительная", "нейтральная", "отрицательная"] }, "summary": { "type": "string", "description": "Резюме разговора в одном-двух предложениях" }, "action_items": { "type": "array", "items": { "type": "string" } } }, "required": ["topic", "sentiment", "summary", "action_items"] } ``` Схема проверяется при приёме запроса. Если `json_schema` — не корректная JSON Schema или пустой объект, запрос отклоняется с ошибкой `400` ещё до распознавания. Используйте `description` у полей, чтобы точнее подсказать модели, что в них ожидается. ### Запрос [#запрос] `json_schema` передаётся строкой с JSON. В `prompt` опишите задачу, а форму ответа задаст схема. В SDK схема передаётся обычным словарём Python — без `json.dumps`. Поле `llm_output` возвращается уже разобранным объектом. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="call.mp3", prompt="Проанализируй разговор в поддержке.", json_schema={ "type": "object", "properties": { "topic": {"type": "string"}, "sentiment": { "type": "string", "enum": ["положительная", "нейтральная", "отрицательная"], }, "summary": {"type": "string"}, "action_items": {"type": "array", "items": {"type": "string"}}, }, "required": ["topic", "sentiment", "summary", "action_items"], }, ) print(result.llm_output["summary"]) # dict по вашей схеме ``` В SDK схема передаётся обычным объектом — без `JSON.stringify`. Поле `llm_output` возвращается уже разобранным. ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "call.mp3", prompt: "Проанализируй разговор в поддержке.", json_schema: { type: "object", properties: { topic: { type: "string" }, sentiment: { type: "string", enum: ["положительная", "нейтральная", "отрицательная"], }, summary: { type: "string" }, action_items: { type: "array", items: { type: "string" } }, }, required: ["topic", "sentiment", "summary", "action_items"], }, }); console.log(result.llm_output); // объект по вашей схеме ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=whisper-1" \ -F "prompt=Проанализируй разговор в поддержке." \ -F 'json_schema={"type":"object","properties":{"topic":{"type":"string"},"sentiment":{"type":"string","enum":["положительная","нейтральная","отрицательная"]},"summary":{"type":"string"},"action_items":{"type":"array","items":{"type":"string"}}},"required":["topic","sentiment","summary","action_items"]}' ``` ```python import json import requests schema = { "type": "object", "properties": { "topic": {"type": "string"}, "sentiment": { "type": "string", "enum": ["положительная", "нейтральная", "отрицательная"], }, "summary": {"type": "string"}, "action_items": {"type": "array", "items": {"type": "string"}}, }, "required": ["topic", "sentiment", "summary", "action_items"], } with open("call.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={ "model": "whisper-1", "prompt": "Проанализируй разговор в поддержке.", "json_schema": json.dumps(schema), }, ) print(response.json()["llm_output"]) ``` Node.js 18+: ```javascript import fs from "fs"; const schema = { type: "object", properties: { topic: { type: "string" }, sentiment: { type: "string", enum: ["положительная", "нейтральная", "отрицательная"], }, summary: { type: "string" }, action_items: { type: "array", items: { type: "string" } }, }, required: ["topic", "sentiment", "summary", "action_items"], }; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("call.mp3")]), "call.mp3"); form.append("model", "whisper-1"); form.append("prompt", "Проанализируй разговор в поддержке."); form.append("json_schema", JSON.stringify(schema)); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const result = await response.json(); console.log(result.llm_output); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { schema := `{"type":"object","properties":{"topic":{"type":"string"},"sentiment":{"type":"string","enum":["положительная","нейтральная","отрицательная"]},"summary":{"type":"string"},"action_items":{"type":"array","items":{"type":"string"}}},"required":["topic","sentiment","summary","action_items"]}` file, _ := os.Open("call.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "call.mp3") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.WriteField("prompt", "Проанализируй разговор в поддержке.") writer.WriteField("json_schema", schema) writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" schema = { "type" => "object", "properties" => { "topic" => { "type" => "string" }, "sentiment" => { "type" => "string", "enum" => ["положительная", "нейтральная", "отрицательная"], }, "summary" => { "type" => "string" }, "action_items" => { "type" => "array", "items" => { "type" => "string" } }, }, "required" => ["topic", "sentiment", "summary", "action_items"], } uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("call.mp3")], ["model", "whisper-1"], ["prompt", "Проанализируй разговор в поддержке."], ["json_schema", schema.to_json], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)["llm_output"] ``` Теперь `llm_output` — это JSON-объект, соответствующий вашей схеме: ```json { "transcription": { "task": "transcribe", "text": "...", "segments": [ ... ] }, "llm_output": { "topic": "Возврат товара", "sentiment": "нейтральная", "summary": "Клиент попросил оформить возврат, оператор объяснил условия и создал заявку.", "action_items": [ "Отправить клиенту инструкцию по возврату", "Перезвонить на следующий день" ] } } ``` LLM анализ тарифицируется дополнительно к стоимости распознавания. Стоимость услуги смотрите на странице [тарифов](/pricing). # Локальный файл (/local-file) Самый частый сценарий — отправка аудиофайла с диска и получение его расшифровки. Файл передаётся напрямую в теле запроса `multipart/form-data` на эндпоинт `POST /v1/audio/transcriptions`, а результат приходит в ответе. ## Транскрибация [#транскрибация] Ниже представлен один и тот же запрос на разных языках программирования. Замените `nx-XXXXXXXXXXXXXXXXXXXXXXXX` на свой [API-ключ](/authentication), а `audio.mp3` — на путь к вашему файлу. Официальная библиотека [`nexara`](/python-sdk) (`pip install nexara`). Файл, переданный путём, стримится с диска и не загружается в память целиком. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create(file="audio.mp3") print(result.text) ``` Официальная библиотека [`nexara-sdk`](/typescript-sdk) (`npm install nexara-sdk`). Файл, переданный путём, стримится с диска и не загружается в память целиком. ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "audio.mp3" }); console.log(result.text); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@audio.mp3" \ -F "model=whisper-1" ``` ```python import requests with open("audio.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={"model": "whisper-1"}, ) print(response.json()["text"]) ``` Nexara API совместим с OpenAI SDK — достаточно указать `base_url`. Обратите внимание: `Bearer` к ключу добавлять не нужно. ```python from openai import OpenAI client = OpenAI( api_key="nx-XXXXXXXXXXXXXXXXXXXXXXXX", base_url="https://api.nexara.ru/v1", ) with open("audio.mp3", "rb") as audio_file: transcript = client.audio.transcriptions.create( model="whisper-1", file=audio_file, ) print(transcript.text) ``` Node.js 18+: ```javascript import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("audio.mp3")]), "audio.mp3"); form.append("model", "whisper-1"); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const result = await response.json(); console.log(result.text); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("audio.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "audio.mp3") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("audio.mp3")], ["model", "whisper-1"], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)["text"] ``` ## Результат [#результат] По умолчанию (`response_format=json`) в ответе возвращается распознанный текст: ```json { "text": "Привет! Это пример транскрибации." } ``` Формат ответа можно менять параметром `response_format` — `json` (по умолчанию), `text`, `verbose_json`, `srt` или `vtt`. Подробнее — на странице [форматы ответа](/response-formats). ## Что учитывать [#что-учитывать] * **Форматы файлов.** Принимается большинство существующих аудио- и видеоформатов. Полный список — на странице [поддерживаемых файлов](/supported-files). * **Размер файла.** Максимальный размер одного файла — **3 ГБ**. Подробнее — на странице [лимитов](/limits). * **Частота запросов.** Синхронный эндпоинт ограничен **10 запросами в секунду**. Подробнее читайте здесь — [Лимиты](/limits#%D1%87%D0%B0%D1%81%D1%82%D0%BE%D1%82%D0%B0-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%BE%D0%B2). * **Хранение ключа.** Не вставляйте API-ключ прямо в код. Храните его в переменной окружения (например, `export NEXARA_API_KEY="nx-..."`) и ссылайтесь на неё. Подробнее — на странице [аутентификации](/authentication#curl). Для длинных записей вместо синхронного запроса используйте [асинхронный режим](/async-transcription). # Ключевые пункты встречи (/meeting-highlights) # Модели (/models) Nexara предлагает две модели распознавания речи. Модель указывается в параметре `model` при запросе. По умолчанию используется `whisper-1`. Мультиязычная и устойчивая модель для большинства сценариев. Подходит, когда аудио может быть на разных языках или когда нужен универсальный вариант «по умолчанию». Оптимизирована под русскую речь — телефонию и сложные записи (шум, удалённый микрофон, плохое качество связи). Выбирайте её для русскоязычных звонков и «далёких» записей. ## Какую модель выбрать [#какую-модель-выбрать] * `whisper-1` — мультиязычные записи, подкасты, встречи, диктовка, любой сценарий «по умолчанию». * `nexara-ru` — русскоязычная телефония, колл-центры, записи с плохим качеством связи и удалённым микрофоном. ## Как указать модель [#как-указать-модель] Передайте нужный идентификатор в параметре `model`: ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="audio/example.mp3", model="nexara-ru", # или "whisper-1" ) print(result.text) ``` ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "audio/example.mp3", model: "nexara-ru", // или "whisper-1" }); console.log(result.text); ``` ```python import requests url = "https://api.nexara.ru/v1/audio/transcriptions" api_key = "NEXARA_API_KEY" with open("audio/example.mp3", "rb") as audio_file: files = {"file": ("example.mp3", audio_file, "audio/mp3")} data = { "model": "nexara-ru", # или "whisper-1" "response_format": "json", } response = requests.post( url, headers={"Authorization": f"Bearer {api_key}"}, files=files, data=data, ) print(response.json()) ``` ```bash curl --request POST \ --url https://api.nexara.ru/v1/audio/transcriptions \ --header 'Authorization: Bearer NEXARA_API_KEY' \ --header 'Content-Type: multipart/form-data' \ --form model="nexara-ru" \ --form response_format="json" \ --form file="@example.mp3" ``` Стоимость дополнений (диаризация, определение ролей, LLM-обработка) не зависит от выбранной модели. См. [Тарифы](/pricing). # Мультиканальное аудио (/multichannel) # Гайд по n8n (/n8n) [n8n](https://n8n.io) позволяет вызвать Nexara API без единой строки кода. Есть два способа: * **Community-нода Nexara** (рекомендуется) — отдельный узел с готовыми операциями транскрибации и диаризации. * **Узел HTTP Request** — ручная настройка запроса, если community-ноды недоступны. ## Способ 1: Community-нода Nexara (рекомендуется) [#способ-1-community-нода-nexara-рекомендуется] Отдельный узел [`n8n-nodes-nexara`](https://www.npmjs.com/package/n8n-nodes-nexara) избавляет от ручной настройки запроса: транскрибация, диаризация, роли говорящих и структурированный вывод через LLM доступны как обычные операции узла. ### Установите ноду [#установите-ноду] В n8n откройте **Settings → Community Nodes → Install** и введите: ```text n8n-nodes-nexara ``` Подробнее — в [документации n8n по community-нодам](https://docs.n8n.io/integrations/community-nodes/installation/). ### Добавьте учётные данные (Credentials) [#добавьте-учётные-данные-credentials] Создайте в [панели Nexara](https://nexara.ru) API-ключ. Затем в n8n добавьте учётные данные типа **Nexara API** и вставьте ключ — он будет отправляться как `Bearer`-токен в каждом запросе. У учётных данных нет кнопки «Test»: в API нет отдельного health-эндпоинта. Чтобы проверить ключ, запустите операцию **Transcribe** на коротком аудио. ### Выберите операцию [#выберите-операцию] Добавьте узел **Nexara** и выберите операцию: | Операция | Что делает | | -------------- | ----------------------------------------------------------------------------------------------------------- | | **Transcribe** | Транскрибирует аудио в текст и дожидается результата. | | **Diarize** | Транскрибирует и размечает говорящих, с опциональными ролями. | | **Create Job** | Отправляет длинное аудио на отложенную обработку. Возвращает задачу сразу либо опрашивает её до завершения. | | **Get Job** | Получает отложенную задачу по её ID. | Аудио передаётся либо как **бинарный файл** из предыдущего узла, либо как **URL** — сервер скачает файл сам. ### Настройте параметры [#настройте-параметры] Основные опции узла: * **Response Format** — `JSON`, `Verbose JSON`, `Text`, `SRT` или `VTT`. * **Language** — код [ISO-639-1](/languages) (например, `ru`, `en`) или пусто для автоопределения. * **LLM Prompt** / **LLM JSON Schema** — прогнать расшифровку через [LLM](/llm-usage) и, при необходимости, получить строго структурированный JSON. Промпт автоматически включает `Verbose JSON`. * **Timestamp Granularity**, **Profanity Filter**, **Dictionary**, **Model**. * Для диаризации: **Number of Speakers**, **Diarization Setting** и **Roles** — `auto`, JSON-массив вида `["client","agent"]` или JSON-объект «метка → описание». ### Примеры [#примеры] **Транскрибация загруженного файла** 1. Узел, отдающий бинарное аудио (например, *HTTP Request* или *Read Binary File*). 2. **Nexara → Transcribe**, Input Type — *Binary File*, Input Binary Field — `data`. **Диаризация звонка с ролями** 1. **Nexara → Diarize**. 2. В *Diarization Options* задайте **Roles** — `["client","agent"]`. **Длинное аудио (отложенная обработка)** 1. **Nexara → Create Job** с включённым **Wait for Completion** — узел сам опрашивает задачу и возвращает результат. За неуспешную задачу оплата не списывается. 2. Либо оставьте опрос выключенным и позже вызовите **Get Job** с полученным Job ID (в другом воркфлоу или запуске). Результаты хранятся 12 часов после создания. ## Способ 2: Узел HTTP Request (вручную) [#способ-2-узел-http-request-вручную] Если community-ноды недоступны, Nexara API можно вызвать напрямую через встроенный узел **HTTP Request**. Ниже — пошаговая настройка транскрибации аудиофайла. ### Добавьте узел HTTP Request [#добавьте-узел-http-request] Найдите и добавьте узел **HTTP Request** в ваш рабочий процесс n8n. ### Импортируйте команду cURL [#импортируйте-команду-curl] Нажмите **Import cURL** в настройках узла HTTP Request и вставьте команду ниже. ```bash curl --request POST \ --url https://api.nexara.ru/v1/audio/transcriptions \ --header 'Authorization: Bearer YOUR_NEXARA_API_KEY' \ --header 'Content-Type: multipart/form-data' ``` Не забудьте заменить `YOUR_NEXARA_API_KEY` на ваш реальный API-ключ Nexara. ### Настройте тело запроса (Body Parameters) [#настройте-тело-запроса-body-parameters] * Прокрутите вниз до раздела **Body** в параметрах узла. * Убедитесь, что **Body Content Type** установлен как `Form-Data`. * В разделе **Body Parameters** нажмите **Add Parameter**. * **Parameter Type** — `n8n Binary File`. * **Name** — `file`. * **Input Data Field Name** — `data`. Настройка Body Parameters для файла Поле `data` предполагает, что бинарный файл приходит из предыдущего узла (например, **Read Binary File**) и доступен под именем `data`. Если имя поля другое — укажите своё. А если у вас есть ссылка на файл, а не сам файл, передайте её напрямую через параметр `url`. ### Готово — при необходимости добавьте параметры [#готово--при-необходимости-добавьте-параметры] Базовая настройка завершена: узел уже отправляет файл на транскрибацию. Чтобы добавить другие параметры API (например, `language` или `response_format`), создайте новые параметры в **Body Parameters** — обычно с типом `String` в поле **Parameter Type**: Дополнительные параметры запроса * Убедитесь, что перед узлом HTTP Request есть узел, который читает аудиофайл и делает его бинарные данные доступными (обычно под именем поля `data`). Либо передавайте ссылку на файл через параметр `url`. * Чтобы выполнить диаризацию (разделение на говорящих), добавьте параметр `task` со значением `diarize`. Подробнее о всех доступных параметрах API читайте в [Референсе API](/api-reference/create_transcription_audio_transcriptions_post). # OpenAI SDK (/openai-sdk) # Обзор (/overview) # Телефонные звонки (/phone-call) Телефонные звонки — самый частый сценарий для колл-центров и отделов продаж. Из одной записи можно получить сразу всё: расшифровку, разделение на оператора и клиента, эмоции говорящих и готовую аналитику звонка. Ниже разберём блок за блоком, а в конце соберём всё в один запрос и поделимся [лайфхаками](#лайфхаки). ## Разделение на говорящих [#разделение-на-говорящих] Для звонков включите диаризацию (`task=diarize`) и передайте `num_speakers=2` — в разговоре обычно двое. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="call.mp3", task="diarize", num_speakers=2, ) ``` ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "call.mp3", task: "diarize", num_speakers: 2, }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F "num_speakers=2" ``` Если телефония умеет писать звонки в двухканальном (стерео) формате, где каждый участник на своём канале, — используйте именно его. В связке с `num_speakers=2` это даёт самое точное разделение. ## Роли вместо speaker\_0 и speaker\_1 [#роли-вместо-speaker_0-и-speaker_1] Чтобы в расшифровке сразу были понятные роли, а не безликие `speaker_0`/`speaker_1`, передайте `roles` со своими метками. Для аналитики это гораздо удобнее. ```python result = client.transcriptions.create( file="call.mp3", task="diarize", num_speakers=2, roles=["Оператор", "Клиент"], ) ``` ```typescript const result = await client.transcriptions.create({ file: "call.mp3", task: "diarize", num_speakers: 2, roles: ["Оператор", "Клиент"], }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F "num_speakers=2" \ -F 'roles=["Оператор", "Клиент"]' ``` В сегментах вместо `speaker_N` появятся `Оператор` и `Клиент`. Подробнее — на странице [спикеры и роли](/speakers-roles). ## Русские звонки [#русские-звонки] Если разговор на русском языке, используйте модель `nexara-ru` — она заточена под русскую речь и даёт более точную расшифровку, чем универсальная `whisper-1`. ```python result = client.transcriptions.create( file="call.mp3", model="nexara-ru", task="diarize", num_speakers=2, ) ``` ```typescript const result = await client.transcriptions.create({ file: "call.mp3", model: "nexara-ru", task: "diarize", num_speakers: 2, }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=nexara-ru" \ -F "task=diarize" \ -F "num_speakers=2" ``` ## Фильтрация мата [#фильтрация-мата] В звонках нередко проскакивает нецензурная лексика. Чтобы замаскировать её в расшифровке (например, для отчётов и выгрузок), добавьте `profanity_filter=true`. ```python result = client.transcriptions.create( file="call.mp3", model="nexara-ru", task="diarize", num_speakers=2, profanity_filter=True, ) ``` ```typescript const result = await client.transcriptions.create({ file: "call.mp3", model: "nexara-ru", task: "diarize", num_speakers: 2, profanity_filter: true, }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=nexara-ru" \ -F "task=diarize" \ -F "num_speakers=2" \ -F "profanity_filter=true" ``` ## Эмоции в звонке [#эмоции-в-звонке] Чтобы видеть не только *что* сказали, но и *как*, добавьте `emotions=true`. К каждому сегменту диаризации добавится объект `emotion` с меткой (`angry`, `sad`, `neutral`, `positive`) и уверенностью модели — сразу видно, на какой минуте клиент начал злиться. Параметр работает только с моделью `nexara-ru` и диаризацией — то есть ровно в той конфигурации, которую мы собрали выше. ```python result = client.transcriptions.create( file="call.mp3", model="nexara-ru", task="diarize", num_speakers=2, roles=["Оператор", "Клиент"], emotions=True, ) for segment in result.segments: if segment.emotion: print(f"{segment.speaker} [{segment.emotion.label}]: {segment.text}") else: print(f"{segment.speaker}: {segment.text}") ``` ```typescript const result = await client.transcriptions.create({ file: "call.mp3", model: "nexara-ru", task: "diarize", num_speakers: 2, roles: ["Оператор", "Клиент"], emotions: true, }); for (const segment of result.segments) { if (segment.emotion) { console.log(`${segment.speaker} [${segment.emotion.label}]: ${segment.text}`); } else { console.log(`${segment.speaker}: ${segment.text}`); } } ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=nexara-ru" \ -F "task=diarize" \ -F "num_speakers=2" \ -F 'roles=["Оператор", "Клиент"]' \ -F "emotions=true" ``` В сегментах появится эмоция рядом с ролью: ```json { "segments": [ { "start": 0.0, "end": 2.1, "text": "Служба поддержки, здравствуйте.", "speaker": "Оператор", "emotion": { "label": "neutral", "confidence": 0.88 } }, { "start": 2.5, "end": 6.4, "text": "Я третий раз звоню по одному и тому же вопросу!", "speaker": "Клиент", "emotion": { "label": "angry", "confidence": 0.91 } } ] } ``` Эмоция приходит не для каждого сегмента — если модель не смогла оценить фрагмент, поля `emotion` в нём не будет, поэтому проверяйте его наличие. Подробнее — на странице [распознавание эмоций](/emotions). Эмоции усиливают аналитику ниже. Если в том же запросе передан `prompt`, языковая модель получает расшифровку уже с метками — реплики приходят к ней в виде `Клиент [angry]: …`. Поэтому и `sentiment` в карточке звонка, и оценка работы оператора опираются не только на слова, но и на тон голоса: вежливая по тексту фраза, сказанная раздражённо, больше не выглядит нейтральной. ## Аналитика звонка [#аналитика-звонка] Самое ценное — сразу получить из звонка структурированную аналитику. Для этого добавьте `prompt` (и, для строгой структуры, `json_schema`). Модель получает расшифровку с ролями — а если включены эмоции, то и с ними, — поэтому анализ учитывает, кто что сказал и с какой интонацией. Подробно про режимы — на странице [LLM-анализ](/llm-usage). Ниже — три самых частых сценария. ### Карточка звонка [#карточка-звонка] Готовая карточка для записи в CRM: тема, тональность, резюме, решён ли вопрос и список задач. ```json { "type": "object", "properties": { "topic": { "type": "string", "description": "Причина обращения" }, "sentiment": { "type": "string", "enum": ["положительная", "нейтральная", "отрицательная"] }, "summary": { "type": "string", "description": "Резюме разговора" }, "resolved": { "type": "boolean", "description": "Решён ли вопрос клиента" }, "action_items": { "type": "array", "items": { "type": "string" } } }, "required": ["topic", "sentiment", "summary", "resolved", "action_items"] } ``` Prompt: `Проанализируй телефонный разговор в поддержке и заполни карточку звонка.` ### Оценка качества (QA) [#оценка-качества-qa] Проверка работы оператора по чек-листу для контроля качества. ```json { "type": "object", "properties": { "greeting": { "type": "boolean", "description": "Оператор поздоровался и представился" }, "polite": { "type": "boolean", "description": "Оператор был вежлив" }, "resolved": { "type": "boolean", "description": "Вопрос клиента решён" }, "score": { "type": "integer", "description": "Оценка звонка от 1 до 5" }, "comment": { "type": "string", "description": "Короткий комментарий к оценке" } }, "required": ["greeting", "polite", "resolved", "score", "comment"] } ``` Prompt: `Оцени работу оператора в этом звонке по чек-листу.` ### Извлечение данных в CRM [#извлечение-данных-в-crm] Вытащить из разговора конкретные поля для автоматического заполнения карточки сделки. ```json { "type": "object", "properties": { "client_name": { "type": "string" }, "phone": { "type": "string" }, "product": { "type": "string", "description": "Обсуждаемый товар или услуга" }, "amount": { "type": "number", "description": "Сумма сделки, если названа" }, "next_step": { "type": "string", "description": "О чём договорились" } }, "required": ["client_name", "product"] } ``` Prompt: `Извлеки данные о клиенте и сделке из разговора.` ## Собираем всё вместе [#собираем-всё-вместе] Один запрос для русского звонка: распознавание `nexara-ru`, диаризация с `num_speakers=2`, роли `Оператор`/`Клиент` и карточка звонка через `prompt` + `json_schema`. В [Python SDK](/python-sdk) роли и схема передаются обычными списками и словарями — без ручной сериализации в JSON. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="call.mp3", model="nexara-ru", task="diarize", num_speakers=2, roles=["Оператор", "Клиент"], prompt="Проанализируй телефонный разговор в поддержке и заполни карточку звонка.", json_schema={ "type": "object", "properties": { "topic": {"type": "string"}, "sentiment": { "type": "string", "enum": ["положительная", "нейтральная", "отрицательная"], }, "summary": {"type": "string"}, "resolved": {"type": "boolean"}, "action_items": {"type": "array", "items": {"type": "string"}}, }, "required": ["topic", "sentiment", "summary", "resolved", "action_items"], }, ) print(result.llm_output) # готовая карточка звонка (dict) ``` В [TypeScript SDK](/typescript-sdk) роли и схема передаются обычными массивами и объектами — без ручной сериализации в JSON. ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "call.mp3", model: "nexara-ru", task: "diarize", num_speakers: 2, roles: ["Оператор", "Клиент"], prompt: "Проанализируй телефонный разговор в поддержке и заполни карточку звонка.", json_schema: { type: "object", properties: { topic: { type: "string" }, sentiment: { type: "string", enum: ["положительная", "нейтральная", "отрицательная"], }, summary: { type: "string" }, resolved: { type: "boolean" }, action_items: { type: "array", items: { type: "string" } }, }, required: ["topic", "sentiment", "summary", "resolved", "action_items"], }, }); console.log(result.llm_output); // готовая карточка звонка (объект) ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=nexara-ru" \ -F "task=diarize" \ -F "num_speakers=2" \ -F 'roles=["Оператор", "Клиент"]' \ -F "prompt=Проанализируй телефонный разговор в поддержке и заполни карточку звонка." \ -F 'json_schema={"type":"object","properties":{"topic":{"type":"string"},"sentiment":{"type":"string","enum":["положительная","нейтральная","отрицательная"]},"summary":{"type":"string"},"resolved":{"type":"boolean"},"action_items":{"type":"array","items":{"type":"string"}}},"required":["topic","sentiment","summary","resolved","action_items"]}' ``` ```python import json import requests schema = { "type": "object", "properties": { "topic": {"type": "string"}, "sentiment": { "type": "string", "enum": ["положительная", "нейтральная", "отрицательная"], }, "summary": {"type": "string"}, "resolved": {"type": "boolean"}, "action_items": {"type": "array", "items": {"type": "string"}}, }, "required": ["topic", "sentiment", "summary", "resolved", "action_items"], } with open("call.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={ "model": "nexara-ru", "task": "diarize", "num_speakers": "2", "roles": json.dumps(["Оператор", "Клиент"]), "prompt": "Проанализируй телефонный разговор в поддержке и заполни карточку звонка.", "json_schema": json.dumps(schema), }, ) result = response.json() print(result["llm_output"]) ``` Node.js 18+: ```javascript import fs from "fs"; const schema = { type: "object", properties: { topic: { type: "string" }, sentiment: { type: "string", enum: ["положительная", "нейтральная", "отрицательная"], }, summary: { type: "string" }, resolved: { type: "boolean" }, action_items: { type: "array", items: { type: "string" } }, }, required: ["topic", "sentiment", "summary", "resolved", "action_items"], }; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("call.mp3")]), "call.mp3"); form.append("model", "nexara-ru"); form.append("task", "diarize"); form.append("num_speakers", "2"); form.append("roles", JSON.stringify(["Оператор", "Клиент"])); form.append("prompt", "Проанализируй телефонный разговор в поддержке и заполни карточку звонка."); form.append("json_schema", JSON.stringify(schema)); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const result = await response.json(); console.log(result.llm_output); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { schema := `{"type":"object","properties":{"topic":{"type":"string"},"sentiment":{"type":"string","enum":["положительная","нейтральная","отрицательная"]},"summary":{"type":"string"},"resolved":{"type":"boolean"},"action_items":{"type":"array","items":{"type":"string"}}},"required":["topic","sentiment","summary","resolved","action_items"]}` file, _ := os.Open("call.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "call.mp3") io.Copy(part, file) writer.WriteField("model", "nexara-ru") writer.WriteField("task", "diarize") writer.WriteField("num_speakers", "2") writer.WriteField("roles", `["Оператор", "Клиент"]`) writer.WriteField("prompt", "Проанализируй телефонный разговор в поддержке и заполни карточку звонка.") writer.WriteField("json_schema", schema) writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" schema = { "type" => "object", "properties" => { "topic" => { "type" => "string" }, "sentiment" => { "type" => "string", "enum" => ["положительная", "нейтральная", "отрицательная"], }, "summary" => { "type" => "string" }, "resolved" => { "type" => "boolean" }, "action_items" => { "type" => "array", "items" => { "type" => "string" } }, }, "required" => ["topic", "sentiment", "summary", "resolved", "action_items"], } uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("call.mp3")], ["model", "nexara-ru"], ["task", "diarize"], ["num_speakers", "2"], ["roles", ["Оператор", "Клиент"].to_json], ["prompt", "Проанализируй телефонный разговор в поддержке и заполни карточку звонка."], ["json_schema", schema.to_json], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)["llm_output"] ``` В ответе придёт расшифровка с ролями и готовая карточка звонка: ```json { "transcription": { "task": "diarize", "segments": [ { "start": 0.0, "end": 2.1, "text": "Служба поддержки, здравствуйте.", "speaker": "Оператор" }, { "start": 2.5, "end": 5.0, "text": "Здравствуйте, хочу оформить возврат.", "speaker": "Клиент" } ] }, "llm_output": { "topic": "Возврат товара", "sentiment": "нейтральная", "summary": "Клиент попросил оформить возврат, оператор объяснил условия и создал заявку.", "resolved": true, "action_items": ["Отправить клиенту инструкцию по возврату"] } } ``` ## Лайфхаки [#лайфхаки] * **Двухканальная запись.** Если телефония поддерживает раздельную запись каналов, включите её — вместе с `num_speakers=2` это даёт максимально точное разделение. * **Точные роли.** В сложных звонках передавайте роли с описаниями (`{"Оператор": "сотрудник поддержки", "Клиент": "позвонивший"}`) — так модель распределяет их точнее. * **Русская речь — `nexara-ru`.** Для звонков на русском эта модель точнее универсальной. Эмоциональный анализ поддерживается только на этой модели. * **Передавайте большие файлы [по ссылке](/file-url).** Удобно отдавать запись по URL (например, pre-signed ссылке на хранилище), а не загружать файлом. Диаризация, разметка ролей, распознавание эмоций и LLM-анализ тарифицируются дополнительно к стоимости распознавания. Итоговую стоимость смотрите на странице [тарифов](/pricing). # Тарифы (/pricing) Оплата рассчитывается по фактическому использованию. Распознавание речи (транскрибация) и дополнения тарифицируются посекундно (например, если в запросе аудио длиной 15 секунд, то спишется 9 копеек). Округление секунд идет вверх. # Фильтрация мата (/profanity-filtering) Когда включён параметр `profanity_filter`, русский мат в расшифровке маскируется: первая и последняя буквы слова сохраняются, а середина заменяется звёздочками. Маскирование применяется ко всему ответу — к полю `text`, к сегментам и к словам с временными метками. Фильтр рассчитан на русскую нецензурную лексику. Он определяет мат по форме слова, а не по совпадению букв, поэтому безобидные похожие слова (`рубля`, `страховка`, `ребаланс`) не затрагиваются. ## Как включить [#как-включить] Передайте `profanity_filter=true` в запросе на `POST /v1/audio/transcriptions`. Замените `nx-XXXXXXXXXXXXXXXXXXXXXXXX` на свой [API-ключ](/authentication), а `audio.mp3` — на путь к вашему файлу. Официальная библиотека [`nexara`](/python-sdk) (`pip install nexara`): ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="audio.mp3", profanity_filter=True, ) print(result.text) ``` Официальная библиотека [`nexara-sdk`](/typescript-sdk) (`npm install nexara-sdk`): ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "audio.mp3", profanity_filter: true, }); console.log(result.text); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@audio.mp3" \ -F "model=whisper-1" \ -F "profanity_filter=true" ``` ```python import requests with open("audio.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={ "model": "whisper-1", "profanity_filter": "true", }, ) print(response.json()["text"]) ``` Параметр `profanity_filter` не входит в стандартный набор OpenAI SDK — передайте его через `extra_body`. ```python from openai import OpenAI client = OpenAI( api_key="nx-XXXXXXXXXXXXXXXXXXXXXXXX", base_url="https://api.nexara.ru/v1", ) with open("audio.mp3", "rb") as audio_file: transcript = client.audio.transcriptions.create( model="whisper-1", file=audio_file, extra_body={"profanity_filter": True}, ) print(transcript.text) ``` Node.js 18+: ```javascript import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("audio.mp3")]), "audio.mp3"); form.append("model", "whisper-1"); form.append("profanity_filter", "true"); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const result = await response.json(); console.log(result.text); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("audio.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "audio.mp3") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.WriteField("profanity_filter", "true") writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("audio.mp3")], ["model", "whisper-1"], ["profanity_filter", "true"], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)["text"] ``` ## Результат [#результат] В ответе нецензурные слова приходят уже замаскированными: ```json { "text": "Чтобы у тебя х*й во лбу вырос, у***к, б***ь! Переписываем. Бибикало е****е, б***ь." } ``` Фильтрация мата тарифицируется дополнительно к стоимости распознавания. Стоимость услуги смотрите на странице [тарифов](/pricing). # Python SDK (/python-sdk) **`nexara`** — официальная Python-библиотека для Nexara API. Она берёт на себя сборку запросов, разбор ответов в типизированные модели, обработку ошибок и повторные попытки, а асинхронные задачи превращает в один вызов `job.wait()`. Библиотека полностью типизирована (mypy strict) и работает на Python 3.10+. ```bash pip install nexara ``` Сохраните API-ключ в переменной окружения `NEXARA_API_KEY`. SDK читает её автоматически при создании клиента, поэтому ключ не нужно указывать в коде — и он не попадёт в репозиторий: ```bash export NEXARA_API_KEY=nx-... ``` Команда `export` задаёт переменную только для текущей сессии терминала. Чтобы ключ сохранялся между сессиями, добавьте эту строку в файл конфигурации вашей оболочки (например, `~/.zshrc` или `~/.bashrc`). На сервере переменную удобнее задавать через настройки окружения или менеджер секретов. ## Быстрый старт [#быстрый-старт] ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY text = client.transcriptions.create(file="audio.mp3").text print(text) ``` Клиент берёт ключ из `NEXARA_API_KEY` — той самой переменной, что вы задали выше, — поэтому в коде его указывать не нужно. Передать ключ явным аргументом `api_key=` тоже можно, но не храните его в коде и не коммитьте в репозиторий. ## Клиент [#клиент] ```python client = Nexara( # api_key по умолчанию берётся из переменной окружения NEXARA_API_KEY, # либо можете передать ключ сами: api_key="nx-..." timeout=600.0, # таймаут запроса в секундах max_retries=2, # повторы при 429 и сетевых сбоях ) ``` Клиент можно использовать как контекстный менеджер (`with Nexara() as client:`) — тогда HTTP-соединения закроются автоматически. ## Транскрибация [#транскрибация] Передайте ровно один из параметров: `file` (путь, `bytes` или открытый бинарный файл) или `url` (прямая ссылка). Файл, переданный путём, стримится с диска и не загружается в память целиком. ```python # Локальный файл result = client.transcriptions.create(file="audio.mp3") print(result.text) # Файл по ссылке result = client.transcriptions.create(url="https://example.com/audio.mp3") print(result.text) ``` Параметры те же, что и у [API](/api-reference/create_transcription_audio_transcriptions_post): `model`, `language`, `response_format`, `timestamp_granularities`, `profanity_filter` и другие. Форматы `text`, `srt` и `vtt` возвращаются обычной строкой, `json` и `verbose_json` — типизированными объектами: ```python srt = client.transcriptions.create(file="video.mp4", response_format="srt") with open("video.srt", "w", encoding="utf-8") as f: f.write(srt) verbose = client.transcriptions.create( file="audio.mp3", response_format="verbose_json", timestamp_granularities=["word"], ) for word in verbose.words: print(f"{word.start:.2f}–{word.end:.2f}: {word.word}") ``` ## Диаризация и роли [#диаризация-и-роли] Разделение по говорящим включается так же, как в API, — `task="diarize"`. Список ролей передаётся обычным списком или словарём Python: SDK сам сериализует его в JSON. ```python call = client.transcriptions.create( file="call.mp3", task="diarize", num_speakers=2, roles=["Оператор", "Клиент"], # или "auto", или {"Роль": "описание"} ) for segment in call.segments: print(f"{segment.speaker}: {segment.text}") ``` Подробнее о режимах — на странице [спикеры и роли](/speakers-roles). ## Эмоции [#эмоции] Флаг `emotions=True` добавляет к каждому сегменту диаризации объект `emotion`: метку `label` (`angry`, `sad`, `neutral` или `positive`), уверенность модели `confidence` и распределение вероятностей `probs`. ```python call = client.transcriptions.create( file="call.mp3", task="diarize", model="nexara-ru", emotions=True, ) for segment in call.segments: if segment.emotion: print(f"{segment.speaker}: {segment.emotion.label} " f"({segment.emotion.confidence:.0%}) — {segment.text}") else: print(f"{segment.speaker}: эмоция не определена — {segment.text}") ``` Эмоция приходит не для каждого сегмента: если модель не смогла оценить фрагмент, поле `emotion` останется `None`. Проверяйте его перед чтением, как в примере выше. У слов (`words`) эмоции нет — она считается по сегменту целиком. Полный набор меток доступен константой `EMOTION_LABELS`: ```python from nexara import EMOTION_LABELS print(EMOTION_LABELS) # ('angry', 'sad', 'neutral', 'positive') ``` Подробнее об условиях и ограничениях — на странице [распознавание эмоций](/emotions). Распознавание эмоций тарифицируется дополнительно к стоимости диаризации. Если ни один сегмент не удалось оценить, надбавка не списывается. Стоимость услуги смотрите на странице [тарифов](/pricing). ## Асинхронные задачи [#асинхронные-задачи] Для длинных записей используйте [асинхронный режим](/async-transcription): `create_job()` ставит задачу и сразу возвращает объект `Job`, а `job.wait()` опрашивает статус и возвращает готовый результат. ```python job = client.transcriptions.create_job(file="long_recording.mp3") print(job.job_id, job.status) # in_progress result = job.wait() # опрашивает статус; таймаут по умолчанию — 1800 секунд print(result.text) ``` Задачу можно забрать позже — в том числе из другого процесса: ```python job = client.transcriptions.retrieve_job(job_id) if job.status == "complete": print(job.result) ``` Что важно знать: * Результат хранится **12 часов** с момента создания задачи, затем удаляется — после этого `retrieve_job()` вернёт `NotFoundError`. * Одновременно на один ключ может выполняться до **200** задач. * Если задача завершилась ошибкой, `wait()` бросает `JobFailedError`. Неудавшаяся задача **не тарифицируется** — повторная отправка бесплатна. * Если задача не успела за таймаут, `wait()` бросает `JobTimeoutError`; сама задача при этом не отменяется, её можно забрать позже через `retrieve_job()`. «Асинхронный» здесь означает фоновую обработку на сервере, а не asyncio. Для asyncio используйте [AsyncNexara](#asyncio) — оба клиента поддерживают и `create()`, и `create_job()`. ## LLM-анализ [#llm-анализ] Передайте `prompt`, чтобы прогнать расшифровку через языковую модель, и `json_schema` — чтобы получить [структурированный ответ](/llm-usage). Схема передаётся обычным словарём Python, а `llm_output` возвращается уже разобранным объектом — никакого ручного `json.dumps`/`json.loads`. ```python result = client.transcriptions.create( file="meeting.mp3", prompt="Составь протокол встречи: резюме, решения и задачи.", json_schema={ "type": "object", "properties": { "summary": {"type": "string"}, "decisions": {"type": "array", "items": {"type": "string"}}, "action_items": {"type": "array", "items": {"type": "string"}}, }, "required": ["summary", "decisions", "action_items"], }, ) print(result.llm_output["summary"]) # dict по вашей схеме print(result.transcription.text) # расшифровка, из которой он получен ``` Без `json_schema` поле `llm_output` — строка со свободным ответом модели. ## asyncio [#asyncio] `AsyncNexara` — тот же интерфейс под `await`: ```python import asyncio from nexara import AsyncNexara async def main(): async with AsyncNexara() as client: result = await client.transcriptions.create(file="audio.mp3") print(result.text) asyncio.run(main()) ``` ## Ошибки [#ошибки] Некорректные запросы (например, `file` и `url` одновременно, слишком много ролей) отклоняются на стороне клиента с `NexaraValidationError` — ещё до обращения к серверу. Ошибки сервера превращаются в типизированные исключения по коду статуса: | Исключение | Код | Когда возникает | | -------------------------- | --- | ---------------------------------------------------- | | `BadRequestError` | 400 | Некорректные параметры запроса | | `InsufficientBalanceError` | 402 | Недостаточно средств на балансе | | `AuthenticationError` | 403 | Ключ не передан или недействителен | | `NotFoundError` | 404 | Задача, модель или ключ не найдены | | `RateLimitError` | 429 | Превышен лимит частоты или число одновременных задач | | `InternalServerError` | 500 | Внутренняя ошибка сервера | | `APIConnectionError` | — | Запрос не дошёл до сервера (сеть, таймаут) | Все они наследуются от `NexaraError`, а у ошибок API есть поля `status_code` и `detail` с сообщением сервера. Полный список кодов — на странице [ошибок](/errors). ```python from nexara import Nexara, InsufficientBalanceError, RateLimitError client = Nexara() try: result = client.transcriptions.create(file="audio.mp3") except InsufficientBalanceError as e: # 402 print("Пополните баланс:", e.detail) except RateLimitError: print("Слишком много запросов, попробуйте позже.") ``` ## Ссылки [#ссылки] * Пакет на PyPI: [pypi.org/project/nexara](https://pypi.org/project/nexara/) * Справочник параметров API: [референс API](/api-reference/create_transcription_audio_transcriptions_post) # Быстрый старт (/quickstart) За пару минут вы получите API-ключ и сделаете первый запрос на транскрибацию. ### Получите API-ключ [#получите-api-ключ] Создайте ключ в личном кабинете — [app.nexara.ru](https://app.nexara.ru). Ключ выглядит как `nx-XXXXXXXXXXXXXXXXXXXXXXXX` и передаётся в заголовке `Authorization`. Подробнее — на странице [аутентификация](/authentication). Не вставляйте ключ прямо в код. Сохраните его в переменную окружения: `export NEXARA_API_KEY="nx-..."`. ### Сделайте первый запрос [#сделайте-первый-запрос] Отправьте аудиофайл на `POST /v1/audio/transcriptions`. Замените `nx-XXXXXXXXXXXXXXXXXXXXXXXX` на свой ключ, а `audio.mp3` — на путь к вашему файлу. Самый быстрый способ — официальная библиотека [`nexara`](/python-sdk): ```bash pip install nexara ``` ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create(file="audio.mp3") print(result.text) ``` Официальная библиотека [`nexara-sdk`](/typescript-sdk): ```bash npm install nexara-sdk ``` ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const { text } = await client.transcriptions.create({ file: "audio.mp3" }); console.log(text); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@audio.mp3" \ -F "model=whisper-1" ``` ```python import requests with open("audio.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={"model": "whisper-1"}, ) print(response.json()["text"]) ``` Nexara API совместим с OpenAI SDK — достаточно указать `base_url`. ```python from openai import OpenAI client = OpenAI( api_key="nx-XXXXXXXXXXXXXXXXXXXXXXXX", base_url="https://api.nexara.ru/v1", ) with open("audio.mp3", "rb") as audio_file: transcript = client.audio.transcriptions.create( model="whisper-1", file=audio_file, ) print(transcript.text) ``` Node.js 18+: ```javascript import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("audio.mp3")]), "audio.mp3"); form.append("model", "whisper-1"); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const result = await response.json(); console.log(result.text); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("audio.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "audio.mp3") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("audio.mp3")], ["model", "whisper-1"], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)["text"] ``` В ответе придёт распознанный текст: ```json { "text": "Привет! Это пример транскрибации." } ``` Готово — вы сделали первый запрос. ## Что дальше [#что-дальше] Официальная библиотека: `pip install nexara` — и весь API в несколько строк. Официальная библиотека: `npm install nexara-sdk` — типизированный клиент для Node.js. Транскрибация файла с диска на разных языках программирования. Разделение аудио по говорящим и разметка ролей. `json`, `text`, `verbose_json`, а также субтитры `srt` и `vtt`. Резюме, извлечение данных и структурированный вывод по расшифровке. Фоновая обработка длинных записей по `job_id`. Полное описание эндпоинтов и всех параметров. # Форматы ответа (/response-formats) Nexara API позволяет указать формат, в котором вы хотите получить результаты. Используя параметр `response_format` в вашем запросе, вы можете настроить вывод так, чтобы он наилучшим образом соответствовал вашим потребностям, будь то простой текст, структурированные данные или готовые к использованию файлы субтитров. Выбор `srt` или `vtt` дает не просто текст с таймингами; эти форматы предоставляют оформленные субтитры, автоматически оптимизированные для комфортного чтения и восприятия на экране. Примеры использования API смотрите в [документации API](/api-reference/create_transcription_audio_transcriptions_post). API поддерживает следующие форматы вывода: * **`json`:** Возвращает стандартный JSON-объект, содержащий транскрибированный текст. * **`text`:** Возвращает транскрипцию в виде одной строки простого текста. * **`verbose_json`:** Возвращает подробный JSON-объект, содержащий текст, язык, продолжительность, а также, возможно, временные метки на уровне сегментов и слов (если запрошено через `timestamp_granularities[]`). * **`srt`:** Возвращает транскрипцию, отформатированную как файл субтитров SRT. * **`vtt`:** Возвращает транскрипцию, отформатированную как файл субтитров WebVTT. Чтобы выбрать формат, передайте параметр `response_format` в запросе: ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="audio.mp3", response_format="verbose_json", ) ``` В [Python SDK](/python-sdk) форматы `json` и `verbose_json` возвращаются типизированными объектами, а `text`, `srt` и `vtt` — обычной строкой. ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "audio.mp3", response_format: "verbose_json", }); ``` В [TypeScript SDK](/typescript-sdk) форматы `json` и `verbose_json` возвращаются типизированными объектами, а `text`, `srt` и `vtt` — обычной строкой. ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@audio.mp3" \ -F "model=whisper-1" \ -F "response_format=verbose_json" ``` ## Примеры вывода [#примеры-вывода] Для одной и той же записи разные форматы возвращают результат по-разному. ### `json` [#json] ```json { "text": "Привет! Это пример транскрибации." } ``` ### `text` [#text] Ответ приходит текстом без JSON-обёртки: ```text Привет! Это пример транскрибации. ``` ### `verbose_json` [#verbose_json] Подробный объект с текстом, языком, длительностью и сегментами. Массив `words` появляется, только если запрошены [временные метки слов](/timestamps) через `timestamp_granularities[]=word`. Ответ подобен ответу модели `whisper-1` на официальном API от OpenAI. ```json { "task": "transcribe", "language": "ru", "duration": 4.3, "text": "Привет! Это пример транскрибации.", "segments": [ { "id": 0, "seek": 0, "start": 0.0, "end": 2.1, "text": "Привет! Это пример", "tokens": [50150, 386, 2338, 2608, 1828, ...], "temperature": 0.0, "avg_logprob": 0.0, "compression_ratio": 1.698224852071006, "no_speech_prob": 0.0 }, { "id": 1, "seek": 2.1, "start": 2.1, "end": 4.3, "text": "транскрибации.", "tokens": [2338, 2608, 1828, ...], "temperature": 0.0, "avg_logprob": 0.0, "compression_ratio": 1.5, "no_speech_prob": 0.0 } ] } ``` ### `srt` [#srt] ```text 1 00:00:00,000 --> 00:00:02,100 Привет! Это пример 2 00:00:02,100 --> 00:00:04,300 транскрибации. ``` ### `vtt` [#vtt] ```text WEBVTT 1 00:00:00.000 --> 00:00:02.100 Привет! Это пример 2 00:00:02.100 --> 00:00:04.300 транскрибации. ``` Когда запрашиваете ответ в формате субтитров, (`srt` или `vtt`) **не** используйте `response.text` если делаете запрос через `request` библиотеку в Python. Используйте `response.json`, как и для остальных форматов ответа. # Спикеры и роли (/speakers-roles) **Диаризация** разделяет аудио по говорящим: API определяет, какой фрагмент речи кому принадлежит, и присваивает каждому сегменту метку говорящего (`speaker_0`, `speaker_1`) — или понятную роль (`Клиент`, `Агент`). Концептуально режим описан на странице [диаризация](/diarization). ## Разделение по говорящим [#разделение-по-говорящим] Передайте `task=diarize` в запросе на `POST /v1/audio/transcriptions`. Замените `nx-XXXXXXXXXXXXXXXXXXXXXXXX` на свой [API-ключ](/authentication), а `meeting.mp3` — на путь к вашему файлу. Официальная библиотека [`nexara`](/python-sdk) (`pip install nexara`): ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="meeting.mp3", task="diarize", ) for segment in result.segments: print(f"{segment.speaker}: {segment.text}") ``` Официальная библиотека [`nexara-sdk`](/typescript-sdk) (`npm install nexara-sdk`): ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "meeting.mp3", task: "diarize", }); for (const segment of result.segments) { console.log(`${segment.speaker}: ${segment.text}`); } ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@meeting.mp3" \ -F "model=whisper-1" \ -F "task=diarize" ``` ```python import requests with open("meeting.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={ "model": "whisper-1", "task": "diarize", }, ) print(response.json()) ``` Node.js 18+: ```javascript import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("meeting.mp3")]), "meeting.mp3"); form.append("model", "whisper-1"); form.append("task", "diarize"); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); console.log(await response.json()); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("meeting.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "meeting.mp3") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.WriteField("task", "diarize") writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("meeting.mp3")], ["model", "whisper-1"], ["task", "diarize"], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts response.body ``` В ответе каждый сегмент содержит метку говорящего в поле `speaker`: ```json { "task": "diarize", "language": "ru", "duration": 12.4, "text": "Здравствуйте! Чем могу помочь? Хочу оформить возврат.", "segments": [ { "start": 0.0, "end": 2.1, "text": "Здравствуйте! Чем могу помочь?", "speaker": "speaker_0" }, { "start": 2.5, "end": 5.0, "text": "Хочу оформить возврат.", "speaker": "speaker_1" } ] } ``` Метки говорящих сохраняются во всех [форматах ответа](/response-formats). Например, при `response_format=text`: ``` Speaker 0: Здравствуйте! Чем могу помочь? Speaker 1: Хочу оформить возврат. ``` Минимальная длительность аудио для диаризации — **3 секунды**. Более короткие записи выдают ошибку `400`. Если вы заранее знаете число говорящих, передайте `num_speakers` — это повысит качество разделения. ## Разметка ролей [#разметка-ролей] По умолчанию говорящие обозначаются безликими метками `speaker_0`, `speaker_1`. **Разметка ролей** заменяет их на понятные роли — например, `Клиент` и `Агент`. Она включается параметром `roles` и доступна **только вместе с диаризацией** (`task=diarize`). Поддерживаются три режима: `roles=auto` — модель сама определяет и назначает подходящие роли на основе содержания разговора. JSON-массив строк, например `["Клиент", "Агент"]` — вы отправляете набор ролей, а модель распределяет их между говорящими. JSON-объект `{"Клиент": "Клиент, который обратился в поддержку", "Агент": "Оператор поддержки"}` — фиксированные роли с описаниями, которые помогают модели точнее их распознать. Достаточно добавить поле `roles` к запросу на диаризацию. Значение — либо `auto`, либо JSON-строка со списком или объектом ролей. В SDK роли передаются обычным списком или словарём Python — сериализация в JSON выполняется автоматически. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="call.mp3", task="diarize", roles=["Клиент", "Агент"], # или "auto", или {"Роль": "описание"} ) for segment in result.segments: print(f"{segment.speaker}: {segment.text}") ``` В SDK роли передаются обычным массивом или объектом — сериализация в JSON выполняется автоматически. ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "call.mp3", task: "diarize", roles: ["Клиент", "Агент"], // или "auto", или { "Роль": "описание" } }); for (const segment of result.segments) { console.log(`${segment.speaker}: ${segment.text}`); } ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@call.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F 'roles=["Клиент", "Агент"]' ``` ```python import requests with open("call.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={ "model": "whisper-1", "task": "diarize", "roles": '["Клиент", "Агент"]', }, ) print(response.json()) ``` ```javascript import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("call.mp3")]), "call.mp3"); form.append("model", "whisper-1"); form.append("task", "diarize"); form.append("roles", JSON.stringify(["Клиент", "Агент"])); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); console.log(await response.json()); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("call.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "call.mp3") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.WriteField("task", "diarize") writer.WriteField("roles", `["Клиент", "Агент"]`) writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("call.mp3")], ["model", "whisper-1"], ["task", "diarize"], ["roles", ["Клиент", "Агент"].to_json], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts response.body ``` В результате метки `speaker_N` заменяются на назначенные роли: ```json { "task": "diarize", "segments": [ { "start": 0.0, "end": 2.1, "text": "Здравствуйте! Чем могу помочь?", "speaker": "Агент" }, { "start": 2.5, "end": 5.0, "text": "Хочу оформить возврат.", "speaker": "Клиент" } ] } ``` Если модель определила больше говорящих, чем было передано в списке, остальные спикеры будут помечены как неизвестные. Например, если в записи говорило 4 человека, а в списке передано только 2, остальные будут помечены как `unknown_1` и `unknown_2`. ### Ограничения [#ограничения] * В одном запросе можно задать не более **10** ролей. * Каждое название роли — непустая строка длиной не более **64** символов, роли не должны повторяться. * Описание роли (в режиме с описаниями) не должно превышать **500** символов. Разметка ролей тарифицируется дополнительно к стоимости диаризации. Если разметку не удалось применить (например, в записи не оказалось говорящих), надбавка не списывается, а в ответе остаются исходные метки `speaker_N`. Стоимость услуги смотрите на странице [тарифов](/pricing). ## Что учитывать [#что-учитывать] * **Хранение ключа.** Не вставляйте API-ключ прямо в код. Храните его в переменной окружения (например, `export NEXARA_API_KEY="nx-..."`) и ссылайтесь на неё. Подробнее — на странице [аутентификации](/authentication#curl). # Структурированный вывод (/structured-output) # Субтитры (/subtitles) Nexara умеет возвращать готовые файлы субтитров — `srt` или `vtt`. Их можно сразу подключить к видео в плеере или редакторе. Достаточно указать нужный формат в параметре `response_format`; временные метки при этом рассчитываются автоматически, отдельно запрашивать их не нужно. ## Создание субтитров [#создание-субтитров] Передайте `response_format=srt` (или `vtt`) в запросе на `POST /v1/audio/transcriptions`. Замените `nx-XXXXXXXXXXXXXXXXXXXXXXXX` на свой [API-ключ](/authentication), а `video.mp4` — на путь к вашему файлу. Официальная библиотека [`nexara`](/python-sdk) возвращает форматы `srt` и `vtt` обычной строкой — готовой к записи в файл. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY subtitles = client.transcriptions.create( file="video.mp4", response_format="srt", # или "vtt" ) with open("video.srt", "w", encoding="utf-8") as f: f.write(subtitles) ``` Официальная библиотека [`nexara-sdk`](/typescript-sdk) возвращает форматы `srt` и `vtt` обычной строкой — готовой к записи в файл. ```typescript import { writeFile } from "node:fs/promises"; import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const subtitles = await client.transcriptions.create({ file: "video.mp4", response_format: "srt", // или "vtt" }); await writeFile("video.srt", subtitles); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@video.mp4" \ -F "model=whisper-1" \ -F "response_format=srt" ``` ```python import requests with open("video.mp4", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={ "model": "whisper-1", "response_format": "srt", }, ) subtitles = response.json() with open("video.srt", "w", encoding="utf-8") as f: f.write(subtitles) ``` ```python from openai import OpenAI client = OpenAI( api_key="nx-XXXXXXXXXXXXXXXXXXXXXXXX", base_url="https://api.nexara.ru/v1", ) with open("video.mp4", "rb") as audio_file: subtitles = client.audio.transcriptions.create( model="whisper-1", file=audio_file, response_format="srt", ) print(subtitles) ``` Node.js 18+: ```javascript import fs from "fs"; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("video.mp4")]), "video.mp4"); form.append("model", "whisper-1"); form.append("response_format", "srt"); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const subtitles = await response.json(); fs.writeFileSync("video.srt", subtitles); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { file, _ := os.Open("video.mp4") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "video.mp4") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.WriteField("response_format", "srt") writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("video.mp4")], ["model", "whisper-1"], ["response_format", "srt"], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end File.write("video.srt", JSON.parse(response.body)) ``` В ответе приходит готовый файл субтитров: ```text 1 00:00:00,000 --> 00:00:02,100 Привет! Это пример 2 00:00:02,100 --> 00:00:04,300 транскрибации. ``` Чтобы получить формат WebVTT вместо SRT, замените `response_format=srt` на `response_format=vtt`. Когда запрашиваете субтитры (`srt` или `vtt`) через библиотеку `requests` в Python, читайте ответ через `response.json()`, а **не** `response.text` — как и для остальных форматов ответа. Подробнее — на странице [форматы ответа](/response-formats). ## Как формируются субтитры [#как-формируются-субтитры] Nexara API формирует субтитры, удобные для чтения на экране: * длинные фразы автоматически разбиваются на короткие реплики подходящей длины; * время показа каждой реплики подбирается так, чтобы субтитры не мелькали слишком быстро и не задерживались слишком долго; * соседние субтитры не накладываются друг на друга. Поэтому разбивка на реплики в `srt`/`vtt` может отличаться от сегментов в `verbose_json` — это ожидаемо и сделано ради читаемости. ## Субтитры с диаризацией [#субтитры-с-диаризацией] Если добавить `task=diarize`, в субтитрах перед каждой репликой появится метка говорящего (`Speaker 0`, `Speaker 1`) — или [роль](/speakers-roles), если включена разметка ролей. ```python subtitles = client.transcriptions.create( file="meeting.mp4", task="diarize", response_format="srt", ) ``` ```typescript const subtitles = await client.transcriptions.create({ file: "meeting.mp4", task: "diarize", response_format: "srt", }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@meeting.mp4" \ -F "model=whisper-1" \ -F "task=diarize" \ -F "response_format=srt" ``` Каждая реплика в субтитрах подписана говорящим: ```text 1 00:00:00,000 --> 00:00:02,100 Speaker 0: Здравствуйте! Чем могу помочь? 2 00:00:02,500 --> 00:00:05,000 Speaker 1: Хочу оформить возврат. ``` ## Что учитывать [#что-учитывать] * **Хранение ключа.** Не вставляйте API-ключ прямо в код. Храните его в переменной окружения (например, `export NEXARA_API_KEY="nx-..."`) и ссылайтесь на неё. Подробнее — на странице [аутентификации](/authentication#curl). # Поддерживаемые файлы (/supported-files) Nexara API для, в частности `/audio/transcriptions`, принимает файлы с различными MIME типами, представляющими аудио- и видеоформаты. При отправке файла через `multipart/form-data` убедитесь, что `Content-Type`, связанный с частью файла, содержит правильный формат. Ниже приведен список MIME типов, поддерживаемых в настоящее время: **Аудиоформаты:** * **WAV:** * `audio/wav` * `audio/x-wav` * `audio/wave` * **MP3:** * `audio/mp3` * `audio/mpeg` * `audio/mpg` * `audio/x-mpeg` * **M4A / AAC:** * `audio/x-m4a` * `audio/mp4` * `audio/mp4a-latm` * `audio/mpeg4` * `audio/aac` * `audio/vnd.dlna.adts` * `audio/x-aac` * **FLAC:** * `audio/flac` * **OGG (Vorbis/Opus):** * `audio/ogg` * `application/ogg` * `audio/oga` * **Opus:** * `audio/opus` * **AIFF:** * `audio/aiff` * `audio/x-aiff` * **ASF:** * `audio/asf` * **WebM:** * `audio/webm` **Видеоформаты (аудио будет извлечено):** * **MP4:** * `video/mp4` * **MOV:** * `video/quicktime` * **AVI:** * `video/x-msvideo` * **MKV:** * `video/x-matroska` * **WebM:** * `video/webm` Если есть конкретный MIME тип, поддержку которого вы хотели бы видеть, пожалуйста, напишите в [Поддержку](https://t.me/RND_RandoM). # Синхронная и асинхронная обработка (/sync-async) Nexara API предлагает два режима обработки аудио. Они принимают одни и те же параметры, но по-разному возвращают результат: синхронный режим возвращает готовую транскрипцию прямо в ответе, держа соединение открытым все время обработки, а асинхронный — сразу возвращает идентификатор задачи, результат которой вы забираете позже. `POST /v1/audio/transcriptions` — соединение остаётся открытым, пока файл обрабатывается, и результат приходит в теле ответа. Подходит для коротких аудио. `POST /v1/audio/transcriptions/async` — запрос сразу возвращает `job_id`, а обработка идёт в фоне. Готовый результат забирается опросом по `job_id`. Подходит для длинных файлов и пакетной обработки. ## Синхронная обработка [#синхронная-обработка] Отправьте аудио на `POST /v1/audio/transcriptions`. Соединение удерживается до конца обработки, после чего в ответе возвращается готовый текст. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create(file="audio.mp3") print(result.text) ``` ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "audio.mp3" }); console.log(result.text); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@audio.mp3" \ -F "model=whisper-1" ``` Ответ содержит результат сразу: ```json { "text": "Привет! Это пример транскрибации." } ``` Синхронный режим — это самый простой способ использовать API: один запрос, один ответ. Он оптимален для коротких записей, где время обработки невелико. Для длинных файлов синхронный запрос держит HTTP-соединение открытым всё время обработки, что чувствительно к сетевым таймаутам на стороне клиента и прокси. Для продолжительного аудио рекомендуется использовать асинхронный режим. ## Асинхронная обработка [#асинхронная-обработка] Асинхронный режим разбивает работу на два шага: постановку задачи и получение результата. ### Шаг 1. Поставить задачу [#шаг-1-поставить-задачу] Отправьте аудио на `POST /v1/audio/transcriptions/async`. Запрос принимает те же параметры, что и синхронный, но возвращается сразу, не дожидаясь обработки. ```python job = client.transcriptions.create_job(file="long-audio.mp3") print(job.job_id, job.status) # 5f1c2e3a-... in_progress ``` ```typescript const job = await client.transcriptions.createJob({ file: "long-audio.mp3" }); console.log(job.job_id, job.status); // 5f1c2e3a-... in_progress ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions/async \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@long-audio.mp3" \ -F "model=whisper-1" ``` В ответ приходит идентификатор задачи и её статус: ```json { "job_id": "5f1c2e3a-...", "status": "in_progress", "created_at": "2026-07-13T10:00:00+00:00" } ``` Сохраните `job_id` — по нему вы будете забирать результат. ### Шаг 2. Опросить статус [#шаг-2-опросить-статус] Отправляйте запросы на `GET /v1/audio/transcriptions/async/{job_id}`, пока задача не завершится. В [Python SDK](/python-sdk) опрос делает `job.wait()` — он возвращает готовый результат, когда задача завершится. Проверить статус вручную можно через `retrieve_job()`. ```python result = job.wait() print(result.text) ``` В [TypeScript SDK](/typescript-sdk) опрос делает `job.wait()` — он возвращает готовый результат, когда задача завершится. Проверить статус вручную можно через `retrieveJob()`. ```typescript const result = await job.wait(); console.log((result as { text: string }).text); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions/async/5f1c2e3a-... \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" ``` Так выглядит ответ API пока задача выполняется: ```json { "job_id": "5f1c2e3a-...", "status": "in_progress", "created_at": "2026-07-13T10:00:00+00:00", "completed_at": null, "result": null, "error": null } ``` А так — когда ответ получен: ```json { "job_id": "5f1c2e3a-...", "status": "complete", "created_at": "2026-07-13T10:00:00+00:00", "completed_at": "2026-07-13T10:01:12+00:00", "result": { "text": "Привет! Это пример транскрибации." }, "error": null } ``` После завершения в поле `result` появляется готовый текст расшифровки. Поле `result` может содержать любые [форматы ответа](/response-formats). После генерации ответа, результат хранится на сервере в течение 12 часов, а потом удаляется безвозвратно. ### Статусы задачи [#статусы-задачи] * `in_progress`: задача поставлена в очередь или обрабатывается. Поле `result` пустое; * `complete`: обработка завершена успешно. Результат находится в поле `result`, а время завершения — в `completed_at`. * `error`: обработка завершилась ошибкой. Описание причины находится в поле `error`. ## Что выбрать [#что-выбрать] для коротких записей, интерактивных сценариев и быстрой интеграции, когда важно получить результат одним запросом. для длинных файлов и пакетной обработки, чтобы не держать открытым HTTP-соединение и не упираться в клиентские таймауты. Оба режима ограничены частотой **10 запросов в секунду**. Для асинхронного режима действует дополнительный лимит — не более **200** одновременно выполняющихся задач на один API-ключ. Подробнее читайте на странице [лимитов](/docs/limits). # Постобработка текста (/text-postprocessing) # Временные метки (/timestamps) По умолчанию API возвращает временные метки на уровне **сегментов** — крупных фрагментов речи. Но можно запросить метки на уровне каждого слова: тогда для любого слова будет известно точное время начала и конца. Это удобно для субтитров, поиска по аудио и синхронизации текста со звуком. ## Как получить временные метки слов [#как-получить-временные-метки-слов] Передайте два параметра: * `timestamp_granularities[]=word` — уровень детализации меток; * `response_format=verbose_json` — подробный формат ответа, в котором содержится массив слов. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="audio.mp3", response_format="verbose_json", timestamp_granularities=["word"], ) for word in result.words: print(f"{word.start:.2f}–{word.end:.2f}: {word.word}") ``` ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "audio.mp3", response_format: "verbose_json", timestamp_granularities: ["word"], }); for (const word of result.words ?? []) { console.log(`${word.start.toFixed(2)}–${word.end.toFixed(2)}: ${word.word}`); } ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@audio.mp3" \ -F "model=whisper-1" \ -F "response_format=verbose_json" \ -F "timestamp_granularities[]=word" ``` Временные метки слов возвращаются только в формате `verbose_json`. В форматах `json` и `text` массив слов не передаётся. ## Структура ответа [#структура-ответа] В ответе появляется массив `words`, где для каждого слова указаны время начала (`start`), время конца (`end`) в секундах и уверенность модели (`prob`): ```json { "task": "transcribe", "text": "Привет мир", "words": [ { "word": "Привет", "start": 0.0, "end": 0.42, "prob": 0.99 }, { "word": "мир", "start": 0.42, "end": 0.88, "prob": 0.98 } ], ... } ``` ## Уровни детализации [#уровни-детализации] Метки на уровне крупных фрагментов речи. Подходит для большинства сценариев и работает без дополнительных параметров. Временные метки для каждого отдельного слова. Полезно для генерации субтитров. ## Временные метки и диаризация [#временные-метки-и-диаризация] При диаризации (`task=diarize`) временные метки слов дополнительно содержат идентификатор или роль говорящего в поле `speaker`. Пример вывода: ```json { "task": "diarize", "words": [ { "word": "Здравствуйте", "start": 0.0, "end": 0.8, "prob": 0.99, "speaker": "speaker_0" }, { "word": "Слушаю", "start": 1.2, "end": 1.7, "prob": 0.97, "speaker": "speaker_1" } ], ... } ``` Если включена [разметка ролей](/diarization#%D1%80%D0%B0%D0%B7%D0%BC%D0%B5%D1%82%D0%BA%D0%B0-%D1%80%D0%BE%D0%BB%D0%B5%D0%B9-role-tagging), метки `speaker_N` в словах заменяются на назначенные роли (например, `Клиент`, `Агент`). # TypeScript SDK (/typescript-sdk) **`nexara-sdk`** — официальная TypeScript/JavaScript-библиотека для Nexara API. Она берёт на себя сборку запросов, разбор ответов в типизированные объекты, обработку ошибок и повторные попытки, а асинхронные задачи превращает в один вызов `job.wait()`. Библиотека полностью типизирована и поставляет собственные `.d.ts`, тип результата сужается автоматически по `task` и `response_format`. Требуется Node.js 20+. ```bash npm install nexara-sdk ``` Сохраните API-ключ в переменной окружения `NEXARA_API_KEY`. SDK читает её автоматически при создании клиента, поэтому ключ не нужно указывать в коде — и он не попадёт в репозиторий: ```bash export NEXARA_API_KEY=nx-... ``` Команда `export` задаёт переменную только для текущей сессии терминала. Чтобы ключ сохранялся между сессиями, добавьте эту строку в файл конфигурации вашей оболочки (например, `~/.zshrc` или `~/.bashrc`). На сервере переменную удобнее задавать через настройки окружения или менеджер секретов. ## Быстрый старт [#быстрый-старт] ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const { text } = await client.transcriptions.create({ file: "audio.mp3" }); console.log(text); ``` Клиент берёт ключ из `NEXARA_API_KEY` — той самой переменной, что вы задали выше, — поэтому в коде его указывать не нужно. Передать ключ явным полем `apiKey` тоже можно, но не храните его в коде и не коммитьте в репозиторий. ## Клиент [#клиент] ```typescript const client = new Nexara({ // apiKey по умолчанию берётся из переменной окружения NEXARA_API_KEY, // либо можете передать ключ сами: apiKey: "nx-..." timeoutMs: 600_000, // таймаут запроса в миллисекундах maxRetries: 2, // повторы при 429 и сетевых сбоях }); ``` Клиент асинхронный целиком — отдельного синхронного варианта нет (в отличие от `Nexara`/`AsyncNexara` в [Python SDK](/python-sdk)). Все методы возвращают промисы. ## Транскрибация [#транскрибация] Передайте ровно один из параметров: `file` (путь, `Uint8Array` или `Blob`) или `url` (прямая ссылка). Файл, переданный путём, стримится с диска и не загружается в память целиком. ```typescript // Локальный файл const local = await client.transcriptions.create({ file: "audio.mp3" }); console.log(local.text); // Файл по ссылке const remote = await client.transcriptions.create({ url: "https://example.com/audio.mp3", }); console.log(remote.text); ``` Параметры те же, что и у [API](/api-reference/create_transcription_audio_transcriptions_post): `model`, `language`, `response_format`, `timestamp_granularities`, `profanity_filter` и другие. Тип результата сужается автоматически: `text`, `srt` и `vtt` возвращаются строкой, `json` и `verbose_json` — типизированными объектами: ```typescript import { writeFile } from "node:fs/promises"; const srt = await client.transcriptions.create({ file: "video.mp4", response_format: "srt", }); await writeFile("video.srt", srt); const verbose = await client.transcriptions.create({ file: "audio.mp3", response_format: "verbose_json", timestamp_granularities: ["word"], }); for (const word of verbose.words ?? []) { console.log(`${word.start.toFixed(2)}–${word.end.toFixed(2)}: ${word.word}`); } ``` ## Диаризация и роли [#диаризация-и-роли] Разделение по говорящим включается так же, как в API, — `task: "diarize"`. Роли передаются обычным массивом или объектом: SDK сам сериализует их в JSON. ```typescript const call = await client.transcriptions.create({ file: "call.mp3", task: "diarize", num_speakers: 2, roles: ["Оператор", "Клиент"], // или "auto", или { "Роль": "описание" } }); for (const segment of call.segments) { console.log(`${segment.speaker}: ${segment.text}`); } ``` Подробнее о режимах — на странице [спикеры и роли](/speakers-roles). ## Эмоции [#эмоции] Флаг `emotions: true` добавляет к каждому сегменту диаризации объект `emotion`: метку `label` (`angry`, `sad`, `neutral` или `positive`), уверенность модели `confidence` и распределение вероятностей `probs`. ```typescript const call = await client.transcriptions.create({ file: "call.mp3", task: "diarize", model: "nexara-ru", emotions: true, }); for (const segment of call.segments) { if (segment.emotion) { const confidence = Math.round(segment.emotion.confidence * 100); console.log(`${segment.speaker}: ${segment.emotion.label} (${confidence}%) — ${segment.text}`); } else { console.log(`${segment.speaker}: эмоция не определена — ${segment.text}`); } } ``` Эмоция приходит не для каждого сегмента: если модель не смогла оценить фрагмент, поле `emotion` будет `undefined`. Проверяйте его перед чтением, как в примере выше. У слов (`words`) эмоции нет — она считается по сегменту целиком. Метки доступны типом `EmotionLabel`, а сам объект — типом `Emotion`: ```typescript import type { Emotion, EmotionLabel } from "nexara-sdk"; ``` Подробнее об условиях и ограничениях — на странице [распознавание эмоций](/emotions). Распознавание эмоций тарифицируется дополнительно к стоимости диаризации. Если ни один сегмент не удалось оценить, надбавка не списывается. Стоимость услуги смотрите на странице [тарифов](/pricing). ## Асинхронные задачи [#асинхронные-задачи] Для длинных записей используйте [асинхронный режим](/async-transcription): `createJob()` ставит задачу и сразу возвращает объект `Job`, а `job.wait()` опрашивает статус и возвращает готовый результат. ```typescript const job = await client.transcriptions.createJob({ file: "long_recording.mp3", }); console.log(job.job_id, job.status); // in_progress const result = await job.wait(); // опрашивает статус; таймаут по умолчанию — 1_800_000 мс console.log((result as { text: string }).text); ``` Задачу можно забрать позже — в том числе из другого процесса: ```typescript const job = await client.transcriptions.retrieveJob(jobId); if (job.status === "complete") { console.log(job.result); } ``` Что важно знать: * Результат хранится **12 часов** с момента создания задачи, затем удаляется — после этого `retrieveJob()` бросит `NotFoundError`. * Одновременно на один ключ может выполняться до **200** задач. * Если задача завершилась ошибкой, `wait()` бросает `JobFailedError`. Неудавшаяся задача **не тарифицируется** — повторная отправка бесплатна. * Если задача не успела за таймаут, `wait()` бросает `JobTimeoutError`; сама задача при этом не отменяется, её можно забрать позже через `retrieveJob()`. ## LLM-анализ [#llm-анализ] Передайте `prompt`, чтобы прогнать расшифровку через языковую модель, и `json_schema` — чтобы получить [структурированный ответ](/llm-usage). Схема передаётся обычным объектом, а `llm_output` возвращается уже разобранным — никакого ручного `JSON.parse`. ```typescript const result = await client.transcriptions.create({ file: "meeting.mp3", prompt: "Составь протокол встречи: резюме, решения и задачи.", json_schema: { type: "object", properties: { summary: { type: "string" }, decisions: { type: "array", items: { type: "string" } }, action_items: { type: "array", items: { type: "string" } }, }, required: ["summary", "decisions", "action_items"], }, }); console.log(result.llm_output); // объект по вашей схеме console.log(result.transcription.text); // расшифровка, из которой он получен ``` Без `json_schema` поле `llm_output` — строка со свободным ответом модели. ## Параллельные запросы [#параллельные-запросы] Клиент асинхронный целиком — «async» здесь означает промисы. Отдельного синхронного клиента нет, поэтому несколько запросов запускаются обычным `Promise.all`: ```typescript const urls = [ "https://example.com/a.mp3", "https://example.com/b.mp3", "https://example.com/c.mp3", ]; const results = await Promise.all( urls.map((url) => client.transcriptions.create({ url })), ); for (const result of results) { console.log(result.text); } ``` ## Ошибки [#ошибки] Некорректные запросы (например, `file` и `url` одновременно, слишком много ролей) отклоняются на стороне клиента с `NexaraValidationError` — ещё до обращения к серверу. Ошибки сервера превращаются в типизированные классы по коду статуса: | Класс | Код | Когда возникает | | -------------------------- | --- | ---------------------------------------------------- | | `BadRequestError` | 400 | Некорректные параметры запроса | | `InsufficientBalanceError` | 402 | Недостаточно средств на балансе | | `AuthenticationError` | 403 | Ключ не передан или недействителен | | `NotFoundError` | 404 | Задача, модель или ключ не найдены | | `RateLimitError` | 429 | Превышен лимит частоты или число одновременных задач | | `InternalServerError` | 500 | Внутренняя ошибка сервера | | `APIConnectionError` | — | Запрос не дошёл до сервера (сеть, таймаут) | Все они наследуются от `NexaraError`, а у ошибок API есть поля `status_code` и `detail` с сообщением сервера. Тип ошибки проверяется через `instanceof`. Полный список кодов — на странице [ошибок](/errors). ```typescript import { Nexara, InsufficientBalanceError, RateLimitError } from "nexara-sdk"; const client = new Nexara(); try { await client.transcriptions.create({ file: "audio.mp3" }); } catch (e) { if (e instanceof InsufficientBalanceError) { console.log("Пополните баланс:", e.detail); // 402 } else if (e instanceof RateLimitError) { console.log("Слишком много запросов, попробуйте позже."); } else { throw e; } } ``` ## Ссылки [#ссылки] * Пакет на npm: [npmjs.com/package/nexara-sdk](https://www.npmjs.com/package/nexara-sdk) * Справочник параметров API: [референс API](/api-reference/create_transcription_audio_transcriptions_post) # Учёт расходов (/usage-tracking) # Zoom встречи (/zoom) Записи Zoom-встреч и онлайн-переговоров — частый сценарий: из одной записи можно получить расшифровку, разделение по участникам и готовый протокол встречи. Ниже разберём блок за блоком, а в конце соберём всё в один запрос и поделимся [лайфхаками](#лайфхаки). ## Разделение на говорящих [#разделение-на-говорящих] Включите диаризацию (`task=diarize`) и укажите `diarization_setting=telephonic`. Этот режим хорошо работает, где участники часто перебивают друг друга и говорят одновременно, как это обычно и бывает на встречах. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="meeting.mp3", task="diarize", diarization_setting="telephonic", ) ``` ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "meeting.mp3", task: "diarize", diarization_setting: "telephonic", }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@meeting.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F "diarization_setting=telephonic" ``` Модель `whisper-1` — универсальная и хорошо подходит для встреч на разных языках. Число участников определяется автоматически. ## Роли участников [#роли-участников] Чтобы вместо безликих `speaker_0`/`speaker_1` в расшифровке были понятные роли, добавьте `roles=auto` — модель сама подберёт подписи по содержанию разговора (например, `Ведущий`, `Участник`). Если роли известны заранее, можно передать их списком. ```python result = client.transcriptions.create( file="meeting.mp3", task="diarize", diarization_setting="telephonic", roles="auto", ) ``` ```typescript const result = await client.transcriptions.create({ file: "meeting.mp3", task: "diarize", diarization_setting: "telephonic", roles: "auto", }); ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@meeting.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F "diarization_setting=telephonic" \ -F "roles=auto" ``` Подробнее о режимах разметки — на странице [спикеры и роли](/speakers-roles). ## Аналитика встречи [#аналитика-встречи] Самое ценное — сразу получить из встречи структурированный результат. Добавьте `prompt` (и, для строгой структуры, `json_schema`). Модель получает расшифровку с ролями, поэтому учитывает, кто что говорил. Подробно про режимы — на странице [LLM-анализ](/llm-usage). Ниже — три частых сценария. ### Протокол встречи [#протокол-встречи] Готовый протокол, в котором будут указаны краткое резюме, принятые решения и задачи с ответственными. ```json { "type": "object", "properties": { "summary": { "type": "string", "description": "Краткое резюме встречи" }, "decisions": { "type": "array", "items": { "type": "string" }, "description": "Принятые решения" }, "action_items": { "type": "array", "items": { "type": "object", "properties": { "task": { "type": "string" }, "owner": { "type": "string", "description": "Ответственный, если назван" } }, "required": ["task"] } } }, "required": ["summary", "decisions", "action_items"] } ``` Prompt: `Составь протокол встречи: резюме, решения и задачи с ответственными.` ### Краткое резюме [#краткое-резюме] Самый простой вариант — без схемы, ответ придёт текстом. Prompt: `Сделай краткое резюме встречи в пяти предложениях.` ### Список задач [#список-задач] Только задачи, поставленные на встрече, — для трекера или таск-менеджера. ```json { "type": "object", "properties": { "action_items": { "type": "array", "items": { "type": "object", "properties": { "task": { "type": "string" }, "owner": { "type": "string" }, "deadline": { "type": "string", "description": "Срок, если назван" } }, "required": ["task"] } } }, "required": ["action_items"] } ``` Prompt: `Выпиши все задачи, поставленные на встрече, с ответственными и сроками.` ## Собираем всё вместе [#собираем-всё-вместе] Один запрос: распознавание `whisper-1`, диаризация в режиме `telephonic`, авто-роли и протокол встречи через `prompt` + `json_schema`. В [Python SDK](/python-sdk) схема передаётся обычным словарём — без ручной сериализации в JSON. ```python from nexara import Nexara client = Nexara() # ключ из переменной окружения NEXARA_API_KEY result = client.transcriptions.create( file="meeting.mp3", task="diarize", diarization_setting="telephonic", roles="auto", prompt="Составь протокол встречи: резюме, решения и задачи с ответственными.", json_schema={ "type": "object", "properties": { "summary": {"type": "string"}, "decisions": {"type": "array", "items": {"type": "string"}}, "action_items": { "type": "array", "items": { "type": "object", "properties": { "task": {"type": "string"}, "owner": {"type": "string"}, }, "required": ["task"], }, }, }, "required": ["summary", "decisions", "action_items"], }, ) print(result.llm_output) # готовый протокол встречи (dict) ``` В [TypeScript SDK](/typescript-sdk) схема передаётся обычным объектом — без ручной сериализации в JSON. ```typescript import { Nexara } from "nexara-sdk"; const client = new Nexara(); // ключ из переменной окружения NEXARA_API_KEY const result = await client.transcriptions.create({ file: "meeting.mp3", task: "diarize", diarization_setting: "telephonic", roles: "auto", prompt: "Составь протокол встречи: резюме, решения и задачи с ответственными.", json_schema: { type: "object", properties: { summary: { type: "string" }, decisions: { type: "array", items: { type: "string" } }, action_items: { type: "array", items: { type: "object", properties: { task: { type: "string" }, owner: { type: "string" }, }, required: ["task"], }, }, }, required: ["summary", "decisions", "action_items"], }, }); console.log(result.llm_output); // готовый протокол встречи (объект) ``` ```bash curl https://api.nexara.ru/v1/audio/transcriptions \ -H "Authorization: Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" \ -F "file=@meeting.mp3" \ -F "model=whisper-1" \ -F "task=diarize" \ -F "diarization_setting=telephonic" \ -F "roles=auto" \ -F "prompt=Составь протокол встречи: резюме, решения и задачи с ответственными." \ -F 'json_schema={"type":"object","properties":{"summary":{"type":"string"},"decisions":{"type":"array","items":{"type":"string"}},"action_items":{"type":"array","items":{"type":"object","properties":{"task":{"type":"string"},"owner":{"type":"string"}},"required":["task"]}}},"required":["summary","decisions","action_items"]}' ``` ```python import json import requests schema = { "type": "object", "properties": { "summary": {"type": "string"}, "decisions": {"type": "array", "items": {"type": "string"}}, "action_items": { "type": "array", "items": { "type": "object", "properties": { "task": {"type": "string"}, "owner": {"type": "string"}, }, "required": ["task"], }, }, }, "required": ["summary", "decisions", "action_items"], } with open("meeting.mp3", "rb") as audio_file: response = requests.post( "https://api.nexara.ru/v1/audio/transcriptions", headers={"Authorization": "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX"}, files={"file": audio_file}, data={ "model": "whisper-1", "task": "diarize", "diarization_setting": "telephonic", "roles": "auto", "prompt": "Составь протокол встречи: резюме, решения и задачи с ответственными.", "json_schema": json.dumps(schema), }, ) result = response.json() print(result["llm_output"]) ``` Node.js 18+: ```javascript import fs from "fs"; const schema = { type: "object", properties: { summary: { type: "string" }, decisions: { type: "array", items: { type: "string" } }, action_items: { type: "array", items: { type: "object", properties: { task: { type: "string" }, owner: { type: "string" }, }, required: ["task"], }, }, }, required: ["summary", "decisions", "action_items"], }; const form = new FormData(); form.append("file", new Blob([fs.readFileSync("meeting.mp3")]), "meeting.mp3"); form.append("model", "whisper-1"); form.append("task", "diarize"); form.append("diarization_setting", "telephonic"); form.append("roles", "auto"); form.append("prompt", "Составь протокол встречи: резюме, решения и задачи с ответственными."); form.append("json_schema", JSON.stringify(schema)); const response = await fetch( "https://api.nexara.ru/v1/audio/transcriptions", { method: "POST", headers: { Authorization: "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" }, body: form, }, ); const result = await response.json(); console.log(result.llm_output); ``` ```go package main import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "os" ) func main() { schema := `{"type":"object","properties":{"summary":{"type":"string"},"decisions":{"type":"array","items":{"type":"string"}},"action_items":{"type":"array","items":{"type":"object","properties":{"task":{"type":"string"},"owner":{"type":"string"}},"required":["task"]}}},"required":["summary","decisions","action_items"]}` file, _ := os.Open("meeting.mp3") defer file.Close() var buf bytes.Buffer writer := multipart.NewWriter(&buf) part, _ := writer.CreateFormFile("file", "meeting.mp3") io.Copy(part, file) writer.WriteField("model", "whisper-1") writer.WriteField("task", "diarize") writer.WriteField("diarization_setting", "telephonic") writer.WriteField("roles", "auto") writer.WriteField("prompt", "Составь протокол встречи: резюме, решения и задачи с ответственными.") writer.WriteField("json_schema", schema) writer.Close() req, _ := http.NewRequest("POST", "https://api.nexara.ru/v1/audio/transcriptions", &buf) req.Header.Set("Authorization", "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX") req.Header.Set("Content-Type", writer.FormDataContentType()) resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body)) } ``` ```ruby require "net/http" require "uri" require "json" schema = { "type" => "object", "properties" => { "summary" => { "type" => "string" }, "decisions" => { "type" => "array", "items" => { "type" => "string" } }, "action_items" => { "type" => "array", "items" => { "type" => "object", "properties" => { "task" => { "type" => "string" }, "owner" => { "type" => "string" }, }, "required" => ["task"], }, }, }, "required" => ["summary", "decisions", "action_items"], } uri = URI("https://api.nexara.ru/v1/audio/transcriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer nx-XXXXXXXXXXXXXXXXXXXXXXXX" request.set_form( [ ["file", File.open("meeting.mp3")], ["model", "whisper-1"], ["task", "diarize"], ["diarization_setting", "telephonic"], ["roles", "auto"], ["prompt", "Составь протокол встречи: резюме, решения и задачи с ответственными."], ["json_schema", schema.to_json], ], "multipart/form-data", ) response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(request) end puts JSON.parse(response.body)["llm_output"] ``` В ответе придёт расшифровка с ролями и готовый протокол встречи: ```json { "transcription": { "task": "diarize", "segments": [ { "start": 0.0, "end": 3.4, "text": "Давайте начнём. Сегодня обсудим релиз.", "speaker": "Ведущий" }, { "start": 3.8, "end": 7.2, "text": "Я подготовлю тестовую сборку к пятнице.", "speaker": "Участник" } ] }, "llm_output": { "summary": "Команда обсудила предстоящий релиз и распределила задачи.", "decisions": ["Выпустить релиз в конце недели"], "action_items": [ { "task": "Подготовить тестовую сборку", "owner": "Участник" } ] } } ``` ## Лайфхаки [#лайфхаки] * **Режим `telephonic`.** На встречах участники часто перебивают друг друга — этот режим справляется с такими записями лучше стандартного. * **Авто-роли.** На встречах состав участников заранее не известен, поэтому `roles=auto` обычно удобнее фиксированного списка, но если у вас есть список, лучше его передать для лучшего качества. * **Длинные встречи — [асинхронный режим](/async-transcription).** Записи совещаний бывают долгими, не держите HTTP-соединение открытым всё это время. * **Большие файлы — [по ссылке](/file-url).** Запись встречи удобно отдавать по URL (например, pre-signed ссылке на хранилище), а не загружать файлом. * **Вложенные схемы.** Для задач с ответственными используйте массив объектов в `json_schema` — так каждую задачу можно сразу разложить на поля. Диаризация, разметка ролей и LLM-анализ тарифицируются дополнительно к стоимости распознавания. Итоговую стоимость смотрите на странице [тарифов](/pricing). # Создать асинхронную задачу транскрибации (/api-reference/createAsyncTranscription) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Транскрибировать аудио (/api-reference/create_transcription_audio_transcriptions_post) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Получить статус асинхронной задачи (/api-reference/getAsyncTranscription) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Получить баланс (/api-reference/getBalance) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Получить информацию о модели (/api-reference/getModel) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Список доступных моделей (/api-reference/listModels) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}