Authentication & capabilities
The token
Section titled “The token”POST /api/v1/vendor/auth/token exchanges a clientId + clientSecret for a short-lived JWT
bearer token (audience vendor-api). Send it as Authorization: Bearer <token> on every other
/api/v1/vendor request.
// POST /api/v1/vendor/auth/token{ "clientId": "vnd_...", "clientSecret": "..." }// 200 OK{ "state": "SUCCESS", "payload": { "accessToken": "eyJhbGciOi...", "tokenType": "Bearer", "expiresIn": 300 }}Why 5 minutes, and no refresh token
Section titled “Why 5 minutes, and no refresh token”Same trade-off the partner plane makes, applied here: your integration already holds a permanent
clientId + clientSecret, so re-authenticating with that credential is the standard
machine-to-machine pattern — there’s no separate short-lived-session actor that a refresh token
would exist to serve. 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 caching pattern
Section titled “The caching pattern”Cache the token in memory and refresh proactively — a little before expiry, not reactively after a 401:
let cached = null; // { accessToken, expiresAt }
async function getAccessToken() { const now = Date.now(); if (cached && cached.expiresAt - 30_000 > now) { return cached.accessToken; }
const res = await fetch('https://api-omni-stg.linra.net/api/v1/vendor/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;}If a call still comes back 401, treat it as “the cache was stale for some other reason” and fetch
once more before giving up — don’t loop.
No delegation
Section titled “No delegation”Unlike the partner plane’s onBehalfOf, the vendor token request accepts no delegation field.
Vendor has no hierarchy the way a partner does — your credential’s identity is always your own.
Capabilities live in the token
Section titled “Capabilities live in the token”Your credential is issued with a fixed set of capabilities (Offers, Stock, Cost, Orders —
any combination, see the Vendor Integration Standard).
The issued token’s claims carry exactly that set — every vendor endpoint checks it from the token
alone, with no additional lookup back to your credential record.
A missing capability is a 403, not a partial/filtered response. If your credential doesn’t
hold Orders, calling GET /api/v1/vendor/orders doesn’t return an empty or truncated list — it
rejects outright:
// 403 Forbidden{ "state": "FORBIDDEN_CAPABILITY_NOT_GRANTED", "payload": null, "details": { "requiredCapability": "Orders" }}If you believe your integration needs a capability your current credential doesn’t have, contact your onboarding contact — capabilities are set when the credential is issued, and there is no self-service way to add one to an existing credential today (see the Standard’s out-of-scope section).
Because capabilities are baked into the token at issuance time, a capability change to your credential takes effect on your NEXT token fetch, not retroactively on an already-issued token — one more reason the short 5-minute lifetime is convenient rather than costly.