npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

getcourse-api

v3.0.1

Published

TypeScript SDK для GetCourse API

Readme

⚙️ GetCourse API

All in one пакет для нового и старого API

npm version license

Перед обновлением до любой новой версии заглядывайте в релизы — там описано, что изменилось

Официальная документация:


💡 Основная идея

GetCourse предоставляет два API: новое и старое. Оба этих API независимы друг от друга, не имеют типизацию и неудобны в использовании

Мне, как разработчику, захотелось создать пакет, который объединяет оба API в одном SDK с полной TypeScript-типизацией и с понятным неймингом методов, который был в Chatium SDK

Теперь не нужно вручную разбираться в документации, формировать HTTP-запросы и описывать типы. Просто добавь воды (вызывай методы) и работай

Пакет покрывает все актуальные эндпоинты: работу с пользователями, заказами, предложениями, уроками, вебинарами, диалогами и экспорт данных

Приятной работы, коллеги 😉


📦 Установка

npm install getcourse-api

Нужен Node 22 или новее

🚀 Быстрый старт

import GetCourse from "getcourse-api";

const gc = new GetCourse({
  devKey: "XXXXXXXX", // Ключ разработчика
  apiKey: "YYYYYYYYYYYYYYY", // Ключ API школы
  domain: "test.getcourse.ru", // Подойдет технический или любой подключенный домен к аккаунту
});

// Получить данные пользователя
const userInfo = await gc.getUserInfo({ userId: 123 });

// Получить данные заказа
const dealInfo = await gc.getDealInfo(12345);

🔑 Получение токена

  • Ключ разработчика — после заполнения анкеты
  • Ключ API школы — выдаётся в каждой школе отдельно. Должны быть права на чтение и запись

🎛 Конфигурация

| Параметр | Тип | Обязательный | По умолчанию | Описание | | ---------- | -------------------------------- | ------------ | ------------ | ----------------------------------------- | | devKey | string | да | — | Ключ разработчика | | apiKey | string | да | — | Ключ API школы | | domain | string | да | — | Домен школы, например test.getcourse.ru | | timeout | number | нет | 15000 | Таймаут запросов в мс | | logLevel | 'silent' \| 'error' \| 'debug' | нет | 'silent' | Уровень встроенного логгера | | logger | Logger | нет | — | Кастомный логгер (winston, pino и др.) |

const gc = new GetCourse({
  devKey: "XXXXXXXX",
  apiKey: "YYYYYYYYYYYYYYY",
  domain: "test.getcourse.ru",
  timeout: 10_000,
  logLevel: "debug",
});

🪵 Кастомный логгер (winston, pino)

import winston from "winston";
import GetCourse from "getcourse-api";

const logger = winston.createLogger({
  /* ... */
});

const gc = new GetCourse({
  devKey: "XXXXXXXX",
  apiKey: "YYYYYYYYYYYYYYY",
  domain: "test.getcourse.ru",
  logger, // Совместим с интерфейсом Logger
});

🚨 Обработка ошибок

Все методы бросают исключения — используй try/catch:

import { GetCourseApiError, GetCourseNetworkError, GetCourseValidationError } from "getcourse-api";

try {
  const deal = await gc.getDealInfo(99999);
} catch (err) {
  if (err instanceof GetCourseValidationError) {
    console.error(err.message); // Текст ошибки
    console.error(err.details); // Причина с машиночитаемым кодом

    return;
  }

  if (err instanceof GetCourseApiError) {
    console.error(err.message); // Текст ошибки
    console.error(err.statusCode); // HTTP статус: 400, 403, 404...
    console.error(err.apiCode); // Код ошибки из тела ответа
    console.error(err.errors); // string[] — список ошибок валидации

    return;
  }

  if (err instanceof GetCourseNetworkError) {
    console.error(err.message); // Таймаут, DNS, connection refused
    console.error(err.cause); // Исходная ошибка

    return;
  }

  throw err;
}

