Skip to content
Get started
TypeScript

Bearer Auth

Utilities for bearer JWT authentication, including automatic token refresh on expiry.

Bearer (JWT) authentication is used in multi-project contexts where the caller belongs to more than one project and must pass projectId per request. Bearer tokens are typically short-lived session tokens that can expire after ~60 seconds. Any SDK call that outlives the token window — a long .wait() poll or a slow uploadFile() — risks a mid-flight 403. withBearerRefresh wraps fetch to detect these rejections and retry transparently.

Not applicable to API-key auth — API keys are long-lived credentials and don’t expire mid-session.


Wraps fetch so that AWS API Gateway authorizer rejections (HTTP 403 with header x-amzn-errortype: AccessDeniedException) automatically trigger a JWT refresh and a transparent retry.

Signature

withBearerRefresh(
refresh: () => Promise<string | null>,
options?: BearerRefreshOptions,
): typeof fetch

Parameters

NameTypeDescription
refresh() => Promise<string | null>Async callback that returns a fresh JWT string, or null if the refresh fails (the original 403 is then propagated unchanged).
options?BearerRefreshOptionsOptional configuration.

Returns

typeof fetch — a drop-in fetch replacement. Pass it to the fetch option of the Scenario constructor.

Example

import Scenario, { withBearerRefresh } from '@scenario-labs/sdk';
const client = new Scenario({
bearerAuth: initialJwt,
projectId: 'proj_...',
fetch: withBearerRefresh(async () => {
// Replace with your IdP or session-token API call.
return await mintFreshJwt();
}),
});
// Long-running poll — JWT can expire mid-flight; the wrapper handles it:
const run = await client.workflows.run('wflow_...', { body: { prompt: 'a snowy mountain pass' } });
const completed = await run.job.wait({ timeoutMs: 600_000 });

Options for withBearerRefresh().

interface BearerRefreshOptions {
maxRefreshes?: number; // Default: 2
fetch?: typeof fetch; // Default: globalThis.fetch
}
NameTypeDefaultDescription
maxRefreshes?number2Maximum refresh attempts per wrapper instance. After this limit, subsequent authorizer 403s are propagated unchanged.
fetch?typeof fetchglobalThis.fetchUnderlying fetch to wrap. Useful for composing with other wrappers (logging, retries, test mocks).

The enhanced Scenario constructor accepts a projectId option, which is required for bearer auth when the user belongs to multiple projects.

const client = new Scenario({
bearerAuth: jwt,
projectId: 'proj_...',
});
NameTypeDefaultDescription
projectId?string | nullnullDefault project scope for all requests (sent as ?projectId=...). Useful with bearerAuth when the user belongs to multiple projects — the server derives the team from the project, so teamId doesn’t need to be set separately. With apiKey auth the project is implied by the key and this option is unnecessary.

Per-call override: pass options.query.projectId to override for a single request. SDK helpers (.wait(), model.run(), workflow.run()) automatically reuse the scope of the originating call, so an action created in project A always polls in project A even if the client default changes later.