Build interactive web applications that integrate with Orbit's membership system. The Orbit Connector SDK lets your apps identify the current user, check their membership status, and store per-user data — so you can build tools with tiered functionality where members get more than guests.
For a non-technical overview of what Tools can do, see Tools.
Overview
Tools are web applications embedded directly in your Orbit portal. When a user views a tool, the application loads on the page in a seamless embedded view. Tools can also be opened in a new tab.
The key differentiator from a regular link is that tools can talk to Orbit — they know who the user is, whether they're a member, and can save/load data tied to each user.
What You Can Build
- Calculators and tools — financial planners, ROI calculators, assessment tools
- Interactive dashboards — data visualizations, progress trackers
- Quizzes and assessments — with results saved per user
- Configurators — product selectors, plan comparison tools
- Mini-apps — anything your members need, with member-only features gated behind membership status
Building Tools
Any web application served over HTTPS works as an app resource. Popular approaches:
- AI app builders — Lovable, Bolt, v0, Replit, Claude Artifacts — generate a hosted web app from a description
- Static sites — HTML/CSS/JS on Netlify, Vercel, GitHub Pages, or any hosting
- Full-stack apps — Next.js, React, Vue, Svelte, or any framework
Adding a Tool to Orbit
- Go to Tools in the admin portal
- Click Add Tool
- Paste the URL of your web application (e.g.,
https://my-calculator.lovable.app) - The tool is created with the app embedded on its page
The tool's slug becomes the app's identifier (appId) throughout the SDK. For example, a tool with slug fire-calculator is referenced as appId: "fire-calculator".
Orbit Connector SDK
The SDK is a small JavaScript file that your app loads to communicate with Orbit. It handles authentication automatically across all runtime modes.
Installation
Add the SDK via a script tag in your app's HTML:
The script creates a global OrbitConnector object on window.
Initialization
That's the minimum required. The SDK auto-detects its environment and connects to Orbit.
Configuration Options
const orbit = OrbitConnector.create({
// The URL of your Orbit portal. Required in development mode,
// auto-detected when embedded or launched from Orbit.
origin: 'https://your-portal.orbitams.com',
// Your app's identifier (the resource slug in Orbit).
// Auto-detected when embedded, required for state operations in other modes.
appId: 'my-app-slug',
// Force a specific mode instead of auto-detecting.
// Usually you should leave this as 'auto'.
mode: 'auto', // 'auto' | 'iframe' | 'external' | 'development'
// Enable console logging for debugging.
debug: false,
});
Runtime Modes
The SDK operates in three modes, auto-detected based on the environment. Your app code stays the same — the SDK handles the differences internally.
Iframe Mode (Embedded in Orbit)
When your app is embedded on an Orbit resource page, the SDK communicates with the parent page via postMessage. The user is already authenticated in Orbit, so no login is needed.
How it works: The parent page (Orbit) and your app perform a handshake: your app sends orbit:hello, Orbit responds with orbit:hello:ack containing the app's ID and capabilities. Subsequent requests are sent as orbit:request messages and proxied by the parent page to Orbit's internal bridge API using the user's existing session cookie.
No configuration needed — origin and appId are provided by the parent page during the handshake.
External Mode (New Tab from Orbit)
When a user opens your app in a new tab from Orbit, a short-lived token is passed in the URL (?orbit_token=...&orbit_origin=...). The SDK uses this token for direct API calls.
No configuration needed — the token and origin are read from URL parameters.
Development Mode (Standalone / Builder Preview)
When running outside Orbit — in a Lovable preview, Bolt workspace, v0 editor, or localhost — the SDK opens a popup window for the user to log in to Orbit, then receives a session token.
Requires origin configuration — since the SDK can't detect which Orbit portal to connect to. There are three ways to provide it:
Option 1: Query parameters (recommended for testing)
Add orbit_origin and optionally orbit_app_id to the URL:
This is the fastest way to test — no code changes needed. The SDK reads these parameters automatically.
Option 2: SDK configuration (recommended for development)
const orbit = OrbitConnector.create({
origin: 'https://your-portal.orbitams.com',
appId: 'my-app-slug', // enables state storage
debug: true,
});
Option 3: Environment variables
Set NEXT_PUBLIC_ORBIT_ORIGIN and NEXT_PUBLIC_ORBIT_APP_ID (or equivalent for your framework) and read them in your initialization code.
Console guidance: When debug: true is set, the SDK logs detailed setup instructions to the browser console. If origin is missing, the SDK prints the exact steps to configure it. If appId is missing, it explains how to enable state storage. This makes the SDK easy to integrate from AI app builders — the console output guides the agent through setup automatically.
The popup flow:
1. SDK opens a popup to https://your-portal.orbitams.com/tools/connect/auth/
2. If the user isn't logged in, they see the Orbit login page
3. After authentication, Orbit generates a signed session token (valid 24 hours)
4. The token is posted back to your app via postMessage and the popup closes
5. The token is cached in sessionStorage for the browser session
SDK API Reference
orbit.getMember()
Returns information about the current user, their membership, and the tenant.
Response:
{
user: {
id: "uuid-string",
email: "user@example.com",
firstName: "Jane",
lastName: "Doe"
},
tenant: {
name: "My Organization",
origin: "https://my-org.orbitams.com"
},
member: {
isAuthenticated: true,
roles: ["MEMBER"],
membership: {
active: true,
status: "active",
planName: "Professional",
tierName: "Annual", // pricing tier selected at signup, or null
startsAt: "2024-01-15T00:00:00",
endsAt: "2025-01-15T00:00:00"
}
},
theme: {
light: { // always present if a theme is configured
primaryColor: "#3B82F6",
secondaryColor: "#10B981",
textColor: "#1F2937",
backgroundColor: "#FFFFFF",
menuColor: "#FFFFFFA6"
},
dark: { // null if no dark theme is configured
primaryColor: "#60A5FA",
secondaryColor: "#34D399",
textColor: "#F9FAFB",
backgroundColor: "#111827",
menuColor: "#1F2937CC"
}
},
app: {
appId: "fire-calculator"
}
}
Common patterns:
// Check if user is an active member
const isMember = ctx.member?.membership?.active === true;
// Get the user's plan and tier
const plan = ctx.member?.membership?.planName;
const tier = ctx.member?.membership?.tierName; // e.g., "Annual", "Monthly"
// Check if authenticated at all
const isLoggedIn = ctx.member?.isAuthenticated === true;
Theme colors: The response includes a theme object with light and dark color sets. Use these to match the portal's branding. light is always present when a theme is configured; dark is null if the portal hasn't set up a dark theme.
// Apply light theme by default, use dark if user prefers it and it's available
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const colors = (prefersDark && ctx.theme?.dark) || ctx.theme?.light;
if (colors) {
document.documentElement.style.setProperty('--primary', colors.primaryColor);
document.documentElement.style.setProperty('--secondary', colors.secondaryColor);
document.documentElement.style.setProperty('--text', colors.textColor);
document.documentElement.style.setProperty('--bg', colors.backgroundColor);
}
orbit.state.get(key)
Retrieve stored data for the current user and app.
const result = await orbit.state.get('my-key');
// result: { key: "my-key", value: { ... }, updatedAt: "2024-..." } or null
keydefaults to'default'if omitted- Returns
nullif no data exists for that key - Requires
appIdto be resolved (automatic when embedded)
orbit.state.set(key, value)
Save data for the current user and app.
await orbit.state.set('preferences', { theme: 'dark', fontSize: 16 });
await orbit.state.set('progress', { level: 3, score: 850 });
valuecan be any JSON-serializable data (objects, arrays, strings, numbers, booleans)- Maximum value size: 64 KB per key (after JSON serialization)
- Maximum keys: 100 per user per app
- Creates the key if it doesn't exist, updates if it does
Encrypted Storage
By default, stored values are visible to portal staff on the registration detail page. For sensitive data, use the REST API's encrypted mode — the value is encrypted at rest and staff see [encrypted] instead:
// Direct API call for encrypted storage
await fetch(`${orbitOrigin}/api/apps/state`, {
method: 'PUT',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'api_credentials', value: { token: 'sk_...' }, encrypted: true }),
});
Encrypted values are decrypted automatically when your app reads them back via orbit.state.get().
orbit.state.delete(key)
Remove stored data.
Notifications
Apps can send notifications to staff via the REST API. Notifications appear on the registration detail page for the user who triggered them.
await fetch(`${orbitOrigin}/api/apps/notifications`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
title: 'Quiz completed',
message: 'Score: 95%',
level: 'success', // info | warning | error | success
data: JSON.stringify({ quizId: 'q-123', score: 95 }), // optional, max 64KB
}),
});
Rate limits: 5 per minute, 30 per day (per user per app).
The data field: When present, staff see a "View" button that opens your app in a new tab with orbit_notification_data as a URL query parameter. Your app reads it and renders the appropriate view:
const params = new URLSearchParams(window.location.search);
const notificationData = params.get('orbit_notification_data');
if (notificationData) {
const data = JSON.parse(notificationData);
showQuizResult(data.quizId, data.score);
}
orbit.ui.resize(height, options?)
Set the iframe height in pixels. Call this after your app renders to ensure the iframe fits your content. Only works in iframe mode — in other modes this is a no-op.
// Set height — iframe scrollbar is hidden by default for a seamless look
await orbit.ui.resize(800);
// Keep the iframe scrollbar if the content may exceed the height
await orbit.ui.resize(600, { overflow: 'scroll' });
Options:
- overflow: 'hidden' (default) removes the iframe scrollbar for a seamless embedded experience. 'scroll' keeps the scrollbar.
Tip: Call resize after your content renders or changes (e.g., after loading data, expanding sections). A common React pattern:
useEffect(() => {
if (orbit?.getConnectionStatus().mode === 'iframe') {
requestAnimationFrame(() => {
orbit.ui.resize(document.body.scrollHeight);
});
}
}); // runs after every render
orbit.ui.openExternal(url)
Open a URL in a new browser tab.
orbit.getConnectionStatus()
Check the current connection state. Useful for displaying connection indicators in your UI.
Response:
{
mode: "iframe", // "iframe" | "external" | "development" | "unknown"
transport: "iframe", // "iframe" | "api" | "none"
originResolved: true,
appIdResolved: true,
capabilities: {
member: true, // Can call getMember()
access: true, // Can check membership
storage: true, // Can use state.get/set/delete
iframeBridge: true // Using iframe postMessage transport
}
}
Building an App: Step by Step
Here's a complete walkthrough for building an app that integrates with Orbit.
1. Create Your Web App
Use any tool or framework. Here's a minimal example:
<!DOCTYPE html>
<html>
<head>
<title>My Orbit App</title>
<script src="https://orbitams.com/static/js/orbit-connector.js" defer></script>
</head>
<body>
<div id="app">Loading...</div>
<script>
async function main() {
const app = document.getElementById('app');
// Initialize the SDK
const orbit = OrbitConnector.create({
origin: 'https://your-portal.orbitams.com', // only needed during development
debug: true,
});
try {
await orbit.init();
const ctx = await orbit.getMember();
if (ctx.member?.membership?.active) {
app.innerHTML = `<h1>Welcome, ${ctx.user.firstName}!</h1>
<p>Your plan: ${ctx.member.membership.planName}</p>`;
} else {
app.innerHTML = `<h1>Welcome!</h1>
<p>Join as a member to unlock premium features.</p>`;
}
} catch (e) {
// Not connected to Orbit — show standalone mode
app.innerHTML = `<h1>Welcome!</h1>
<p>Running in standalone mode.</p>`;
}
}
// Wait for SDK script to load
if (window.OrbitConnector) {
main();
} else {
document.querySelector('script[src*="orbit-connector"]')
.addEventListener('load', main);
}
</script>
</body>
</html>
2. Using AI App Builders
When building with Lovable, Bolt, v0, or Replit, you can give the AI agent Orbit's integration guide directly. This is a machine-readable guide designed for AI agents, hosted at:
Option A — Point the agent to the guide (recommended):
Read the integration guide at
https://orbitams.com/llms.txtand use it to integrate this app with Orbit AMS. My portal domain ismyorg.orbitams.com.
Replace myorg.orbitams.com with your actual portal domain — this is the domain you see in your browser when logged in to Orbit (e.g., myorg.orbitams.com or a custom domain like members.example.com). The guide will instruct the agent to use this domain for the SDK origin config.
Option B — Include instructions directly in your prompt:
This app integrates with Orbit AMS. My portal domain is
myorg.orbitams.com. Load the Orbit Connector SDK via<script src="https://orbitams.com/static/js/orbit-connector.js" defer></script>in the HTML head. Initialize withconst orbit = OrbitConnector.create({ origin: 'https://myorg.orbitams.com', debug: true }); await orbit.init();. Useorbit.getMember()to check if the user is a member. Useorbit.state.get(key)andorbit.state.set(key, value)to persist data. When embedded in Orbit: use full width layout, hide the app title (Orbit shows it), and use a white or transparent background. Check the browser console for setup guidance.
Replace myorg.orbitams.com with your actual portal domain. Note that the SDK script tag always loads from orbitams.com — only the origin config needs your portal domain.
The debug: true flag is especially useful — the SDK prints actionable setup instructions to the browser console, which helps AI agents resolve configuration issues automatically.
3. Implement Permission Gating
A common pattern — show different features based on membership:
const ctx = await orbit.getMember();
const isMember = ctx.member?.membership?.active === true;
// Show/hide UI elements
document.querySelectorAll('[data-member-only]').forEach(el => {
el.style.display = isMember ? '' : 'none';
});
document.querySelectorAll('[data-guest-only]').forEach(el => {
el.style.display = isMember ? 'none' : '';
});
Or with React:
function App() {
const [member, setMember] = useState(null);
useEffect(() => {
async function connect() {
const orbit = OrbitConnector.create();
await orbit.init();
const ctx = await orbit.getMember();
setMember(ctx);
}
connect().catch(() => setMember(false));
}, []);
const isMember = member?.member?.membership?.active;
return (
<div>
<BasicFeature />
{isMember ? <PremiumFeature /> : <UpgradePrompt />}
</div>
);
}
4. Save and Load User Data
Store settings, progress, or results per user:
// Save
await orbit.state.set('settings', {
theme: 'dark',
notifications: true,
});
// Load
const result = await orbit.state.get('settings');
if (result) {
applySettings(result.value);
}
// Save multiple keys (up to 10 per user per app)
await orbit.state.set('progress', { level: 5, score: 1200 });
await orbit.state.set('preferences', { language: 'en' });
// Delete
await orbit.state.delete('progress');
Storage limits: - Up to 100 keys per user per app - Up to 64 KB per value (after JSON serialization) - Data is stored server-side and persists across sessions and devices
5. Test During Development
Standalone (no Orbit connection): Just open your app in a browser. SDK initialization will fail gracefully — wrap it in a try/catch and show a standalone experience.
Connected to your Orbit portal:
Set origin to your portal URL (e.g., https://myorg.orbitams.com). The SDK opens a login popup when running in development mode. You can also add ?orbit_origin=https://myorg.orbitams.com to your app's URL to test without code changes.
6. Deploy and Add to Orbit
- Deploy your app to any hosting service (Vercel, Netlify, your own server)
- In the Orbit admin portal, go to Tools and add a new tool with your deployed URL
- Users can now access your app from the portal
Important: If your app uses the SDK in development mode with a hardcoded origin, you can safely remove it before deploying — when embedded in Orbit, origin and appId are detected automatically.
App Connector REST API
For advanced use cases, you can call the App Connector API directly instead of using the SDK.
Authentication
App connector requests use Bearer token authentication:
Tokens are short-lived and signed. They're generated automatically by the SDK — you shouldn't need to manage tokens manually in most cases.
Endpoints
Base URL: https://your-portal.orbitams.com/api/apps/
GET /api/apps/context
Get the current user's identity, membership status, and tenant info.
Query parameters:
- app_id (optional) — the app's resource slug
Response:
{
"user": {
"id": "uuid",
"email": "user@example.com",
"first_name": "Jane",
"last_name": "Doe"
},
"tenant": {
"name": "My Organization",
"origin": "https://my-org.orbitams.com"
},
"member": {
"is_authenticated": true,
"roles": ["MEMBER"],
"membership": {
"active": true,
"status": "active",
"plan_name": "Professional",
"tier_name": "Annual",
"starts_at": "2024-01-15",
"ends_at": "2025-01-15"
}
},
"theme": {
"light": {
"primary_color": "#3B82F6",
"secondary_color": "#10B981",
"text_color": "#1F2937",
"background_color": "#FFFFFF",
"menu_color": "#FFFFFFA6"
},
"dark": null
},
"app": { "app_id": "fire-calculator" }
}
GET /api/apps/state?app_id=<slug>&key=<key>
Get stored state. Returns 404 if no state exists for the key.
PUT /api/apps/state?app_id=<slug>
Save state. Body: { "key": "my-key", "value": { ... }, "encrypted": false }
Set encrypted: true to encrypt the value at rest (staff see [encrypted] in the admin portal).
DELETE /api/apps/state?app_id=<slug>&key=<key>
Delete stored state.
POST /api/apps/notifications?app_id=<slug>
Send a notification to staff. Body: { "title": "...", "message": "...", "level": "info", "data": "..." }
level:info(default),warning,error,successdata(optional, max 64KB): opaque payload — when present, staff see a "View" button- Rate limits: 5/minute, 30/day per user per app
What You Can Build: Examples
Here are some examples of what you can build with the SDK:
- Financial calculator — free projections for everyone, member-only detailed analysis, saved scenarios per user
- Training quiz — questions available to all, scores saved per member, staff notified on completion via the notifications API
- Assessment tool — gated behind membership, results stored encrypted, staff see a "View" link to review submissions
- Interactive dashboard — themed to match the portal's branding, data persisted across sessions