| Класс | Когда бросается | Дополнительные поля | | -------------------------- | ------------------------------------------ | --------------------------------- | | GetCourseError | базовый класс, наследник Error | — | | GetCourseValidationError | данные не прошли проверку, запроса не было | details — причина с кодом | | GetCourseApiError | сервер ответил ошибкой или status: false | statusCode, apiCode, errors | | GetCourseNetworkError | таймаут, DNS, connection refused | cause — исходная ошибка |

GetCourseValidationError отличается от остальных тем, что запрос к API вообще не выполнялся — данные не прошли проверку на стороне пакета. Разбирать причину по тексту не нужно, для этого есть details с кодом:

if (err instanceof GetCourseValidationError) {
  if (err.details.code === "attachments_limit") {
    // Файлов больше разрешённого: details.limit и details.received
  }

  if (err.details.code === "attachment_size") {
    // Файл больше лимита: details.filename, details.size и details.limit
  }
}

Чтобы поймать любую ошибку SDK разом, хватит базового класса:

import { GetCourseError } from "getcourse-api";

if (err instanceof GetCourseError) {
  // и ошибка проверки, и ошибка API, и сетевая
}

🧩 Типы

Всё, что встречается в сигнатурах методов, импортируется из корня пакета — отдельных путей вроде getcourse-api/types нет:

import type { CallWebhook, User } from "getcourse-api";

// Получить Telegram-ID пользователя
function getTelegramId(user: User): number | undefined {
  return user.bot_link.telegram[0]?.tg_user_id;
}

app.post("/gc/calls", (req, res) => {
  const call = req.body as CallWebhook;

  if (call.finish_status === "failed") {
    console.log("Недозвон:", call.failed_reason);
  }

  res.sendStatus(200);
});

В чистом JavaScript типы доступны через JSDoc:

/** @param {import("getcourse-api").User} user */
function getTelegramId(user) {
  return user.bot_link.telegram[0]?.tg_user_id;
}

📚 Справочник методов

🔔 Вебхуки

| Метод | Описание | | --------------------------- | ---------------------- | | subscribeWebhook(body) | Подписаться на событие | | unsubscribeWebhook(event) | Отписаться от события |

| Объект события | event_object_id | event_id | | --------------------- | ----------------- | ---------------------------------------------------------------------------------------------- | | Входящие сообщения | 1 | 1 — новый диалог, 2 — переоткрыт диалог, 3 — сообщение от ученика, 4 — сообщение от сотрудника | | Заказы | 2 | 1 — создан, 2 — смена статуса, 3 — оплачен | | Комментарии к урокам | 4 | 1 — добавлен ответ на урок | | Комментарии к ответам | 5 | 1 — добавлен комментарий к ответу | | Комментарии вебинаров | 7 | 1 — новый комментарий от зрителя | | Звонки | 8 | 1 — новый звонок | | HelpDesk | 9 | 1 — новый тикет, 2 — сообщение от клиента, 3 — сообщение от сотрудника |

await gc.subscribeWebhook({
  uri: "https://example.com/webhook",
  event_object_id: 2,
  event_id: 3,
});

await gc.unsubscribeWebhook({
  uri: "https://example.com/webhook",
  event_object_id: 2,
  event_id: 3,
});

Здесь собраны примеры тел вебхуков, которые прилетают на подписанный URI: examples/webhooks


🏫 Общее

| Метод | Описание | | -------------------------- | ------------------------------------- | | getGroups() | Получить все группы пользователей | | getPersonalManagers() | Получить всех персональных менеджеров | | getTrainings() | Получить все тренинги | | getDepartments() | Получить все отделы | | getSurveyAnswers(params) | Получить ответы анкеты |


🧾 Заказы

| Метод | Описание | | ------------------------------ | ----------------------------------- | | getDealInfo(dealId) | Получить информацию по заказу | | getDealCustomFields(dealId) | Получить дополнительные поля заказа | | getDealComments(dealId) | Получить комментарии заказа | | getDealCalls(dealId) | Получить звонки по заказу | | getDealCancelReasons() | Получить причины отмены заказовы | | getDealsWithTags(params?) | Получить заказы с тегами | | addDealComment(body) | Добавить комментарий заказу | | addDealPositions(body) | Добавить позиции в заказ | | removeDealPositions(body) | Удалить позиции из заказа | | updateDealInfo(body) | Обновить информацию по заказу | | updateDealCustomFields(body) | Обновить дополнительные поля заказа |

