Skip to content
HomeConsoleGet started

Environment variables, client options, API key permissions, worker identity, timeouts and transport retries for the TypeScript and Python SDKs.

Both SDKs read the same three variables. Explicit constructor options take precedence.

Variable Required Default Purpose
RUNSTATE_API_KEY yes none API key, sent as Authorization: Bearer <key> on every request.
RUNSTATE_SPACE_ID yes none The space (project or environment) every call is bound to.
RUNSTATE_BASE_URL no http://localhost:8080 API origin. Set it to https://api.getrunstate.com for the hosted service.

If the API key or space id is missing, constructing the client throws ConfigError immediately, before any network call. There is no environment variable for the holder name; pass it as an option.

import { Runstate } from 'runstate-sdk';
const rs = new Runstate({
baseURL: 'https://api.getrunstate.com',
apiKey: process.env.RUNSTATE_API_KEY,
spaceId: process.env.RUNSTATE_SPACE_ID,
holder: 'research-worker-1', // stable name for this logical worker
requestTimeoutMs: 10_000, // per attempt
retries: 2, // transport retries after the first attempt
});
Option (TS / Python) Default Notes
apiKey / api_key RUNSTATE_API_KEY Required.
spaceId / space_id RUNSTATE_SPACE_ID Required.
baseURL / base_url RUNSTATE_BASE_URL, else http://localhost:8080 Trailing slashes are removed.
holder / holder random worker-xxxxxx Logical worker name, stable across restarts.
requestTimeoutMs / request_timeout_ms 10000 Deadline for each attempt, not the whole call.
retries / retries 2 Extra attempts after a transport failure.
fetchImpl (TS only) global fetch Test seam.

Constructing a client makes no network calls. The TypeScript client holds no connections, and rs.close() exists only for symmetry. In Python, close the client when you’re done: await rs.aclose() or async with AsyncRunstate(...) as rs: (blocking client: rs.close() or with Runstate(...) as rs:).

To use several spaces from one process, rs.withSpace(spaceId) (Python: rs.with_space(space_id)) returns a client bound to another space that shares the configuration and identity.

Create and revoke keys in the console under API keys in a space. The secret is shown once and never stored in readable form. Organization, space and key management is console-only today; there is no public API or SDK method for it.

Each key has a set of permissions. Using coordination while agents run is separate from setting it up, so a worker key doesn’t need to be able to create or resize resources:

Permission Allows
coordination_read Reading runs, claims, queues and messages, tasks, task groups, events and watch, and the resources agents use: pools, quotas, budgets, barriers, work limits and timers (run.status(), ticket.result(), budget.status(), name lookups).
coordination_write Runtime work: creating, cancelling and completing runs, claims, sending and receiving messages, submitting, admitting, cancelling and joining tasks, task groups, taking and returning pool units, taking quota units and reporting cooldowns, reserving, settling and voiding budget, barrier arrivals, timers.
resource_config Setting up the space: creating queues, pools, quotas, budgets, barriers, work limits and webhook destinations (every ensure() that has to create), and changing space limits (PUT /limits).
usage_read Usage, space limits, diagnostics (rs.diagnostics, census, backlog, waits, run and task views) and webhook delivery history.

Keys created in the console start with all four. A typical least-privilege split is an admin key with all four that runs the ensure() calls once, and worker keys with coordination_read and coordination_write. With a worker key, ensure() still works for resources that already exist with the same settings: the SDK verifies the existing resource instead of creating it, and throws FORBIDDEN only when the resource is missing. Every operation’s permission is listed in the HTTP API reference.

A key without the permission a call needs gets FORBIDDEN with the message credential lacks <permission>.

A key can also be limited to specific spaces; using it for another space returns FORBIDDEN. An id from another organization returns NOT_FOUND, so resource ids don’t leak between tenants.

Every claim, delivery and pool lease records a holder and a session:

  • holder names the logical worker. Keep it stable across restarts (for example crawler-3 or the pod name) so the console and diagnostics show who owns what.
  • The session is generated fresh every time you construct a client. Two live processes never present the same holder and session pair, even if you give them the same holder name.
  • Per-attempt timeout. requestTimeoutMs applies to each HTTP attempt. With the defaults (10 s, 2 retries) a call to an unreachable API can take about 30 seconds before it throws UnavailableError.
  • Transport retries only. The SDKs retry network failures and timeouts, with 25 to 100 ms of jitter. They never retry a well-formed error response such as CLAIM_HELD or INSUFFICIENT_BUDGET; those surface as typed errors immediately. Waiting helpers like claim.acquire({ wait: true }) handle specific codes on top of this, see Errors & retries.
  • Idempotency keys. Every non-GET request carries a fresh Idempotency-Key header, reused across the transport retries of that one call, so a retried request is never applied twice. Separate calls get separate keys: calling rs.scopes.create() twice creates two runs.
  • Client-side waits end locally. Methods like ticket.result(), group.wait() and timer.wait() poll until their timeoutMs and then throw WaitTimeoutError. The task, group or timer keeps going on the server.