For AI agents: markdown of this page — /docs-content-en/cowork/off-peak.md documentation index — /llms.txt
Off-peak hours in the Cowork/Code subscription
During certain hours of the week the subscription quota is consumed more slowly — the same request takes a smaller share of the monthly limit. The subscription responses show whether the discount applies right now and when it starts.
The discount applies on its own, there is nothing to switch on. The offPeak block reports what has already been taken into account when the quota was consumed, so an app can offer to move a bulk job into a cheap hour. Off-peak hours are not in effect on this platform yet, so today the block does not arrive here at all.
Examples
curl — personal key
curl https://vibecode.bitrix24.com/v1/cowork/state \
-H "X-Api-Key: YOUR_API_KEY"
curl — OAuth application
curl https://vibecode.bitrix24.com/v1/cowork/state \
-H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN"
JavaScript — personal key
const res = await fetch('https://vibecode.bitrix24.com/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('Off-peak hours are not in effect for this subscription')
} else if (off.visible === 'now') {
const discount = Math.round((1 - off.currentMultiplier) * 100)
console.log(`Quota is consumed ${discount}% more slowly for another ${off.currentWindowEndsInHours} h`)
} else if (off.visible === 'next' && off.nextAtOrBelow) {
console.log(`An off-peak hour starts in ${off.nextAtOrBelow.inHours} h`)
}
JavaScript — OAuth application
const res = await fetch('https://vibecode.bitrix24.com/v1/cowork/state', {
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
})
const state = await res.json()
Response fields
The block arrives in two responses and differs in composition.
| Response | Block composition |
|---|---|
GET /v1/cowork/state |
The whole block — together with the grid and the touSavedPct field |
GET /v1/cowork/me |
The block without the grid and without touSavedPct |
Both keys are optional. The capability is switched on account by account, and until it is on, the offPeak and touSavedPct keys are absent from the response body entirely.
| Field | Type | Description |
|---|---|---|
offPeak |
object | Off-peak hours block |
offPeak.enabled |
boolean | Whether off-peak hours are in effect for this subscription. When false, the remaining fields are empty and every hour is at full price |
offPeak.visible |
string | What to show the user: now — the discount applies right now, next — a meaningful discount starts within the next few hours, none — nothing to show |
offPeak.timezone |
string or null | Schedule timezone — an identifier from the IANA time zone database, for example UTC. The day of week and hour are computed against it |
offPeak.currentMultiplier |
number | Quota-consumption multiplier for the current hour, greater than 0 and no more than 1. A value of 0.5 means the quota is consumed twice as slowly, a value of 1 means no discount |
offPeak.currentWindowEndsInHours |
number or null | In how many hours consumption stops being this favourable. null when it never gets more expensive within a week ahead |
offPeak.nextAtOrBelow |
object or null | The nearest hour whose discount is deep enough to be worth showing. null when there is no such hour within a week ahead |
offPeak.nextAtOrBelow.inHours |
number | How many hours until that hour arrives |
offPeak.nextAtOrBelow.multiplier |
number | Quota-consumption multiplier in that hour |
offPeak.perModel |
boolean | true when the schedule was picked for a single model rather than shared by every model of the subscription |
offPeak.modelName |
string or null | Name of the model the schedule was picked for. null when the schedule is shared |
offPeak.grid |
array or null | Grid of multipliers grid[day][hour]. Seven rows of 24 values. Day 0 is Sunday, day 6 is Saturday. The hour is 0 to 23 in the timezone time zone. Arrives only in GET /v1/cowork/state |
offPeak.nowCell |
object or null | The grid cell that the current moment maps to. Computed on the server, so it matches currentMultiplier |
offPeak.nowCell.dow |
number | Day of week, from 0 (Sunday) to 6 (Saturday) |
offPeak.nowCell.hour |
number | Hour, from 0 to 23 |
touSavedPct |
number or null | What share of the subscription MONTHLY ALLOWANCE the off-peak hours gave back over the current billing period, in percent with one decimal place, capped at 100. Arrives only in GET /v1/cowork/state |
Response example
A GET /v1/cowork/state response. It shows the off-peak block and the saved share, the remaining fields are in Cowork/Code subscription state.
{
"offPeak": {
"enabled": true,
"visible": "now",
"timezone": "UTC",
"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
}
The same moment in a GET /v1/cowork/me response — without the grid and without the saved share:
{
"offPeak": {
"enabled": true,
"visible": "now",
"timezone": "UTC",
"currentMultiplier": 0.5,
"currentWindowEndsInHours": 6,
"nextAtOrBelow": { "inHours": 1, "multiplier": 0.5 },
"perModel": false,
"modelName": null,
"nowCell": { "dow": 1, "hour": 3 }
}
}
Error response example
404 — subscription not activated:
{
"success": false,
"error": {
"code": "COWORK_NOT_ACTIVATED",
"message": "No active Cowork/Code subscription for this user+portal"
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| 401 | MISSING_API_KEY |
The X-Api-Key header was not passed |
| 401 | INVALID_API_KEY |
Invalid API key |
| 403 | INSUFFICIENT_SCOPE |
The key lacks the vibe:cowork scope |
| 404 | COWORK_NOT_ACTIVATED |
No Cowork/Code subscription found for this user and Bitrix24 account |
| 500 | INVALID_TIER_CONFIGURATION |
The tier configuration on the platform is invalid |
| 503 | COWORK_FEATURE_DISABLED |
Cowork/Code is disabled at the platform level |
The full list of common API errors — Errors.
The hint in the exhausted-quota refusal
When a subscription quota window is exhausted, POST /v1/chat/completions answers 402 with the code cowork_quota_exhausted. The body of that refusal carries an optional error.offPeakHint field — in how many hours the block is lifted and which consumption multiplier will be in effect at that moment. An app reads it to decide whether simply waiting is enough.
| Field | Type | Description |
|---|---|---|
error.offPeakHint |
object | Hint about the off-peak hour at the moment the block is lifted |
error.offPeakHint.inHours |
number | In how many hours the block is lifted. Rounded up — the same source as the Retry-After header |
error.offPeakHint.multiplier |
number | The quota-consumption multiplier that will be in effect at that moment |
{
"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 }
}
}
The hint arrives only when the capability is enabled for the Bitrix24 account and the unblock moment falls into a discounted hour. Otherwise the offPeakHint key is absent from the body entirely — it never arrives as null, exactly like the other off-peak fields. Off-peak hours are not in effect on this platform yet, so today the hint does not arrive here at all.
The multiplier is computed exactly at the unblock moment; the nearest off-peak window is not searched for. If that hour carries no discount, no hint arrives either — even when a cheap hour starts a little later.
Known specifics
Check for the presence of the offPeak key, not for its value. Until the capability is on for the account, the key is absent from the response body entirely — it does not arrive as null. A client that reads state.offPeak.enabled without checking offPeak itself will throw on such a response.
Branch the interface on visible, not on a threshold of your own. The threshold above which a discount is worth showing is decided on the platform side and is not exposed. Comparing currentMultiplier against a number baked into the client will diverge from the platform the next time that threshold is tuned.
The multiplier is a consumption coefficient, not the discount size. A value of 0.62 means the call takes 62% of the quota share it would take without a discount, that is, consumption is reduced by 38%. The discount size is computed as 1 - currentMultiplier.
nextAtOrBelow looks for a meaningful discount, not for any price drop. That is how it differs from the nextWindow field in the Off-peak schedule: there it is the nearest strictly cheaper hour, here it is the nearest hour whose discount is deep enough to show.
The block stays silent while the subscription is not active. A paused or cancelled subscription gets enabled: false — the schedule no longer applies to it.
touSavedPct is measured against the monthly allowance, not against consumption. The denominator is the same one the month quota bar is a share of, so the two read side by side: «N% used» and «off-peak hours gave back M%». The value is null only when nothing has been saved yet in the current period.