For AI agents: markdown of this page — /docs-content-en/infra/wake-schedules.md documentation index — /llms.txt

Scheduled wake

Recurring wake windows for a server in BLACKHOLE mode: the platform brings the server up in time for a moment set by a cron expression, and the task itself is fired by the app's own cron once the machine is up — the platform never reads or runs the app's code.

On a preemptible plan, waking is best-effort, not a guarantee. A server of this type comes up as capacity frees up — through a queue — so there may be no free capacity by the window's moment, and the window can be skipped. The create and update response carries preemptibleAdvisoryCode: "PREEMPTIBLE_BEST_EFFORT" for exactly this case — see Create a window. For time-critical tasks, use a non-preemptible plan or the always-on (24/7) mode.

A deploy changes neither the windows nor the sleep timer. The wake schedule and the auto-sleep timer are stored on the platform, not in the application code, so shipping a new version does not reset them — the windows keep working with no extra action. The only thing the deploy takes from the schedule is the windows' timezone: on a dedicated virtual machine it lands in the TZ environment variable when every enabled window declares the same zone. Details — Full application deploy.

Creating or updating a wake schedule normally requires a numeric auto-sleep setting. Creating or updating any window while sleepAfterMinutes is null (“Never”) is rejected as an always-on conflict, even when that window is disabled. In the reverse direction, setting null is rejected only while an enabled window exists; existing disabled windows neither affect the timeout nor block that sleep-setting change. An explicit numeric sleepAfterMinutes remains the effective timeout and is not shortened by the schedule. Existing older records are not rewritten: if one already contains null plus an enabled and entitled schedule with a computed next wake, the platform's post-window timeout applies — 15 minutes of inactivity by default.

Scope: vibe:infra

Create a wake window

POST /v1/infra/servers/:id/wake-schedules

Declares a new recurring wake window for a server in BLACKHOLE mode. The body is a flat object, with no fields wrapper.

Parameters

Parameter In Type Required Description
id path string (UUID) yes ID of the BLACKHOLE server

Request fields (body)

Field Type Required Default Description
cronExpr string yes A 5-field cron expression (minute hour day-of-month month day-of-week), 9 to 120 characters
timezone string yes IANA timezone of the window, e.g. America/New_York. Checked against Intl.supportedValuesOf('timeZone') — an unknown value is rejected at validation time
label string no Display label for the window, up to 80 characters
lead number no the server's margin, else the platform's Margin, in seconds, before the cronExpr moment by which the server must already be up — covers VM boot time. 0 to 3600
enabled boolean no true Whether the window is active. A disabled window never triggers a wake but stays in the list

The lead margin resolves in three tiers: the window's own value, else a server-level margin set outside this CRUD, else the platform default — currently 180 seconds. For a server with kind: "GALAXY_APP", waking runs in two phases — bringing up the host and starting the container — so budget up to 900 seconds for lead.

This schedule write requires a numeric auto-sleep setting. If the server stores sleepAfterMinutes: null (“Never”), the request is rejected as an always-on conflict even when enabled is false. In the reverse direction, setting null is rejected only while an enabled window exists; an already stored disabled window neither affects the timeout nor blocks that sleep-setting change. An explicit numeric value remains the effective timeout and is not shortened by the schedule. Existing older records are not rewritten: if one already contains null plus an effective schedule, the platform's post-window fallback applies — 15 minutes of inactivity by default.

Examples

curl — personal key

Terminal
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/wake-schedules" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cronExpr": "0 9 * * 1-5",
    "timezone": "America/New_York",
    "label": "daily report",
    "enabled": true
  }'

curl — OAuth application

Terminal
curl -X POST "https://vibecode.bitrix24.com/v1/infra/servers/SERVER_ID/wake-schedules" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "cronExpr": "0 9 * * 1-5",
    "timezone": "America/New_York",
    "label": "daily report",
    "enabled": true
  }'

JavaScript — personal key

javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/wake-schedules`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      cronExpr: '0 9 * * 1-5',
      timezone: 'America/New_York',
      label: 'daily report',
      enabled: true,
    }),
  }
)
const body = await res.json()
if (body.tzWarningCode === 'MULTI_ZONE') {
  console.warn('Windows span more than one timezone — TZ is not injected at deploy time')
}

JavaScript — OAuth application

javascript
const res = await fetch(
  `https://vibecode.bitrix24.com/v1/infra/servers/${serverId}/wake-schedules`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      cronExpr: '0 9 * * 1-5',
      timezone: 'America/New_York',
      label: 'daily report',
      enabled: true,
    }),
  }
)

Response fields

Field Type Description
success boolean Always true on success
data.id string Window ID for a later update or delete
data.serverId string (UUID) ID of the owning server
data.cronExpr string The stored cron expression
data.timezone string The stored timezone
data.label string | null Window label
data.lead number | null Margin in seconds. null when not set on the window itself
data.enabled boolean Whether the window is active
data.source string How the window was created. Always "MANUAL" for windows created through this CRUD
data.lastFiredAt string (ISO 8601) | null Timestamp of the last confirmed firing. null for a freshly created window
data.wakeAttemptStartedAt string (ISO 8601) | null Internal scheduler claim marker — non-null only for the short interval while a wake is in flight. Normally null
data.lastWakeLateAt string (ISO 8601) | null Timestamp of the last wake that fired later than its computed window. null if there have been no late wakes
data.createdAt string (ISO 8601) Record creation date
data.updatedAt string (ISO 8601) Date of the last update to the record
tzWarning string | null Timezone-mismatch advisory, as text. null when the server has no active windows left after this mutation
tzWarningCode string | null Machine-readable code for the same advisory: "SINGLE_ZONE", "MULTI_ZONE", or null
preemptibleAdvisoryCode string | null "PREEMPTIBLE_BEST_EFFORT" when the server runs on a preemptible plan — otherwise null
preemptibleAdvisory string | null An explanation of the same advisory, as text (in English, for non-UI API clients). null when the server is on a non-preemptible plan

tzWarning, tzWarningCode, preemptibleAdvisoryCode, and preemptibleAdvisory are top-level response fields, not nested under data.

Response example

The server's only active window — the timezone is unambiguous:

JSON
{
  "success": true,
  "data": {
    "id": "cm38x02qp0001ml08g7k3h2a",
    "serverId": "e765edfc-ba0a-43de-b8ea-838dd872c522",
    "cronExpr": "0 9 * * 1-5",
    "timezone": "America/New_York",
    "label": "daily report",
    "lead": null,
    "enabled": true,
    "source": "MANUAL",
    "lastFiredAt": null,
    "wakeAttemptStartedAt": null,
    "lastWakeLateAt": null,
    "createdAt": "2026-07-10T08:12:00.000Z",
    "updatedAt": "2026-07-10T08:12:00.000Z"
  },
  "tzWarning": "The VM may have been deployed with a different timezone (or not redeployed since this window was declared) — verify the in-VM cron or redeploy the app so TZ is re-injected.",
  "tzWarningCode": "SINGLE_ZONE",
  "preemptibleAdvisoryCode": null,
  "preemptibleAdvisory": null
}

A server on a preemptible plan would get "preemptibleAdvisoryCode": "PREEMPTIBLE_BEST_EFFORT" and a non-empty preemptibleAdvisory instead of null in those same two fields — the rest of the response shape is unchanged.

Error response example

400 — the window conflicts with an always-on server:

JSON
{
  "success": false,
  "error": {
    "code": "ALWAYS_ON_CONFLICT",
    "message": "Scheduled wake conflicts with an always-on (24/7) server, which never auto-sleeps. Turn off the always-on toggle, or set a sleep timeout instead of \"Never\", to schedule wake windows."
  }
}

Errors

