
## Off-peak schedule

`GET /v1/off-peak`

Returns the off-peak schedule: how much more slowly the company AI quota is consumed during each hour of the week and when the next discounted window arrives.

The discount applies to quota consumption — during off-peak hours the same call takes a smaller share of the monthly limit. Token payments from the wallet do not receive the discount. The endpoint is meant for planning: an agent reads the schedule and moves deferrable bulk tasks into the cheap windows.

## Parameters

| Parameter | Type | Required | Default | Description |
|----------|-----|:-----:|-----------|---------|
| `model` (query) | string | no | — | Model identifier from [`GET /v1/models`](/docs/ai/models/list). Returns the schedule for that model. Without this parameter, returns the schedule the platform marked as the main one |

## Examples

### curl — personal key

```bash
curl "https://vibecode.bitrix24.com/v1/off-peak?model=bitrix/bitrixgpt-5.5" \
  -H "X-Api-Key: YOUR_API_KEY"
```

### curl — OAuth application

```bash
curl "https://vibecode.bitrix24.com/v1/off-peak?model=bitrix/bitrixgpt-5.5" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN"
```

### JavaScript — personal key

```javascript
const url = 'https://vibecode.bitrix24.com/v1/off-peak?model=bitrix/bitrixgpt-5.5'
const res = await fetch(url, {
  headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})

const schedule = await res.json()

if (schedule.enabled && schedule.currentMultiplier < 1) {
  const discount = Math.round((1 - schedule.currentMultiplier) * 100)
  console.log(`Off-peak hour now: quota is consumed ${discount}% more slowly`)
} else if (schedule.nextWindow) {
  console.log(`Cheaper in ${schedule.nextWindow.inHours} h`)
}
```

### JavaScript — OAuth application

```javascript
const url = 'https://vibecode.bitrix24.com/v1/off-peak?model=bitrix/bitrixgpt-5.5'
const res = await fetch(url, {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})

const schedule = await res.json()
```

## Response fields

The successful response is the schedule object itself, without a `success` and `data` wrapper.

| Field | Type | Description |
|------|-----|---------|
| `enabled` | boolean | Whether a schedule is in effect for the requested model. When `false`, every hour is at full price and the remaining fields are empty |
| `timezone` | string \| null | Schedule timezone — an identifier from the IANA time zone database, for example `UTC`. The day of week and hour are computed against it |
| `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 |
| `nextWindow` | object \| null | The nearest hour strictly cheaper than the current one, within a week ahead. `null` when there is no such hour |
| `nextWindow.inHours` | number | How many hours until that hour arrives |
| `nextWindow.multiplier` | number | Quota-consumption multiplier in that hour |
| `grid` | array \| 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 |
| `nowCell` | object \| null | The grid cell that the current moment maps to. Computed on the server, so it matches `currentMultiplier` |
| `nowCell.dow` | number | Day of week, from `0` (Sunday) to `6` (Saturday) |
| `nowCell.hour` | number | Hour, from `0` to `23` |

## Response example

The schedule is in effect:

```json
{
  "enabled": true,
  "timezone": "UTC",
  "currentMultiplier": 1,
  "nextWindow": { "inHours": 4, "multiplier": 0.87 },
  "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": 4, "hour": 11 }
}
```

The schedule is not in effect — the model has none, or off-peak hours are disabled on the platform:

```json
{
  "enabled": false,
  "timezone": null,
  "currentMultiplier": 1,
  "nextWindow": null,
  "grid": null,
  "nowCell": null
}
```

## Error response example

`401 MISSING_API_KEY` — the `X-Api-Key` header is missing:

```json
{
  "success": false,
  "error": {
    "code": "MISSING_API_KEY",
    "message": "API key required. Pass via X-Api-Key header."
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | Key not found or revoked |
| 403 | `scope_missing` | The API key lacks the `vibe:ai` scope |
| 429 | `rate_limit_exceeded` | AI endpoint request limit exceeded. Time until reset is in the `Retry-After` header |

The full list of common API errors — [Errors](/docs/errors).

## Known specifics

**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`.

**Pass the model.** Without the `model` parameter the endpoint returns the schedule the platform marked as the main one. Until a main schedule is assigned, such a call returns `enabled: false` even when individual models do have discounts.

**An unknown model does not raise an error.** If a model has no schedule or the identifier does not exist, a `200` arrives with `enabled: false` and empty fields. The presence of discounts is signaled by the `enabled` field, not by the response code.

**The schedule changes.** The platform recomputes the grid against actual load, so re-read the schedule before planning rather than storing it for a long time.

**`nextWindow` looks for a strictly cheaper hour.** If the current hour is already the cheapest within a week ahead, the field is `null`. This does not mean there is no discount — check `currentMultiplier`.

**The discount also slows down the pacing windows.** The daily and weekly windows grow by the same discounted amount as the monthly quota. Moving deferrable jobs into the off-peak hours postpones both the `429 ai_pacing_limited` response and the exhaustion of the monthly quota.

## See also

- [Company AI quota](/docs/ai/consumption/quota)
- [Model list](/docs/ai/models/list)
- [Chat completions](/docs/ai/chat/completions)
- [AI Router](/docs/ai)
