VISH app — RUT (MIIT) timetable

The timetable of the Russian University of Transport for students and staff of every institute. The timetable of your own group and of any teacher by day and week, free MIIT rooms right now, occupancy of rooms, groups and teachers, notifications about timetable changes and an AI timetable assistant.

Institutes

The RUT (MIIT) app covers five institutes: VISH — Higher Engineering School, AGA — Civil Aviation Academy, ADKH — Road Management Academy, PISH — Advanced Engineering School “HSR Academy” and IMTK — Institute of International Transport Communications. A group’s and a teacher’s timetable are searched the same way in any of them.

What the VISH app can do
Sections
Guide

Getting started for developers

Updated 27.10.2026

VISH has an open HTTP API: the application itself uses it, and your scripts, bots, and websites can also use it. Schedules, groups, teachers, and classrooms are read without any key; you need a key to act on your own behalf, and “Entry through VISH” is to read another person's data with their consent.

This page is where to start. The remaining pages of the “Developers” section sort out the individual parts of the API. Checked with booth d1 and production on October 23, 2026.

Booth addresses

BoothAPI Base AddressOpenAPI Documentation
d1 (test)https://d1.vish-app.ru/api/v1https://d1.vish-app.ru/documentation
productionhttps://api.vish-app.ru/api/v1 (or https://www.vish-app.ru/api/v1)https://www.vish-app.ru/documentation
  • d1 — testbed: new features appear here earlier, data may change. Start with him.
  • production is something that students use. New things are released here every two weeks.
  • An address without a www (https://vish-app.ru) responds with a 301 redirect. Specify the full address, otherwise some libraries will lose their headers during the transition.
  • The machine description of all methods is /documentation/json on the same stand (OpenAPI 3).

In all examples of the section, the address is taken from the environment variable VISH_API (d1 by default), and the key is taken from VISH_API_KEY. Do not enter the key in the code.

The first request

You don't need a key: the list of institutes and groups is open to everyone.

// List institutes and the first groups; no key needed.
const API = process.env.VISH_API ?? 'https://d1.vish-app.ru/api/v1'

const get = async (path) => {
	const response = await fetch(`${API}${path}`, { headers: { Accept: 'application/json' } })
	if (!response.ok) throw new Error(`${path}: HTTP ${response.status}`)
	return response.json()
}

const institutes = await get('/institutes')
if (!Array.isArray(institutes.list) || institutes.list.length === 0) throw new Error('no institutes')

const groups = await get('/groups?perPage=3')
if (groups.list.length === 0 || typeof groups.meta?.total !== 'number') throw new Error('unexpected /groups')

console.log(`${institutes.list.length} institutes, ${groups.meta.total} groups`)
for (const group of groups.list) console.log(group.name, '—', group.institute?.code)

Try it right here:

Try the request

Only methods with a lock need a key. It lives on this page only: it is never stored, goes out only as a header of this request and is forgotten when you leave. Where to get a key

How are the answers arranged?

  • Everything is JSON in UTF-8. Dates are ISO 8601 in UTC (2026-10-19T13:45:00.000Z).
  • The lists come as { "list": [...], "meta": { "total", "page", "perPage", "pages" } }. The page is set to page (with 1) and perPage (by default, 20, no more than 200). The lists have all=1 — all on one page, but no more than 2000 entries.
  • The names that are being translated come with object { "ru": "…", "en": "…", "zh": "…" }. There is always Russian, and there may be no translation.
  • New fields appear in the answers without warning — do not fall on unfamiliar fields.

Access key

You don't need a key to read public data. You need it when the script acts on your behalf: it uploads a report that your role has access to, or writes notes for classes. How to get the key is written on the API and access keys page; how to use it and how it differs from OAuth is on the Authentication: API and OAuth keys page.

Error format

Any API error is the same object.:

json
{
  "error": {
    "code": "AUTH_REQUIRED",
    "message": "Нужно войти: передайте заголовок Authorization с Bearer-токеном или заголовок X-Api-Key.",
    "requestId": "e47fc5d6-a210-49f2-b480-1384633d1d9f"
  }
}
  • code is the machine code, and rely on it. It does not change depending on the language.
  • message — explanation for the person in the language from heading Accept-Language (ru, en or zh; Russian by default). The text may change — do not disassemble it.
  • requestId is the request number; the same number comes in the header X-Request-Id. Name it when you write about the problem.
  • details — validation errors occur (VALIDATION_ERROR): details.issues — a list of fields and what is wrong with them.

The most frequent codes:

HTTPcodeЧто значит
400VALIDATION_ERROR, BAD_REQUESTInvalid parameter or request body
401AUTH_REQUIREDThe method requires login, but there is no key or token
401UNAUTHORIZEDThe key is incorrect, revoked or expired
401ACCESS_TOKEN_INVALIDBearer token is invalid or expired
403FORBIDDENНе хватает прав; в сообщении — каких именно
403SESSION_REQUIREDThe method is only for a person in the application, the key does not call it
404NOT_FOUND, ROUTE_NOT_FOUNDThere is no such record or address
429RATE_LIMITEDToo many requests, wait
500INTERNAL_ERRORThe error is on our side

For more information about 403, see the Access Rights (RBAC) page. OAuth protocol addresses (/oauth/token, /oauth/userinfo, and others) comply with the OAuth:{ "error": "invalid_grant", "error_description": "…" } standard.

To view the entire error, request a method without a key that requires logging in.:

// /auth/me needs a credential: without one the answer shows the error format.
const API = process.env.VISH_API ?? 'https://d1.vish-app.ru/api/v1'

const response = await fetch(`${API}/auth/me`, { headers: { 'Accept-Language': 'en' } })
const body = await response.json()

if (response.status !== 401 || body.error?.code !== 'AUTH_REQUIRED' || !body.error.requestId) {
	throw new Error(`unexpected answer: ${response.status} ${JSON.stringify(body)}`)
}
console.log(body.error.code, '-', body.error.message)
console.log('requestId:', body.error.requestId)

Limiting the frequency of requests

  • The total limit is 300 requests per minute per logged—in person or, without logging in, to an IP address.
  • Each key has its own limit, by default 3000 requests per minute. A fictitious key does not increase the limit: such requests are considered to be at the address.
  • Heavy methods have their own limits and are stricter: calendar feed — 60 per minute, summaries for the period (workload, reports) and the exchange of OAuth code for a token — 30 per minute, PDF — 10 per minute.
  • Exceeding is the response 429 with error.code RATE_LIMITED; the message says how many seconds to repeat. The production responses carry headings RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, and you can slow down in advance (there were none on d1 on October 23, 2026).

API Versions

All addresses start with /api/v1. Inside v1, the API only changes while maintaining compatibility: methods, parameters, and response fields are added. If something needs to be changed incompatibly, it will be a new version at a new address, and we will write about the plans in advance on the API Changes page.

What's next

Verified: 2026-10, PWA aa337c45, platform bff65fce._