Optare v1.0 is now available. Get started →
Guides
Marketplace

App Marketplace - Plug & Play Apps with Optare ID 🔌

🎯 Overview

The App Marketplace allows you to offer your apps (Customer Service, Bot, CRM, etc.) as plug-and-play modules that tenants can instantly activate with Optare ID authentication.


🏗️ Architecture

┌─────────────────────────────────────────────────────────────┐
│                    OPTARE ID (Control Plane)                 │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              App Marketplace                          │   │
│  │  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐    │   │
│  │  │Customer │ │   Bot   │ │   CRM   │ │Analytics│    │   │
│  │  │Service  │ │         │ │         │ │         │    │   │
│  │  └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘    │   │
│  └───────┼──────────┼──────────┼──────────┼───────────┘   │
│          │          │          │          │                │
│          └──────────┴──────────┴──────────┘                │
│                         │                                   │
│              ┌──────────┴──────────┐                       │
│              │   OAuth / SSO       │                       │
│              │   (Auto-generated)  │                       │
│              └─────────────────────┘                       │
└─────────────────────────────────────────────────────────────┘

🚀 How It Works

For Tenants (End Users)

1. Go to Portal → Apps
2. Browse available apps
3. Click "Install" on desired app
4. App is instantly available with:
   ✅ SSO already configured
   ✅ Users already synced
   ✅ Organization data connected
5. Click "Open App" → Seamless login via Optare ID

For Your Apps (Technical)

1. App is registered in the marketplace
2. When tenant installs:
   - OAuth client auto-created
   - Credentials sent to app via webhook
   - All org members get access
3. User clicks "Open App":
   - Redirected to OAuth authorize
   - Auto-logged in via SSO
   - Lands in app with full context

📁 Files Created

Database Schema

  • libs/database/src/schema-apps.ts
    • available_app - App catalog
    • installed_app - Per-org installations
    • app_permission - User access control

Services

  • libs/apps/src/app-service.ts
    • getAvailableApps() - List marketplace
    • installApp() - Install for org
    • uninstallApp() - Remove app
    • checkUserAccess() - Permission check
    • getAppSSOUrl() - Generate SSO link

API Routes

  • app/routes/api.apps.ts
    • GET /api/apps - List available apps
    • GET /api/apps?action=installed - List installed
    • GET /api/apps?action=sso&slug=xxx - Get SSO URL
    • POST /api/apps - Install/uninstall

UI

  • app/routes/portal.apps._index.tsx
    • App marketplace page
    • Install/uninstall buttons
    • Open app with SSO

Scripts

  • scripts/seed-marketplace-apps.ts
    • Seeds your apps into marketplace

🔧 Setup Instructions

Step 1: Run Database Migration

# Create the new tables
pnpm drizzle-kit push

Step 2: Seed Your Apps

# Add your apps to the marketplace
pnpm tsx scripts/seed-marketplace-apps.ts

Step 3: Configure App URLs

Add to your .env:

# Customer Service App
CUSTOMER_SERVICE_APP_URL=https://support.optare.one
CUSTOMER_SERVICE_CALLBACK_URL=https://support.optare.one/auth/callback
CUSTOMER_SERVICE_WEBHOOK_URL=https://support.optare.one/api/webhooks/optare

# Bot App
BOT_APP_URL=https://bot.optare.one
BOT_CALLBACK_URL=https://bot.optare.one/auth/callback
BOT_WEBHOOK_URL=https://bot.optare.one/api/webhooks/optare

# CRM App
CRM_APP_URL=https://crm.optare.one
CRM_CALLBACK_URL=https://crm.optare.one/auth/callback
CRM_WEBHOOK_URL=https://crm.optare.one/api/webhooks/optare

Step 4: Configure Your Apps to Accept SSO

Each app needs to:

  1. Handle OAuth Callback
// /auth/callback route in your app
app.get('/auth/callback', async (req, res) => {
  const { code, state } = req.query;
  
  // Exchange code for tokens
  const tokens = await exchangeCodeForTokens(code);
  
  // Get user info
  const userInfo = await getUserInfo(tokens.access_token);
  
  // Create session in your app
  await createSession(userInfo);
  
  // Redirect to app
  res.redirect(state || '/');
});
  1. Handle Installation Webhook
// /api/webhooks/optare route
app.post('/api/webhooks/optare', async (req, res) => {
  const { event, organizationId, clientId, clientSecret } = req.body;
  
  if (event === 'app.installed') {
    // Store credentials for this org
    await storeOAuthCredentials(organizationId, clientId, clientSecret);
  }
  
  res.json({ received: true });
});

🔐 Security

OAuth Credentials

  • Auto-generated per installation
  • Stored encrypted in database
  • Sent to app once via webhook
  • Never exposed to end users

