Начало работы

Документация Bonch Go API

Read-only REST API расписания СПбГУТ. Группы, преподаватели, аудитории и предметы — из того же кэша, которым пользуется бот.

база
/api/v1
методы
GET
ключ
Bearer · X-API-Key
формат
JSON

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

⚠️ Все запросы требуют ключа. Без него ответ — 401 KEY_REQUIRED; открыт только /health, и то в урезанном виде. Проверить, что сервис жив, можно без ключа:

shell
curl https://api.bonchgo.ru/api/v1/health

Всё остальное — с ключом, заголовком Authorization: Bearer <ключ> (или X-API-Key):

shell
curl -H "Authorization: Bearer $BONCHGO_API_KEY" \
"https://api.bonchgo.ru/api/v1/groups?search=ИКТМ&limit=10"

На Go:

Go · net/http
var client = &http.Client{Timeout: 10 * time.Second}

// Имя группы кириллическое, а номер аудитории бывает со слэшем («507/2») —
// поэтому сегмент пути всегда экранируем.
u := "https://api.bonchgo.ru/api/v1/groups/" + url.PathEscape("ИКТМ-41") + "/schedule?semester=current"

req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
    return nil, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("BONCHGO_API_KEY"))

resp, err := client.Do(req)
if err != nil {
    return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
    return nil, fmt.Errorf("bonchgo: %s", resp.Status)
}

var out struct {
    Data []Lesson `json:"data"`
}
err = json.NewDecoder(resp.Body).Decode(&out)
return out.Data, err

Ключ принимается ещё и как ?api_key= в строке запроса — но так его лучше не передавать: строка запроса оседает в логах прокси и в истории браузера, а заголовок — нет.

Ключ выдаётся по договорённости с разработчиком — напишите ему лично, а затем управляйте ключом в личном кабинете (вход только через аккаунт бота Bonch Go).

Эндпоинты

Все методы — GET, все пути — от /api/v1. Имя группы, ФИО и номер аудитории подставляются прямо в путь и должны быть URL-экранированы.

Группы
/groupsСписок групп. По умолчанию отдаёт все; ?search= — подстрока имени, ?limit= — не больше 5000
/groups/{group}Одна группа: её id и каноническое имя
/groups/{group}/scheduleРасписание группы
/groups/{group}/daysТолько даты, в которые есть пары. Ответ — массив строк «2026-09-03», без занятий: календарю нужно знать, какие дни подсвечивать, а не их содержимое
/groups/{group}/teachersПреподаватели, которые ведут у группы в этом семестре
/groups/{group}/classroomsАудитории, где у группы проходят пары
/groups/{group}/subjectsПредметы группы за семестр
/groups/{group}/subjects/{subject}/scheduleРасписание одного предмета у группы
Преподаватели
/teachersСписок преподавателей. ?search= — подстрока ФИО, ?limit= — по умолчанию 50, максимум 500
/teachers/{name}/scheduleРасписание преподавателя по всем его группам
/teachers/{name}/daysДаты, в которые у него есть пары
/teachers/{name}/groupsГруппы, у которых он ведёт: id и имя
Аудитории
/classroomsСписок аудиторий. ?search=, ?limit= — по умолчанию 50, максимум 2000
/classrooms/{room}/scheduleЧто проходит в аудитории
/classrooms/{room}/daysДаты, когда аудитория занята
Кто ведёт предметы
/subject-teachingКаталог предметов: у каждого — сколько преподавателей, групп и пар. Страницами: ?offset=, ?limit= (максимум 60)
/subject-teaching/subjectКарточка предмета: кто ведёт, у каких групп, какими типами пар. Название — в ?name=, а не в пути: в названиях встречается «/»
/subject-teaching/teacherКарточка преподавателя: что ведёт и у кого. ФИО — в ?name=
/subject-teaching/teachersПреподаватели, попавшие в каталог, — для выпадающих списков
Справочники
/facultiesФакультеты, у каждого — полное название и все его группы
/semestersСеместры в кэше: id, границы дат и признак current
/weeksТекущая учебная неделя: номер, чётность, границы, до какого числа идёт семестр
/nowКто и что занято прямо сейчас: занятые аудитории вуза, отдельно колледжа, и занятые преподаватели
/versionВерсия данных и время последнего обновления кэша. Крошечный ответ: по нему решают, надо ли перекачивать справочники
/healthЖивость сервиса: загружен ли кэш, сколько в нём групп и преподавателей. Единственная ручка, которой ключ не нужен никогда
Имя группы сверяется нормализованно: регистр, дефис и пробел значения не имеют — ИКТМ-41, ИКТМ41 и иктм 41 ведут к одной группе. Оборванное имя точного совпадения не даёт: ИКТМ-4 вернёт 404 со списком похожих.

