Для AI-агентов: markdown этой страницы — /docs-content/cowork/off-peak.md индекс документации — /llms.txt

Выгодные часы в подписке Cowork/Code

В отдельные часы недели квота подписки расходуется медленнее — один и тот же запрос забирает меньшую долю месячного лимита. Ответы подписки показывают, действует ли скидка прямо сейчас и когда она начнётся.

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

Примеры

curl — личный ключ

Terminal
curl https://vibecode.bitrix24.tech/v1/cowork/state \
  -H "X-Api-Key: YOUR_API_KEY"

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

Terminal
curl https://vibecode.bitrix24.tech/v1/cowork/state \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN"

JavaScript — личный ключ

javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/cowork/state', {
  headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})

const state = await res.json()
const off = state.offPeak

if (!off || !off.enabled) {
  console.log('Выгодные часы для этой подписки не действуют')
} else if (off.visible === 'now') {
  const discount = Math.round((1 - off.currentMultiplier) * 100)
  console.log(`Квота расходуется на ${discount}% медленнее ещё ${off.currentWindowEndsInHours} ч`)
} else if (off.visible === 'next' && off.nextAtOrBelow) {
  console.log(`Выгодный час начнётся через ${off.nextAtOrBelow.inHours} ч`)
}

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

javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/cowork/state', {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})

const state = await res.json()

Поля ответа

Блок приезжает в двух ответах и различается составом.

Ответ Состав блока
GET /v1/cowork/state Блок целиком — вместе с сеткой grid и полем touSavedPct
GET /v1/cowork/me Блок без сетки и без touSavedPct

Оба ключа необязательные. Возможность включается для аккаунтов по очереди, и пока она не включена, ключей offPeak и touSavedPct в теле ответа нет вовсе.

Поле Тип Описание
offPeak object Блок выгодных часов
offPeak.enabled boolean Действуют ли выгодные часы для этой подписки. При false остальные поля пустые, все часы идут по полной цене
offPeak.visible string Что показать пользователю: now — скидка действует прямо сейчас, next — заметная скидка начнётся в ближайшие часы, none — показывать нечего
offPeak.timezone string или null Часовой пояс расписания — идентификатор из базы часовых поясов IANA, например Europe/Moscow. По нему считаются день недели и час
offPeak.currentMultiplier number Множитель расхода квоты в текущий час, больше 0 и не больше 1. Значение 0.5 означает, что квота расходуется вдвое медленнее, значение 1 — без скидки
offPeak.currentWindowEndsInHours number или null Через сколько часов расход перестанет быть таким же выгодным. null, если в пределах недели вперёд дороже не станет
offPeak.nextAtOrBelow object или null Ближайший час, скидка в котором достаточна, чтобы её показывать. null, когда такого часа нет в пределах недели вперёд
offPeak.nextAtOrBelow.inHours number Через сколько часов наступит этот час
offPeak.nextAtOrBelow.multiplier number Множитель расхода квоты в этом часе
offPeak.perModel boolean true, если расписание подобрано под одну модель, а не общее для всех моделей подписки
offPeak.modelName string или null Название модели, под которую подобрано расписание. null, когда расписание общее
offPeak.grid array или null Сетка множителей grid[день][час]. Семь строк по 24 значения. День 0 — воскресенье, день 6 — суббота. Час — от 0 до 23 в часовом поясе timezone. Приходит только в GET /v1/cowork/state
offPeak.nowCell object или null Ячейка сетки, которой соответствует текущий момент. Считается на сервере, поэтому совпадает с currentMultiplier
offPeak.nowCell.dow number День недели, от 0 (воскресенье) до 6 (суббота)
offPeak.nowCell.hour number Час, от 0 до 23
touSavedPct number или null Какую долю расхода за текущий расчётный период сняли выгодные часы, в процентах с одним знаком после запятой. Приходит только в GET /v1/cowork/state

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

Ответ GET /v1/cowork/state. Показаны блок выгодных часов и доля сэкономленного, остальные поля — в Состоянии подписки Cowork/Code.

JSON
{
  "offPeak": {
    "enabled": true,
    "visible": "now",
    "timezone": "Europe/Moscow",
    "currentMultiplier": 0.5,
    "currentWindowEndsInHours": 6,
    "nextAtOrBelow": { "inHours": 1, "multiplier": 0.5 },
    "perModel": false,
    "modelName": null,
    "grid": [
      [0.75, 0.87, 0.87, 0.87, 0.87, 0.87, 0.87, 0.87, 0.75, 0.75, 0.75, 0.75, 0.62, 0.5, 0.75, 0.87, 1, 1, 0.87, 0.87, 0.87, 0.75, 0.75, 0.75],
      [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.62, 0.62, 0.62, 0.62, 0.75, 0.75, 0.87, 0.87, 0.75, 0.75, 0.75, 0.75, 0.62, 0.5, 0.5],
      [0.5, 0.5, 0.62, 0.62, 0.75, 0.62, 0.62, 0.62, 0.75, 0.75, 0.87, 1, 1, 0.87, 0.75, 0.87, 0.87, 0.75, 0.75, 0.62, 0.62, 0.62, 0.62, 0.62],
      [0.75, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.62, 0.62, 0.62, 0.75, 0.75, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.87],
      [1, 1, 1, 1, 1, 0.87, 0.87, 0.87, 0.87, 1, 1, 1, 1, 1, 1, 0.87, 0.75, 0.75, 0.75, 0.87, 1, 1, 1, 1],
      [1, 1, 1, 1, 1, 0.87, 0.87, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.87, 0.87, 0.75, 0.87],
      [0.75, 0.62, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.62, 0.75, 0.75, 0.62, 0.62, 0.75, 0.75, 0.87, 0.87, 1, 1, 1, 1, 0.87]
    ],
    "nowCell": { "dow": 1, "hour": 3 }
  },
  "touSavedPct": 12.4
}

