@dotbots-boutique/auth-sdk
v1.0.24
Published
Authentication SDK for DotBots marketplace apps
Downloads
602
Maintainers
Readme
@dotbots-boutique/auth-sdk
The official authentication and authorisation SDK for apps running on the DotBots Boutique platform.
What is DotBots Boutique?
DotBots Boutique is a marketplace platform where vibe coders build and publish apps. The platform handles user management, authentication, authorisation, payments, and infrastructure, and offers a growing set of additional services — so you can focus on building your app.
Installation
npm install @dotbots-boutique/auth-sdkNote: This package is for browser use only. Do not install it in a Deno or Node backend. A separate
@dotbots-boutique/server-sdkfor backend use is coming soon.
Quick start
React
// main.tsx
import { DotBotsAuth } from '@dotbots-boutique/auth-sdk';
import { DotBotsAuthProvider } from '@dotbots-boutique/auth-sdk/react';
const auth = new DotBotsAuth({
appId: import.meta.env.VITE_DOTBOTS_APP_ID,
apiUrl: import.meta.env.VITE_DOTBOTS_API_URL
});
ReactDOM.createRoot(document.getElementById('root')!).render(
<DotBotsAuthProvider auth={auth}>
<App />
</DotBotsAuthProvider>
);// In any component
import { useDotBotsAuth } from '@dotbots-boutique/auth-sdk/react';
const MyComponent = () => {
const { user, can, hasRole, fetch, logout } = useDotBotsAuth();
return (
<div>
<p>Welcome, {user?.name}</p>
{can('customers.read') && <CustomerList />}
{hasRole('admin') && <AdminPanel />}
<button onClick={logout}>Log out</button>
</div>
);
};Custom loading and error screens:
<DotBotsAuthProvider
auth={auth}
loadingComponent={<MySpinner />}
errorComponent={(error) => <MyError error={error} />}
>
<App />
</DotBotsAuthProvider>Without React
import { DotBotsAuth } from '@dotbots-boutique/auth-sdk';
const auth = new DotBotsAuth({
appId: import.meta.env.VITE_DOTBOTS_APP_ID,
apiUrl: import.meta.env.VITE_DOTBOTS_API_URL
});
await auth.initialize();
const user = await auth.getUser();
console.log(user.name, user.roles);How authentication works
Your app can run in two modes — the SDK handles both automatically.
Inside an iframe (embedded in the DotBots Boutique marketplace):
- The SDK sends a
DOTBOTS_READYmessage to the marketplace - The marketplace responds with a short-lived auth code
- The SDK exchanges the code for an access token and refresh token
Standalone (opened directly on your app's domain):
- The SDK checks for a
?code=parameter in the URL - If absent, it redirects the user to the DotBots login page
- After login, the user is redirected back with a code that the SDK exchanges for tokens
All tokens are stored in memory only — never in localStorage, sessionStorage, or cookies.
Proxy architecture
The SDK does not communicate directly with api.dotbots.ai for app-related calls. Each app server runs a shared proxy that handles caching, local database access, and request forwarding:
App (frontend) → Local proxy (per server) → api.dotbots.aiDuring initialize(), the SDK fetches the proxy config from GET {apiUrl}/api/proxy/config. After that, all auth.fetch() calls are routed through the proxy automatically. If the proxy config cannot be fetched, the SDK falls back to direct communication with apiUrl.
Only GET /api/proxy/config always goes directly to apiUrl. All other calls — auth token exchange, refresh, revoke, user info, payments, and auth.fetch() — go to proxyUrl when available, falling back to apiUrl when proxy is unavailable.
X-Environment header
The SDK automatically detects the environment based on window.location.hostname:
- Hostnames containing
test-apps→test - All other hostnames →
prod
The X-Environment header is included on every request the SDK makes — auth endpoints, proxy config, and all auth.fetch() calls. No configuration is needed.
Making API calls
Always use auth.fetch() instead of the native fetch(). The SDK automatically:
- Adds the
Authorization,X-App-IdandX-Environmentheaders - Routes calls through the local proxy when available
- Refreshes the access token when it is about to expire
- Retries once on a
401response
// GET
const response = await auth.fetch('/api/customers');
const customers = await response.json();
// POST
const response = await auth.fetch('/api/customers', {
method: 'POST',
body: JSON.stringify({ name: 'Acme' })
});All SDK calls route via proxyUrl when available. Only GET /api/proxy/config always goes to apiUrl.
Payments
Use auth.charge() to charge a feature usage to the organisation or app budget. The app never handles token amounts, balances or budgets directly — the platform manages all of that.
const { transactionId } = await auth.charge('ai.generate', 'org');With a custom quantity:
const { transactionId } = await auth.charge('ai.generate', 'org', 5);Error handling
import { DotBotsAuthError } from '@dotbots-boutique/auth-sdk';
try {
const { transactionId } = await auth.charge('ai.generate', 'org', 1);
} catch (error) {
if (error instanceof DotBotsAuthError && error.code === 'PAYMENT_FAILED') {
if (error.message === 'INSUFFICIENT_BALANCE') {
// show top-up prompt
} else if (error.message === 'BUDGET_EXCEEDED') {
// show budget limit message
}
}
}Possible PAYMENT_FAILED messages: INSUFFICIENT_BALANCE, BUDGET_EXCEEDED, FEATURE_NOT_FOUND, PAYMENT_REJECTED.
AI Gateway
The SDK provides access to the platform AI gateway. The developer never needs to know which provider or model is used — the platform handles routing, billing and rate limiting.
Non-streaming
import type { AiResponse } from '@dotbots-boutique/auth-sdk';
const response: AiResponse = await auth.ai('generate-text', {
messages: [{ role: 'user', content: 'Summarise this document' }],
maxTokens: 1000,
});
console.log(response.content);
console.log(response.usage.totalTokens);
console.log(response.cost.eur);Streaming
let fullText = '';
await auth.aiStream('generate-text', {
messages: [{ role: 'user', content: 'Write a poem' }],
}, (delta) => {
fullText += delta;
updateUI(fullText);
}, (response) => {
console.log('Done — model:', response.model, 'tokens:', response.usage.totalTokens);
});Tool calling
const response = await auth.ai('agent', {
messages: [{ role: 'user', content: 'What is the weather in Brussels?' }],
tools: [{
name: 'get_weather',
description: 'Get current weather for a city',
parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
}],
});
if (response.toolCalls?.length) {
console.log(response.toolCalls[0].name, response.toolCalls[0].input);
}Error handling
try {
const response = await auth.ai('generate-text', { messages });
} catch (error) {
if (error instanceof DotBotsAuthError) {
switch (error.code) {
case 'AI_INSUFFICIENT_BALANCE':
// show top-up prompt
break;
case 'AI_FEATURE_NOT_FOUND':
// feature not configured
break;
case 'AI_CALL_FAILED':
// provider error
break;
}
}
}Checking permissions
auth.can('customers.read') // true/false
auth.canAll(['customers.read', 'customers.write']) // true/false — all required
auth.canAny(['customers.read', 'invoices.read']) // true/false — at least one
auth.hasRole('admin') // true/falseEvents
auth.on('tokenRefreshed', () => { });
auth.on('sessionExpired', () => {
// Show a message to the user
});
auth.on('loggedOut', () => { });
auth.on('userLoaded', () => { });
auth.on('charged', () => { });| Event | Description |
|-------|-------------|
| tokenRefreshed | Access token was refreshed |
| loggedOut | User logged out |
| sessionExpired | Refresh token expired, user must re-authenticate |
| userLoaded | User data was fetched |
| charged | Successful charge — also sent as DOTBOTS_CHARGE postMessage to parent |
Error handling
import { DotBotsAuthError } from '@dotbots-boutique/auth-sdk';
try {
await auth.initialize();
} catch (error) {
if (error instanceof DotBotsAuthError) {
switch (error.code) {
case 'IFRAME_TIMEOUT':
// Marketplace did not respond in time
break;
case 'UNAUTHORIZED':
// User has no access to this app
break;
case 'NETWORK_ERROR':
// API unreachable
break;
case 'CODE_EXPIRED':
// Auth code was already expired or invalid
break;
}
}
}Error codes
| Code | Description | Fatal |
|------|-------------|-------|
| IFRAME_TIMEOUT | Marketplace did not respond within the timeout | Yes |
| CODE_EXPIRED | Auth code was already expired or invalid | Yes |
| UNAUTHORIZED | User has no access to this app | Yes |
| REFRESH_FAILED | Token refresh failed | Yes |
| NETWORK_ERROR | API unreachable | Yes |
| NOT_INITIALIZED | initialize() has not been called yet | Yes |
| PROXY_UNAVAILABLE | Proxy config could not be fetched | No — falls back to direct API |
| PAYMENT_FAILED | Payment was rejected (402) — see message for reason | No |
| AI_FEATURE_NOT_FOUND | AI feature code not configured in platform | No |
| AI_PROVIDER_NOT_CONFIGURED | API key not set for AI provider | No |
| AI_INSUFFICIENT_BALANCE | Organisation balance is 0 | No |
| AI_CALL_FAILED | AI provider returned an error | No |
| AI_STREAM_ERROR | Streaming connection failed | No |
Configuration
interface DotBotsConfig {
appId: string; // Required — app ID assigned by the platform
apiUrl: string; // Required — always 'https://api.dotbots.ai'
marketplaceOrigin?: string; // Default: 'https://dotbots.boutique'
tokenRefreshBuffer?: number; // Default: 60000 (ms before expiry to refresh)
iframeTimeout?: number; // Default: 5000 (ms to wait for auth code)
onTokenRefreshFailed?: () => void; // Called when token refresh fails
}DotBotsUser
interface DotBotsUser {
id: string;
name: string;
email: string;
orgId: string;
orgName: string;
teams: { teamId: string; teamName: string }[];
roles: string[];
permissions: string[];
avatarUrl?: string;
}API Reference
DotBotsAuth
constructor(config: DotBotsConfig)
Creates a new SDK instance.
initialize(): Promise<void>
Starts the authentication flow. Must be called before any other method.
- Fetches proxy config from
apiUrl - Detects the auth scenario (iframe or standalone)
- Authenticates the user
getUser(): Promise<DotBotsUser>
Returns the current user. Cached until the access token expires.
can(permission: string): boolean
Check if the user has a specific permission. Throws if getUser() hasn't been called.
canAll(permissions: string[]): boolean
Check if the user has all specified permissions.
canAny(permissions: string[]): boolean
Check if the user has at least one of the specified permissions.
hasRole(role: string): boolean
Check if the user has a specific role.
fetch(url: string, options?: RequestInit): Promise<Response>
Authenticated fetch wrapper. Routes through the proxy when available, falls back to apiUrl when not. Retries once on 401 after refreshing the token.
charge(featureCode: string, paidBy: 'org' | 'app', quantity?: number): Promise<{ transactionId: string }>
Charges a feature usage. Calls POST {proxyUrl}/payments/charge. On success, sends a DOTBOTS_CHARGE postMessage to the parent window (iframe only) and emits the charged event. Throws PAYMENT_FAILED on 402 with a message indicating the reason (INSUFFICIENT_BALANCE, BUDGET_EXCEEDED, FEATURE_NOT_FOUND, PAYMENT_REJECTED).
ai(feature: string, request: AiRequest): Promise<AiResponse>
Non-streaming AI call via the platform AI gateway. Calls POST {proxyUrl}/ai/call. Returns the full response with content, model, usage and cost.
aiStream(feature: string, request: AiRequest, onDelta: (delta: string) => void, onDone?: (response: AiResponse) => void): Promise<void>
Streaming AI call. onDelta fires for each text chunk, onDone fires with usage/cost metadata when the stream completes.
logout(): Promise<void>
Logs out the user. Revokes tokens on apiUrl, clears state, and redirects (standalone) or notifies the parent (iframe).
on(event: DotBotsAuthEvent, handler: Function): void
Register an event listener.
off(event: DotBotsAuthEvent, handler: Function): void
Remove an event listener.
useDotBotsAuth() (React)
const {
user, // DotBotsUser | null
isLoading, // boolean
error, // DotBotsAuthError | null
can, // (permission: string) => boolean
canAll, // (permissions: string[]) => boolean
canAny, // (permissions: string[]) => boolean
hasRole, // (role: string) => boolean
fetch, // auth.fetch proxied
charge, // auth.charge proxied
ai, // auth.ai proxied
aiStream, // auth.aiStream proxied
logout, // auth.logout proxied
} = useDotBotsAuth();Backend integration
If your app has a backend, forward the three headers from the frontend request to your backend, and from your backend to the proxy.
Frontend → Proxy (direct)
Use this when your app has no backend or when the data is public within the organisation. The SDK handles everything automatically.
const response = await auth.fetch('/api/customers');Frontend → Backend → Proxy
Use this when your backend performs extra logic before sending data to the frontend, such as validation, transformation, or aggregation of multiple proxy calls.
// Deno backend example
app.get('/api/customers', async (req) => {
const proxyUrl = Deno.env.get('DOTBOTS_PROXY_URL');
const response = await fetch(`${proxyUrl}/api/customers`, {
headers: {
'Authorization': req.headers.get('authorization') ?? '',
'X-App-Id': req.headers.get('x-app-id') ?? '',
'X-Environment': req.headers.get('x-environment') ?? '',
}
});
return response;
});Forward the headers exactly as received from the frontend. Do not add your own logic to the token.
Never store tokens in a database or log file, and never forward them to services outside the DotBots platform. The access token is scoped to your specific app — it cannot be used to access any other app's data.
dotbots.boutique.json
Your repository must contain a dotbots.boutique.json file. Use the env field to declare any environment variables your app needs beyond the platform defaults. The platform will ask the installer to fill these in before deployment.
{
"dotbotsPromptVersion": "2.1.0",
"database": true,
"scopes": {
"profile": { "description": "Read the user's name", "access": "required" },
"email": { "description": "Read the user's email", "access": "optional" }
},
"roles": [
{ "name": "admin", "description": "Full access" }
],
"permissions": [
{ "name": "customers.read", "description": "View customers" }
],
"env": [
{
"name": "STRIPE_API_KEY",
"description": "Stripe API key for payment processing",
"required": true,
"secret": true
}
]
}Platform variables (DATABASE_URL, PORT, DOTBOTS_APP_ID, DOTBOTS_PROXY_URL, DOTBOTS_API_URL) are injected automatically and do not need to be declared here.
Security
- Tokens are stored in memory only — never persisted
postMessageorigin is always validated — only messages frommarketplaceOriginare accepted- Auth codes are single-use and removed from the URL immediately after exchange
- Tokens are never logged, not even in development mode
- No runtime dependencies — eliminates supply chain risk entirely
- Open source — view the source on GitHub
To report a security vulnerability, please email [email protected] instead of opening a public issue.
Exports
import { DotBotsAuth, DotBotsAuthError } from '@dotbots-boutique/auth-sdk';
import type { DotBotsUser, DotBotsConfig, DotBotsAuthEvent, DotBotsProxyConfig, AiRequest, AiResponse, AiMessage, AiTool, AiToolCall } from '@dotbots-boutique/auth-sdk';
import { DotBotsAuthProvider, useDotBotsAuth } from '@dotbots-boutique/auth-sdk/react';License
MIT — © DotBots Boutique
