Документация Bonch Go API
Read-only REST API расписания СПбГУТ. Группы, преподаватели, аудитории и предметы — из того же кэша, которым пользуется бот.
Быстрый старт
⚠️ Все запросы требуют ключа. Без него ответ — 401 KEY_REQUIRED; открыт только /health, и то в урезанном виде. Проверить, что сервис жив, можно без ключа:
curl https://api.bonchgo.ru/api/v1/health
Всё остальное — с ключом, заголовком Authorization: Bearer <ключ> (или X-API-Key):
curl -H "Authorization: Bearer $BONCHGO_API_KEY" \"https://api.bonchgo.ru/api/v1/groups?search=ИКТМ&limit=10"
На Go:
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= в строке запроса — но так его лучше не передавать: строка запроса оседает в логах прокси и в истории браузера, а заголовок — нет.
Эндпоинты
Все методы — 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-2025dateYYYY-MM-DD — ровно один деньfrom · toYYYY-MM-DD — диапазон дат включительноweekномер учебной недели, 1…52searchподстрока имениlimit · offsetсколько вернуть · сколько пропуститьsemester там молча игнорируется, и в ответ приходят пары за все семестры, которые есть в кэше (сейчас их три). Ограничивайте выборку явными датами: ?from=2026-08-31&to=2027-02-07. Границы семестров отдаёт /semesters. Формат ответа
Успех — конверт {data, meta}. Вот настоящий ответ на расписание группы:
{ "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}. Настоящий ответ на оборванное имя группы:
{ "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 — видно и неделю, и все группы потока:
{ "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-DDGROUP_NOT_FOUNDГруппа не найдена. Если есть похожие имена, они придут в suggestionsTEACHER_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 и топ эндпоинтов. Оттуда же можно сузить доступ, лимиты и срок или отозвать ключ. Расширить права уже выданного ключа из кабинета нельзя — это делает только администратор.