NormaHub knowledge base

Первый запрос на Node.js

OpenAI SDK с baseURL NormaHub: установка, синхронный и потоковый запросы, обработка ошибок и безопасный backend route.

1. Установка и ключ

Установите официальный пакет openai — отдельного клиента NormaHub не существует, совместимость достигается сменой baseURL. Ключ храните в переменных окружения процесса и никогда не коммитьте .env в репозиторий.

npm install openai
export NORMAHUB_API_KEY="ваш_api_ключ"

Создайте API-ключ в личном кабинете NormaHub и скопируйте значение сразу: повторно оно не показывается. Под разные окружения (dev, prod, конкретный бот) заводите отдельные ключи с собственными лимитами расходов.

2. Первый запрос

Клиент работает как с оригинальным OpenAI: та же сигнатура create, те же choices. Отличие только в двух строках конфигурации — apiKey из окружения и baseURL NormaHub. Длину ответа ограничивайте max_tokens: это и защита от зависших диалогов, и контроль бюджета.

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.NORMAHUB_API_KEY,
  baseURL: "https://api.normahub.cc/v1",
});

const response = await client.chat.completions.create({
  model: "gpt-5.6-sol",
  messages: [{ role: "user", content: "Привет" }],
  max_tokens: 512,
});

console.log(response.choices[0].message.content);

Модель указывайте точным ID из актуального каталога. После первого успешного ответа сверьте usage и стоимость в истории личного кабинета — успешный HTTP-ответ ещё не означает корректный биллинг.

3. Streaming ответов

Потоковый режим отдаёт токены по мере генерации: интерфейс оживает мгновенно, а пользователь не смотрит на спиннер. Поток гарантирован только через Responses API — для /v1/chat/completions и /v1/messages отправляйте stream: false. Обрабатывайте обрыв отдельно от нормального завершения — часть ответа могла уже уйти пользователю, а сервер — завершить операцию.

const stream = await client.responses.stream({
  model: "gpt-5.6-sol",
  input: "Объясни коротко",
});

for await (const event of stream) {
  console.log(event);
}

Streaming — свойство конкретного endpoint и модели. Если поток не поддержан, используйте обычный режим, а не бесконечные повторы. Детали событий и завершения — в гайде по streaming.

4. Обработка ошибок

SDK бросает типизированные исключения: AuthenticationError для 401, RateLimitError для 429, APIError с полем status для остальных. Разделяйте постоянные ошибки (400, 401, 402, 403 — чинить причину) и временные (429, 5xx — повторять с backoff). Всегда задавайте timeout: зависший запрос хуже быстрой ошибки.

import OpenAI from "openai";

try {
  await client.chat.completions.create({
    model: "gpt-5.6-sol",
    messages: [{ role: "user", content: "Привет" }],
    timeout: 30_000,
  });
} catch (error) {
  if (error instanceof OpenAI.AuthenticationError) {
    console.error("401: ключ неверен или отозван");
  } else if (error instanceof OpenAI.RateLimitError) {
    console.error("429: лимит ключа, повторите позже");
  } else if (error instanceof OpenAI.APIError) {
    console.error(`HTTP ${error.status}: смотрите гайд по ошибкам`);
  } else {
    throw error;
  }
}

Сохраняйте X-Request-Id каждого запроса — по нему поддержка находит операцию без вашего ключа и промпта. Полная таблица кодов и retry-политик — в гайде по ошибкам API.

5. Безопасность ключа

Ключ должен оставаться на сервере. Для браузерного приложения создайте собственный защищённый backend route: фронт отправляет сообщение вашему серверу, сервер добавляет секретный ключ и обращается к NormaHub. По пути обрежьте длину входного сообщения и ограничьте max_tokens — иначе чужой скрипт через вашу форму потратит весь баланс.

// app/api/chat/route.ts — ключ живёт только на сервере
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.NORMAHUB_API_KEY,
  baseURL: "https://api.normahub.cc/v1",
});

export async function POST(request: Request) {
  const { message } = await request.json();
  const response = await client.chat.completions.create({
    model: "gpt-5.6-sol",
    messages: [{ role: "user", content: String(message).slice(0, 4000) }],
    max_tokens: 512,
  });
  return Response.json({ text: response.choices[0].message.content });
}

Дополнительно включайте rate limit на своём route и логируйте только статусы и request ID, а не ключи и содержимое. Диагностику упавших вызовов начинайте с кабинета: баланс, лимит ключа, затем таблица кодов.

Частые вопросы

Где взять model ID для Node.js-запроса?

Точный идентификатор берите из каталога моделей или ответом GET /v1/models вашим ключом. Возможности (tools, streaming) зависят от конкретной модели — проверяйте их до переключения.

Можно ли вызывать API прямо из браузера?

Нет. Секретный ключ нельзя встраивать в клиентский JavaScript: его извлекут из bundle за минуту. Делайте собственный backend route (пример выше), а браузер пусть ходит в него.

Как обрабатывать 429 и 503?

Это временные ошибки: повторяйте с экспоненциальной задержкой, учитывайте Retry-After и ограничивайте общее число попыток и deadline операции. Постоянные 401/402/403 повтором не лечатся.

Нужен ли VPN для запросов из России?

Нет. Клиент обращается к https://api.normahub.cc/v1 напрямую. Оплата списывается с рублёвого баланса, пополняемого удобным способом.