Learn how to build applications on the DRIP platform that can be installed and used by multiple communities. This guide covers the entire app development lifecycle from creation to distribution.

Overview

DRIP supports two types of integrations:

Realm Clients (Direct Integration)

  • Purpose: Direct access to your own realm’s data
  • Scope: Single realm only
  • Use case: Custom dashboards, internal tools, automation scripts
  • Authorization: Scoped to your realm automatically

App Clients (Multi-Realm Apps)

  • Purpose: Build applications that can be used by multiple communities
  • Scope: Can access multiple realms with their permission
  • Use case: Third-party integrations, marketplace apps, SaaS tools
  • Authorization: Each realm must explicitly authorize your app
This guide focuses on App Clients - building applications for the broader DRIP ecosystem.

App Development Lifecycle

Creating Your First App

1. Access the Developer Portal

1

Navigate to Developer Portal

Go to Admin > Developer in your DRIP dashboard
2

Switch to Drip Apps Tab

Click on the Drip Apps tab to manage your applications
3

Submit New App

Click Submit New App to start the creation process

2. App Configuration

You can create an API client and start building/testing your app before publishing. While your app is not published, its API client only works on your own realm. To allow other realms to authorize and use your app, you must publish it. Publishing enables other realms to authorize your app. App Store listing happens after manual verification via email to [email protected]. Read more about the submission process here.
When creating your app, you’ll need to provide:
{
  "name": "My Awesome App",
  "description": "A comprehensive tool for community engagement",
  "category": "productivity",
  "website": "https://myapp.com",
  "supportUrl": "https://myapp.com/support",
  "privacyPolicy": "https://myapp.com/privacy",
  "termsOfService": "https://myapp.com/terms",
  "logoUrl": "https://myapp.com/logo.png",
  "screenshots": [
    "https://myapp.com/screenshot1.png",
    "https://myapp.com/screenshot2.png"
  ],
  "requestedScopes": [
    "realm:read",
    "members:read",
    "members:write",
    "points:write"
  ]
}

Required Information

FieldDescriptionExample
NameApp display name”DRIP Analytics”
DescriptionDetailed app description”Advanced analytics and insights for your community”
CategoryApp category”analytics”, “productivity”, “gaming”
WebsiteOfficial app websitehttps://drip.re
Support URLCustomer support linkhttps://drip.re/support
Privacy PolicyPrivacy policy URLhttps://drip.re/privacy
LogoApp logo (512x512 PNG)High-quality square logo
ScreenshotsApp screenshots (up to 5)Showing key features

API Client Types Explained

Realm Client Workflow

Characteristics:
  • Immediate access to your realm
  • No approval process needed
  • Scoped to single realm
  • Full permissions within your realm

App Client Workflow

Characteristics:
  • Multi-realm capability
  • Publishing enables cross-realm authorization
  • Each realm authorizes independently
  • Restricted to realm-granted scopes at authorization

Scopes and Permissions

For now, no scopes require special platform approval. You may request all available realm scopes; realms grant scopes when they authorize your app.

Understanding Scopes

Scopes define what your app can access. All scopes are granted per realm at authorization; there are no public scopes available across realms.

Read Scopes

const readScopes = [
  'realm:read',           // Basic realm information
  'members:read',         // Member profiles and stats
  'quests:read',          // Quest information
  'store:read',           // Store items and categories
  'leaderboards:read'     // Public leaderboards
];
These scopes are not public or auto-approved across realms. Realm admins must grant them when authorizing your app.

Sensitive Scopes (Granted by realm at authorization)

const restrictedScopes = [
  'members:write',        // Modify member data
  'points:write',         // Award/deduct points
  'quests:write',         // Create/modify quests
  'store:write',          // Manage store items
  'admin:read',           // Admin-level data access
  'webhooks:write'        // Configure webhooks
];

Scope Request Guidance

  • Declare the scopes your app needs in its configuration so realms see them during authorization.
  • Explain why each scope is needed in your app description or docs to help realm admins make informed decisions.
  • Realms grant scopes at authorization time; there is no separate platform scope approval at this time.

