Для 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), а activeOperationnull. Форма ответа при этом не меняется, поэтому пустая сводка сама по себе не значит «версий нет». Причин у неё три, и снаружи они выглядят одинаково: у приложения нет сервера, версий действительно нет, или данные вам не отдаются, потому что приложение чужое.

Идентификаторы: что с чем совпадает

Раздел возвращает два идентификатора, и оба ведут в другие разделы 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 показывает как «квота исчерпана», напугает человека на обычном всплеске частоты.

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

Свои приложения одним запросом:

Terminal
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20"
JSON
{
  "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
}

Показаны одно приложение и основные его поля. Полный разбор — Карточка приложения.

Полный пример

Скрипт собирает все свои приложения постранично, показывает те, над которыми идёт операция, и берёт у одного из них адрес последней версии исходников.

javascript
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 — личный ключ

Terminal
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://vibecode.bitrix24.tech/v1/applications?scope=mine&limit=20"

curl — OAuth-приложение

Terminal
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 — личный ключ

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-приложение

javascript
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. falsetotal точное число. truetotal это размер выборки, а не «сколько есть», и страниц за ним нет

Пример ответа

JSON
{
  "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 вне допустимого набора:

JSON
{
  "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 приходит пустым, а activeOperationnull. Форма строки при этом не меняется, и отличить «данные не отдаются» от «версий нет» по самой строке нельзя. Экран, который рисует статус кода для всей выдачи, обязан это учитывать.
  • Пустой shared — рабочее состояние, а не отказ. Ответ 200 с пустым data и total: 0 означает, что доступных вам чужих приложений нет. Ошибку в этом случае искать негде.
  • requestable в списке не возвращается — ни в одном scope. В выдачу попадают только приложения, которые ваши, доступны вам через политику сервера, открыты вам лично грантом или не имеют сервера и открыты всему порталу. То есть кнопка «запросить доступ» разделу не нужна — и вести её было бы некуда: операций записи здесь нет. Значение requestable существует для полноты набора и приходит только там, где карточка отказывает: 403 FORBIDDEN на прямой запрос чужого закрытого приложения.
  • У scope и viewerState разные границы. В shared попадают не только приложения, доступ к которым открыли лично вам, но и приложения без сервера, открытые всему порталу, — а такая строка приходит с viewerState: "usable", не "shared". Клиент, который отбирает строки по viewerState === "shared", часть выдачи потеряет.

Смотрите также