Параметры запроса

параметр
semestercurrent · previous · preprevious · fall-2026 · spring-2026 · fall-2025 · spring-2025
Расписание и срезы группы, «Кто ведёт предметы». ⚠️ Расписанием преподавателя и аудитории не поддерживается — см. врезку ниже
dateYYYY-MM-DD — ровно один день
Любое расписание
from · toYYYY-MM-DD — диапазон дат включительно
Любое расписание
weekномер учебной недели, 1…52
Любое расписание
searchподстрока имени
Списки групп, преподавателей, аудиторий и каталог предметов
limit · offsetсколько вернуть · сколько пропустить
Списки; offset — только у каталога предметов
Расписание преподавателя и аудитории не фильтруется по семестру. Параметр semester там молча игнорируется, и в ответ приходят пары за все семестры, которые есть в кэше (сейчас их три). Ограничивайте выборку явными датами: ?from=2026-08-31&to=2027-02-07. Границы семестров отдаёт /semesters.

Формат ответа

Успех — конверт {data, meta}. Вот настоящий ответ на расписание группы:

200 OK
{
  "data": [
    {
      "date": "2026-09-03",
      "day_of_week": 4,
      "order": 2,
      "number": 2,
      "number_label": "2",
      "time": "10:45-12:20",
      "subject": "Цифровая обработка сигналов",
      "lesson_type": "Лекция",
      "location": "700; Б22/1",
      "classroom": "700/1",
      "teacher": "Межевов Павел Александрович",
      "source": "regular"
    }
  ],
  "meta": {
    "total": 3,
    "semester": "fall-2026",
    "generated_at": "2026-08-31T12:58:17Z"
  }
}

В meta три поля:

meta.totalСколько элементов в data этого ответа — НЕ сколько их всего в базе. Исключение одно: у каталога предметов (/subject-teaching) это полный размер каталога до offset/limit, чтобы можно было листать. ⚠️ При пустом ответе поля нет вовсе: читайте его как data.length, а не как обязательное число
meta.semesterСеместр, по которому отфильтрован ответ. Есть там, где semester вообще применяется; у расписания преподавателя и аудитории его нет
meta.generated_atВремя формирования ОТВЕТА в UTC. Это не время обновления расписания — оно в /version и /health

Ошибка — конверт {error}. Настоящий ответ на оборванное имя группы:

404 Not Found
{ "error": {
  "code": "GROUP_NOT_FOUND",
  "message": "Группа 'ИКТМ-4' не найдена.",
  "suggestions": ["ИКТМ-41", "ИКТМ-42", "ИКТМ-42м (арх.)", "ИКТМ-43"]
} }

suggestions появляется, только когда похожие имена действительно нашлись, — поле необязательное.

Поля занятия

Одно занятие описывается одним объектом. Набор полей у разных эндпоинтов отличается, и это не случайность: в расписании группы поля группы избыточны — вы её и запрашивали, — а в расписании преподавателя без них нельзя понять, кому он читает.

dateДата занятия, «2026-09-03»
day_of_weekДень недели числом: 1 — понедельник, 7 — воскресенье
orderМесто занятия внутри своего ДНЯ (1, 2, 3…) по времени начала. У двух подгрупп в одно время номера будут разными — это позиция в списке, а не номер пары
numberНомер пары по сетке вуза, как он стоит в расписании. У факультативов и консультаций встречаются номера вне общего ряда, поэтому сортировать по нему не стоит — для порядка есть order
number_labelТот же номер строкой — то, что показывают человеку
timeИнтервал занятия, «10:45-12:20»
subjectНазвание предмета. «(1)», «(2)» в конце — номер подгруппы, а не разные предметы
lesson_typeТип словами, как на сайте вуза: Лекция · Практические занятия · Лабораторная работа · Зачет · Экзамен · Консультация
locationКорпус и адрес в том виде, в каком их пишет сайт вуза: «700; Б22/1»
classroomНомер аудитории, приведённый к единому виду. По нему же аудиторию можно запросить: «507/2», «700/1»
teacherФИО полностью. Если пару ведут несколько человек, они перечислены через «;»
sourceОткуда занятие: regular — обычное расписание, exams — экзамены, credits — зачёты, zaochny — сессия заочников
week_numberНомер учебной недели Только у: преподаватель · аудитория · предмет
week_type«Чётная» или «Нечётная» Только у: преподаватель · аудитория · предмет
group_idid группы — тот самый, что принимает /groups/{group}. Избавляет от резолва имени: по нему сразу можно запросить расписание, предметы или дни этой группы Только у: преподаватель · аудитория · предмет
group_nameИмя той же группы. У потоковой пары это первая по алфавиту из groups — поле осталось от версии API без groups, и для потока полным ответом служит именно groups Только у: преподаватель · аудитория · предмет
groupsВсе группы, которые сидят на этой паре. Появляется потому, что поток из трёх групп — одно занятие, а не три: сервис склеивает такие записи в одну Только у: преподаватель · аудитория

