Authentication
Every request to the Helm Core API carries a bearer token in the Authorization header. There are three ways to get one; answer two questions below and we'll point you at the right one.
Which method should I use?
Authorization: Bearer hlm_8Kq2mZ…
Whichever way you get it, a bearer token is checked the same way on every call — REST and MCP alike:
- Is the token real? Missing, unknown, revoked or expired tokens get a
401(invalid_token,revoked,expired). - Is the API switched on for your organisation, and is the scope's area enabled? Otherwise
403 org_disabledor403 scope_disabled. - Is the caller's IP allowed? Organisation and token allowlists are both checked; otherwise
403 ip_blocked. - Is the token within its rate limit? Otherwise
429 rate_limitedwithRetry-After. - Does the token carry the operation's scope — and does the user behind it still hold the matching platform permission? Otherwise
403 missing_scope.
Every response carries an X-Request-Id, and every call — accepted or rejected — is written to your organisation's API audit log.
Static tokens
The simplest option: a token created in Settings → API Suite and sent as-is.
- It looks like
hlm_followed by 40 random characters. - The full value is shown once, when you create it. Helm stores only a hash, so a lost token can't be recovered — revoke it and make a new one.
- You choose its scopes, and optionally an expiry, an IP allowlist (CIDR ranges) and a rate limit.
- Revoking it takes effect on the next request.
There are two kinds, and the difference matters:
| Organisation token | Personal token | |
|---|---|---|
| Acts as | Its own service identity — a non-login user with a role holding exactly the token's scopes | You |
| Permissions | Fixed by the scopes you picked | Your live platform permissions, narrowed by the token's scopes |
| Survives you leaving | Yes | No — suspend the user and the token stops working |
| Who can create one | Users who manage the API Suite | Users who manage it, and anyone with self-service API access |
| Good for | Integrations, sync jobs, pipelines | Scripts, notebooks, an assistant acting as you |
Every scope maps to platform permissions. On each call Helm checks that the
token's user — your service identity or you — still holds one of them, so a
scope on the token is necessary but never sufficient. Writes are recorded
against that user too, which is why v2 rejects user_id-style "act as" fields
in request bodies with a 422.
curl https://your-org-domain/kong/core/api/v2/ping \
-H "Authorization: Bearer $HELM_API_KEY"OAuth2 client credentials
If your platform prefers short-lived tokens — or already speaks OAuth2, like most iPaaS tools and API gateways — create the organisation token with the OAuth2 authentication method instead. Rather than a bearer, you get a client:
- a client id, prefixed
hlmc_, and - a client secret, prefixed
hlms_, shown once.
Exchange them for an access token at the public token endpoint. Ask for a subset of the client's scopes with scope, or leave it out to get them all:
curl -X POST https://your-org-domain/kong/core/api/v2/oauth/token \
-d grant_type=client_credentials \
-d client_id=hlmc_… \
-d client_secret=hlms_… \
-d scope="investors:read deals:read"The access token (prefixed hlma_) lasts an hour and is used exactly like a static token. Cache it and fetch a new one shortly before expires_in runs out.
Failures use the OAuth2 error shape, { "error": …, "error_description": … }:
| Status | error | Meaning |
|---|---|---|
| 400 | unsupported_grant_type | Only client_credentials is accepted here. |
| 400 | invalid_scope | You asked for a scope the client doesn't have. |
| 401 | invalid_client | Unknown, revoked or wrong secret — deliberately vague. |
| 403 | invalid_client | The organisation's API access is switched off. |
| 429 | slow_down | Over 20 token requests a minute from your IP; wait Retry-After seconds. |
The 20-a-minute limit per IP address slows down secret guessing; cache access tokens and you'll never reach it. See Rate limits.
Interactive OAuth for MCP clients
AI assistants connect to the MCP server as you, and modern MCP clients can do the whole sign-in themselves. Point the client at your MCP URL and it:
- Calls the server without a token and gets a
401with aWWW-Authenticateheader pointing at the protected resource metadata. - Reads
/.well-known/oauth-protected-resource, then the authorization server metadata at/.well-known/oauth-authorization-server. - Registers itself at
POST /oauth/register(dynamic client registration). Public clients are de-duplicated, so reconnecting doesn't pile up registrations. - Opens a browser at
/oauth/authorize— authorization code flow with PKCE (S256) — where you sign in and see a consent screen. - Exchanges the code for an access token and a refresh token, and starts calling tools.
All of these live under https://your-org-domain/kong/core/api/v2.
Clients usually ask for every scope going. The consent screen sorts each one into:
- Granted — you hold the permissions behind it.
- Switched off — that area is disabled for your organisation.
- Not yours to grant — your roles don't carry it.
Only the granted scopes are issued (OAuth allows a narrower grant than asked for), so the assistant's tool list is exactly the set of calls that will succeed. If nothing can be granted, approval fails with 403 no_grantable_scope.
Approving a connection requires a platform permission that lets you use the API Suite yourself. Without it the consent screen explains you're not enrolled; ask an administrator to grant it.
Keeping tokens safe
- One token per integration. Revoking one shouldn't take down another, and the audit log stays readable.
- Least privilege. Grant only the scopes the job needs — and for MCP, the token's scopes decide which tools the assistant can even see.
- Set an expiry and an IP allowlist on static tokens used from servers.
- Never ship a token to a browser or mobile app. Call Helm from your backend.
- Store secrets in a secret manager, not in code or
.envfiles in git. - Rotate by creating the new token, deploying it, then revoking the old one.