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:

Permission Allows
coordination_read Every GET request: task, run, group and budget status, lists, events, watch, diagnostics.
coordination_write Non-GET requests that don’t need resource_config: runs, claims, queues, messages, tasks, admission, pools, task groups, barriers, timers.
resource_config PUT requests, and every non-GET request under /allowances, /budgets, /work-limits and /limits.
usage_read Reserved for usage data. The coordination API doesn’t check it today; GET /usage needs coordination_read like every other read.

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.