How App Scopes Are Applied

  • Your app selects its own scopes in the app configuration (requestedScopes).
  • When a realm/project authorizes your app, those selected scopes are granted for that realm. Your app’s API client can access endpoints protected by those scopes within that realm only.
  • If you change your app’s requested scopes (add or remove), each realm/project must reauthorize your app for the changes to take effect.
  • Until reauthorization happens, the previously granted scopes remain active for that realm.
Tip: When you add new scopes, prompt realm admins to re-authorize your app to avoid failures due to missing permissions.

App Authorization Flow

Current process: there is no platform-level approval step. Publishing enables cross-realm authorization immediately. App Store listing is a separate manual verification step via email to [email protected].

1. Realm-Level Authorization

Each realm admin must explicitly authorize your app after you publish it. There is no platform approval step at this time.

2. Verifying Authorization Programmatically

Each realm admin must explicitly authorize your app:
async function checkRealmAuthorization(realmId, appId) {
  const response = await fetch(`https://api.drip.re/api/v1/apps/${appId}/authorized-realms`, {
    headers: {
      'Authorization': 'Bearer YOUR_APP_CLIENT_SECRET',
      'Content-Type': 'application/json'
    }
  });
  
  const authorizations = await response.json();
  return authorizations.data.find(auth => auth.realmId === realmId);
}

// Usage
const authorization = await checkRealmAuthorization('REALM_ID', 'YOUR_APP_ID');
if (authorization) {
  console.log('Authorized scopes:', authorization.approvedScopes);
} else {
  console.log('App not authorized for this realm');
}

Development Best Practices

1. Scope Minimization

Only request scopes you actually need:
// ❌ Bad: Requesting unnecessary scopes
const badScopes = [
  'realm:read',
  'members:read',
  'members:write',  // Not needed for read-only analytics
  'points:write',   // Not needed for analytics
  'admin:read',     // Overly broad
  'webhooks:write'  // Not needed
];

// ✅ Good: Minimal necessary scopes
const goodScopes = [
  'realm:read',     // Need realm info
  'members:read'    // Need member data for analytics
];

2. Graceful Authorization Handling

Handle authorization failures gracefully:
class DripAppClient {
  async makeRequest(realmId, endpoint, options = {}) {
    try {
      const response = await fetch(`https://api.drip.re/api/v1${endpoint}`, {
        ...options,
        headers: {
          'Authorization': `Bearer ${this.appClientSecret}`,
          'Content-Type': 'application/json',
          ...options.headers
        }
      });

      if (response.status === 403) {
        const error = await response.json();
        
        if (error.reason?.includes('not authorized for realm')) {
          throw new AppNotAuthorizedError(realmId);
        } else if (error.restrictedScopes?.length > 0) {
          throw new MissingScopesError(error.restrictedScopes);
        }
      }

      return response.json();
    } catch (error) {
      // Handle different error types appropriately
      throw error;
    }
  }
}

class AppNotAuthorizedError extends Error {
  constructor(realmId) {
    super(`App not authorized for realm ${realmId}`);
    this.name = 'AppNotAuthorizedError';
    this.realmId = realmId;
  }
}

class MissingScopesError extends Error {
  constructor(scopes) {
    super(`Missing required scopes: ${scopes.join(', ')}`);
    this.name = 'MissingScopesError';
    this.missingScopes = scopes;
  }
}

3. Multi-Realm Data Handling

Handle data from multiple realms efficiently:
class MultiRealmManager {
  constructor(appClientSecret) {
    this.client = new DripAppClient(appClientSecret);
    this.authorizedRealms = new Map();
  }

  async loadAuthorizedRealms() {
    const response = await this.client.makeRequest(null, '/apps/YOUR_APP_ID/authorized-realms');
    
    for (const auth of response.data) {
      this.authorizedRealms.set(auth.realmId, {
        scopes: auth.approvedScopes,
        authorizedAt: auth.authorizedAt,
        realm: auth.realm
      });
    }
  }

  async batchProcessRealms(operation) {
    const results = new Map();
    const promises = [];

    for (const [realmId, auth] of this.authorizedRealms) {
      promises.push(
        operation(realmId, auth.scopes)
          .then(result => results.set(realmId, result))
          .catch(error => results.set(realmId, { error }))
      );
    }

    await Promise.all(promises);
    return results;
  }
}

