logo
Development
Search
SDK API Reference

SDK API Reference

The official SDK consists of two framework-agnostic, zero-runtime-dependency packages. The source code is available on GitHub at: https://github.com/gptbots/workspace-extension-sdk.

Package Runs in Entry points
@gptbots/workspace-extension-verify Backend (Node ≥ 18) verifyWsa, exchangeWorkspaceCode
@gptbots/workspace-extension-sdk Browser consumeHandoff, startWorkspaceLogin, completeWorkspaceLogin

Both packages are ESM ("type": "module"). CommonJS projects should use a dynamic import() or switch to ESM.


Backend Package @gptbots/workspace-extension-verify

verifyWsa(token, options): WorkspaceIdentity

Verifies the wsa and returns the workspace identity. Verification order: signature → issaud → expiration (exp±leeway, including iat/nbf) → required claims.

interface VerifyOptions { secret?: string; // HS256 secret (per-app or shared). Required when verifying HS256 publicKey?: string; // RS256 PEM public key. Required when verifying RS256 (roadmap) audience: string; // extension app host, must equal the token's aud (required) issuer?: string; // default 'gptbots-workspace' leewaySeconds?: number; // clock drift tolerance (seconds), default 30 algorithms?: ('HS256' | 'RS256')[]; // default ['HS256'] } interface WorkspaceIdentity { accountId: string; // = JWT sub role: 'OWNER' | 'ADMIN' | 'MEMBER'; // unknown values are normalized to MEMBER workspaceId: string; // = JWT workspace_id username?: string; email?: string; avatar?: string; appName?: string; issuedAt?: number; // = iat (seconds) expiresAt?: number; // = exp (seconds) }
                      
                      interface VerifyOptions {
  secret?: string;        // HS256 secret (per-app or shared). Required when verifying HS256
  publicKey?: string;     // RS256 PEM public key. Required when verifying RS256 (roadmap)
  audience: string;       // extension app host, must equal the token's aud (required)
  issuer?: string;        // default 'gptbots-workspace'
  leewaySeconds?: number; // clock drift tolerance (seconds), default 30
  algorithms?: ('HS256' | 'RS256')[]; // default ['HS256']
}

interface WorkspaceIdentity {
  accountId: string;                  // = JWT sub
  role: 'OWNER' | 'ADMIN' | 'MEMBER'; // unknown values are normalized to MEMBER
  workspaceId: string;                // = JWT workspace_id
  username?: string; email?: string; avatar?: string; appName?: string;
  issuedAt?: number;   // = iat (seconds)
  expiresAt?: number;  // = exp (seconds)
}

                    
This code block in the floating window
  • exp enforced: missing or a non-finite number → throws MissingClaim (never treated as "never expires").
  • iat / nbf (if present) in the future beyond leeway → throws NotYetValid.
  • Failures throw WsaVerificationError (with .code); a caller configuration error (e.g., a missing secret) throws TypeError.

WsaVerificationError.code values:

code Meaning
InvalidToken The token is empty / structurally invalid / a segment is not a JSON object
InvalidSignature The signature does not match (wrong secret, or tampered with)
Expired Already expired (before exp + leeway)
NotYetValid iat/nbf is in the future (beyond leeway)
WrongIssuer iss ≠ the expected value
WrongAudience aud ≠ your audience
MissingClaim Missing exp / sub / role / workspace_id
UnsupportedAlgorithm alg is not in the allowlist (by default only HS256)

exchangeWorkspaceCode(options): Promise<WorkspaceCodeExchangeResult>

For pull login: on the extension app backend, exchange the one-time code + PKCE codeVerifier for the wsa. Never call this in the browser.

interface ExchangeWorkspaceCodeOptions { tokenUrl: string; // the platform /token endpoint (absolute URL) code: string; // the one-time authorization code obtained on callback codeVerifier: string; // the PKCE verifier corresponding to code_challenge fetch?: typeof fetch; // injectable fetch (testing/legacy runtimes); default is the global fetch timeoutMs?: number; // request timeout, default 10000; pass 0 to disable } interface WorkspaceCodeExchangeResult { wsa: string; // the signed wsa, to pass to verifyWsa tokenType?: string; // 'Bearer' expiresIn?: number; // wsa validity period (seconds) }
                      