Тот же момент в ответе GET /v1/cowork/me — без сетки и без доли сэкономленного:

JSON
{
  "offPeak": {
    "enabled": true,
    "visible": "now",
    "timezone": "Europe/Moscow",
    "currentMultiplier": 0.5,
    "currentWindowEndsInHours": 6,
    "nextAtOrBelow": { "inHours": 1, "multiplier": 0.5 },
    "perModel": false,
    "modelName": null,
    "nowCell": { "dow": 1, "hour": 3 }
  }
}

Пример ответа при ошибке

404 — подписка не активирована:

JSON
{
  "success": false,
  "error": {
    "code": "COWORK_NOT_ACTIVATED",
    "message": "No active Cowork/Code subscription for this user+portal"
  }
}

Ошибки

HTTP Код Описание
401 MISSING_API_KEY Не передан заголовок X-Api-Key
401 INVALID_API_KEY Неверный API-ключ
403 INSUFFICIENT_SCOPE У ключа нет скоупа vibe:cowork
404 COWORK_NOT_ACTIVATED Подписка Cowork/Code не найдена для пары пользователь и портал
500 INVALID_TIER_CONFIGURATION Конфигурация тарифов на платформе некорректна
503 COWORK_FEATURE_DISABLED Cowork/Code отключён на уровне платформы

Полный список общих ошибок API — Ошибки.

Подсказка в отказе по исчерпанной квоте

Когда окно квоты подписки исчерпано, POST /v1/chat/completions отвечает 402 с кодом cowork_quota_exhausted. В теле такого отказа приезжает необязательное поле error.offPeakHint — через сколько часов блокировка снимется и какой множитель расхода будет действовать в этот момент. По нему приложение решает, достаточно ли просто подождать.

Поле Тип Описание
error.offPeakHint object Подсказка о выгодном часе на момент разблокировки
error.offPeakHint.inHours number Через сколько часов снимется блокировка. Округляется вверх — тот же источник, что и у заголовка Retry-After
error.offPeakHint.multiplier number Множитель расхода квоты, который будет действовать в этот момент
JSON
{
  "error": {
    "message": "Cowork/Code quota exhausted for the 5h window. Wait until 2026-08-10T15:00:00.000Z or upgrade your tier.",
    "type": "insufficient_quota",
    "code": "cowork_quota_exhausted",
    "window": "5h",
    "resetAt": "2026-08-10T15:00:00.000Z",
    "nextTier": "PRO",
    "offPeakHint": { "inHours": 3, "multiplier": 0.5 }
  }
}

Подсказка приходит, только когда возможность включена для аккаунта и момент разблокировки попадает в час со скидкой. В остальных случаях ключа offPeakHint в теле нет вовсе — со значением null он не приходит, как и остальные поля выгодных часов.

Множитель считается ровно на момент разблокировки, ближайшее выгодное окно при этом не ищется. Если в этот час скидки не будет, подсказки не будет тоже — даже когда выгодный час наступит немного позже.

Известные особенности

Проверяйте наличие ключа offPeak, а не его значение. Пока возможность не включена для аккаунта, ключа в теле ответа нет вовсе — он не приходит со значением null. Клиент, читающий state.offPeak.enabled без проверки самого offPeak, упадёт на таком ответе.

Ветвите интерфейс по visible, а не по собственному порогу. Порог, начиная с которого скидку стоит показывать, платформа решает на своей стороне и наружу не отдаёт. Сравнение currentMultiplier с числом, вписанным в клиент, разойдётся с платформой при следующей настройке порога.

Множитель — коэффициент расхода, а не размер скидки. Значение 0.62 означает, что вызов забирает 62% от той доли квоты, которую он забрал бы без скидки, то есть расход снижен на 38%. Размер скидки считается как 1 - currentMultiplier.

nextAtOrBelow ищет заметную скидку, а не любое удешевление. Этим он отличается от поля nextWindow в Расписании выгодных часов: там ближайший строго более дешёвый час, здесь — ближайший час, скидка в котором достаточна для показа.

Блок молчит, пока подписка не активна. Приостановленная и отменённая подписка получает enabled: false — расписание к ней уже не применяется.

touSavedPct считается от расхода, а не от лимита. Это доля того, что было бы списано без скидки. Значение null приходит, когда считать не из чего: расчётный период начался раньше, чем платформа стала вести счёт, либо за период не было расхода.

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