Для AI-агентов: markdown этой страницы — /docs-content/applications.md индекс документации — /llms.txt
Витрина приложений
Раздел даёт основу для экрана «мои приложения» в вашем продукте: одним запросом — и свои приложения человека, и доступные ему чужие. Внутри две операции: список и карточка одного приложения.
Другого способа собрать такой список нет: GET /v1/infra/servers ограничен текущим ключом, поэтому приложение, заведённое в личном кабинете, оттуда не видно. Каждая строка вдобавок несёт состояние, за которым иначе пришлось бы ходить отдельными запросами: сервер, сохранённые версии исходников, признак идущей операции и готовый адрес, по которому приложение открывают. Раздел только читает — создание, правка и удаление приложений в него не входят.
Базовый URL: https://vibecode.bitrix24.tech/v1 | Авторизация: X-Api-Key | Ключ: API-ключ или ключ авторизации
Что попадает в выдачу | Что несёт карточка | Идентификаторы | Порядок и постраничный обход | Ограничение частоты | Быстрый старт | Полный пример | Справочник эндпоинтов | Коды ошибок
Что попадает в выдачу
Выборка идёт по владельцу ключа, а не по самому ключу. Это главное отличие раздела от Серверов: там выдача ограничена серверами текущего ключа, поэтому приложение, заведённое в личном кабинете, оттуда не видно — его сервер привязан к другому ключу того же человека. Здесь такое приложение есть, и выпуск нового ключа список не обнуляет.
Раздел работает с ключом, привязанным к порталу: подойдёт и API-ключ vibe_api_…, и ключ авторизации vibe_app_…. Токен сессии этому разделу не нужен. Под ключом авторизации выдача принадлежит владельцу ключа — тому, кто ключ выпустил, — а не сотруднику, от чьего лица работает приложение. Отдельного скоупа раздел не требует. Ключ без привязки к порталу до раздела не доходит — его отбивает проверка ключа с ответом 401 NO_PORTAL; управляющий ключ получает 403 MANAGEMENT_KEY_NO_ENTITY_ACCESS. Полный разбор — в «Кодах ошибок» ниже.
Что именно вернётся, задаёт параметр scope:
scope |
Что попадает |
|---|---|
feed |
Общий список: свои приложения и все доступные вам чужие. Значение по умолчанию |
mine |
Только свои приложения |
shared |
Чужие приложения, доступные вам. Кроме тех, доступ к которым открыли лично вам, сюда попадают и приложения без сервера, открытые всему порталу |
Удалённые приложения не приезжают ни в одном scope.
У feed выборка ограничена сверху, потому что порядок для неё считает платформа, а не база. Поэтому total там — размер выборки, а не число приложений портала, и конверт списка говорит об этом признаком truncated. У mine и shared предела нет.
Отношение зрителя к каждой строке приходит в поле viewerState: owner — приложение ваше, shared — доступ открыли лично вам или через сервер, usable — у приложения нет сервера и оно открыто всему порталу, requestable — доступа нет. У приложения с сервером доступ определяет политика сервера, поэтому открытое всем такое приложение приходит как shared, а не usable. Отдельного признака «можно ли управлять» раздел не отдаёт — он только читает. Различие «своё против чужого» закрывает сам viewerState.
Что несёт карточка
Строка списка и карточка одного приложения — одна и та же форма, поэтому клиенту не нужно знать про два разных объекта. Кроме названия, описания и иконки в ней адрес открытия и три блока состояния.
openUrl и openTarget — один ответ на вопрос «куда открывать это приложение». Есть сервер с адресом — openUrl отдаёт этот адрес, а openTarget равен app. Во всех остальных случаях оба поля приходят null, и пустое место здесь штатный ответ, а не сбой.
Пустые поля приходят и у приложения, зарегистрированного в одном из мест встраивания портала, то есть открывающегося внутри интерфейса Битрикс24: адреса, по которому открывают такое приложение, платформа пока не знает и выдумывать его вместо честного null не станет. Подставлять туда server.url самостоятельно нельзя — почему, разобрано в Карточке приложения.
isEmbedded — признак, который эти два случая различает. Пустая пара открытия приходит и у встроенного приложения, и у того, которое ещё не опубликовано, а пользователю нужно показать разные вещи: «открывается внутри Битрикс24» против «пока негде открыть». Выводить встройку из наличия сервера НЕЛЬЗЯ: встроенное приложение без собственного сервера — обычное состояние, встройку сделали раньше, чем выложили код, и признак от сервера не зависит. Поле приходит всем, кто видит карточку, а не только владельцу.
server — сводка по серверу приложения: идентификатор, состояние, адрес, тип ресурса и признак reachable. null, если сервера нет или он удалён. Поле reachable отвечает не на тот вопрос, что status: status говорит, что контейнер поднят, а reachable — что он ещё и отвечает по сети. Расходятся они не в теории: контейнер бывает поднят, а туннель до него не поднят ни разу, и по одному status приложение выглядит рабочим.
Признак уже включает status === "RUNNING": истинным при другом состоянии он не приходит никогда, поэтому конъюнктить его со status на клиенте не нужно.
⚠️ У приложения на общем хосте галактики (server.kind: "GALAXY_APP") вторая половина признака берётся с ХОСТА, а не с самого контейнера. Связность там держит хост, у контейнера своего туннеля нет по устройству. То есть true означает сразу три вещи: контейнер работает, хост работает, туннель хоста поднят. Практическое следствие: только что созданный контейнер, который ещё не дошёл до RUNNING (а доходит он до него лишь после первой загрузки исходников), приедет с reachable: false даже при совершенно живом хосте. Это «контейнер пока не поднялся», а не «хост недоступен», и по одному этому полю два случая не различить — смотрите status.
iconUrl — относительный путь без хоста, например /api/app-icons/7f3…?v=1755500000000, или null, если указателя на иконку у карточки нет. Раздача анонимная: ни ключа, ни подписи, ни срока жизни у ссылки нет, поэтому путь достаточно склеить с базовым URL API и вставить прямо в <img src> — проксировать её через свой сервер не нужно. Отдаётся PNG 256×256 с Cache-Control: public, max-age=300, must-revalidate и ETag, так что кэш карточек не ломается: через пять минут клиент получит дешёвый 304. Плейсхолдер для null рисует клиент — платформа иконку не придумывает.
sources — сводка по сохранённым версиям исходников. Поле latestVersionId приходит в форме v<N> — ровно в той, которую принимает GET /v1/infra/servers/:id/sources/:versionId/download. Это не первичный ключ записи: подставив его в адрес скачивания, вы получите последнюю версию, и отдельный запрос за списком версий не нужен.
activeOperation — идущая операция над сервером: вид, шаг и время старта. И здесь важна оговорка, которая экономит часы отладки: null означает «операции с сохранённой записью нет», а не «с приложением ничего не происходит». Признак честен для выкладки, починки, изменения тарифа сервера и переноса контейнера между галактиками — остальные действия платформа не журналирует, и в этом поле они не появляются. Третье состояние — status: "unknown": операция начиналась, а её исход неизвестен, и ждать её завершения бессмысленно.
Оба последних блока наполняются только тому, кто приложением управляет. Зрителю, которому приложение просто открыли, sources приходит пустым (hasVersions: false, оба поля null), а activeOperation — null. Форма ответа при этом не меняется, поэтому пустая сводка сама по себе не значит «версий нет». Причин у неё три, и снаружи они выглядят одинаково: у приложения нет сервера, версий действительно нет, или данные вам не отдаются, потому что приложение чужое.
Идентификаторы: что с чем совпадает
Раздел возвращает два идентификатора, и оба ведут в другие разделы API — но не туда, куда можно решить по имени поля.
server.id — тот же идентификатор, что принимают пути Серверов. Подставляйте его в GET /v1/infra/servers/:id и в загрузку исходников напрямую. Одна оговорка: совпадает формат, а не право доступа — та ручка ограничена серверами вызывающего ключа, поэтому сервер, который витрина показала (она выбирает по владельцу ключа, а не по ключу), может ответить там 404.
id приложения и id из GET /v1/apps — РАЗНЫЕ значения разных сущностей. Здесь это карточка приложения, там — регистрация OAuth-приложения на портале Битрикс24; у них даже формат разный. Мост между ними существует в платформе, но витрина его намеренно не отдаёт. Практическое следствие: подставив id из этого раздела в пути /v1/apps/..., вы получите 404, а не чужие данные.
⚠️ Разные имена одного смысла. Название приложения здесь приходит в name, а в GET /v1/apps заголовок регистрации называется title. Это два независимых поля двух сущностей: они не синхронизируются и могут расходиться, поэтому переносить одно в другое нельзя.
⚠️ Исходники: два пути, и :appId в первом — не то, что кажется. Запись версии идёт в POST /v1/apps/:appId/sources, где :appId — идентификатор OAuth-приложения, а НЕ приложения из этого раздела и не сервера. Чтение и скачивание версии идут по серверу: GET /v1/infra/servers/:id/sources/:versionId/download, и вот туда подставляется server.id отсюда вместе с sources.latestVersionId. Оба пути живые, один другой не заменяет.
Порядок и постраничный обход
Порядок назван здесь целиком, потому что от него зависит, можно ли обойти список по страницам и получить каждое приложение ровно один раз.
scope |
Порядок |
|---|---|
feed |
Закреплённые вами → свои → чужие. Внутри группы — по updatedAt от свежих к старым, при равных метках — по id |
mine, shared |
По createdAt от свежих к старым, при равных метках — по id |
⚠️ updatedAt двигается только правкой карточки — выкладка её не трогает. То есть «свежесть» в ленте означает «когда карточку меняли», а не «когда приложение выкладывали»: только что выложенное приложение окажется там, где было. Когда нужна свежесть кода, смотрите sources.latestSavedAt.
Ключ id в конце — не формальность. У приложений, созданных пакетно, метки времени совпадают, и без вторичного ключа две страницы одного обхода могли бы пересечься или пропустить приложение: порядок внутри группы с равными метками база не обещает. С ним обход воспроизводим.
Ограничение частоты
| Операция | Лимит на флот | Доля одной реплики сегодня |
|---|---|---|
GET /v1/applications |
60 запросов в минуту | 20 |
GET /v1/applications/:id |
120 запросов в минуту | 40 |
⚠️ Числа в левом столбце — суммарный лимит платформы, а не тот, который увидит ваш клиент. Запросы обслуживает несколько реплик бэкенда, каждая держит свою долю лимита и свой счётчик, независимый от соседей: сегодня реплик три, поэтому одна реплика пропускает 20 запросов списка в минуту и 40 запросов карточки. Полные 60 и 120 достигаются, только если запросы равномерно разошлись по репликам; клиент, у которого соединение держится на одной реплике, упрётся в её долю раньше.
Поэтому планируйте по заголовку, а не по таблице: X-RateLimit-Limit приходит с фактическим лимитом обслужившей реплики, X-RateLimit-Reset — с числом секунд до конца окна. Число реплик может меняться без записи в журнале изменений, а заголовок не врёт никогда.
Счётчик — на владельца ключа в рамках портала, а не на IP-адрес и не на отдельный ключ. Это важно в двух направлениях: несколько ключей одного человека делят один счётчик (выпуск нового ключа лимит не обнуляет), а сотрудники одного портала друг другу не мешают — даже когда все их запросы уходят в интернет через один адрес. У списка и карточки счётчики раздельные.
Превышение приходит как 429 с кодом RATE_LIMITED и заголовком Retry-After (целые секунды до конца окна).
⚠️ 429 бывает не только про частоту, поэтому ветвиться нужно по error.code, а не по статусу. Суточная квота бесплатных вызовов ключа отвечает тем же статусом, но кодом QUOTA_EXCEEDED, и ожидание её не лечит — нужен платный тариф. Клиент, который любой 429 показывает как «слишком часто, подождите», на исчерпанной квоте будет ждать бесконечно; клиент, который любой 429 показывает как «квота исчерпана», напугает человека на обычном всплеске частоты.
Быстрый старт
Свои приложения одним запросом:
curl -H "X-Api-Key: YOUR_API_KEY" \
"https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20"
{
"success": true,
"data": [
{
"id": "cmsw806qi0000tdskiw2aegii",
"name": "Отчёт по сделкам",
"type": "PERSONAL",
"viewerState": "owner",
"pinned": false,
"author": { "name": "Автор приложений" },
"isEmbedded": false,
"openUrl": "https://app-05b67cf7.vibecode.bitrix24.tech",
"openTarget": "app",
"server": {
"id": "5cbb50f9-f95f-4ddf-ba3b-b771209cb6fb",
"status": "RUNNING",
"url": "https://app-05b67cf7.vibecode.bitrix24.tech",
"kind": "STANDALONE",
"reachable": true,
"lastDeployedAt": "2026-08-16T19:50:41.302Z"
},
"sources": { "hasVersions": true, "latestVersionId": "v2", "latestSavedAt": "2026-08-16T19:41:18.818Z" },
"activeOperation": { "kind": "deploy", "status": "running", "step": "build", "startedAt": "2026-08-16T19:50:33.822Z" }
}
],
"total": 2,
"page": 1,
"limit": 20,
"truncated": false
}
Показаны одно приложение и основные его поля. Полный разбор — Карточка приложения.
Полный пример
Скрипт собирает все свои приложения постранично, показывает те, над которыми идёт операция, и берёт у одного из них адрес последней версии исходников.
const BASE = 'https://vibecode.bitrix24.tech/v1'
const API_KEY = process.env.VIBE_API_KEY
async function api(path) {
const res = await fetch(`${BASE}${path}`, { headers: { 'X-Api-Key': API_KEY } })
const body = await res.json()
if (!res.ok) throw new Error(`${path}: ${body.error.code} — ${body.error.message}`)
return body
}
// 1. Своя витрина: обходим страницы, пока не соберём все приложения.
const applications = []
let page = 1
let total = 0
do {
const chunk = await api(`/applications?scope=mine&page=${page}&limit=100`)
applications.push(...chunk.data)
total = chunk.total
page += 1
} while (applications.length < total && page <= 100)
console.log(`Приложений: ${applications.length} из ${total}`)
// 2. Приложения, над которыми прямо сейчас идёт операция.
for (const app of applications.filter(a => a.activeOperation !== null)) {
const { kind, status, step, startedAt } = app.activeOperation
console.log(`${app.name}: ${kind} — ${status}, шаг ${step ?? 'не сообщён'}, старт ${startedAt}`)
}
// 3. Карточка приложения: куда открывать и адрес последней версии исходников.
const target = applications.find(a => a.server !== null && a.sources.hasVersions)
if (target) {
const { data: card } = await api(`/applications/${target.id}`)
console.log(`${card.name}: открывать ${card.openUrl ?? 'негде'} (${card.openTarget ?? 'адреса нет'})`)
console.log(`Сервер ${card.server.status}, отвечает по сети: ${card.server.reachable}`)
console.log(`Версия ${card.sources.latestVersionId} от ${card.sources.latestSavedAt}`)
console.log(`Скачать: ${BASE}/infra/servers/${card.server.id}/sources/${card.sources.latestVersionId}/download`)
}
Вывод на портале из примера:
Приложений: 2 из 2
Отчёт по сделкам: deploy — unknown, шаг build, старт 2026-08-16T19:50:33.822Z
Отчёт по сделкам: открывать https://app-05b67cf7.vibecode.bitrix24.tech (app)
Сервер RUNNING, отвечает по сети: true
Версия v2 от 2026-08-16T19:41:18.818Z
Скачать: https://vibecode.bitrix24.tech/v1/infra/servers/5cbb50f9-f95f-4ddf-ba3b-b771209cb6fb/sources/v2/download
Список приложений
GET /v1/applications
Возвращает приложения портала, которые видит владелец ключа, — свои, открытые коллегами или всё вместе. Выборка постраничная, набор полей строки совпадает с карточкой приложения.
Параметры
| Параметр | Тип | Обяз. | По умолч. | Описание |
|---|---|---|---|---|
scope (query) |
string | нет | feed |
Какие приложения вернуть: mine — свои, shared — доступные вам чужие, feed — общий список из первых двух. Значения регистрозависимы, а пустое scope= отвечает 400 INVALID_SCOPE наравне с неизвестным: параметр либо опускают, либо задают одним из трёх значений |
page (query) |
number | нет | 1 |
Номер страницы, считая с единицы. Значение меньше единицы и нечисловое читаются как 1 |
limit (query) |
number | нет | 50 |
Сколько приложений на странице, максимум 100. Большее значение приводится к 100, 0 и нечисловое — к 50, отрицательное — к 1 |
Постраничный обход строится по total, page и limit из ответа: страницы запрашиваются подряд, пока сумма полученных строк не догонит total. Страница за пределами выдачи отвечает 200 с пустым массивом data, а не ошибкой.
Считать число страниц как total / limit можно только при truncated: false. При true total перестаёт быть полным числом, страниц за ним нет, и за остатком идут в mine и shared — у них предела нет.
Примеры
curl — личный ключ
curl -H "X-Api-Key: YOUR_API_KEY" \
"https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20"
curl — OAuth-приложение
curl -H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
"https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20"
JavaScript — личный ключ
const res = await fetch('https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20', {
headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})
const { data: applications, total } = await res.json()
applications.forEach(app => {
const state = app.activeOperation
? `${app.activeOperation.kind} — ${app.activeOperation.status}`
: app.server?.status ?? 'сервера нет'
console.log(`${app.name}: ${state}`)
})
console.log(`Показано ${applications.length} из ${total}`)
JavaScript — OAuth-приложение
const res = await fetch('https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20', {
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
})
const { data: applications, total } = await res.json()
Поля ответа
| Поле | Тип | Описание |
|---|---|---|
success |
boolean | Всегда true при успехе |
data |
array | Массив приложений. Элемент массива и data одной карточки — одна и та же форма, её поля разобраны в Карточке приложения |
total |
number | Сколько приложений попало под запрошенный scope. У feed это размер выборки, а не число приложений портала — прежде чем считать по нему страницы, читайте truncated |
page |
number | Номер выданной страницы, уже приведённый к допустимому значению |
limit |
number | Размер страницы, уже приведённый к допустимому значению |
truncated |
boolean | Урезана ли выдача. Приходит всегда, в любом scope. false — total точное число. true — total это размер выборки, а не «сколько есть», и страниц за ним нет |
Пример ответа
{
"success": true,
"data": [
{
"id": "cmsw806qp0001tdskzf6mitka",
"name": "Черновик без сервера",
"description": null,
"type": "PERSONAL",
"iconUrl": null,
"createdAt": "2026-08-16T19:51:18.817Z",
"updatedAt": "2026-08-16T19:51:18.817Z",
"viewerState": "owner",
"pinned": false,
"author": { "name": "Автор приложений" },
"isEmbedded": true,
"openUrl": null,
"openTarget": null,
"server": null,
"sources": { "hasVersions": false, "latestVersionId": null, "latestSavedAt": null },
"activeOperation": null
},
{
"id": "cmsw806qi0000tdskiw2aegii",
"name": "Отчёт по сделкам",
"description": "Сводка по воронке за период",
"type": "PERSONAL",
"iconUrl": null,
"createdAt": "2026-08-16T19:51:18.810Z",
"updatedAt": "2026-08-16T19:51:18.810Z",
"viewerState": "owner",
"pinned": false,
"author": { "name": "Автор приложений" },
"isEmbedded": false,
"openUrl": "https://app-05b67cf7.vibecode.bitrix24.tech",
"openTarget": "app",
"server": {
"id": "5cbb50f9-f95f-4ddf-ba3b-b771209cb6fb",
"status": "RUNNING",
"url": "https://app-05b67cf7.vibecode.bitrix24.tech",
"kind": "STANDALONE",
"reachable": true,
"lastDeployedAt": "2026-08-16T19:50:41.302Z"
},
"sources": {
"hasVersions": true,
"latestVersionId": "v2",
"latestSavedAt": "2026-08-16T19:41:18.818Z"
},
"activeOperation": {
"kind": "deploy",
"status": "running",
"step": "build",
"startedAt": "2026-08-16T19:50:33.822Z"
}
}
],
"total": 2,
"page": 1,
"limit": 50,
"truncated": false
}
Пример ответа при ошибке
400 — значение scope вне допустимого набора:
{
"success": false,
"error": {
"code": "INVALID_SCOPE",
"message": "scope must be one of: mine, shared, feed"
}
}
Ошибки
| HTTP | Код | Описание |
|---|---|---|
| 400 | INVALID_SCOPE |
Значение scope вне набора mine, shared, feed. Сюда же попадают пустое значение и другой регистр |
| 401 | NO_PORTAL |
Ключ не привязан к порталу — фактический ответ на этот случай |
| 403 | MANAGEMENT_KEY_NO_ENTITY_ACCESS |
Управляющий ключ: нужен API-ключ или ключ авторизации |
| 403 | PORTAL_KEY_REQUIRED |
Страховка раздела: повреждённая запись ключа без владельца. Обычный «ключ без портала» — это 401 NO_PORTAL выше |
| 401 | MISSING_API_KEY |
Не передан заголовок X-Api-Key |
| 401 | INVALID_API_KEY |
Неверный или просроченный API-ключ |
| 429 | RATE_LIMITED |
Превышена частота. 60 запросов в минуту — суммарный лимит платформы, доля одной реплики сегодня 20; ориентируйтесь на X-RateLimit-Limit из ответа. Счётчик на владельца ключа. Лимит строже, чем у карточки: список рассчитан на обновление экрана, а не на опрос в цикле. Есть Retry-After — пауза в целых секундах |
| 429 | QUOTA_EXCEEDED |
Исчерпана суточная квота бесплатных вызовов ключа. Тот же статус, другая причина: ожидание не поможет |
Текст error.message всегда английский и предназначен для журналов: тексты для интерфейса стройте по error.code. Полный список общих ошибок API — Ошибки.
Известные особенности
- Выборка идёт по владельцу ключа, а не по самому ключу. В список попадают и приложения, заведённые в личном кабинете: их серверы привязаны к другим ключам того же человека, поэтому в
GET /v1/infra/serversони не видны, а здесь видны. Обратное тоже верно: сменив ключ на новый, вы увидите тот же список приложений. - Порядок строк зависит от
scope, и он полный. Уfeedсверху идут закреплённые приложения, за ними свои, затем открытые вам, и внутри каждой группы новее поupdatedAt. Уmineиsharedпорядок один — поcreatedAt, новые сверху. В обоих случаях при равных метках времени порядок доопределяется поid, и это важно для обхода: у приложений, созданных пакетно, метки совпадают, а без вторичного ключа две страницы одного обхода могли бы пересечься или пропустить приложение. - У
feedвыборка ограничена сверху, и об этом говоритtruncated. Порядок для этого списка считается на стороне сервиса, а не выборкой из базы, поэтому страница нарезается уже после сортировки, аtotalпоказывает размер выборки, а не всего портала. Признак читайте осторожно: он поднимается, когда выборка упёрлась в предел, а отличить «упёрлись» от «ровно столько и было» одним запросом нельзя, поэтому платформа ошибается в сторону признания неполноты. Ложноеtrueвы диагностируете сами — сходите за следующей страницей и получите пустую. Ложноеfalseне диагностируется никак, поэтому его и не допускают. Уmineиsharedпредела нет,total— полное число строк под фильтр,truncatedвсегдаfalse. updatedAt— не свежесть кода. Поле двигает запись в саму карточку: переименование, смена описания, отвязка удалённого сервера. Выкладка его не двигает, поэтому приложение, куда сегодня выкладывались десять раз, поupdatedAtуедет в хвостfeed. Свежесть кода —sources.latestSavedAt.- В
sharedиfeedсводка исходников почти всегда пустая. Оба блока обогащения наполняются только тому, кто приложением управляет, поэтому у чужих строкsourcesприходит пустым, аactiveOperation—null. Форма строки при этом не меняется, и отличить «данные не отдаются» от «версий нет» по самой строке нельзя. Экран, который рисует статус кода для всей выдачи, обязан это учитывать. - Пустой
shared— рабочее состояние, а не отказ. Ответ200с пустымdataиtotal: 0означает, что доступных вам чужих приложений нет. Ошибку в этом случае искать негде. requestableв списке не возвращается — ни в одномscope. В выдачу попадают только приложения, которые ваши, доступны вам через политику сервера, открыты вам лично грантом или не имеют сервера и открыты всему порталу. То есть кнопка «запросить доступ» разделу не нужна — и вести её было бы некуда: операций записи здесь нет. Значениеrequestableсуществует для полноты набора и приходит только там, где карточка отказывает:403 FORBIDDENна прямой запрос чужого закрытого приложения.- У
scopeиviewerStateразные границы. Вsharedпопадают не только приложения, доступ к которым открыли лично вам, но и приложения без сервера, открытые всему порталу, — а такая строка приходит сviewerState: "usable", не"shared". Клиент, который отбирает строки поviewerState === "shared", часть выдачи потеряет.