                      interface ExchangeWorkspaceCodeOptions {
  tokenUrl: string;      // the platform /token endpoint (absolute URL)
  code: string;          // the one-time authorization code obtained on callback
  codeVerifier: string;  // the PKCE verifier corresponding to code_challenge
  fetch?: typeof fetch;  // injectable fetch (testing/legacy runtimes); default is the global fetch
  timeoutMs?: number;    // request timeout, default 10000; pass 0 to disable
}

interface WorkspaceCodeExchangeResult {
  wsa: string;           // the signed wsa, to pass to verifyWsa
  tokenType?: string;    // 'Bearer'
  expiresIn?: number;    // wsa validity period (seconds)
}

                    
This code block in the floating window
  • Transport failure / non-2xx / a business code≠0 (e.g., 403209 invalid_grant, 403210 invalid_verifier) / a missing data.wsa → throws Error.
  • A timeout throws token exchange timed out after <ms>ms (default 10s, preventing a slow platform response from hanging your backend).

Express Middleware Example

import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify'; export function requireWorkspaceIdentity(secret: string, audience: string) { return (req, res, next) => { try { req.identity = verifyWsa(req.body.wsa, { secret, audience }); next(); } catch (e) { const code = e instanceof WsaVerificationError ? e.code : 'Error'; res.status(401).json({ code }); } }; }
                      
                      import { verifyWsa, WsaVerificationError } from '@gptbots/workspace-extension-verify';

export function requireWorkspaceIdentity(secret: string, audience: string) {
  return (req, res, next) => {
    try {
      req.identity = verifyWsa(req.body.wsa, { secret, audience });
      next();
    } catch (e) {
      const code = e instanceof WsaVerificationError ? e.code : 'Error';
      res.status(401).json({ code });
    }
  };
}

                    
This code block in the floating window

Browser Package @gptbots/workspace-extension-sdk

Push (Push handoff)

readHandoffToken(search?, paramName?): string | null

A pure function that reads the raw wsa from the query string. search defaults to location.search, and paramName defaults to 'wsa'.

stripHandoffToken(paramName?, ctx?): void

Uses history.replaceState to remove the wsa from the current URL so it does not linger in the address bar/history/Referer. It is a safe no-op outside the browser. ctx?: { history?, location? } is injectable (testing).

consumeHandoff(options): Promise<WorkspaceIdentity>

A convenience flow for the use level: read the wsa → POST to the extension app backend → on success, strip the wsa → return the identity.

interface ConsumeHandoffOptions { exchangeUrl: string; // the extension app's backend verification endpoint fetch?: typeof fetch; // injectable search?: string; // default location.search paramName?: string; // default 'wsa' strip?: boolean; // default true (strips only on success) }
                      
                      interface ConsumeHandoffOptions {
  exchangeUrl: string;   // the extension app's backend verification endpoint
  fetch?: typeof fetch;  // injectable
  search?: string;       // default location.search
  paramName?: string;    // default 'wsa'
  strip?: boolean;       // default true (strips only on success)
}

                    
This code block in the floating window

The token is stripped only after a successful exchange, so that a transient failure can be retried by refreshing. If you want it stripped immediately even on failure, call stripHandoffToken() manually in the catch block.

Pull (Pull / M-Auth)

startWorkspaceLogin(options): Promise<WorkspaceLoginRequest>

Generates PKCE, stores it in sessionStorage, builds the /authorize URL, and (by default) redirects to it. Requires a secure context (HTTPS/localhost).

interface StartWorkspaceLoginOptions { authorizeUrl: string; // the platform /authorize endpoint (absolute URL) clientId: string; // the extension app's app home URL redirectUri: string; // the callback address, host must be same-origin as clientId state?: string; // default: auto-generate a 16-byte random CSRF state workspaceId?: string; // preselect a workspace, skipping the organization selection page storage?: StorageLike; // default sessionStorage redirect?: (url: string) => void; // default location.assign navigate?: boolean; // false = only build the URL without redirecting (popup/testing) } interface WorkspaceLoginRequest { url: string; state: string; codeVerifier: string; }
                      
