TypeScript SDK
Официальная TypeScript/JavaScript-библиотека Nexara: транскрибация, диаризация, роли, эмоции и LLM-анализ в несколько строк кода.
nexara-sdk — официальная TypeScript/JavaScript-библиотека для Nexara API. Она берёт на себя сборку запросов, разбор ответов в типизированные объекты, обработку ошибок и повторные попытки, а асинхронные задачи превращает в один вызов job.wait(). Библиотека полностью типизирована и поставляет собственные .d.ts, тип результата сужается автоматически по task и response_format. Требуется Node.js 20+.
npm install nexara-sdkСохраните API-ключ в переменной окружения NEXARA_API_KEY. SDK читает её автоматически при создании клиента, поэтому ключ не нужно указывать в коде — и он не попадёт в репозиторий:
export NEXARA_API_KEY=nx-...Команда export задаёт переменную только для текущей сессии терминала. Чтобы
ключ сохранялся между сессиями, добавьте эту строку в файл конфигурации вашей
оболочки (например, ~/.zshrc или ~/.bashrc). На сервере переменную удобнее
задавать через настройки окружения или менеджер секретов.
Быстрый старт
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 тоже можно, но не храните его в коде и не коммитьте в репозиторий.
Клиент
const client = new Nexara({
// apiKey по умолчанию берётся из переменной окружения NEXARA_API_KEY,
// либо можете передать ключ сами: apiKey: "nx-..."
timeoutMs: 600_000, // таймаут запроса в миллисекундах
maxRetries: 2, // повторы при 429 и сетевых сбоях
});Клиент асинхронный целиком — отдельного синхронного варианта нет (в отличие от Nexara/AsyncNexara в Python SDK). Все методы возвращают промисы.
Транскрибация
Передайте ровно один из параметров: file (путь, Uint8Array или Blob) или url (прямая ссылка). Файл, переданный путём, стримится с диска и не загружается в память целиком.
// Локальный файл
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: model, language, response_format, timestamp_granularities, profanity_filter и другие. Тип результата сужается автоматически: text, srt и vtt возвращаются строкой, json и verbose_json — типизированными объектами:
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.
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}`);
}Подробнее о режимах — на странице спикеры и роли.
Эмоции
Флаг emotions: true добавляет к каждому сегменту диаризации объект emotion: метку label (angry, sad, neutral или positive), уверенность модели confidence и распределение вероятностей probs.
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:
import type { Emotion, EmotionLabel } from "nexara-sdk";Подробнее об условиях и ограничениях — на странице распознавание эмоций.
Распознавание эмоций тарифицируется дополнительно к стоимости диаризации. Если ни один сегмент не удалось оценить, надбавка не списывается. Стоимость услуги смотрите на странице тарифов.
Асинхронные задачи
Для длинных записей используйте асинхронный режим: createJob() ставит задачу и сразу возвращает объект Job, а job.wait() опрашивает статус и возвращает готовый результат.
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);Задачу можно забрать позже — в том числе из другого процесса:
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-анализ
Передайте prompt, чтобы прогнать расшифровку через языковую модель, и json_schema — чтобы получить структурированный ответ. Схема передаётся обычным объектом, а llm_output возвращается уже разобранным — никакого ручного JSON.parse.
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:
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);
}Realtime
Распознавание в реальном времени: connect() открывает WebSocket-сессию и резолвится, когда сервер прислал session.created; stream() отправляет аудио из любого асинхронного итерируемого и отдаёт события RealtimeTranscript по мере распознавания:
const session = await client.realtime.connect({ sampleRate: 16000, diarize: true });
for await (const event of session.stream(microphone())) { // AsyncIterable<Uint8Array>
console.log(`spk${event.speaker} ${(event.words[0]!.start / 1000).toFixed(2)}s ${event.text}`);
}
console.log(session.ended?.text); // полная транскрипция и billed_msЕсли аудио приходит не как итератор, отправляйте его сами и завершите сессию через finish() — он посылает input_audio.end и резолвится с session.ended. События также доступны через on("transcript"), on("ended"), on("error") и on("close"):
const session = await client.realtime.connect();
session.on("transcript", (event) => process.stdout.write(event.text));
for await (const chunk of microphone()) await session.sendAudio(chunk);
const { billed_ms } = await session.finish();Параметры connect(): encoding, sampleRate, channels, multichannel, diarize, delayMs, turnDetection, clientId — те же, что у протокола; недопустимые значения отклоняются NexaraValidationError до подключения.
Для голосовых агентов есть определение реплик: с turnDetection: "smart" сессия кроме RealtimeTranscript отдаёт события RealtimeSpeechStarted, RealtimeSpeechEnded и RealtimeTurnEnd — итератор типизирован объединением RealtimeEvent, различайте по event.type (те же события доступны через on("speech.started"), on("speech.ended"), on("turn.end")), а у каждого transcript появляется номер реплики turn:
const session = await client.realtime.connect({ turnDetection: "smart" });
for await (const event of session.stream(microphone())) {
switch (event.type) {
case "speech.started": agent.interrupt(); break; // ~100 мс после начала речи
case "turn.end": await llm(event.text); break; // полный текст реплики — в языковую модель
case "transcript": console.log(event.turn, event.text);
}
}turn.end приходит после всех transcript своей реплики и до первого transcript следующей, так что event.text полон; event.confidence — вероятность конца реплики (null, если реплику закрыл finish()).
Ключ передаётся субпротоколом WebSocket, поэтому подходит любой стандартный WebSocket: в браузерах и Node.js 22+ он встроен, на Node.js 20 передайте класс из пакета ws — connect({ WebSocket }) или один раз в new Nexara({ WebSocket }).
Если сервер завершает сессию с ошибкой, итератор выдает RealtimeError с полями code и reason (session_full, idle_timeout, audio_backlog, service_restart, …) или InsufficientBalanceError, если баланс закончился посреди сессии. Отказ на этапе подключения стандартный WebSocket показывает без HTTP-статуса — это APIConnectionError; с пакетом ws он превращается в AuthenticationError / InsufficientBalanceError / RateLimitError, как в REST. Полное описание протокола — на странице распознавания в реальном времени.
Ошибки
Некорректные запросы (например, file и url одновременно, слишком много ролей) отклоняются на стороне клиента с NexaraValidationError — ещё до обращения к серверу. Ошибки сервера превращаются в типизированные классы по коду статуса:
| Класс | Код | Когда возникает |
|---|---|---|
BadRequestError | 400 | Некорректные параметры запроса |
InsufficientBalanceError | 402 | Недостаточно средств на балансе |
AuthenticationError | 403 | Ключ не передан или недействителен |
NotFoundError | 404 | Задача, модель или ключ не найдены |
RateLimitError | 429 | Превышен лимит частоты или число одновременных задач |
InternalServerError | 500 | Внутренняя ошибка сервера |
RealtimeError | — | Сервер завершил realtime-сессию; код и причина в code / reason |
APIConnectionError | — | Запрос не дошёл до сервера (сеть, таймаут) |
Все они наследуются от NexaraError, а у ошибок API есть поля status_code и detail с сообщением сервера. Тип ошибки проверяется через instanceof. Полный список кодов — на странице ошибок.
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
- Справочник параметров API: референс API