// Usage
const manager = new MultiRealmManager('YOUR_APP_CLIENT_SECRET');
await manager.loadAuthorizedRealms();

const memberStats = await manager.batchProcessRealms(async (realmId, scopes) => {
  if (!scopes.includes('members:read')) {
    throw new Error('Missing members:read scope');
  }
  
  return await client.makeRequest(realmId, `/realm/${realmId}/members/search?type=drip-id&values=all`);
});

App Store Guidelines

Submission Requirements

Before submitting your app, ensure you have:

Technical Requirements

  • Functional app with proper error handling
  • Secure API key management
  • Proper scope usage
  • Performance optimization

Content Requirements

  • Clear app description
  • High-quality screenshots
  • Privacy policy and terms of service
  • Support documentation

Design Requirements

  • Professional app logo (512x512)
  • Consistent branding
  • User-friendly interface
  • Mobile responsiveness

Security Requirements

  • Secure data handling
  • Proper authentication
  • No unauthorized data collection
  • Regular security updates

Review Process

1

Initial Submission

Submit your app through the Developer Portal
2

Automated Checks

Basic validation of app metadata and requirements
3

Security Review

Manual review of requested scopes and security practices
4

Functionality Testing

Testing of core app functionality and API integration
5

Final Approval

App published to the DRIP App Store

Testing Your App

Development Environment

Set up a proper testing environment:
const config = {
  development: {
    apiUrl: 'https://api.drip.re/api/v1',
    appClientSecret: process.env.DEV_APP_CLIENT_SECRET,
    testRealmId: process.env.DEV_TEST_REALM_ID
  },
  production: {
    apiUrl: 'https://api.drip.re/api/v1',
    appClientSecret: process.env.PROD_APP_CLIENT_SECRET
  }
};

Testing Authorization Flows

async function testAuthorizationFlow() {
  const client = new DripAppClient(config.development.appClientSecret);
  
  try {
    // Test 1: Check app authorization
    const authorizations = await client.getAuthorizedRealms();
    console.log('✅ Successfully retrieved authorized realms:', authorizations.length);
    
    // Test 2: Test API access with authorized realm
    if (authorizations.length > 0) {
      const realmId = authorizations[0].realmId;
      const members = await client.searchMembers(realmId, 'drip-id', 'test-member');
      console.log('✅ Successfully accessed member data');
    }
    
    // Test 3: Test unauthorized realm access
    try {
      await client.searchMembers('unauthorized-realm-id', 'drip-id', 'test');
      console.log('❌ Should have failed for unauthorized realm');
    } catch (error) {
      console.log('✅ Correctly blocked unauthorized realm access');
    }
    
  } catch (error) {
    console.error('❌ Authorization test failed:', error.message);
  }
}

App Analytics and Monitoring

Track your app’s usage across realms:
class AppAnalytics {
  constructor(appClientSecret) {
    this.client = new DripAppClient(appClientSecret);
  }

  async getUsageStats() {
    // Track API usage across all authorized realms
    const authorizations = await this.client.getAuthorizedRealms();
    
    const stats = {
      totalRealms: authorizations.length,
      activeRealms: 0,
      totalApiCalls: 0,
      errorRate: 0
    };

    // Implementation would track actual usage metrics
    return stats;
  }

  async getRealmEngagement() {
    // Track how realms are using your app
    const engagement = new Map();
    
    // Implementation would analyze usage patterns
    return engagement;
  }
}

Creating Secure Installation URLs

For advanced app distribution, you can create encrypted installation URLs that include platform-specific data and expiration times.

Prerequisites

Encryption Key

You will need the encryption key provided for your app. This key is unique and ensures secure encryption.

Development Environment

Use a JavaScript environment (Node.js or browser console) to run the encryption logic.

Encryption Implementation

// Import the CryptoJS library
const CryptoJS = require("crypto-js");

// Encryption function
function encryptData(data, passphrase) {
  const json = JSON.stringify(data);
  return CryptoJS.AES.encrypt(json, passphrase).toString();
}

