В этом материале
В Node.js вызов должен выполняться на сервере, который хранит ключ и контролирует параметры. Проверяйте HTTP-статус и тело ответа, прежде чем считать операцию успешной.
Для первого обращения к OpenRouter из Node.js достаточно серверного fetch. Главная архитектурная граница проходит между браузером пользователя и процессом, которому разрешено тратить средства API. Ключ OpenRouter должен оставаться у второго, даже если интерфейс сайта написан на JavaScript.
Ниже приведён небольшой пример Chat Completions. Он предполагает версию Node.js со встроенными fetch и AbortSignal.timeout; конкретную среду выполнения проверьте перед переносом.
Отделите серверный вызов
В Quickstart OpenRouter описан единый доступ к моделям, а руководство аутентификации показывает Bearer-заголовок. Не помещайте этот заголовок в код, который сборщик отправляет браузеру.
const key = process.env.OPENROUTER_API_KEY;
const model = process.env.OPENROUTER_MODEL;
if (!key || !model) throw new Error('Missing server configuration');
const response = await fetch(
'https://openrouter.ai/api/v1/chat/completions',
{
method: 'POST',
headers: {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model,
messages: [{ role: 'user', content: 'Ответь: готово' }],
max_tokens: 32,
}),
signal: AbortSignal.timeout(60000),
},
);
const data = await response.json();
if (!response.ok || data.error) {
throw new Error(`API request failed: ${response.status}`);
}
console.log(data.id, data.model);
Модель выбирается через окружение, а не фиксируется случайным устаревающим названием. Этот тест может расходовать средства по условиям выбранного маршрута.
Ограничьте возможности входящего клиента
В рабочем приложении браузер обращается к вашему внутреннему маршруту. Сервер решает, какие параметры передать дальше. Если позволить посетителю задавать любую модель, размер ответа и число параллельных вызовов, серверный секрет сам по себе не ограничит расходы.
Для начала примите небольшой текст и фиксированный набор параметров. Добавьте ограничения частоты и размера данных, соответствующие вашему сценарию. Эти меры должны проверяться отдельными тестовыми запросами, а не только присутствовать в описании проекта.
Разделяйте состояния ошибки
Сбой DNS, тайм-аут, неуспешный HTTP-статус и ошибка внутри JSON имеют разные причины. В примере вывод пользователю намеренно сокращён; во внутреннем отчёте можно сохранить безопасные поля ошибки и идентификатор операции, исключив секреты и ненужное содержимое запроса.
Не добавляйте бесконечный повтор в catch. Если фронтенд, ваш backend и внешний gateway одновременно повторяют одну операцию, фактическое число вызовов может стать неожиданным. Сначала назначьте один слой, отвечающий за повторы, и общий предел времени.
Проверьте поведение при отмене
Пользователь может закрыть вкладку раньше завершения ответа. Определите, прекращает ли это работу вашего backend и что происходит с внешним запросом. Прерывание локального ожидания не является универсальным подтверждением прекращения начислений у любого провайдера.
Условный пилот должен включать обычный ответ, отказ по лимиту, медленный ответ и разрыв клиентского соединения. Результаты показывают поведение вашей интеграции. Они не служат сравнительным бенчмарком моделей или облаков.
Отдельно ограничьте размер принимаемого ответа и правила его хранения. Успешный короткий тест не проверяет поведение на длинном содержимом. Если результат будет использоваться как JSON или аргументы инструмента, потребуется соответствующая проверка структуры до передачи следующему компоненту приложения.
Сохраните границы ответственности
Зафиксируйте, где хранятся настройки, кто видит журналы и кто меняет бюджет. Для следующего этапа прочитайте про ключ приложения и streaming.