Stream environment events
Streams state-change events for a VM as Server-Sent Events.
Replaces polling GET /v1/environments/{vmId} for state
transitions. The control-server should use this stream during
provisioning instead of a 2s polling loop.
Event semantics
- The first event is always a snapshot of the current state, emitted immediately on connect. Clients do not need to GET first.
- Subsequent events fire on every state transition
(
provisioning→running→draining→destroying→destroyed/failed). - The stream closes cleanly after a terminal event
(
destroyedorfailed). - The stream closes cleanly on client disconnect.
- A keepalive comment line (
: keepalive\n\n) is emitted every 15s so proxies don’t idle-out the connection.
Wire format — standard SSE. Each event is two lines plus a blank-line terminator:
id: <128-bit hex>
data: {"event":"state","vm_id":"...","state":"running","url":"...","updated_at":"..."}
Replication caveat — this is a single-process pub/sub. Subscribers connected to a different orchestrator replica than the publishing replica will not see events. The orchestrator runs as a single process today, so this is acceptable; cross-replica fanout would require a Redis or NATS backplane.
GET
/v1/environments/{vmId}/eventsAuthorization
AuthorizationBearer token · headerrequired`Authorization: Bearer <token>` — either the static master token
(`ORCH_AUTH_TOKEN`) or a revocable API key issued via
/v1/api-keys (API keys require Postgres; without a database only
the master token is accepted). Master-only surfaces (exec,
attach, API key management) refuse API keys with 403.
**Authentication can be disabled entirely.** When neither
`ORCH_AUTH_TOKEN` nor an API key store (`DATABASE_URL`) is
configured — the default local-development posture — the bearer
middleware becomes a pass-through: every operation this document
marks as secured is reachable with no credentials, requests carry
no principal, and master-only surfaces therefore admit everyone.
Never run that configuration on a reachable network; set
`ORCH_REQUIRE_AUTH=true` to make the orchestrator refuse to start
without a master token.
or
fuse_sessionAPI key · cookierequiredHttpOnly session cookie set by POST /login for browser callers.
Consulted only when no Authorization header is present.
Path parameters
vmIdstringrequiredVM identifier assigned by the orchestrator.
Query parameters
last_event_idstringOptional resume cursor. If supplied, the server will replay
events newer than this id before tailing live. v1 ignores
this parameter — it is documented for forward compatibility
and to match the SSE `Last-Event-ID` request header pattern.
Responses
200SSE stream of EnvironmentEvent objects.
idstringrequiredOpaque 128-bit hex event identifier. Round-trip via Last-Event-ID for resume.
eventstringrequiredEvent kind. Currently only "state" is emitted.
Allowed:
statevm_idstringrequiredstatestringrequiredVM lifecycle state. `destroyed` and `failed` are terminal
and signal the end of the stream.
Allowed:
provisioningrunningdrainingdestroyingdestroyedfailedurlstringhost:port address where the guest agent (fused) is
reachable, via per-VM DNAT; empty before Running and after
Destroyed.
errorstringLast error message attached to the VM, empty on the happy path.
updated_atstring<date-time>required404Resource does not exist. Note the distinct code
`route_not_found`, returned when the URL matches no registered
route at all (wrong host, port, or path prefix) rather than a
known route with a missing resource.
errorobjectrequiredShow propertiesHide properties
codestringrequiredStable machine-readable code. `route_not_found` (404)
means the URL matches no route this server exposes —
usually a wrong host, port, or path prefix — as opposed
to `not_found`, where the route exists but the resource
does not. `forbidden` is emitted only by CIDR-allowlist
rejections; master-only refusals (exec, attach, API key
management) return 403 with code `unauthorized`.
Allowed:
not_foundroute_not_foundconflictinvalid_argumentunauthorizedforbiddenunavailableinternalunimplementedmessagestringrequiredHuman-readable description.
detailsobjectOptional stable, non-sensitive metadata (e.g. ids,
counts). Omitted when empty.
500Unexpected server error.
errorobjectrequiredShow propertiesHide properties
codestringrequiredStable machine-readable code. `route_not_found` (404)
means the URL matches no route this server exposes —
usually a wrong host, port, or path prefix — as opposed
to `not_found`, where the route exists but the resource
does not. `forbidden` is emitted only by CIDR-allowlist
rejections; master-only refusals (exec, attach, API key
management) return 403 with code `unauthorized`.
Allowed:
not_foundroute_not_foundconflictinvalid_argumentunauthorizedforbiddenunavailableinternalunimplementedmessagestringrequiredHuman-readable description.
detailsobjectOptional stable, non-sensitive metadata (e.g. ids,
counts). Omitted when empty.
Request
curl -X GET "http://localhost:8080/v1/environments/string/events" \
-H "Authorization: Bearer YOUR_TOKEN"const response = await fetch("http://localhost:8080/v1/environments/string/events", {
method: "GET",
headers: {
"Authorization": "Bearer YOUR_TOKEN"
}
});import requests
response = requests.get(
"http://localhost:8080/v1/environments/string/events",
headers={
"Authorization": "Bearer YOUR_TOKEN"
},
)Response
{
"id": "string",
"event": "state",
"vm_id": "string",
"state": "provisioning",
"url": "string",
"error": "string",
"updated_at": "2024-01-01T00:00:00Z"
}{
"error": {
"code": "not_found",
"message": "string",
"details": {}
}
}{
"error": {
"code": "not_found",
"message": "string",
"details": {}
}
}