// Получить информацию по заказу
const dealInfo = await gc.getDealInfo(12345);

// Обновить статус заказа
await gc.updateDealInfo({
  dealId: 12345,
  status: "cancelled",
  cancel_reason_comment: "Клиент передумал",
});

// Добавить позиции
await gc.addDealPositions({
  dealId: 12345,
  positions: [{ offerId: 1, price: 1000, quantity: 2 }, { offerId: 3 }],
});

📞 Звонки

| Метод | Описание | | ---------------------------- | ---------------------------------------- | | addCallComment(body) | Добавить комментарий (в поле «Описание») | | addCallTranscription(body) | Добавить транскрибацию звонка |

await gc.addCallComment({
  callId: 8421,
  text: "Клиент просил перезвонить в пятницу",
});

await gc.addCallTranscription({
  callId: 8421,
  text: "<p>— Здравствуйте! Вам удобно говорить?</p><p>— Да, слушаю вас</p>",
});

У полей разный формат, и переносы строк в них делаются по-разному:

| Поле | Формат | Перенос строки | | --------------------------- | ------ | ---------------------------------------------- | | «Описание» — description | текст | \n, \r или \r\n — работает любой | | «Транскрибация» — comment | HTML | <p>текст</p> для абзаца, <br> для переноса |

Транскрибация отображается в школе как HTML, поэтому обычный \n сохранится и вернётся в ответе, но в интерфейсе схлопнется в пробел — разговор будет выглядеть сплошным абзацем:

const replicas = ["— Здравствуйте! Вам удобно говорить?", "— Да, слушаю вас"];

await gc.addCallTranscription({
  callId: 8421,
  text: replicas.map((line) => `<p>${line}</p>`).join(""),
});

// Короче и без map — реплики пойдут подряд, без абзацных отступов
await gc.addCallTranscription({ callId: 8421, text: replicas.join("<br />") });

Расшифровка попадает в разметку как есть, поэтому символы <, > и & в тексте нужно экранировать

Оба метода перезаписывают значение: повторный вызов заменяет текст, а не добавляет ещё один

У транскрибации есть недокументированный предел длины: на превышении API отвечает 500 с пустым errors, отличить это от падения сервера нельзя. Замеры по живому API — кириллица упирается около 4600 символов, латиница держит больше 60 000, текст пополам — около 8700. Для русского текста безопасный ориентир — до 4000 символов


💬 Диалоги (раздел «Входящие»)

| Метод | Описание | | ------------------------------ | ---------------------------- | | getDialogHistory(body) | Получить историю диалога | | sendDialogMessage(body) | Отправить сообщение в диалог | | startDialog(body) | Начать диалог с учеником | | addDialogNote(body) | Добавить заметку | | changeDialogDepartment(body) | Изменить отдел диалога | | closeDialog(body) | Закрыть диалог |

Написать ученику первым — по ID ученика, а не по ID диалога

await gc.startDialog({
  recipientId: 251804773,
  commentText: "Здравствуйте! Ваш заказ готов",
  transport: [3],
  userId: 903417,
});

🔗 Вложения в сообщениях

К сообщению в диалоге или тикете можно приложить до 5 файлов, каждый до 5 МБ

import { readFile } from "node:fs/promises";

await gc.sendDialogMessage({
  dialogId: 48812,
  commentText: "Счёт во вложении",
  transport: [1],
  userId: 903417,
  attachedFiles: [{ filename: "Счёт №1024.pdf", content: await readFile("./invoice.pdf") }],
});

Файл принимается байтами — Buffer, Uint8Array или Blob