// Example Usage:
const encryptionKey = "your-app-encryption-key"; // Replace with your app's key
const platformData = {
  platformType: "slack",       // Replace with your platform type
  platformId: "MY_TEAM_ID",    // Replace with your platform ID
  expirationTime: Date.now() + 30 * 60 * 1000 // Optional: expires in 30 minutes
};

// Encrypt the data
const encrypted = encryptData(platformData, encryptionKey);
console.log("Encrypted Data:", encrypted);

// Build the URL
const BASE_URL = "https://app.drip.re/admin/apps/authorize";
const appId = "your-app-id"; // Replace with your app's ID

// Final URL
const installUrl = `${BASE_URL}?appId=${appId}&epfo=${encodeURIComponent(encrypted)}`;
console.log("Installation URL:", installUrl);

URL Parameters Explained

Example Output

When you run the encryption code, you’ll get:
// Encrypted Data Example:
"U2FsdGVkX1+L+8EzH4IbNHRgXLSOh9OUCzPZ8tjVdNA1M5..."

// Generated Installation URL:
"https://app.drip.re/admin/apps/authorize?appId=your-app-id&epfo=U2FsdGVkX1%2BL%2B8EzH4IbNHRgXLSOh9OUCzPZ8tjVdNA1M5..."

Security Best Practices

Keep Keys Secure

  • Never share your encryption key publicly
  • Store keys in environment variables
  • Rotate keys periodically
  • Use different keys for different environments

URL Expiration

  • Always set expiration times for sensitive URLs
  • Use shorter expiration times for production
  • Monitor for expired URL usage attempts
  • Provide clear error messages for expired URLs

Verify Integration

  • Test encrypted URLs before sharing
  • Verify decryption works correctly
  • Test with different platform types
  • Monitor installation success rates

Error Handling

  • Handle decryption failures gracefully
  • Provide fallback installation methods
  • Log encryption/decryption errors
  • Validate platform data before encryption

Advanced URL Generation

For more complex scenarios, you can create a URL generator utility:
class InstallationURLGenerator {
  constructor(appId, encryptionKey) {
    this.appId = appId;
    this.encryptionKey = encryptionKey;
    this.baseUrl = "https://app.drip.re/admin/apps/authorize";
  }

  generateURL(platformType, platformId, options = {}) {
    const {
      expirationMinutes = 30,
      customData = {},
      redirectUrl = null
    } = options;

    const platformData = {
      platformType,
      platformId,
      expirationTime: Date.now() + (expirationMinutes * 60 * 1000),
      ...customData
    };

    if (redirectUrl) {
      platformData.redirectUrl = redirectUrl;
    }

    const encrypted = this.encryptData(platformData);
    let url = `${this.baseUrl}?appId=${this.appId}&epfo=${encodeURIComponent(encrypted)}`;

    return {
      url,
      expiresAt: new Date(platformData.expirationTime),
      platformData
    };
  }

  encryptData(data) {
    const json = JSON.stringify(data);
    return CryptoJS.AES.encrypt(json, this.encryptionKey).toString();
  }

  // Batch generate URLs for multiple platforms
  generateBatch(platforms) {
    return platforms.map(platform => ({
      platform,
      ...this.generateURL(platform.type, platform.id, platform.options)
    }));
  }
}

// Usage
const generator = new InstallationURLGenerator("your-app-id", "your-encryption-key");

// Single URL
const installation = generator.generateURL("discord", "123456789", {
  expirationMinutes: 60,
  customData: { source: "marketing-campaign" }
});

console.log("Install URL:", installation.url);
console.log("Expires at:", installation.expiresAt);

// Batch generation
const platforms = [
  { type: "discord", id: "guild1", options: { expirationMinutes: 30 } },
  { type: "slack", id: "team1", options: { expirationMinutes: 60 } },
  { type: "teams", id: "team1", options: { expirationMinutes: 120 } }
];

const batchUrls = generator.generateBatch(platforms);
batchUrls.forEach(({ platform, url }) => {
  console.log(`${platform.type}: ${url}`);
});
Important Security Notes:
  • Keep your encryption key secure and never expose it in client-side code
  • Test your encrypted URLs thoroughly before distributing them
  • Monitor for any decryption failures and investigate potential security issues
  • Consider implementing rate limiting on URL generation to prevent abuse

Next Steps