HESO has two kinds of work. The first is local: capturing an action, evaluating it against policy, signing it, and verifying a receipt. None of that talks to a server, so none of it needs authentication. The second is the cloud: pulling your team’s policy, pushing receipts to the outbox, and opening approvals. Every cloud call carries an API key as a Bearer token. This page covers how to get that key, where to put it, and what genuinely depends on it.
API keys
A HESO API key authenticates one team to the control plane — the HESO servers your team talks to. Create a key in the dashboard under Settings / Billing, then read it from an environment variable. Every request sends it in the Authorization header as a Bearer token, with Content-Type: application/json on calls that send a body.
- Header shape:
Authorization: Bearer <API_KEY>. - The base URL is your configured endpoint, not a fixed host.
- The key scopes requests to a single team; team-scoped routes take the team id in the path.
Here is a plain request to pull a team’s policy bundle, reading the key from the environment:
curl https://api.heso.example/v1/teams/team_19/policy \ -H "Authorization: Bearer $HESO_API_KEY"
A successful pull returns a PolicyBundle — version, rules, and fetchedAt. The SDKs wrap this same request, so you rarely write the curl by hand. See the API reference for the full endpoint list.
Configure the client
Both SDKs read the same key — they just install it differently. In TypeScript you call configure once, with the key and the endpoint, before any cloud call. In Python, heso.init resolves configuration from arguments, environment, and your heso.toml.
import { configure, pullPolicy } from "@hesohq/sdk" // Once, at startup — before any cloud call.configure(process.env.HESO_API_KEY!, process.env.HESO_ENDPOINT!) // Now the cloud client is authenticated.const bundle = await pullPolicy("team_19")console.log(bundle.version, bundle.rules.length)
On the TypeScript side, configure(apiKey, endpoint) sets the credentials for the cloud client. Call it once at startup; every later pullPolicy, pushReceipt, and approval helper uses what you set. The verifying side of the SDK — gate and assertGate — calls @hesohq/core in-process and needs no key.
Call configure before the first cloud call. The Python init checks explicit arguments first, then environment variables, then heso.toml, then defaults. Calling init again replaces the active config.
Environment variables
The Python SDK reads its configuration from environment variables when explicit arguments to init are not given. None of these is an API key — they describe the local project and run, not cloud credentials.
| Variable | What it sets |
|---|---|
HESO_PROJECT_ROOT | Project root used to discover the config and the local data dir. |
HESO_WORKFLOW | Default workflow label stamped onto captured actions. |
HESO_ACCOUNT | Default account stamped onto captured actions. |
HESO_CLOCK | Clock override for deterministic capture timestamps. |
HESO_TIMEOUT | Timeout for gate operations. |
HESO_BLOCKING | Whether a blocked or suspended action raises (default) or is observe-only. |
HESO_BIN | Path to the Rust engine binary, when not on the default path. |
On the TypeScript side there is no env-var convention — the SDK reads whatever key and endpoint you pass to configure. The common pattern is to pull them from process.env, for example process.env.HESO_API_KEY and process.env.HESO_ENDPOINT, as the example above does.
What needs auth
This is the line that matters most: verification never depends on HESO. The crypto runs in your process or in a reviewer’s browser, so checking a receipt needs no key, no account, and no network. Only the cloud endpoints require the Bearer key.
| Operation | Needs the API key? |
|---|---|
gate / assertGate — verify a receipt | No — runs in-process via @hesohq/core |
| Signing and hashing an action | No — local, no network |
| Offline verification in the browser | No — pure WASM, no infrastructure |
pullPolicy — fetch the policy bundle | Yes |
pushReceipt / pushReceipts — outbox | Yes |
openApproval / pollApproval — route to a human | Yes |
An API key authenticates a request to our control plane. It does not give the cloud the power to forge a receipt: the server re-verifies every receipt before accepting it, and a verdict is re-derived from the signatures themselves. Because the cloud holds no signing key, an API key can never produce a valid operator or approver signature. Trust comes from the math, not from being logged in.
Where keys live
The cloud API key is the only secret you pass in — read it from the environment at startup. Everything else is handled for you: heso init writes the local data directory (your operator key, the audit log, the outbox) and ignores it in git. The approver key never leaves the approver’s device. That is the whole point of an L1 co-signature — the cloud can’t hold the key, so it can’t forge it.
Next steps
With auth in place, pull your policy and start pushing receipts.
