# Transcodes — Full SDK Reference
> Passkey-first B2B authentication service. Integrates via **HTML CDN** (`webworker.js`) or **`@bigstrider/transcodes-sdk`** (`init({ projectId })`). Pre-built modals for login, step-up MFA, member console, and admin panel. Issues JWTs. No backend auth code required.
Base URL: https://transcodes.io
Dashboard: https://app.transcodes.io
CDN: https://cdn.transcodes.link/{PROJECT_ID}/webworker.js
---
## What is Transcodes?
Transcodes is **Authentication Infrastructure as a Service (AaaS)** for B2B internal tools. It replaces DIY WebAuthn with:
- **Passkey-first login** — biometric (Face ID, Touch ID, Windows Hello)
- **Step-up MFA** — verify identity before sensitive actions (payments, deletes, admin)
- **Member management** — RBAC, audit logs, backup
- **Zero backend** — no auth endpoints, no credential DB, no token logic
---
## Two ways to integrate
### Option A — HTML / CDN (required for PWA)
Add one `
```
```html
```
PWA note: download `sw.js` from the Console → Web App Cluster → Installation Guide and serve at `/sw.js`.
After load, all APIs are available via `window.transcodes` (or just `transcodes`).
### Option B — @bigstrider/transcodes-sdk
Install once, call `init` at your client entry point (never in SSR/server components).
```bash
npm install @bigstrider/transcodes-sdk
```
```typescript
import { init } from '@bigstrider/transcodes-sdk';
// Call once at client entry (e.g. main.tsx, layout.tsx with 'use client')
await init({ projectId: 'YOUR_PROJECT_ID' });
// Optional: await init({ projectId: '...', customUserId: 'uid_xxx', debug: true });
```
Then use **named exports** — not `window.transcodes`:
```typescript
import {
init, // await init({ projectId, customUserId?, debug? })
openAuthLoginModal, // await openAuthLoginModal({ webhookNotification? })
openAuthConsoleModal, // await openAuthConsoleModal()
openAuthAdminModal, // await openAuthAdminModal({ allowedRoles })
openAuthIdpModal, // await openAuthIdpModal({ resource, action })
isAuthenticated, // await isAuthenticated() → boolean (ASYNC!)
getCurrentMember, // await getCurrentMember() → Member | null
getAccessToken, // await getAccessToken() → string | null
hasToken, // hasToken() → boolean (SYNC)
signOut, // await signOut({ webhookNotification? })
getMember, // await getMember({ email?, memberId? }) → ApiResponse
on, off, // on('AUTH_STATE_CHANGED', cb) → unsubscribeFn
trackUserAction, // await trackUserAction({ tag, severity?, ... }, opts?)
isPwaInstalled, // isPwaInstalled() → boolean (SYNC)
} from '@bigstrider/transcodes-sdk';
```
**CDN vs @bigstrider/transcodes-sdk naming:**
| Action | CDN (window.transcodes) | @bigstrider/transcodes-sdk (named export) |
| ---------------------- | ------------------------------------- | ------------------------ |
| Init | (auto on script load) | `init({ projectId })` |
| Login modal | `transcodes.openAuthLoginModal({})` | `openAuthLoginModal({})` |
| Is authenticated? | `transcodes.token.isAuthenticated()` | `isAuthenticated()` |
| Get current member | `transcodes.token.getCurrentMember()` | `getCurrentMember()` |
| Get access token | `transcodes.token.getAccessToken()` | `getAccessToken()` |
| Sign out | `transcodes.token.signOut()` | `signOut()` |
| Lookup member | `transcodes.member.get({ email })` | `getMember({ email })` |
| Subscribe to events | `transcodes.on('EVENT', cb)` | `on('EVENT', cb)` |
---
## TranscodesInitOptions (@bigstrider/transcodes-sdk only)
```typescript
interface TranscodesInitOptions {
projectId: string; // Required — from Transcodes Console
customUserId?: string; // Optional — associate with your own user ID
debug?: boolean; // Optional — enable debug logging
}
```
---
## Dynamic SDK utility methods (CDN and @bigstrider/transcodes-sdk)
```typescript
// Check if SDK is already initialized (avoid double-init)
transcodes.isInitialized(): boolean
// Update config at runtime (e.g. set memberId after login)
transcodes.setConfig({ memberId?: string }): void
// Build info for debugging
transcodes.getBuildInfo(): { buildTimestamp: string }
```
```javascript
// Guard against double-init
if (!transcodes.isInitialized()) {
await transcodes.init({ projectId: 'YOUR_PROJECT_ID' });
}
// Link your own member ID after login
transcodes.setConfig({ memberId: 'your_internal_user_id' });
```
---
## Modal APIs
### openAuthLoginModal
Login + signup modal. Returns `ApiResponse`.
```typescript
// CDN
const result = await transcodes.openAuthLoginModal({
projectId?: string; // optional — usually inferred from script URL
webhookNotification?: boolean; // send Slack webhook on login. Default: false
});
// @bigstrider/transcodes-sdk
import { openAuthLoginModal } from '@bigstrider/transcodes-sdk';
const result = await openAuthLoginModal({ webhookNotification: false });
```
Success response:
```typescript
if (result.success) {
const { token, member } = result.payload[0];
// token: string (JWT)
// member: Member
console.log('Logged in:', member.email);
}
```
### openAuthConsoleModal
Member account management (passkeys, profile, settings).
```typescript
// CDN
await transcodes.openAuthConsoleModal(); // params optional
// @bigstrider/transcodes-sdk
import { openAuthConsoleModal } from '@bigstrider/transcodes-sdk';
await openAuthConsoleModal();
```
### openAuthAdminModal
Admin panel — only members with `allowedRoles` can pass through.
```typescript
// CDN
await transcodes.openAuthAdminModal({
allowedRoles: ['admin', 'moderator'], // Required
projectId?: string;
});
// @bigstrider/transcodes-sdk
import { openAuthAdminModal } from '@bigstrider/transcodes-sdk';
await openAuthAdminModal({ allowedRoles: ['admin'] });
```
### openAuthIdpModal
**Step-up MFA modal** — RBAC-based. Verify identity before sensitive actions.
```typescript
// Params (IdpOpenParams)
interface IdpOpenParams {
resource: string; // e.g. 'users', 'revenue', 'settings'
action: 'create' | 'read' | 'update' | 'delete'; // CRUD action
forceStepUp?: boolean; // force MFA regardless of permission. Default: false
webhookNotification?: boolean; // Slack webhook on result. Default: false
projectId?: string;
}
// CDN
const result = await transcodes.openAuthIdpModal({
resource: 'users',
action: 'delete',
});
// @bigstrider/transcodes-sdk
import { openAuthIdpModal } from '@bigstrider/transcodes-sdk';
const result = await openAuthIdpModal({ resource: 'revenue', action: 'update' });
```
Success response:
```typescript
// IMPORTANT: check BOTH result.success AND result.payload[0]?.success
if (result.success && result.payload[0]?.success) {
const { sid, timestamp, action } = result.payload[0];
// sid: string — Step-up Session ID; send to your server to verify
// timestamp: number — Unix ms
await yourApi.deleteUser({ stepUpSid: sid });
}
```
Full `IdpAuthResponse`:
```typescript
interface IdpAuthResponse {
success: boolean;
sid?: string; // step-up session ID (on success)
error?: string; // error message (on failure)
timestamp: number; // Unix timestamp (ms)
action?: string; // the requested action
}
```
---
## Token API
CDN: `transcodes.token.*` — `@bigstrider/transcodes-sdk`: named exports (`isAuthenticated`, `getCurrentMember`, `getAccessToken`, `hasToken`, `signOut`)
```typescript
// Get current authenticated member (from JWT, no API call)
// Returns null if not authenticated
await transcodes.token.getCurrentMember(): Promise
// Get valid access token (Memory → IndexedDB → new issuance)
// Returns null if unavailable/expired
await transcodes.token.getAccessToken(): Promise
// SYNC: check if token is in memory (fast, no I/O)
transcodes.token.hasToken(): boolean
// ASYNC: full validity check (Memory → IndexedDB) — ALWAYS use await!
await transcodes.token.isAuthenticated(): Promise
// Sign out — clears session
await transcodes.token.signOut({ webhookNotification?: boolean }): Promise
```
**Critical:** `isAuthenticated()` is async. A common mistake:
```javascript
// WRONG — Promise is always truthy
if (transcodes.token.isAuthenticated()) { ... }
// CORRECT
if (await transcodes.token.isAuthenticated()) { ... }
```
---
## Member API
CDN: `transcodes.member.*` — `@bigstrider/transcodes-sdk`: `getMember`
```typescript
transcodes.member.get({
projectId?: string;
memberId?: string;
email?: string;
fields?: string; // comma-separated field names to return
}): Promise>
```
```javascript
// CDN
const res = await transcodes.member.get({ email: 'user@example.com' });
// @bigstrider/transcodes-sdk
import { getMember } from '@bigstrider/transcodes-sdk';
const res = await getMember({ email: 'user@example.com' });
if (res.success) console.log(res.payload[0].name);
```
---
## Events API
`on()` returns an unsubscribe function.
```typescript
// CDN
const unsub = transcodes.on('AUTH_STATE_CHANGED', (payload) => {
if (payload.isAuthenticated) {
console.log('Signed in as', payload.member?.email);
console.log('Token expires at', new Date(payload.expiresAt!));
}
});
unsub(); // unsubscribe
// @bigstrider/transcodes-sdk
import { on } from '@bigstrider/transcodes-sdk';
const unsub = on('AUTH_STATE_CHANGED', (payload) => { ... });
```
### Event: AUTH_STATE_CHANGED
```typescript
interface AuthStateChangedPayload {
isAuthenticated: boolean;
accessToken: string | null;
expiresAt: number | null; // Unix ms
member: Member | null;
}
```
### Event: TOKEN_REFRESHED
```typescript
interface TokenRefreshedPayload {
accessToken: string;
expiresAt: number; // Unix ms
}
```
### Event: TOKEN_EXPIRED
```typescript
interface TokenExpiredPayload {
expiredAt: number; // Unix ms
}
```
### Event: ERROR
```typescript
interface ErrorPayload {
code: string;
message: string;
context?: string;
}
```
---
## Audit API — trackUserAction
Records user actions in Transcodes audit logs (visible in Console → Audit Logs).
```typescript
// CDN
await transcodes.trackUserAction(
{
tag: string; // Required. e.g. 'user:login', 'document:create'
severity?: 'low' | 'medium' | 'high'; // Default: 'low'
status?: boolean; // true = success, false = failure. Default: true
error?: string; // Error message when status is false
metadata?: Record; // Extra data, e.g. { amount: 99.99 }
page?: string; // Page URL. Default: window.location.href
},
{
requireAuth?: boolean; // Open login modal if not authed. Default: false
webhookNotification?: boolean; // Slack webhook. Default: false
}
);
// @bigstrider/transcodes-sdk — same signature
import { trackUserAction } from '@bigstrider/transcodes-sdk';
await trackUserAction({ tag: 'payment:process', severity: 'high', status: true,
metadata: { amount: 500 } });
```
---
## Type Definitions
```typescript
interface Member {
id?: string;
projectId?: string;
name?: string;
email?: string;
role?: string;
metadata?: Record;
createdAt?: Date | string;
updatedAt?: Date | string;
}
interface AuthResult {
token: string; // JWT access token
member: Member;
}
interface ApiResponse {
success: boolean;
payload: T;
error?: string;
message?: string;
status?: number;
}
interface IdpOpenParams {
resource: string;
action: 'create' | 'read' | 'update' | 'delete';
forceStepUp?: boolean;
webhookNotification?: boolean;
projectId?: string;
}
interface IdpAuthResponse {
success: boolean;
sid?: string;
error?: string;
timestamp: number;
action?: string;
}
type TranscodesEventName =
| 'AUTH_STATE_CHANGED'
| 'TOKEN_REFRESHED'
| 'TOKEN_EXPIRED'
| 'ERROR';
```
---
## Framework setup (quick reference)
### React + Vite
```html
```
```bash
# .env
VITE_TRANSCODES_PROJECT_ID=proj_abc123xyz
```
### Next.js App Router (CDN path)
```tsx
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
{children}
);
}
```
```bash
# .env.local
NEXT_PUBLIC_TRANSCODES_PROJECT_ID=proj_abc123xyz
```
### Next.js App Router (@bigstrider/transcodes-sdk)
```tsx
// app/providers.tsx — 'use client'
'use client';
import { init } from '@bigstrider/transcodes-sdk';
import { useEffect } from 'react';
export function TranscodesProvider({ children }) {
useEffect(() => {
init({ projectId: process.env.NEXT_PUBLIC_TRANSCODES_PROJECT_ID! });
}, []);
return <>{children}>;
}
```
### Vue 3 + Vite (@bigstrider/transcodes-sdk)
```typescript
// main.ts
import { init } from '@bigstrider/transcodes-sdk';
await init({ projectId: import.meta.env.VITE_TRANSCODES_PROJECT_ID });
```
---
## Quick start example (CDN)
```html
Not signed in
```
---
## CDN TypeScript setup
When using the CDN approach, download `transcodes.d.ts` from the Transcodes Console and place it in `types/transcodes.d.ts`. Then update `tsconfig.json`:
```json
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"]
},
"include": ["src", "types"]
}
```
When using `@bigstrider/transcodes-sdk`, TypeScript types ship with the package — no extra setup needed.
---
## React AuthContext pattern (recommended)
The standard pattern for React apps — works with both CDN and `@bigstrider/transcodes-sdk`.
```tsx
// src/context/AuthContext.tsx
import { createContext, useEffect, useState, type ReactNode } from 'react';
import {
isAuthenticated as sdkIsAuthenticated,
openAuthLoginModal as sdkLogin,
openAuthConsoleModal as sdkConsole,
openAuthIdpModal as sdkIdp,
signOut as sdkSignOut,
on,
} from '@bigstrider/transcodes-sdk';
interface AuthContextValue {
isAuthenticated: boolean;
isLoading: boolean;
memberId: string | null;
openAuthLoginModal: () => Promise;
openAuthConsoleModal: () => Promise;
openAuthIdpModal: (params: {
resource: string;
action: 'create' | 'read' | 'update' | 'delete';
}) => Promise;
signOut: () => Promise;
}
export const AuthContext = createContext({ /* defaults */ } as AuthContextValue);
export function AuthProvider({ children }: { children: ReactNode }) {
const [isAuth, setIsAuth] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [memberId, setMemberId] = useState(null);
useEffect(() => {
sdkIsAuthenticated().then((auth) => {
setIsAuth(auth);
setIsLoading(false);
});
const unsubscribe = on('AUTH_STATE_CHANGED', ({ isAuthenticated, member }) => {
setIsAuth(isAuthenticated);
setMemberId(member?.id ?? null);
});
return () => unsubscribe();
}, []);
return (
{
const result = await sdkLogin({});
if (result.success) setMemberId(result.payload[0]?.member?.id ?? null);
},
openAuthConsoleModal: async () => { await sdkConsole(); },
openAuthIdpModal: async (params) => { await sdkIdp(params); },
signOut: async () => {
await sdkSignOut();
setIsAuth(false);
setMemberId(null);
},
}}>
{children}
);
}
```
Wrap your app in `` and consume with `use(AuthContext)` in components.
---
## Quick start example (@bigstrider/transcodes-sdk — React)
```tsx
// src/App.tsx
'use client'; // Next.js
import { openAuthLoginModal, on, getCurrentMember } from '@bigstrider/transcodes-sdk';
import { useEffect, useState } from 'react';
import type { Member } from '@bigstrider/transcodes-sdk';
export default function App() {
const [member, setMember] = useState(null);
useEffect(() => {
getCurrentMember().then(setMember);
const unsub = on('AUTH_STATE_CHANGED', (p) => setMember(p.member));
return unsub;
}, []);
const handleLogin = async () => {
const result = await openAuthLoginModal({});
if (result.success) setMember(result.payload[0].member);
};
return member
? Hello, {member.email}
: ;
}
```
---
## Step-up MFA example
Use before any sensitive action (delete account, payment, admin operation).
```typescript
async function deleteUser(userId: string) {
// 1. Request step-up verification
const mfa = await transcodes.openAuthIdpModal({
resource: 'users',
action: 'delete',
});
if (!mfa.success) return; // User cancelled or failed
const { sid } = mfa.payload[0]; // Step-up Session ID
// 2. Call your API with the SID — verify it server-side
await fetch(`/api/users/${userId}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${await transcodes.token.getAccessToken()}`,
'X-Step-Up-Sid': sid ?? '',
},
});
// 3. Log the action
await transcodes.trackUserAction({
tag: 'user:delete',
severity: 'high',
status: true,
metadata: { userId },
});
}
```
---
## Server-side JWT verification
Transcodes uses **JWKS** (JSON Web Key Set) for token verification. Fetch the public keys from the JWKS endpoint — no static file needed.
```
JWKS endpoint: https://cdn.transcodes.link/{YOUR_PROJECT_ID}/jwks.json
JWT issuer:
Algorithm: RS256
```
```javascript
// Node.js — recommended approach with jwks-rsa
// npm install jsonwebtoken jwks-rsa
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: 'https://cdn.transcodes.link/YOUR_PROJECT_ID/jwks.json',
cache: true,
cacheMaxAge: 86400000, // 24 hours
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
callback(err, key?.getPublicKey());
});
}
async function verifyToken(token) {
return new Promise((resolve, reject) => {
jwt.verify(token, getKey, {
algorithms: ['RS256'],
issuer: '',
}, (err, decoded) => {
if (err) reject(err);
else resolve(decoded);
});
});
}
// Express middleware
app.use(async (req, res, next) => {
const auth = req.headers.authorization;
if (!auth) return res.status(401).json({ error: 'No token' });
try {
const token = auth.replace('Bearer ', '');
req.member = await verifyToken(token);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
});
```
```python
# Python — pip install pyjwt cryptography requests
import jwt, requests
from functools import lru_cache
@lru_cache(maxsize=1)
def get_jwks():
return requests.get('https://cdn.transcodes.link/YOUR_PROJECT_ID/jwks.json').json()
def verify_token(token: str) -> dict:
header = jwt.get_unverified_header(token)
jwks = get_jwks()
key = next(k for k in jwks['keys'] if k['kid'] == header['kid'])
public_key = jwt.algorithms.RSAAlgorithm.from_jwk(key)
return jwt.decode(token, public_key, algorithms=['RS256'],
options={'verify_iss': True},
issuer='')
```
---
## CSP (Content Security Policy)
At minimum, allow the CDN for the SDK script. The exact API domains for `connect-src` and `frame-src` are listed in your Transcodes Console → Authentication Cluster → Installation Guide.
```html
```
---
## Common mistakes
```javascript
// WRONG: isAuthenticated() is async — Promise is always truthy
if (transcodes.token.isAuthenticated()) { ... }
// CORRECT
if (await transcodes.token.isAuthenticated()) { ... }
```
```javascript
// WRONG: method doesn't exist
await transcodes.token.logout();
// CORRECT
await transcodes.token.signOut();
```
```javascript
// WRONG: openAuthMfaModal doesn't exist
await transcodes.openAuthMfaModal();
// CORRECT: use openAuthIdpModal for step-up MFA
await transcodes.openAuthIdpModal({ resource: 'settings', action: 'delete' });
```
```javascript
// WRONG: response uses .user (old), not .member
const { token, user } = result.payload[0];
// CORRECT
const { token, member } = result.payload[0];
```
```javascript
// WRONG: openAuthIdpModal takes resource+action, not action+allowedRoles
await transcodes.openAuthIdpModal({ action: 'delete', allowedRoles: ['admin'] });
// CORRECT
await transcodes.openAuthIdpModal({ resource: 'users', action: 'delete' });
```
```javascript
// WRONG: only checking outer result.success is not enough for IDP modal
if (result.success) { proceedWithSensitiveAction(); }
// CORRECT: check BOTH success flags
if (result.success && result.payload[0]?.success) { proceedWithSensitiveAction(); }
```
```javascript
// WRONG: JWT issuer
issuer: ''
// CORRECT
issuer: ''
```
---
## Browser support
| Browser | Minimum |
| ------- | ------- |
| Chrome | 67+ |
| Safari | 14+ |
| Firefox | 60+ |
| Edge | 79+ |
WebAuthn requires **HTTPS** in production. `localhost` / `127.0.0.1` work for development.
---
## What Transcodes handles vs what you handle
| Transcodes handles ✅ | You handle ❌ |
| ----------------------------------- | -------------------------------------- |
| Passkey credential storage | XSS / CSRF protection |
| JWT issuance and refresh | Application-level security |
| WebAuthn ceremony (challenge/verify)| Server-side token verification |
| Pre-built UI modals | HTTPS setup |
| Token memory management | Rate limiting on your API |
---
## Links
- [Full docs](https://transcodes.io/docs)
- [API Reference](https://transcodes.io/docs/api-reference)
- [Quick Integration](https://transcodes.io/docs/quick-integration)
- [Passkey Login demo](https://transcodes.io/docs/demonstration/passkey-login)
- [Step-up MFA demo](https://transcodes.io/docs/demonstration/stepup-mfa)
- [Authentication Cluster](https://transcodes.io/docs/authentication-cluster)
- [Console](https://app.transcodes.io)
- [FAQ](https://transcodes.io/qnas)
- [Book a demo](https://transcodes.io/booking)