Для AI-агентов: markdown этой страницы — /docs-content/cowork/off-peak.md индекс документации — /llms.txt
Выгодные часы в подписке Cowork/Code
В отдельные часы недели квота подписки расходуется медленнее — один и тот же запрос забирает меньшую долю месячного лимита. Ответы подписки показывают, действует ли скидка прямо сейчас и когда она начнётся.
Скидка применяется сама, включать её не нужно. Блок offPeak показывает то, что уже учтено при расходе квоты, чтобы приложение могло предложить перенести объёмную задачу на выгодный час.
Примеры
curl — личный ключ
curl https://vibecode.bitrix24.tech/v1/cowork/state \
-H "X-Api-Key: YOUR_API_KEY"
curl — OAuth-приложение
curl https://vibecode.bitrix24.tech/v1/cowork/state \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN"
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-приложение
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.
{
"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 — без сетки и без доли сэкономленного:
{
"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 — подписка не активирована:
{
"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 | Множитель расхода квоты, который будет действовать в этот момент |
{
"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 приходит, когда считать не из чего: расчётный период начался раньше, чем платформа стала вести счёт, либо за период не было расхода.