Configuration
Everything the clients can be told at construction time and per request, in both SDKs.
Authentication
Both clients take one credential: an org service-account sk_ key, which
requires the API plan — personal seat keys and
browser sessions are not supported. Pass it as apiKey / api_key or let
the client read SLOTHBOX_API_KEY; either way it is sent verbatim in the
Authorization header. The SDKs also send an x-slothbox-sdk header on
every request, naming the SDK and its version — it carries no secrets and
exists so SDK traffic can be identified, for support diagnostics.
Constructor options
- TypeScript
- Python
import { Slothbox } from "@slothbox/sdk";
const slothbox = new Slothbox({
apiKey: "sk_…", // default: SLOTHBOX_API_KEY env var
baseUrl: "https://api.slothbox.dev", // default: the spec's server
maxRetries: 3, // default: 3; 0 disables retries
fetch: myFetch, // default: the global fetch
});
| Option | Default | What it does |
|---|---|---|
apiKey | SLOTHBOX_API_KEY env var | The sk_ key, sent verbatim in Authorization (no Bearer prefix). The constructor throws if neither is set. |
baseUrl | https://api.slothbox.dev | Target a different stack; trailing slashes are stripped. |
maxRetries | 3 | Retries after the initial attempt — see Errors, retries & rate limits. 0 disables. |
fetch | global fetch | Any (url, init) => Promise<Response> — your seam for middleware, proxying, and testing. Runtimes without a global fetch require this. |
There is no constructor-level timeout — bound requests with an
AbortSignal per request instead.
import httpx
from slothbox import Slothbox # AsyncSlothbox takes the same arguments
client = Slothbox(
api_key="sk_…", # default: SLOTHBOX_API_KEY env var
base_url="https://api.slothbox.dev", # default: the spec's server
max_retries=3, # default: 3; 0 disables retries
timeout=60.0, # seconds, or an httpx.Timeout
httpx_args={"transport": my_transport}, # extra httpx.Client kwargs
)
| Option | Default | What it does |
|---|---|---|
api_key | SLOTHBOX_API_KEY env var | The sk_ key, sent verbatim in Authorization (no Bearer prefix). The constructor raises SlothboxError if neither is set. |
base_url | https://api.slothbox.dev | Target a different stack; trailing slashes are stripped. |
max_retries | 3 | Retries after the initial attempt — see Errors, retries & rate limits. 0 disables. |
timeout | 60.0 | Request timeout in seconds, or an httpx.Timeout for per-phase control (connect/read/write/pool). |
httpx_args | — | Extra keyword arguments for the underlying httpx.Client / httpx.AsyncClient — transports, proxies, event hooks. This is the supported customization seam. |
The generated core under the hood exposes a set_httpx_client() method —
don't use it: it bypasses the SDK's auth-header injection entirely. Pass
transports and other httpx configuration through httpx_args instead.
Environment variables
| Variable | Read by | Meaning |
|---|---|---|
SLOTHBOX_API_KEY | both SDKs | The service-account sk_ key used when the constructor gets no explicit key. In the TypeScript SDK this requires a Node-style process.env — in runtimes without one (e.g. Cloudflare Workers), pass apiKey explicitly. |
SLOTHBOX_WEBHOOK_SECRET, SLOTHBOX_ORG_ID, and friends appear throughout
these docs and the examples — those are conventions of the
sample code, not variables the SDKs read themselves.
Sync and async Python
Both Python clients expose the same resource surface; AsyncSlothbox is the
await-able twin, with async for pagination and async waiters. Both are
context managers — use with / async with (or call close() / aclose())
so the underlying httpx client is released:
from slothbox import AsyncSlothbox
async with AsyncSlothbox() as client:
box = await client.environments.launch_and_wait(
org_id, {"templateId": template_id}
)
async for event in await client.audit.list_org_events(org_id):
...
In TypeScript there is nothing to choose — the client is async by nature and
holds no connection state, so there is no close().
Per-request options
Every resource method takes options after its arguments:
- TypeScript
- Python
const env = await slothbox.environments.get(
{ orgId, envId },
{
signal: AbortSignal.timeout(10_000), // cancellation / timeout
headers: { "x-trace-id": traceId }, // merged over the SDK's defaults
maxRetries: 0, // override the client's setting
},
);
Operations that accept an idempotency key (environments.launch,
launchAndWait) additionally take idempotencyKey — see
below.
from slothbox import RequestOptions
env = client.environments.get(
org_id,
env_id,
options=RequestOptions(
headers={"x-trace-id": trace_id}, # merged over the SDK's defaults
max_retries=0, # override the client's setting
),
)
environments.launch and launch_and_wait additionally take an
idempotency_key= keyword — see below.
Cancellation & timeouts
- TypeScript
- Python
Pass an AbortSignal as options.signal to any method. The signal reaches
the underlying fetch, cancels the backoff sleeps between
retries, stops for await
auto-pagination, and aborts a waiter mid-poll:
// A hard deadline on one call:
await slothbox.environments.get({ orgId, envId }, { signal: AbortSignal.timeout(10_000) });
// One controller cancelling a whole workflow:
const controller = new AbortController();
const ready = slothbox.environments.waitUntilReady(
{ orgId, envId },
{ signal: controller.signal },
);
// elsewhere: controller.abort();
Aborting rejects with the signal's reason (an AbortError by default) —
never a SlothboxError — so your cancellation handling stays separate from
your failure handling.
The Python SDK bounds time rather than offering cooperative cancellation
(there is no idiomatic AbortSignal in sync Python):
- Requests are bounded by the client's
timeout(httpx semantics, default 60s) — pass anhttpx.Timeoutfor per-phase control. - Waiters take a
timeout=deadline in seconds (wait_until_ready(org_id, env_id, timeout=300.0)), raisingWaiterTimeoutErrorat the deadline.
Idempotency keys
The launch operations accept an idempotency key, sent as the
Idempotency-Key header. Retrying the request with the same key returns the
original box instead of provisioning a duplicate — and carrying the header is
what makes a POST eligible for the SDK's
automatic retries at all:
- TypeScript
- Python
await slothbox.environments.launch(
{ orgId, body: { templateId } },
{ idempotencyKey: `task-${taskId}` },
);
// launchAndWait always sends one (yours, or an auto-generated UUID).
client.environments.launch(
org_id, {"templateId": template_id}, idempotency_key=f"task-{task_id}"
)
# launch_and_wait always sends one (yours, or an auto-generated UUID).
Use a key that is stable across retries of the same logical launch (a task id, a CI run id) and fresh for each new box you actually intend. The launch walkthrough shows the full pattern.
Waiter tuning
The lifecycle waiters take the same three knobs in both SDKs — only the units differ:
| Knob | TypeScript | Python | Default |
|---|---|---|---|
| Overall deadline | timeoutMs | timeout (seconds) | 10 min (environments), 20 min (bakes) |
| First poll interval | pollIntervalMs | poll_interval (seconds) | 7s |
| Poll-interval ceiling | maxPollIntervalMs | max_poll_interval (seconds) | 30s |
The defaults are deliberately gentle — status polls do real work against your connected AWS account and share the org's compute rate tier. Tune them down only if you know why.
Escape hatches
When you need a route the typed surface doesn't cover (or doesn't cover
yet), both clients expose a low-level request method that keeps the
client's auth, retry policy, and typed error mapping:
- TypeScript
- Python
const result = await slothbox.request("GET", "/organizations/{orgId}/environments", {
pathParams: { orgId },
query: { someNewParam: "value" }, // undefined/null entries are skipped
});
There is also slothbox.call(operationId, args, options) — the typed
dispatch the resource methods are thin wrappers over.
result = client.request(
"GET",
"/organizations/{orgId}/environments",
path_params={"orgId": org_id},
query={"someNewParam": "value"}, # None entries are skipped
)
There is also client.generated — the generated core client, for calling
generated endpoint functions directly. (Same warning as above: customize its
transport via httpx_args, never set_httpx_client().)
In both SDKs, JSON responses parse to plain data, 204s resolve to
nothing (undefined / None), and the API's one non-JSON success — the
CloudFormation template YAML — comes back as a raw string.