Configuration & auth
Environment variables, client options, API key permissions, worker identity, timeouts and transport retries for the TypeScript and Python SDKs.
Environment variables
Section titled “Environment variables”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.
Creating a client
Section titled “Creating a client”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});import os
from runstate import AsyncRunstate
rs = AsyncRunstate( base_url="https://api.getrunstate.com", api_key=os.environ["RUNSTATE_API_KEY"], space_id=os.environ["RUNSTATE_SPACE_ID"], holder="research-worker-1", # stable name for this logical worker request_timeout_ms=10_000, # per attempt retries=2, # transport retries after the first attempt)Python has two clients with the same object graph: AsyncRunstate (async, the primary client) and Runstate (blocking; runs the async client on a dedicated background thread). See the Python SDK reference.
| 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.
API keys and permissions
Section titled “API keys and permissions”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.
Worker identity
Section titled “Worker identity”Every claim, delivery and pool lease records a holder and a session:
holdernames the logical worker. Keep it stable across restarts (for examplecrawler-3or 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.
Timeouts, retries and idempotency
Section titled “Timeouts, retries and idempotency”- Per-attempt timeout.
requestTimeoutMsapplies to each HTTP attempt. With the defaults (10 s, 2 retries) a call to an unreachable API can take about 30 seconds before it throwsUnavailableError. - 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_HELDorINSUFFICIENT_BUDGET; those surface as typed errors immediately. Waiting helpers likeclaim.acquire({ wait: true })handle specific codes on top of this, see Errors & retries. - Idempotency keys. Every non-
GETrequest carries a freshIdempotency-Keyheader, reused across the transport retries of that one call, so a retried request is never applied twice. Separate calls get separate keys: callingrs.scopes.create()twice creates two runs. - Client-side waits end locally. Methods like
ticket.result(),group.wait()andtimer.wait()poll until theirtimeoutMsand then throwWaitTimeoutError. The task, group or timer keeps going on the server.