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 to an off-peak 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 its contents differ between them.
| Response | Block composition |
|---|---|
ResponseGET /v1/cowork/state |
Block compositionThe whole block — together with the grid and the touSavedPct field |
ResponseGET /v1/cowork/me |
Block compositionThe block without the grid and without touSavedPct |
offPeak arrives in every successful response of both endpoints, and touSavedPct in every successful response of GET /v1/cowork/state.
| Field | Type | Description |
|---|---|---|
FieldoffPeak |
Typeobject | DescriptionOff-peak hours block |
FieldoffPeak.enabled |
Typeboolean | DescriptionWhether off-peak hours are in effect for this subscription. When false, the remaining fields are empty and every hour is at full price |
FieldoffPeak.visible |
Typestring | DescriptionWhat to show the user: now — the discount applies right now, next — a meaningful discount starts within the next few hours, none — nothing to show |
FieldoffPeak.timezone |
Typestring or null | DescriptionSchedule time zone — an identifier from the IANA time zone database, for example UTC. The day of week and hour are computed against it |
FieldoffPeak.currentMultiplier |
Typenumber | DescriptionQuota-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 |
FieldoffPeak.currentWindowEndsInHours |
Typenumber or null | DescriptionHow many hours until consumption stops being this favorable. null when it does not get more expensive within the next week |
FieldoffPeak.nextAtOrBelow |
Typeobject or null | DescriptionThe nearest hour whose discount is deep enough to be worth showing. null when there is no such hour within the next week |
FieldoffPeak.nextAtOrBelow.inHours |
Typenumber | DescriptionHow many hours until that hour arrives |
FieldoffPeak.nextAtOrBelow.multiplier |
Typenumber | DescriptionQuota-consumption multiplier in that hour |
FieldoffPeak.perModel |
Typeboolean | Descriptiontrue when the schedule is tailored to a single model rather than shared by all models of the subscription |
FieldoffPeak.modelName |
Typestring or null | DescriptionName of the model the schedule is tailored to. null when the schedule is shared |
FieldoffPeak.grid |
Typearray or null | DescriptionGrid 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 |
FieldoffPeak.nowCell |
Typeobject or null | DescriptionThe grid cell that the current moment maps to. Computed on the server, so it matches currentMultiplier |
FieldoffPeak.nowCell.dow |
Typenumber | DescriptionDay of week, from 0 (Sunday) to 6 (Saturday) |
FieldoffPeak.nowCell.hour |
Typenumber | DescriptionHour, from 0 to 23 |
FieldtouSavedPct |
Typenumber or null | DescriptionWhat share of the subscription's 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 |
|---|---|---|
| HTTP401 | CodeMISSING_API_KEY |
DescriptionThe X-Api-Key header was not passed |
| HTTP401 | CodeINVALID_API_KEY |
DescriptionThe key is not recognized — no such key exists on the platform |
| HTTP403 | CodeINSUFFICIENT_SCOPE |
DescriptionThe key lacks the vibe:cowork scope |
| HTTP404 | CodeCOWORK_NOT_ACTIVATED |
DescriptionNo Cowork/Code subscription found for the user and portal |
| HTTP500 | CodeINVALID_TIER_CONFIGURATION |
DescriptionThe tier configuration on the platform is invalid |
| HTTP503 | CodeCOWORK_FEATURE_DISABLED |
DescriptionCowork/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 responds with 402 and the code cowork_quota_exhausted. The body of that refusal carries an optional error.offPeakHint field — how many hours until the lockout ends 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 |
|---|---|---|
Fielderror.offPeakHint |
Typeobject | DescriptionHint about the off-peak hour at the moment the lockout ends |
Fielderror.offPeakHint.inHours |
Typenumber | DescriptionHow many hours until the lockout ends. Rounded up, from the same source as the Retry-After header |
Fielderror.offPeakHint.multiplier |
Typenumber | DescriptionThe 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 unblock moment falls into a discounted hour. Otherwise the offPeakHint key is absent from the body entirely — it never arrives as null. 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 platform does not look for the nearest off-peak window. If that hour carries no discount, no hint arrives either — even when an off-peak hour starts a little later.
Known specifics
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 as for the monthly quota bar, so the two shares 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.