Skip to main content

Launch a box and wait

The first thing every integration does: launch an environment ("box") from a template, wait until it's ready, do some work, and wind it down. This page walks that whole lifecycle with the SDK.

You'll need a client from the SDK quickstart, an orgId, and the templateId of a template whose bundle has finished baking (its status is ready — launching from an unbaked template is refused with template_not_baked).

1. Launch — always with an idempotency key

environments.launch provisions a real EC2 instance in your connected AWS account, so a duplicated launch is not just wasted API quota — it's a second box on your AWS bill. Pass an idempotency key with every launch: if the request is retried (by you, or by a flaky network), the API returns the original box instead of provisioning a duplicate.

const box = await slothbox.environments.launch(
{
orgId,
body: { templateId, name: "nightly-runner" },
},
// Sent as the Idempotency-Key header. Reuse the same key when you retry
// the same logical launch; use a fresh key for each new box you intend.
{ idempotencyKey: `nightly-${runId}` },
);

console.log(box.envId, box.status); // e.g. "env_01HXYZ…" "pending"

The launch returns immediately with the new environment in a transitional state — provisioning continues in the background. A box moves through pendingprovisioningrunning; a launch that can't complete lands in failed.

2. Wait until it's ready

The SDK ships lifecycle waiters that encode the status machine for you: polling with backoff, an overall timeout (10 minutes by default — a real EC2 launch usually completes in 2–4 minutes), and terminal-state detection, so a box that lands in a state it can't leave on its own throws a WaiterStateError immediately instead of spinning until the deadline. Running out of time throws a WaiterTimeoutError with the last status it saw.

// Launch + wait in one call. launchAndWait ALWAYS sends an Idempotency-Key —
// the one you pass, or an auto-generated UUID — so the launch half is
// replay-safe; the waiter half resolves once the box is `running`.
const box = await slothbox.environments.launchAndWait(
{ orgId, body: { templateId, name: "nightly-runner" } },
// Pass your own stable key to deduplicate across process restarts:
{ idempotencyKey: `nightly-${runId}` },
);

// Or wait for a box you launched earlier:
await slothbox.environments.waitUntilReady({ orgId, envId: box.envId });

All waiters take a timeout and poll-interval bounds — timeoutMs / pollIntervalMs / maxPollIntervalMs and an abort signal in TypeScript; timeout / poll_interval / max_poll_interval (seconds) in Python (see Configuration). The same machinery covers the other slow lifecycles: environments.waitUntilStopped / wait_until_stopped and — for the bake that must finish before a template is launchable at all — templates.waitUntilBaked / wait_until_baked.

If you'd rather poll yourself, the loop the waiters encapsulate looks like this:

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

async function waitUntilRunning(orgId: string, envId: string) {
const deadline = Date.now() + 10 * 60_000; // overall timeout
for (;;) {
const env = await slothbox.environments.get({ orgId, envId });
if (env.status === "running") return env;
// States a launching box can't leave on its own — fail fast. A `stopped`
// observation usually means auto-sleep or another actor stopped it.
if (env.status !== "pending" && env.status !== "provisioning") {
throw new Error(
`Box ${envId} reached terminal state ${env.status}: ${env.statusReason ?? ""}`,
);
}
if (Date.now() > deadline) throw new Error(`Timed out waiting for ${envId}`);
await sleep(7_000);
}
}

Pace your polling deliberately: launches and bakes sit in a tight org-wide rate tier (see Errors, retries & rate limits), and every status read does real work against your connected AWS account. The built-in waiters start at a 7-second interval and back off toward a 30-second ceiling for exactly that reason — there's nothing to gain from polling harder.

3. Ready isn't forever — auto-sleep

A running box does not stay running unconditionally. Organizations can have auto-sleep policies — idle boxes are stopped automatically, and scheduled sleep windows can stop them at set times — precisely so an unattended box doesn't burn AWS spend overnight. So treat "ready" as a state you observed, not a guarantee:

  • Check before you reconnect. If time has passed since the box was ready, read its status again — and start it if it was put to sleep:
let env = await slothbox.environments.get({ orgId, envId });
if (env.status === "stopped") {
env = await slothbox.environments.start({ orgId, envId });
env = await slothbox.environments.waitUntilReady({ orgId, envId });
}
  • Or let the events come to you. Subscribe a webhook endpoint to environment.stopped / environment.started and your system observes sleep and wake as they happen, with no polling at all.

A stopped box keeps its disk and its identity — starting it again is fast and returns the same envId.

4. Wind it down

Stop a box when you're done for now; terminate it when you're done for good:

// Pause: the instance stops, the disk and envId survive. Frees up your
// seat's concurrent-box headroom too.
await slothbox.environments.stop({ orgId, envId });

// Gone for good: terminates the instance. This is irreversible — further
// lifecycle calls on the box are refused with `environment_terminated`.
await slothbox.environments.terminate({ orgId, envId });

Both return the environment with its new transitional status (stopping, terminating); use waitUntilStopped / wait_until_stopped (or poll, as above) if you need to block until the box has actually wound down.

Stopping promptly is more than tidiness: concurrent-box headroom frees up as soon as a box stops, and an idle box you forgot about is exactly what auto-sleep exists to catch.

Common launch failures

Launches are refused with a 409 and a machine-readable error.code when the org isn't in a launchable state — seat_ceiling_exceeded, no_active_aws_connection, template_not_baked. The error taxonomy covers what each one means and what to do about it.