Тот же лектор, тот же день, но запрошенный через /teachers/{name}/schedule — видно и неделю, и все группы потока:

200 OK
{
  "data": [
    {
      "date": "2026-09-03",
      "day_of_week": 4,
      "order": 1,
      "number": 1,
      "number_label": "1",
      "time": "09:00-10:35",
      "subject": "Техническая электродинамика",
      "lesson_type": "Лекция",
      "location": "700; Б22/1",
      "classroom": "700/1",
      "teacher": "Межевов Павел Александрович",
      "week_number": 1,
      "week_type": "Нечётная",
      "source": "regular",
      "group_id": "56675",
      "group_name": "ИКТМ-41",
      "groups": ["ИКТМ-41", "ИКТМ-42", "ИКТМ-43"]
    }
  ],
  "meta": { "total": 2, "generated_at": "2026-08-31T12:58:17Z" }
}

Коды ошибок

код
BAD_REQUESTПараметр не разобран: например, week вне 1…52 или дата не в формате YYYY-MM-DD
GROUP_NOT_FOUNDГруппа не найдена. Если есть похожие имена, они придут в suggestions
TEACHER_NOT_FOUNDПреподавателя нет в расписании
CLASSROOM_NOT_FOUNDТакой аудитории нет в расписании
RATE_LIMITEDПревышен лимит запросов ключа — в минуту или в сутки
INVALID_KEYКлюч не распознан
KEY_REVOKEDКлюч отозван
KEY_EXPIREDУ ключа истёк срок
IP_NOT_ALLOWEDЗапрос пришёл с адреса вне allowed_ips ключа
SCOPE_FORBIDDENСущность вне скоупа ключа — см. раздел ниже
ENDPOINT_FORBIDDENКлючу не выдан доступ к этому методу — см. раздел ниже
CACHE_NOT_READYСервис поднялся, но кэш расписания ещё грузится. Повторите запрос

При каждом запросе с ключом сервис возвращает заголовки X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset — по ним видно остаток минутной квоты, не дожидаясь 429.

Ключи и скоуп

У каждого ключа есть срок действия, лимиты запросов в минуту/сутки и опциональный скоуп — список групп, преподавателей или аудиторий, к которым ему разрешено обращаться. Пустой список означает «без ограничения»; непустой — запрос к сущности вне списка получает 403 SCOPE_FORBIDDEN.

Сравнение нормализованное: регистр, дефис, пробелы, точки, «ё»/«е» и адрес аудитории значения не имеют — «ИКТУ-41» и «икту 41» в скоупе совпадут, как и «Иванов И.И.» с «Иванов И. И.».

Скоуп сверяется по каноническому имени — тому, которое сервис нашёл, а не тому, что прислал клиент: иначе опечатка обошла бы ограничение мимо нечёткого поиска. Два следствия, о которых стоит знать заранее: Разные написания ФИО одного человека («Иванов Иван Иванович» и «Иванов И.И.») нормализация не сводит вместе — перечислите в скоупе оба. Скоуп ограничивает именно расписания групп, преподавателей и аудиторий. Каталог «Кто ведёт предметы», /now и справочники под него не попадают — их закрывают правами на методы, см. ниже.

Доступ по методам

Отдельно от скоупа у ключа есть список разрешённых методов — тех самых путей из раздела «Эндпоинты». Пустой список означает «все методы»; если он задан, всё, что в него не вошло, отвечает 403 ENDPOINT_FORBIDDEN. Ограничение выдаёт администратор при выпуске ключа, а сузить его до меньшего набора можно самому в кабинете.

Это ответ на другой вопрос, чем скоуп: скоуп говорит, про кого можно спрашивать, права на методы — что можно спрашивать. Ключу виджета расписания незачем уметь выкачивать справочник преподавателей, даже если сами данные ему открыты. Совпадение точное: право на /groups (список групп) не открывает /groups/{group}/schedule.

Личный кабинет

Вход в кабинет — только через аккаунт бота Bonch Go: код из Настроек бота обменивается на сессию сайта, отдельных паролей не существует.

Заявка на ключ — не форма на сайте: напишите разработчику лично и договоритесь о задаче и нужном доступе. Ключ выдаётся вручную и сразу появляется в кабинете. ⚠️ Само значение ключа показывается один раз, при выдаче — сохраните его сразу.

В кабинете видно график использования по дням, список IP и топ эндпоинтов. Оттуда же можно сузить доступ, лимиты и срок или отозвать ключ. Расширить права уже выданного ключа из кабинета нельзя — это делает только администратор.