Skip to content
Linra Omni Developer Docs

Authentication & tokens

The token model, its 5-minute lifetime, and the caching pattern you should build around it.

POST /api/v1/auth/token exchanges a clientId + clientSecret for a short-lived JWT bearer token (audience partner-api). Send it as Authorization: Bearer <token> on every other /api/v1 request.

// POST /api/v1/auth/token
{ "clientId": "live_...", "clientSecret": "..." }
// 200 OK
{
"state": "SUCCESS",
"payload": {
"accessToken": "eyJhbGciOi...",
"tokenType": "Bearer",
"expiresIn": 300
}
}

The token’s lifetime is a deliberate 5 minutes, and there is no refresh-token grant. This is a considered trade-off, not an oversight:

  • Your integration already holds a permanent clientId + clientSecret — re-authenticating with that credential is the standard machine-to-machine pattern. A refresh token exists to let a client that does NOT hold long-lived credentials (a mobile app, a browser session) stay signed in without re-prompting a human; that problem does not apply here.
  • A short-lived, non-refreshable token bounds the damage of a leaked token to at most 5 minutes, with no server-side revocation infrastructure required. The lifetime is accurate: the Gateway allows at most 30 seconds of clock skew, so an expired token stops working within 30 seconds of its exp, not minutes later.
  • At roughly 12 token fetches per hour if you refetch conservatively (well under the token endpoint’s own rate limit — see Rate limits), the “burden” of re-authenticating is negligible once you cache the token client-side, which you should do regardless of TTL.

Cache the token in memory (or your own short-TTL store) and refresh proactively — a little before expiry, not reactively after a 401. A simple, dependency-free shape:

let cached = null; // { accessToken, expiresAt }
async function getAccessToken() {
const now = Date.now();
// Refresh 30s before the token's real expiry to absorb clock skew and in-flight requests.
if (cached && cached.expiresAt - 30_000 > now) {
return cached.accessToken;
}
const res = await fetch('https://api-omni-stg.linra.net/api/v1/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ clientId: CLIENT_ID, clientSecret: CLIENT_SECRET }),
});
const { payload } = await res.json();
cached = {
accessToken: payload.accessToken,
expiresAt: now + payload.expiresIn * 1000,
};
return cached.accessToken;
}

Every call site awaits getAccessToken() instead of holding a token directly — that’s the whole pattern. If a call still comes back 401, branch on state:

  • UNAUTHORIZED_TOKEN_EXPIRED — the cache was stale (e.g. your clock runs behind). Fetch a new token once and retry; don’t loop.
  • UNAUTHORIZED_MISSING_TOKEN / UNAUTHORIZED_INVALID_TOKEN — the Authorization header is absent or the token is not one this API accepts (truncated, tampered, not a partner token). Fix the request; a new token only helps if you were sending the wrong one.
  • UPSTREAM_AUTH_FAILED — not your token: a service behind the Gateway refused the Gateway itself. It’s a server-side fault — retry later with backoff and do not re-mint your token.

Every 401 keeps the standard WWW-Authenticate header. See Errors.

If your credential is a parent partner acting for one of its own descendant partners, the token request accepts an optional onBehalfOf (the descendant’s partner ID). The issued token’s identity claims (sub, partner scoping) become the descendant’s — everything you do with that token acts as the descendant, with an internal audit trail recording your credential as the acting parent. onBehalfOf is rejected (403 FORBIDDEN_NOT_YOUR_DESCENDANT / FORBIDDEN_DELEGATION_TARGET_INVALID) for any partner that isn’t genuinely your descendant. Most integrations never need this field.

You don’t need to decode the token yourself — treat it as opaque — but for context, it carries your partner identity, hierarchy information, your credential’s environment, and a unique jti. None of these are inputs you provide; they’re all derived server-side from the validated credential.