Что стоит знать:

  • Расширение в filename обязательно — GetCourse определяет тип файла только по нему, MIME-тип запроса он игнорирует
  • Лимиты проверяются до отправки — при шести файлах или файле больше 5 МБ пакет бросит GetCourseValidationError, не заливая файлы на сервер
  • Без вложений запрос не меняется — уходит прежний JSON
  • Ссылки на вложения приходят с разных доменов — часть файлов лежит на fs.getcourse.ru, часть на домене школы, причём вторые открываются только авторизованному пользователю. Разбирать URL по домену не стоит

🎫 HelpDesk

| Метод | Описание | | ------------------------------ | --------------------------- | | getTicketHistory(body) | Получить историю тикета | | sendTicketMessage(body) | Отправить сообщение в тикет | | addTicketNote(body) | Добавить заметку | | changeTicketDepartment(body) | Изменить отдел тикета | | closeTicket(body) | Закрыть тикет |

await gc.closeTicket({
  ticketId: 123,
  closedReason: 2,
  closedComment: "Вопрос решён",
});

HelpDesk работает по аналогии с обычными диалогами: те же транспорты, тот же формат истории. Причины закрытия — справочник TicketCloseReason


📘 Уроки

| Метод | Описание | | -------------------------------- | ----------------------------- | | getLessonAnswers(lessonId?) | Получить ответы на урок | | addLessonAnswerComment(body) | Добавить комментарий к ответу | | changeLessonAnswerStatus(body) | Изменить статус ответа |


🏷 Предложения

| Метод | Описание | | ---------------------------- | ----------------------------- | | getOffers() | Получить все предложения | | getOfferById(offerId) | Получить предложение по ID | | getOffersWithTags(params?) | Получить предложения с тегами |


👤 Пользователи

| Метод | Описание | | --------------------------------- | ----------------------------------- | | getUserInfo(params) | Получить информацию по пользователю | | getUserCustomFields(params) | Получить дополнительные поля | | getUserDeals(params) | Получить заказы пользователя | | getUserDiplomas(params) | Получить дипломы | | getUserGroups(params) | Получить группы | | getUserBalance(params) | Получить баланс | | getUserPurchases(params) | Получить покупки | | getUserTrainings(params) | Получить тренинги | | getUserSchedule(params) | Получить расписание | | getUserGoalRecords(params) | Получить записи целей | | getUserSurveyAnswers(params) | Получить ответы на анкеты | | getUserLessonAnswers(params) | Получить ответы на уроки | | getUserDialogs(params) | Получить диалоги из Входящих | | getUserTickets(params) | Получить тикеты HelpDesk | | getUserByTelegramChatId(chatId) | Найти по Telegram Chat ID | | getUserByChatId(params) | Найти по chat ID мессенджера | | addUserBalance(body) | Пополнить баланс | | addUserComment(body) | Добавить комментарий пользователю | | addUserGroups(body) | Добавить в группы | | removeUserGroups(body) | Удалить из групп | | setUserGroups(body) | Установить группы | | setPersonalManager(body) | Закрепить персонального менеджера | | updateUserInfo(body) | Обновить информацию по пользователю | | updateUserCustomFields(body) | Обновить дополнительные поля | | createDiploma(body) | Выдать диплом |

🔍 Поиск пользователя

По userId или email метод возвращает одного пользователя, по телефону — словарь { [userId]: User }: один номер может принадлежать нескольким

const userInfo = await gc.getUserInfo({ userId: 251804773 });

const usersByPhone = await gc.getUserInfo({ phone: "+70000000000" });
const emails = Object.values(usersByPhone.data).map((person) => person.email);

Если совпадений нет ни по одному из способов поиска — метод бросает GetCourseApiError, а не возвращает пустой результат

Поиск по chat ID мессенджера — Telegram, VK или MAX (всегда возвращает одного пользователя)

const userInfo = await gc.getUserByChatId({
  messengerType: "tg",
  chatId: 118472905,
});
// Получить баланс
const userBalance = await gc.getUserBalance({ userId: 123, type: "virtual" });

// Добавить баланс
await gc.addUserBalance({
  userId: 123,
  value: 500,
  type: "virtual",
  comment: "Бонус за активность",
});

