Menu
Platform
AI
App store purchases
Database
Flags
Jobs and cron
Localization
Monitoring
Notifications
Payments
Queues
Sandboxes
Webhooks
Getting Started
Authentication
KV Store
Deploy & Infrastructure
Reference
Lease lifecycle
How a lease lives, why it ends, and how to watch it happen
A lease is granted, runs, and ends. Everything in between is visible: the state is on the lease, the ends are typed, and every step is an event you can read in order or long-poll for.
#The states
status.state is one of seven:
requested— the call was accepted and a machine is being assigned.granted— a machine is assigned, and the guest is booting.ready— the guest answers. Everything in the data plane needs this state.paused— the disk is kept and the machine is destroyed.ending— the end has begun, andstatus.end_reasonis set.ended— the lease is finished. It never returns to another state.refused— no machine was granted, andstatus.refusalsays why.
status.conditions carries the platform's own view beside the state: Ready,
Reconciling and Stalled, each with status, reason and a
customer-safe message.
#The two clocks
A lease has two ways to run out, and either ends it.
ttl is the maximum wall time from the grant, and it is required. It runs
from one minute up to the longest lease the shape allows — every shape in the
catalog today caps it at 24 hours, and each one reports its own bounds in
min_lease_duration and max_lease_duration. Reaching it ends the lease with
the expired reason. renew moves
expire_time out, never past the shape's longest lease, and does not change
the lease's generation:
sylphx sandboxes leases renew orgs/acme/projects/shop/envs/production/leases/lease --ttl 1hidle_timeout is the other clock: no data-plane call, no stream input and no
exec for that long ends the lease IDLE. Leave it unset and an unused lease
lives until its ttl.
The two are complementary rather than redundant. A ttl bounds what a lease
may cost; an idle_timeout bounds what an abandoned one may cost. A lease
that serves an interactive user wants both.
#Pause and resume
pause destroys the machine and keeps the disk. Process memory is not kept,
so a resumed lease starts its processes again. The lease's generation
increases on the pause and again on the resume, which is what invalidates
every stream token and in-flight Cell command from before.
resume asks for a machine now, exactly as create does — including the
possibility of a refusal when nothing can be granted.
#Budgets
Beyond the clocks, a lease can carry a budget, and reaching it ends the lease for that reason:
| Field | Type | What it is |
|---|---|---|
max_cpu_seconds | int64 | Ends the lease CPU_BUDGET after this many vCPU-seconds of guest CPU time. |
max_cost_micros | int64 | Ends the lease COST_BUDGET once its accrued cost at the shape’s list price reaches the cap. [The price list](/pricing) is where those prices live. |
A budget is a bound, not a target: a lease that hits one is ended, and the
stream carries budget_warning before the end so a caller can react first.
#Why a lease ended
status.end_reason is typed, and the event stream carries it too. A lease
ended for one of ten reasons:
released— a caller ended it.expired— it reached itsttl.idle— it went unused past itsidle_timeout.cpu_budget— it reachedmax_cpu_seconds.cost_budget— it reachedmax_cost_micros.abuse— the platform stopped it.quota— an org limit stopped it.org_deleted— the org the lease belonged to went away.machine_lost— the machine it ran on was lost.boot_failed— the guest never became ready.
The distinction between them is the point of the field: a caller that retries
on machine_lost and reports a bug on boot_failed behaves differently from
one that retries everything. usage on the ended lease says what it consumed
— wall seconds, guest vCPU-seconds, egress bytes and the rest.
#The event stream
Every step is a LeaseEvent, in order, and the stream is the lease's own
history rather than a log you have to reconstruct:
sylphx sandboxes lease-events list orgs/acme/projects/shop/envs/production/leases/leasecurl "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases/lease/lease_events" \
-H "Authorization: Bearer $SYLPHX_API_KEY"const response = await sylphx.sandboxes.leaseEvents.list({
parent: 'orgs/acme/projects/shop/envs/production/leases/lease',
})An event's kind is one of twelve: granted, ready, refused, ended,
renewed, network_changed, control_acquired, control_released,
control_expired, budget_warning, paused or resumed. sequence is the
position in the lease's own sequence, from 1, so a reader can tell a gap from
a quiet spell. Each event also carries the state and the lease_generation
after it, the principal that caused it when a caller did, and — on an
ended event — the end_reason and the final usage.
list is a long poll. With wait it blocks for an event after the
page_token you already hold, for at most 60 seconds, which turns the stream
into a loop that reacts instead of one that polls on a timer:
sylphx sandboxes lease-events list \
orgs/acme/projects/shop/envs/production/leases/lease \
--wait 30sA lease can also deliver its own events as signed webhooks: spec.webhook
names an HTTPS uri and, optionally, the kinds to receive. The URI earns
its keep when the thing waiting for an event is not the thing holding the
connection.
#Generations
Two numbers guard against acting on a lease that has moved on.
meta.generation increases on every change to spec; status.lease_generation
increases on grant, pause, resume and end. Every stream token and every Cell
command carries the latter, and a call that names a generation the lease has
left is refused STALE_GENERATION rather than applied to a machine that is no
longer the one you meant.
The practical shape of it: a token minted before a pause cannot be replayed
against the machine after the resume, and a command written for generation 3
cannot land on generation 4. Conditional writes work the same way through
meta.etag — send it back and a stale one fails ETAG_MISMATCH instead of
overwriting a change you have not seen.