                      interface StartWorkspaceLoginOptions {
  authorizeUrl: string;  // the platform /authorize endpoint (absolute URL)
  clientId: string;      // the extension app's app home URL
  redirectUri: string;   // the callback address, host must be same-origin as clientId
  state?: string;        // default: auto-generate a 16-byte random CSRF state
  workspaceId?: string;  // preselect a workspace, skipping the organization selection page
  storage?: StorageLike; // default sessionStorage
  redirect?: (url: string) => void; // default location.assign
  navigate?: boolean;    // false = only build the URL without redirecting (popup/testing)
}
interface WorkspaceLoginRequest { url: string; state: string; codeVerifier: string; }

                    
This code block in the floating window

readAuthorizeCallback(search?, storage?): AuthorizeCallback | null

On the callback landing page: read code + state, verify state (CSRF), and return code + the stored codeVerifier. It does not consume the stored request (consumption is deferred to a successful exchange), so that a transient failure can be retried by refreshing. Returns null when there is neither a code nor an error.

interface AuthorizeCallback { code: string; state: string | null; codeVerifier: string; }
                      
                      interface AuthorizeCallback { code: string; state: string | null; codeVerifier: string; }

                    
This code block in the floating window

stripAuthorizeCallback(ctx?): void

Removes code / state from the current URL.

completeWorkspaceLogin(options): Promise<WorkspaceIdentity>

A convenience flow for the use level: read and verify the callback → POST {code, codeVerifier} to your backend → strip the URL on success → return the identity.

interface CompleteWorkspaceLoginOptions { exchangeUrl: string; // your own backend exchange endpoint fetch?: typeof fetch; search?: string; // default location.search storage?: StorageLike; // default sessionStorage strip?: boolean; // default true }
                      
                      interface CompleteWorkspaceLoginOptions {
  exchangeUrl: string;   // your own backend exchange endpoint
  fetch?: typeof fetch;
  search?: string;       // default location.search
  storage?: StorageLike; // default sessionStorage
  strip?: boolean;       // default true
}

                    
This code block in the floating window

WorkspaceLoginError.code values

code Meaning
NoCallback There is no authorization code in the URL
MissingRequest The stored login request is missing/corrupted (call startWorkspaceLogin first)
StateMismatch The state CSRF verification failed
AuthorizeError The callback is an OAuth error redirect (?error=...)
NoFetch No fetch implementation is available
ExchangeFailed The exchange endpoint returned non-2xx
InvalidResponse The exchange endpoint returned invalid JSON
CryptoUnavailable No Web Crypto (requires an HTTPS/localhost secure context)
StorageUnavailable No sessionStorage (cannot store the PKCE verifier)
InvalidAuthorizeUrl authorizeUrl is not an absolute URL

3. Installing Locally from the Archive

unzip workspace-extension-sdk-0.1.0.zip # both packages have a prebuilt dist (main=dist/index.js, types=dist/index.d.ts) npm i ./workspace-extension-sdk/packages/verify # backend npm i ./workspace-extension-sdk/packages/browser # frontend
                      
                      unzip workspace-extension-sdk-0.1.0.zip
# both packages have a prebuilt dist (main=dist/index.js, types=dist/index.d.ts)
npm i ./workspace-extension-sdk/packages/verify   # backend
npm i ./workspace-extension-sdk/packages/browser  # frontend

                    
This code block in the floating window

To build / run tests yourself:

cd workspace-extension-sdk npm install npm run build # tsc → dist for each package npm test # node --test (zero external test dependencies, requires Node ≥ 22.6 to run .ts directly) npm run type-check # tsc --noEmit
                      
                      cd workspace-extension-sdk
npm install
npm run build       # tsc → dist for each package
npm test            # node --test (zero external test dependencies, requires Node ≥ 22.6 to run .ts directly)
npm run type-check  # tsc --noEmit

                    
This code block in the floating window