Build apps
Authentication
API keys, service accounts, access tokens, token exchange with the user's id_token, and scopes and roles.
This page explains the credentials an app uses to call Hane’s public API, and the token exchange that lets it call on behalf of a user. Every credential is sent as Authorization: Bearer. The organization, project, and environment are not part of the URL; they are determined by the credential.
Credential types
Section titled “Credential types”| Credential | Format | Lifetime | Used for |
|---|---|---|---|
| API key | hane_<env>_… |
Set when issued (no expiry if omitted) | Calling as a service account: batch jobs, CI, the BFF’s token exchange |
| Access token | Issued by Hane through token exchange | 15 minutes | Calling as a user: the BFF, the console, hanectl |
| IdP id_token | Issued by your IdP | Depends on the IdP | Input to token exchange, and APIs used before entering an organization (such as GET /v1/auth/me) |
Service accounts and API keys
Section titled “Service accounts and API keys”A service account is a caller that is not a person. It is bound to one project and environment, and its scopes are set when you create it. Scopes cannot be changed later; if they are not enough, create a new service account.
const sa = await unwrap( admin.POST("/v1/platform/service-accounts", { body: { name: "fleet-app", project: "fleet", environment: "dev", scopes: ["data:records.read", "agents:run"] }, }),);const key = await unwrap( admin.POST("/v1/platform/service-accounts/{id}/keys", { params: { path: { id: sa.sa_id } }, body: { expires_at: "2027-03-31T00:00:00Z" }, }),);// key.secret is returned only in this response- Issuing and revoking API keys requires
platform:keys.manage. You can also do this in the console under Access → Service accounts and API keys. - Revoke a key with
DELETE /v1/platform/service-accounts/{id}/keys/{keyId}. It is refused from the next request. - If you did not receive the response when issuing a key, revoke it and issue a new one.
Token exchange
Section titled “Token exchange”POST /v1/auth/token exchanges an IdP id_token for an access token bound to an organization, project, and environment. It follows the OAuth 2.0 Token Exchange format.
const t = await unwrap( anon.POST("/v1/auth/token", { body: { grant_type: "urn:ietf:params:oauth:grant-type:token-exchange", subject_token: idToken, subject_token_type: "urn:ietf:params:oauth:token-type:id_token", actor_token: process.env.HANE_API_KEY!, actor_token_type: "urn:hane:params:token-type:api_key", organization: "acme", project: "fleet", environment: "dev", }, }),);// t.access_token, t.expires_in, t.context.scopesconst t = await unwrap( anon.POST("/v1/auth/token", { body: { grant_type: "urn:ietf:params:oauth:grant-type:token-exchange", subject_token: idToken, subject_token_type: "urn:ietf:params:oauth:token-type:id_token", organization: "acme", project: "fleet", environment: "dev", }, }),);- When you pass your app’s API key as
actor_token, the user becomes the principal and the app’s service account becomes the actor. The scopes are the intersection of the user’s and the app’s scopes. - The API key must belong to the same organization, project, and environment as the exchange, or the request fails with
FORBIDDEN. If the API key has an expiry, the access token cannot be used past it. - If the user is not a member of the organization, the request fails with
FORBIDDEN. If you passproject,environmentis also required. - The response’s
contextcontains the organization, project, environment, user ID, roles, groups, and scopes. You can use it to decide what to show on screens.
Scopes
Section titled “Scopes”Scopes have the form <service>:<resource>.<action>. You can also grant a whole service at once, such as data:*.
| Service | Main scopes |
|---|---|
| Data | data:schemas.read, data:records.read, data:records.write, data:values.confirm, data:import |
| Definitions | definitions:read, definitions:apply, definitions:release |
| Workflows | workflows:read, workflows:operate, workflows:event-subscriptions.manage |
| Approvals | approvals:tasks.read, approvals:tasks.decide |
| Documents | documents:read, documents:write, documents:download, documents:delete |
| Agents | agents:read, agents:run |
| Communication | communication:read, communication:contacts.write |
| Organization | platform:read, platform:members.manage, platform:projects.manage, platform:keys.manage |
| Audit and usage | audit:read, usage:read |
The reference app’s service account holds data read and write, value confirmation, import, Workflow read, Task read and decide, document read and write, and agent read and run.
Organization members get scopes through roles.
| Role | What it can do |
|---|---|
| owner | Everything |
| admin | All scopes |
| developer | Read, plus writing data, applying and activating definitions, running agents, and more |
| operator | Read, plus writing data and confirming values, deciding Tasks, writing documents, and more |
| viewer | Read only |
Roles are set when a member is invited, and can also be added to people, groups, and service accounts with a RoleBinding manifest. Whenever roles, groups, or API keys change, the policy epoch goes up and tokens from the old epoch are refused (POLICY_EPOCH_STALE). Exchange the token again when that happens.