Skip to main content

Authentication and tokens

Bairav uses four kinds of credential. Pick by what you are connecting.

CredentialLooks likeSent asUsed forCreated in
Session tokenA signed tokenAuthorization: Bearer <token>The Portal APISign-in
Gateway keybst_<8 hex>.<secret>In the URL path: /gw/{token}/v1/...The AI GatewayConsole, OpenAI gateway, or POST /api/gateway/keys (admin)
Sensor tokenbst_<8 hex>.<secret>X-Sensor-Token headerSensor events and governanceSettings, Sensor deployment, or POST /api/admin/enrollment-tokens (admin)
Ingest tokenobt_<8 hex>.<secret>X-Ingest-Token header, or Authorization: BearerSecurity events, OTLP traces, SBOMsSettings, Security data and telemetry, or POST /api/security/ingest-tokens (manage_observability)

Gateway keys and sensor tokens share the bst_ prefix but are different credentials in different tables. Do not use one where the other is expected.

How machine tokens are protected​

  • The part after the dot is the secret. The 8-character prefix is stored in clear as a lookup handle; the secret is stored only as an Argon2id hash.
  • The full token is shown once, when you create it. It cannot be retrieved later. If you lose it, revoke it and create a new one.
  • Revoking a token stops it working immediately. The record stays, marked revoked, for the audit trail.
  • Repeated failed authentication against one token prefix is rate limited (30 a minute), then answered with 429.
  • Tokens are bound to one tenant. Ingest tokens are bound to one telemetry source.

Sign in to the Portal API​

The only sign-in that works without a browser is a local account:

POST /api/auth/breakglass
Content-Type: application/json

{ "email": "svc-automation@example.com", "password": "..." }
{ "access_token": "...", "refresh_token": "...", "token_type": "bearer" }
TokenLifetime
Access token1 hour
Refresh token14 days

Send the access token as Authorization: Bearer <access token>. Before it expires, call POST /api/auth/refresh with the refresh token; you get a new pair, so keep the new refresh token.

samples/auth/login.py
"""Sign in to the Bairav Portal API with a local account, then call it. Standard library only.

export BAIRAV_HOST=your-tenant.example.com
export BAIRAV_EMAIL=svc-automation@example.com
export BAIRAV_PASSWORD=... # use a dedicated account with the narrowest role that works
"""
import json
import os
import urllib.request

base = f"https://{os.environ['BAIRAV_HOST']}"


def call(method: str, path: str, body: dict | None = None, token: str | None = None) -> dict:
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(base + path, data=data, method=method, headers=headers)
with urllib.request.urlopen(req, timeout=15) as resp:
return json.load(resp)


tokens = call("POST", "/api/auth/breakglass", {"email": os.environ["BAIRAV_EMAIL"], "password": os.environ["BAIRAV_PASSWORD"]})
access = tokens["access_token"] # valid for 1 hour; tokens["refresh_token"] is valid for 14 days

me = call("GET", "/api/auth/me", token=access)
print(me["email"], me["role"], me["tenant_name"])
print("permissions:", ", ".join(me["permissions"]))

# Renew before the hour is up. Always keep the NEW refresh token.
tokens = call("POST", "/api/auth/refresh", {"refresh_token": tokens["refresh_token"]})

If the same email and password exist in more than one tenant, sign-in fails with 401 rather than guessing.

Single sign-on with Microsoft Entra ID is for people using the console in a browser. It is not an API.

Keep credentials safe​

  • Use a dedicated account with the narrowest role that works for automation. Do not use a person's account.
  • Keep secrets out of URLs where you can. The gateway key is part of the URL path by design, so the whole gateway base URL is a secret: it appears in access logs, proxies and shell history. Store it in a secret manager, never in source control.
  • Never put a token in a front-end bundle. Call Bairav from a server.
  • Rotate. Create the new token, deploy it, then revoke the old one.
  • Tenant comes from the credential. Do not send a tenant ID; it is ignored or rejected.