// Добавить комментарий пользователю (userId — адресат, authorId — автор)
await gc.addUserComment({
  userId: 123,
  authorId: 456,
  text: "Тестовый комментарий в ленту пользователя",
});

// Обновить информацию
await gc.updateUserInfo({
  userId: 123,
  first_name: "Иван",
  last_name: "Иванов",
  phone: "+70000000001",
});

🎥 Вебинары

| Метод | Описание | | ------------------------------ | ----------------------------------- | | getWebinars() | Получить все вебинары | | getWebinarsByIds(body) | Получить вебинары по ID | | sendWebinarMessage(body) | Отправить сообщение в чат вебинара | | moderateWebinarMessage(body) | Модерация сообщения в чате вебинара | | moderateWebinarUser(body) | Модерация пользователя вебинара |


👴🏻 Старое API (Legacy API)

Используй эти методы для задач, которых нет в новом API: создание пользователей/сделок и массовый экспорт данных

Лимит Export API: 100 запросов за 2 часа

Импорт

| Метод | Описание | | -------------------- | --------------------------------- | | addUser(params) | Создать или обновить пользователя | | createDeal(params) | Создать сделку |

// Создать пользователя
await gc.addUser({
  user: { email: "[email protected]", first_name: "Иван" },
  system: { refresh_if_exists: 1 },
});

// Создать сделку (вариант 1: по offer_code)
await gc.createDeal({
  user: { email: "[email protected]" },
  deal: { offer_code: "offer123", deal_cost: "1990" },
});

// Создать сделку (вариант 2: по offer_id)
await gc.createDeal({
  user: { email: "[email protected]" },
  deal: { offer_id: "42" },
});

Экспорт

Методы экспорта запускают асинхронный процесс на сервере и автоматически ждут результата

| Метод | Описание | | ----------------------------------------------- | --------------------------------------- | | exportUsers(filters?, polling?) | Экспорт пользователей | | exportGroupUsers(groupId, filters?, polling?) | Экспорт пользователей группы | | exportDeals(filters?, polling?) | Экспорт сделок | | exportPayments(filters?, polling?) | Экспорт платежей | | getCustomFields() | Дополнительные поля аккаунта | | getExportResult(exportId) | Результат экспорта по ID (ручной режим) |

// Экспорт всех пользователей
const users = await gc.exportUsers();

// Экспорт с фильтрами
const activeUsers = await gc.exportUsers({ status: "active" });

// Экспорт пользователей группы
const groupUsers = await gc.exportGroupUsers(12345);

// Экспорт сделок за период
const deals = await gc.exportDeals({
  created_at: { from: "2026-01-01", to: "2026-03-31" },
});

// Настройка поллинга (интервал 5с, таймаут 5 мин)
const payments = await gc.exportPayments(
  { status: "accepted" },
  { pollInterval: 5_000, timeout: 300_000 },
);

// Ручное получение результата по ID
const result = await gc.getExportResult(456789);

🧪 Тестирование

Тесты в tests/api работают с реальным проектом на GetCourse. Для запуска нужен файл .env с ключами API и тестовыми ID (пример в .env.example)

Тесты охватывают все методы

# Все тесты
npm run test

# Только экспорт (медленно — тратит лимит API: 100 запросов за 2 часа)
npm run test:export

# Запуск тестов отдельного файла
npm run test -- tests/api/user.test.ts

# Запуск конкретного теста в файле по имени
npm run test -- tests/api/user.test.ts -t "getUserInfo"

# Подписка на все события вебхуков и отписка от всех
npm run test -- tests/api/webhooks/subscribe.test.ts
npm run test -- tests/api/webhooks/unsubscribe.test.ts

# Точечно, по названиям или номерам событий
npm run test -- tests/api/webhooks/subscribe.test.ts -t "1,1"
npm run test -- tests/api/webhooks/unsubscribe.test.ts -t "1,1"
npm run test -- tests/api/webhooks/subscribe.test.ts -t "Заказ оплачен: 2,3"

🤓 Автор

NiktarioN


Если проект полезен — поставь ⭐️ на GitHub


📄 Лицензия

MIT