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
| Booth | API Base Address | OpenAPI Documentation |
|---|---|---|
| d1 (test) | https://d1.vish-app.ru/api/v1 | https://d1.vish-app.ru/documentation |
| production | https://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/jsonon 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 topage(with 1) andperPage(by default, 20, no more than 200). The lists haveall=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.:
{
"error": {
"code": "AUTH_REQUIRED",
"message": "Нужно войти: передайте заголовок Authorization с Bearer-токеном или заголовок X-Api-Key.",
"requestId": "e47fc5d6-a210-49f2-b480-1384633d1d9f"
}
}codeis the machine code, and rely on it. It does not change depending on the language.message— explanation for the person in the language from headingAccept-Language(ru,enorzh; Russian by default). The text may change — do not disassemble it.requestIdis the request number; the same number comes in the headerX-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:
| HTTP | code | Что значит |
|---|---|---|
| 400 | VALIDATION_ERROR, BAD_REQUEST | Invalid parameter or request body |
| 401 | AUTH_REQUIRED | The method requires login, but there is no key or token |
| 401 | UNAUTHORIZED | The key is incorrect, revoked or expired |
| 401 | ACCESS_TOKEN_INVALID | Bearer token is invalid or expired |
| 403 | FORBIDDEN | Не хватает прав; в сообщении — каких именно |
| 403 | SESSION_REQUIRED | The method is only for a person in the application, the key does not call it |
| 404 | NOT_FOUND, ROUTE_NOT_FOUND | There is no such record or address |
| 429 | RATE_LIMITED | Too many requests, wait |
| 500 | INTERNAL_ERROR | The 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.codeRATE_LIMITED; the message says how many seconds to repeat. The production responses carry headingsRateLimit-Limit,RateLimit-RemainingandRateLimit-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
- Authentication: API and OAuth keys
- Login via VISH for developers
- Access Rights (RBAC)
- Schedule API and Reference API
- The Notes API and Notifications and Calendars
- Integration Security, API Changes, Developer Questions
Verified: 2026-10, PWA aa337c45, platform bff65fce._