Für KI-Agenten: Markdown dieser Seite — /docs-content-en/infra/galaxy-sleep.md Dokumentationsindex — /llms.txt
Dokumentationsartikel sind derzeit auf Englisch verfügbar.
Galaxy app sleep and wake
A Galaxy app sleeps on idle together with the galaxy that carries it, and while it sleeps its container is stopped. This page describes what exactly stops, which fields report the real state, how to bring the app up and how long that takes.
What stops · State fields · How to bring it up · Logs · Recurring wake window
What stops during sleep
The app runs in a container on a shared host — the galaxy. Both parts can sleep, and these are different states.
| What sleeps | How it looks | What it takes to bring up |
|---|---|---|
| The app container, the galaxy stays up | The app does not answer, neighbouring apps of the same galaxy do | Starting the container — seconds |
| The galaxy together with every container on it | No app of that galaxy answers | Booting the galaxy machine, then starting the container — minutes |
The persistent /data volume survives sleep. Waking starts the same container instead of creating a new one, so the lines the app wrote to its output stream before it slept are available after the wake too. A fully new container appears only on the next code upload — that one does not keep the previous container's log.
The status and reachability fields answer different questions
The status field of GET /v1/infra/servers/:id is the state of the app's record on the platform. A wake moves the record to running at the end, once the container is already started, so while the galaxy is coming up the record stays sleeping — on a cold machine that takes minutes. The opposite divergence is possible as well: the record can read running while the container is no longer working.
That is why the reachability block sits next to it — it answers the question "can the app respond right now". The block is present only for an app whose kind is GALAXY_APP, for every other server type it is null.
{
"success": true,
"data": {
"id": "e22bb297-8a0a-4597-9b42-47d059cd1090",
"kind": "GALAXY_APP",
"status": "sleeping",
"reachability": {
"effectiveStatus": "running",
"hostStatus": "running",
"hostTunnel": "CONNECTED",
"container": "running",
"forwarder": "active",
"probe": "ok",
"probedAt": "2026-08-12T13:24:58.117Z"
}
}
}
The fields related to sleep are shown. The full response — GET /v1/infra/servers/:id.
| Field | Description |
|---|---|
effectiveStatus |
The summary answer about availability — running, sleeping, waking, unreachable or unknown. How it is derived is shown in the table below |
hostStatus |
State of the carrying galaxy, in the same set of values as the server status |
hostTunnel |
The galaxy's connection to the platform — CONNECTED, DISCONNECTED or NONE |
container |
The app container — running, stopped or unknown. The value unknown means the platform could not look, not that the container is stopped |
forwarder |
Routing to the container on the host — active, inactive or unknown. It stops together with the container during sleep |
probe |
How the live host check ended — ok, host-down (the galaxy is asleep or unreachable and no check was sent), host-busy (the host is occupied by another operation), failed or not-attempted |
probedAt |
Check time in ISO 8601. The value null means no check happened |
How effectiveStatus is derived:
| State | effectiveStatus |
|---|---|
| The galaxy is booting | waking |
| The galaxy is asleep | sleeping |
| The galaxy is up, the container is stopped | sleeping |
| The galaxy is up, the container is up, routing is active | running |
| The galaxy is up, the container is up, routing is stopped | unreachable |
| The galaxy is in error or lost its connection to the platform | unreachable |
| The check could not be made | unknown |
Important: the platform runs the live host check only while the galaxy is up and connected to the platform. Reading state never wakes a sleeping galaxy, so for a sleeping galaxy container and forwarder are always unknown and probe is host-down. The platform keeps the check result for a few seconds and serves it from memory: a check occupies the command channel shared by every app of the galaxy, so frequent state polling does not occupy it.
How to bring a sleeping app up
| Way | What it does |
|---|---|
POST /v1/infra/servers/:id/wake |
Brings the app up through its host. Boots the galaxy when needed, then starts the container |
POST /v1/infra/servers/:id/start |
The same thing. For an app the two calls carry no separate meaning |
| Code upload | Boots the galaxy itself and leaves the app running |
| A request to the app address | A request through the gateway brings the app up and answers with the BH_SERVER_WAKING code and a Retry-After header until it is ready — details in App authorization on Black Hole |
| Portal event delivery | The platform brings the app up before delivering |
The wake call answers immediately rather than on readiness: an app has no cloud machine of its own, and the ?wait=true parameter buys no waiting here. Check readiness by polling GET /v1/infra/servers/:id on reachability.effectiveStatus — the value waking means the galaxy is still booting.
How long to wait: the platform waits up to 15 minutes for a cold galaxy to come up, and is ready to wait the same from the caller. The exact budget arrives in the response to a log read of a sleeping app, field recovery.coldBootBudgetSeconds. An app on a galaxy that is already up starts in seconds.
A wake block on the host refuses both calls, and /start does not clear it. The refusal codes are in the lifecycle table on the Galaxy app page.
Logs of a sleeping app
Reading GET /v1/infra/servers/:id/logs wakes neither the app nor the galaxy. While the galaxy is asleep or unreachable, the response arrives with status 200, an empty data.logs array and two diagnostic fields.
{
"success": true,
"data": {
"logs": [],
"lines": [],
"hint": "Galaxy host is asleep or unreachable, so the container log cannot be read right now. …",
"recovery": {
"reason": "The galaxy host carrying this container is not RUNNING + CONNECTED. …",
"recoveryAction": "POST /v1/infra/servers/e22bb297-8a0a-4597-9b42-47d059cd1090/wake",
"logsPreserved": true,
"coldBootBudgetSeconds": 900,
"poll": "GET /v1/infra/servers/e22bb297-8a0a-4597-9b42-47d059cd1090 (read `reachability`)",
"wakeSchedule": { "available": false, "code": "ALWAYS_ON_CONFLICT" }
}
}
}
| Field | Description |
|---|---|
hint |
The same guidance as plain text, on one line. The wording depends on whether the caller can call the wake: when it can, the text names the wake call; when it cannot, it names no wake address at all, and instead names exactly the condition that applied to this caller. It names the second way up only when that path is open to this caller — that is, together with the recovery.deliveryWakesHost field, not always |
recovery.reason |
Why the log cannot be read right now |
recovery.recoveryAction |
The WAKE call. The field is conditional — it may be absent. It is omitted whenever the caller cannot call the wake; that set of states is open and grows, so branch on the PRESENCE of the field rather than on a list. Today it holds read-only mode, a Cowork/Code key, a disabled galaxy pilot, an agent maintenance key, an owner whose account deletion is pending, and reaching the app through its application link while another key owns the server. Check the key is present before dereferencing it: in that state no field of the response carries a wake address — neither this one nor hint. ⚠️ The converse does not hold: the PRESENCE of the field only means the caller is not refused by their identity or key mode. Billing and an administrative wake ban are separate axes that this field does not cover, so the address it names may still answer 402 (balance exhausted, a tariff is required) or 403 (waking is banned on the host). Handle that refusal rather than treating the field's presence as a guarantee |
recovery.logsPreserved |
The value true means lines written before the sleep are available after the wake |
recovery.coldBootBudgetSeconds |
The budget for bringing a cold galaxy up, in seconds |
recovery.poll |
What to check readiness with instead of reading the log again. The field is conditional — it may be absent. It is omitted when GET /v1/infra/servers/{id} refuses the caller itself: an agent maintenance key does not carry that route in its allowlist, and for an owner whose account deletion is pending it sits in the frozen surface, unlike this log read. No field — just repeat the log read later, which is what the hint says |
recovery.deliveryWakesHost |
The second way up. The field is doubly conditional: it arrives only when waking is unavailable to the caller AND the deploy is available to them. action is the deploy call, cost states its price in words. A deploy to a sleeping galaxy app brings the host up by itself; but it is a CODE DEPLOYMENT and it spends the balance, not a wake — do not call it just to read a log. ⚠️ The field being absent means that path is closed too: the read-only carve-out lifts the ACCESS-MODE gate on deploys and nothing else, and a Cowork/Code key, a disabled galaxy pilot and an agent maintenance key are refused the deploy by the same doors that refuse the wake |
recovery.wakeSchedule |
Whether a recurring wake window is available to this caller: the app's own eligibility and the caller's right to write, in one verdict. The available field is the answer, and on a refusal the code field carries the same code the window creation would answer. When recoveryAction is absent, the field comes back available: false, usually with the code of the door that refused the wake — creating a window is the same write, refused by the same doors. The exception is a window door checked earlier than that one: then code names the window door, and hint names the same door rather than saying the wake's condition also refuses the window. ⚠️ The window also has TWO doors of its own that the wake does not have, so code may name a refusal you will not find in the list of wake doors, and available: false may arrive while recoveryAction is present. The first is checked before every other door: the schedule route is outside the route allowlist of an external collaborator key, whose wake, deploy and log read are all inside it — EXTERNAL_COLLABORATOR_KEY_OUT_OF_SCOPE. The second is checked after the mode, freeze, Cowork and pilot doors but BEFORE server ownership: the schedule route requires the vibe:infra scope, which reading logs and waking do not, so an ordinary key without that scope reaches this refusal — INFRA_SCOPE_REQUIRED, and the cure is to grant the key that scope, not to change its mode. Because ownership is resolved after the scope, a caller admitted through its application link and lacking the scope gets INFRA_SCOPE_REQUIRED rather than NOT_FOUND |
The order of actions: bring the app up with the call from recovery.recoveryAction, wait for reachability.effectiveStatus to be running, repeat the log read.
If the response carries no recoveryAction, waking is refused to this caller. There is no wake address in the prose either — in that state the answer deliberately names neither POST …/wake nor the creation of a scheduled window, which the same door refuses. Instead of an address, hint names exactly the condition that applied to this caller rather than a list joined by "or" — and it names the FIRST one in the order the doors actually refuse. That order is: an agent maintenance key does not carry the wake in its route allowlist; a pending account deletion closes the wake entirely, and changing the key does not help — the owner is frozen, not the key (delivery stays open there, see the second path below); the key mode is switched per Access mode; a Cowork/Code key has no access to the control plane at all; a disabled galaxy pilot is lifted by the Bitrix24 account administrator; and last, reaching the app through its application link while another key owns the server: the wake checks ownership more strictly than the log read, so such a caller is answered 404 and only a call by the server's owning key helps.
Some callers do have a way out, and it is not a wake. A key in read-only mode is allowed to deploy to a server it owns, and a deploy to a sleeping galaxy app brings the host up by itself. The same path is open to two more callers who are refused the wake: an owner whose account deletion is pending (the freeze sits on the /v1/infra plugin, while delivery lives in another one) and a caller admitted through the application link (delivery checks ownership more loosely than the wake). It is closed to exactly three: a Cowork/Code key, an agent maintenance key, and a caller on an account with the galaxy pilot switched off. When that path is open the platform names it for machines — the recovery.deliveryWakesHost field (action is the call, cost states the price in words) — and the price is not a formality: it is a code deployment and it spends the Vibecode balance, not a wake. Do not call it just to read a log.
⚠️ It is not open to everyone, and the field is what tells you. The read-only carve-out lifts the ACCESS-MODE gate on deploys and nothing else. A Cowork/Code key has no access to the control plane at all, a disabled galaxy pilot refuses every non-GET in this family, and an agent maintenance key does not carry the deploy in its route allowlist — all three are refused POST …/deploy exactly as they are refused the wake. So check for deliveryWakesHost rather than assuming it: no field means no open path, and the condition named in hint is what has to change. ⚠️ The converse is not guaranteed either, exactly as with recoveryAction: the field's presence only means the deploy is not refused by identity or key mode. Billing and tariff are separate axes this field does not cover, so the action it names may still answer 402 (balance exhausted, a plan is required) — all the more so because a deploy spends balance by construction. Until it does, repeating the log read in a loop buys nothing: the answer does not change.
Recurring wake window
A wake window brings the app up on a schedule, without a manual call. It is not available to every app, and this should be checked against the platform's answer rather than a general rule: the recovery.wakeSchedule field in the response to a log read of a sleeping app gives the ready answer for that particular app, and its code on a refusal.
That answer is about the app and about the caller at once. Creating a window is still a write, so it is refused to a key in read-only mode, to a Cowork/Code key, and on a Bitrix24 account with the galaxy pilot switched off; in those states the field comes back available: false with the code of the door that refused. Waking is a good first signal — no recovery.recoveryAction means that caller cannot create a window either — but it is not the whole answer, because the window has two doors the wake does not: the route is outside the allowlist of an external collaborator key (EXTERNAL_COLLABORATOR_KEY_OUT_OF_SCOPE), and it requires the vibe:infra scope, which waking does not (INFRA_SCOPE_REQUIRED). Either one refuses the window to a caller who can wake the app, so read recovery.wakeSchedule itself rather than deriving it from recoveryAction.
One refusal is worth knowing in advance. An app that does not fall asleep on idle by itself is treated by the platform as permanently running, and a window for it is refused with the ALWAYS_ON_CONFLICT code. That is how agents and bots in a galaxy are created: they are brought up not by an incoming request but by their own polling of the Bitrix24 account, so no idle timer is assigned to them. Such an app is brought up with the wake call.
Listing, creating, updating and deleting windows — Scheduled wake.