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.
withBearerRefresh(refresh, options?)
Section titled “withBearerRefresh(refresh, options?)”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 fetchParameters
| Name | Type | Description |
|---|---|---|
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? |
BearerRefreshOptions |
Optional 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 });BearerRefreshOptions
Section titled “BearerRefreshOptions”Options for withBearerRefresh().
interface BearerRefreshOptions { maxRefreshes?: number; // Default: 2 fetch?: typeof fetch; // Default: globalThis.fetch}| Name | Type | Default | Description |
|---|---|---|---|
maxRefreshes? |
number |
2 |
Maximum refresh attempts per wrapper instance. After this limit, subsequent authorizer 403s are propagated unchanged. |
fetch? |
typeof fetch |
globalThis.fetch |
Underlying fetch to wrap. Useful for composing with other wrappers (logging, retries, test mocks). |
ClientOptions.projectId
Section titled “ClientOptions.projectId”The enhanced Scenario constructor accepts a projectId option, which is required for bearer auth when the user belongs to multiple projects. This option is declared on the enhanced ClientOptions from @scenario-labs/sdk/lib/scenario; the ClientOptions type exported from the package root is the base type and does not include it.
const client = new Scenario({ bearerAuth: jwt, projectId: 'proj_...',});| Name | Type | Default | Description |
|---|---|---|---|
projectId? |
string | null |
null |
Default 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 (or the typed projectId param, where the method has one; options.query wins if both are set) to override for a single request. SDK helpers (.wait(), model.run(), workflow.run()) read the project from the request that was actually sent, so an action created in project A always polls in project A even if the client default changes later.
Unset values keep the default: a projectId that is undefined — for example from an optional spread — never erases the client default. null and '' are treated the same way for keys the client provides by default, so the request still goes to the client’s project instead of falling back to the team’s default project.