HTTP Code Description
400 VALIDATION_ERROR Body validation failed — missing cronExpr/timezone, timezone not in the IANA timezone list, label over 80 characters, lead outside 0–3600, or an unknown field in the body
400 BLACKHOLE_ONLY The server is not in BLACKHOLE mode
400 GALAXY_NOT_SUPPORTED The server is a Galaxy host (kind: "GALAXY"). A host holds no windows at all: the schedule is declared on an app inside the galaxy, not on the host itself
400 ALWAYS_ON_CONFLICT The server runs in always-on mode — no auto-sleep. A wake schedule would silently break that guarantee
400 CADENCE_TOO_LOW The gap between consecutive cronExpr occurrences is below the platform's floor — currently 5 minutes
401 MISSING_API_KEY The X-Api-Key header is missing
401 INVALID_API_KEY The API key is invalid or expired
403 WAKE_SCHEDULE_DISABLED Scheduled wake is not enabled for this portal
403 WAKE_SCHEDULE_GALAXY_DISABLED The server is an app inside a galaxy (kind: "GALAXY_APP"). Such an app can hold windows, but the capability for Galaxy apps is not enabled yet. The capability is enabled separately from ordinary servers, so a kind: "STANDALONE" server never receives this code
403 WAKE_SCHEDULE_LIMIT The server already has 50 windows — the per-server cap
403 INFRA_SCOPE_REQUIRED The key lacks the vibe:infra scope
403 INFRA_FORBIDDEN_FOR_COWORK_KEY The call was made with a Cowork/Code key — such a key works with data only and cannot perform write operations. To issue a key that can, see Project key for deploy
403 SERVER_ROLE_FORBIDDEN You are on this server's development team with the Developer role, and this operation is open to the Administrator role. error.hint carries your role, the required threshold and the list of calls that are open to you. Role breakdown — List servers
404 NOT_FOUND The server was not found, was deleted, or belongs to a different API key while you are not on its development team
429 RATE_LIMITED The 10-requests-per-minute limit for this endpoint was exceeded

Full list of shared error codes — Errors.

Known specifics

  • Check order. The key's scope and server ownership are checked before the other conditions, so WAKE_SCHEDULE_DISABLED/BLACKHOLE_ONLY/GALAXY_NOT_SUPPORTED/WAKE_SCHEDULE_GALAXY_DISABLED/ALWAYS_ON_CONFLICT only ever surface for a request with a valid key against an owned, existing server. The checks run in that same order, so a Galaxy app receives WAKE_SCHEDULE_GALAXY_DISABLED rather than ALWAYS_ON_CONFLICT even when it also runs in always-on mode. The cron-expression checks (VALIDATION_ERROR, then CADENCE_TOO_LOW) run after these gates.
  • Two different galaxy refusals. GALAXY_NOT_SUPPORTED is about the galaxy host: it holds no windows at all, and enabling the capability will not change that. WAKE_SCHEDULE_GALAXY_DISABLED is about an app inside a galaxy: it can hold windows, but the capability for such apps is switched off for now. The first arrives with status 400, the second with 403, so branch on the response code.
  • tzWarning/tzWarningCode are a conservative heuristic. The platform does not track whether the application was redeployed since a window was declared, so the advisory fires for any set of active windows after a mutation — even when the timezone is already correctly set in the environment. Go by the code: "SINGLE_ZONE" means the timezone is unambiguous and lands in TZ on the next deploy of a dedicated virtual machine, "MULTI_ZONE" means the windows span more than one zone and TZ is not auto-injected.
  • preemptibleAdvisoryCode/preemptibleAdvisory are an advisory, not a gate. The window is created or updated regardless of these fields' values — on a preemptible plan the platform does not forbid a schedule (that is its intended use case); it only states up front that waking the server in time for the window is best-effort. Localize by preemptibleAdvisoryCode, not by the preemptibleAdvisory string — that is a fixed English string for non-UI clients, not meant to be shown to a user as-is.
  • null in the sleep setting blocks schedule creation and update. It is the stored “Never” choice and disables idle auto-sleep; creating or updating a window while it is stored is rejected as an always-on conflict, even when the submitted window is disabled. Setting null through the sleep endpoint is narrower: that write is rejected only while an enabled window exists. If an older record already contains null plus an effective schedule, the platform's post-window timeout applies between windows. V1 does not return the computed session policy: for long-running background work, keep null with no enabled windows or choose a numeric timeout long enough for the task.
  • The platform does not configure the cron inside the VM. POST /wake-schedules only guarantees the server is up by the target moment (best-effort on a preemptible plan — see preemptibleAdvisoryCode above). Firing the task at that moment is the job of the app's own cron/timer.

See also