User Access

  • All org members get access by default
  • Admins can revoke individual access
  • Permissions stored per-user per-app

SSO Flow

  • Standard OAuth 2.0 Authorization Code
  • PKCE supported
  • Tokens scoped to organization

📊 Database Schema

available_app (Marketplace Catalog)

CREATE TABLE available_app (
  id TEXT PRIMARY KEY,
  slug TEXT UNIQUE NOT NULL,        -- "customer-service"
  name TEXT NOT NULL,               -- "Customer Service"
  description TEXT,
  app_url TEXT NOT NULL,            -- Base URL
  callback_url TEXT,                -- OAuth callback
  webhook_url TEXT,                 -- For events
  is_free BOOLEAN DEFAULT false,
  monthly_price TEXT,               -- "29.99"
  required_scopes JSONB,            -- ["openid", "profile"]
  features JSONB,                   -- Feature list
  is_first_party BOOLEAN DEFAULT true,
  is_published BOOLEAN DEFAULT false,
  created_at TIMESTAMP,
  updated_at TIMESTAMP
);

installed_app (Per-Org Installations)

CREATE TABLE installed_app (
  id TEXT PRIMARY KEY,
  organization_id TEXT REFERENCES organization(id),
  app_id TEXT REFERENCES available_app(id),
  status TEXT DEFAULT 'active',     -- active, inactive, suspended
  installed_by TEXT REFERENCES user(id),
  installed_at TIMESTAMP,
  config JSONB,                     -- App-specific settings
  client_id TEXT,                   -- OAuth client ID
  client_secret TEXT,               -- Encrypted
  subscription_id TEXT,             -- If paid app
  created_at TIMESTAMP,
  updated_at TIMESTAMP
);

app_permission (User Access)

CREATE TABLE app_permission (
  id TEXT PRIMARY KEY,
  installed_app_id TEXT REFERENCES installed_app(id),
  user_id TEXT REFERENCES user(id),
  can_access BOOLEAN DEFAULT true,
  is_admin BOOLEAN DEFAULT false,
  permissions JSONB,                -- Custom permissions
  granted_by TEXT REFERENCES user(id),
  granted_at TIMESTAMP
);

🎯 User Flow

Installing an App

Tenant Admin → Portal → Apps

Sees "Customer Service" app

Clicks "Install"

System:
  1. Creates OAuth client
  2. Sends credentials to app webhook
  3. Grants access to all org members
  4. Shows success message

Admin clicks "Open App"

Redirected to OAuth authorize

Auto-logged in (SSO)

Lands in Customer Service app

Using an Installed App

User → Portal → Apps

Sees installed apps

Clicks "Open App"

GET /api/apps?action=sso&slug=customer-service

Returns SSO URL

Redirect to OAuth authorize

User already logged in → Auto-approve

Redirect to app with auth code

App exchanges code for tokens

User is logged in!

🔌 Adding a New App

1. Add to Seed Script

// scripts/seed-marketplace-apps.ts
{
  id: nanoid(),
  slug: 'my-new-app',
  name: 'My New App',
  description: 'Description here',
  appUrl: 'https://myapp.optare.one',
  callbackUrl: 'https://myapp.optare.one/auth/callback',
  webhookUrl: 'https://myapp.optare.one/api/webhooks/optare',
  isFree: false,
  monthlyPrice: '19.99',
  requiredScopes: ['openid', 'profile', 'email', 'organization'],
  features: ['Feature 1', 'Feature 2'],
  isFirstParty: true,
  isPublished: true,
}

2. Add Icon Mapping

// app/routes/portal.apps._index.tsx
const appIcons: Record<string, any> = {
  // ...existing
  "my-new-app": MyIcon,
};

3. Configure App to Accept SSO

See "Configure Your Apps to Accept SSO" section above.


🎉 Benefits

For Tenants

  • ✅ One-click installation
  • ✅ No configuration needed
  • ✅ Seamless SSO
  • ✅ All users automatically have access
  • ✅ Unified billing (future)

For You (Platform Owner)

  • ✅ Ecosystem lock-in
  • ✅ Cross-sell opportunities
  • ✅ Unified user management
  • ✅ Centralized billing
  • ✅ Data mesh foundation

For Your Apps

  • ✅ No auth implementation needed
  • ✅ User data from Optare ID
  • ✅ Organization context
  • ✅ Automatic user provisioning

🚀 Next Steps

  1. Run migrations to create tables
  2. Seed apps into marketplace
  3. Configure app URLs in environment
  4. Update your apps to accept SSO
  5. Test the flow end-to-end

Your apps are now plug-and-play with Optare ID! 🎉