Let’s build a practical tool! We’ll create a Discord Balance Manager that lets you search members by Discord ID and manage their point balances. Perfect first project to get familiar with DRIP. πŸš€

Prerequisites (2 minutes)

1

Get Your API Key

Follow the quickstart guide to get your API key and realm ID (found in the dashboard header when your project is selected)
2

Choose Your Weapon

Pick your favorite:
  • HTML + JavaScript (runs anywhere, no setup)
  • Node.js (if you like npm and stuff)
  • Python (if you’re into that)

Discord Balance Manager

Let’s build another useful app! This time we’ll create a balance management tool that lets you search for users by their Discord ID and manage their point balances.

What We’re Building

A balance management app that:
  • πŸ” Search users by Discord ID
  • πŸ’° View current point balances
  • βž• Award or deduct points
  • πŸ“ Add reasons for balance changes
  • 🎯 Simple, clean interface
Perfect for community managers who need to quickly adjust member balances for events, contests, or corrections!
πŸ”’ SECURITY WARNING: This example is for learning purposes only!The HTML version below exposes your API key in client-side JavaScript, which is NOT SAFE for production use. Anyone can view your API key by inspecting the page source.For production applications:
  • Use server-side code (Node.js, Python, etc.) to keep API keys secure
  • Store API keys in environment variables, never in source code
  • Implement proper authentication and authorization
  • Consider using the App Client flow for multi-realm applications
This tutorial is safe for:
  • Local development and testing
  • Learning DRIP API concepts
  • Prototyping (with test API keys only)

HTML + JavaScript Version

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DRIP Balance Manager πŸ’°</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }
        
        .header h1 {
            font-size: 2.5em;
            margin-bottom: 10px;
        }
        
        .content {
            padding: 30px;
        }
        
        .search-section {
            background: #f8f9ff;
            padding: 25px;
            border-radius: 15px;
            margin-bottom: 30px;
        }
        
        .form-group {
            margin-bottom: 20px;
        }
        
        .form-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #333;
        }
        
        .form-group input, .form-group textarea, .form-group select {
            width: 100%;
            padding: 12px 15px;
            border: 2px solid #e1e5e9;
            border-radius: 10px;
            font-size: 16px;
            transition: border-color 0.3s;
        }
        
        .form-group input:focus, .form-group textarea:focus, .form-group select:focus {
            outline: none;
            border-color: #667eea;
        }
        
        .btn {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            padding: 12px 25px;
            border-radius: 10px;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            transition: transform 0.2s;
            margin-right: 10px;
            margin-bottom: 10px;
        }
        
        .btn:hover {
            transform: translateY(-2px);
        }
        
        .btn.success {
            background: #28a745;
        }
        
        .btn.danger {
            background: #dc3545;
        }
        
        .user-card {
            background: #f8f9ff;
            padding: 25px;
            border-radius: 15px;
            margin-bottom: 20px;
            border-left: 5px solid #667eea;
        }
        
        .user-info {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
        }
        
        .user-details h3 {
            color: #333;
            margin-bottom: 5px;
        }
        
        .user-details p {
            color: #666;
            margin: 2px 0;
        }
        
        .balance-list {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin-bottom: 20px;
        }
        
        .balance-item {
            background: white;
            padding: 15px;
            border-radius: 10px;
            text-align: center;
            border: 2px solid #e1e5e9;
        }
        
        .balance-amount {
            font-size: 1.5em;
            font-weight: bold;
            color: #667eea;
        }
        
        .balance-name {
            font-size: 0.9em;
            color: #666;
            margin-top: 5px;
        }
        
        .balance-actions {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 15px;
            margin-top: 20px;
        }
        
        .loading {
            text-align: center;
            padding: 40px;
            color: #666;
        }
        
        .error {
            background: #f8d7da;
            color: #721c24;
            padding: 15px;
            border-radius: 10px;
            margin-bottom: 20px;
            border-left: 5px solid #dc3545;
        }
        
        .success {
            background: #d4edda;
            color: #155724;
            padding: 15px;
            border-radius: 10px;
            margin-bottom: 20px;
            border-left: 5px solid #28a745;
        }
        
        .hidden {
            display: none;
        }
        
        @media (max-width: 768px) {
            .user-info {
                flex-direction: column;
                align-items: flex-start;
            }
            
            .balance-actions {
                grid-template-columns: 1fr;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>πŸ’° Balance Manager</h1>
            <p>Search and manage member point balances</p>
        </div>
        
        <div class="content">
            <!-- Search Section -->
            <div class="search-section">
                <h2>πŸ” Find Member by Discord ID</h2>
                <div class="form-group">
                    <label for="discordId">Discord User ID</label>
                    <input type="text" id="discordId" placeholder="e.g., 123456789012345678">
                    <small style="color: #666; font-size: 0.9em;">Right-click a user in Discord β†’ Copy User ID</small>
                </div>
                <button class="btn" onclick="searchUser()">Search Member</button>
            </div>
            
            <!-- Loading State -->
            <div id="loading" class="loading hidden">
                <h3>πŸ” Searching for member...</h3>
            </div>
            
            <!-- Error Message -->
            <div id="error" class="error hidden">
                <h4>❌ Error</h4>
                <p id="error-message"></p>
            </div>
            
            <!-- Success Message -->
            <div id="success" class="success hidden">
                <h4>βœ… Success</h4>
                <p id="success-message"></p>
            </div>
            
            <!-- User Details -->
            <div id="user-section" class="hidden">
                <div class="user-card">
                    <div class="user-info">
                        <div class="user-details">
                            <h3 id="user-name">Loading...</h3>
                            <p><strong>Discord ID:</strong> <span id="user-discord-id"></span></p>
                            <p><strong>Member Since:</strong> <span id="user-joined"></span></p>
                        </div>
                    </div>
                    
                    <h4>πŸ’° Current Balances</h4>
                    <div id="balance-list" class="balance-list">
                        <!-- Balances will be populated here -->
                    </div>
                    
                    <div class="balance-actions">
                        <div>
                            <h4>βž• Award Points</h4>
                            <div class="form-group">
                                <select id="award-currency">
                                    <!-- Options populated dynamically -->
                                </select>
                            </div>
                            <div class="form-group">
                                <input type="number" id="award-amount" placeholder="Amount to award" min="1">
                            </div>
                            <div class="form-group">
                                <input type="text" id="award-reason" placeholder="Reason (optional)">
                            </div>
                            <button class="btn success" onclick="awardPoints()">Award Points</button>
                        </div>
                        
                        <div>
                            <h4>βž– Deduct Points</h4>
                            <div class="form-group">
                                <select id="deduct-currency">
                                    <!-- Options populated dynamically -->
                                </select>
                            </div>
                            <div class="form-group">
                                <input type="number" id="deduct-amount" placeholder="Amount to deduct" min="1">
                            </div>
                            <div class="form-group">
                                <input type="text" id="deduct-reason" placeholder="Reason (optional)">
                            </div>
                            <button class="btn danger" onclick="deductPoints()">Deduct Points</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

         <script>
         // πŸ”₯ CONFIGURE THESE WITH YOUR ACTUAL VALUES
         // ⚠️  WARNING: Never expose API keys in client-side code in production!
         // This is only safe for local development and learning.
         const DRIP_API_KEY = 'your_client_secret_here';
         const REALM_ID = 'your_realm_id_here';
         const API_BASE = 'https://api.drip.re/api/v1';
        
        let currentUser = null;
        let realmCurrencies = [];

        // Search for user by Discord ID
        async function searchUser() {
            const discordId = document.getElementById('discordId').value.trim();
            
            if (!discordId) {
                showError('Please enter a Discord ID');
                return;
            }
            
            showLoading(true);
            hideMessages();
            hideUserSection();
            
            try {
                // Search for member by Discord ID
                const response = await fetch(`${API_BASE}/realm/${REALM_ID}/members/search?type=discord&value=${discordId}`, {
                    headers: {
                        'Authorization': `Bearer ${DRIP_API_KEY}`,
                        'Content-Type': 'application/json'
                    }
                });
                
                if (!response.ok) {
                    const error = await response.json();
                    throw new Error(error.message || 'Failed to search for member');
                }
                
                const data = await response.json();
                
                if (!data.data || data.data.length === 0) {
                    throw new Error('No member found with that Discord ID');
                }
                
                currentUser = data.data[0];
                await loadRealmCurrencies();
                displayUser(currentUser);
                
            } catch (error) {
                console.error('Search error:', error);
                showError(error.message);
            } finally {
                showLoading(false);
            }
        }
        
        // Load realm currencies for dropdowns
        async function loadRealmCurrencies() {
            try {
                const response = await fetch(`${API_BASE}/realm/${REALM_ID}/points`, {
                    headers: {
                        'Authorization': `Bearer ${DRIP_API_KEY}`,
                        'Content-Type': 'application/json'
                    }
                });
                
                if (!response.ok) {
                    throw new Error('Failed to load currencies');
                }
                
                const data = await response.json();
                realmCurrencies = data.data || [];
                
                populateCurrencyDropdowns();
                
            } catch (error) {
                console.error('Failed to load currencies:', error);
                // Use fallback if available from user balance
                if (currentUser && currentUser.pointBalances) {
                    realmCurrencies = currentUser.pointBalances.map(balance => balance.realmPoint);
                    populateCurrencyDropdowns();
                }
            }
        }
        
        // Populate currency dropdown options
        function populateCurrencyDropdowns() {
            const awardSelect = document.getElementById('award-currency');
            const deductSelect = document.getElementById('deduct-currency');
            
            // Clear existing options
            awardSelect.innerHTML = '';
            deductSelect.innerHTML = '';
            
            // Add currency options
            realmCurrencies.forEach(currency => {
                const option1 = document.createElement('option');
                option1.value = currency.id;
                option1.textContent = `${currency.emoji || 'πŸ’°'} ${currency.name}`;
                awardSelect.appendChild(option1);
                
                const option2 = document.createElement('option');
                option2.value = currency.id;
                option2.textContent = `${currency.emoji || 'πŸ’°'} ${currency.name}`;
                deductSelect.appendChild(option2);
            });
        }
        
        // Display user information
        function displayUser(user) {
            document.getElementById('user-name').textContent = user.displayName || user.username || 'Unknown User';
            document.getElementById('user-discord-id').textContent = user.credentials?.find(c => c.type === 'discord')?.platformId || 'N/A';
            document.getElementById('user-joined').textContent = user.joinedAt ? new Date(user.joinedAt).toLocaleDateString() : 'Unknown';
            
            // Display balances
            const balanceList = document.getElementById('balance-list');
            balanceList.innerHTML = '';
            
            if (user.pointBalances && user.pointBalances.length > 0) {
                user.pointBalances.forEach(balance => {
                    const balanceItem = document.createElement('div');
                    balanceItem.className = 'balance-item';
                    balanceItem.innerHTML = `
                        <div class="balance-amount">${balance.balance.toLocaleString()}</div>
                        <div class="balance-name">${balance.realmPoint.emoji || 'πŸ’°'} ${balance.realmPoint.name}</div>
                    `;
                    balanceList.appendChild(balanceItem);
                });
            } else {
                balanceList.innerHTML = '<div class="balance-item"><div class="balance-amount">0</div><div class="balance-name">No balances found</div></div>';
            }
            
            showUserSection();
        }
        
        // Award points to user
        async function awardPoints() {
            if (!currentUser) return;
            
            const currencyId = document.getElementById('award-currency').value;
            const amount = parseInt(document.getElementById('award-amount').value);
            const reason = document.getElementById('award-reason').value.trim();
            
            if (!currencyId || !amount || amount <= 0) {
                showError('Please select a currency and enter a valid amount');
                return;
            }
            
            try {
                const requestBody = {
                    tokens: amount
                };
                
                if (currencyId) {
                    requestBody.realmPointId = currencyId;
                }
                
                const response = await fetch(`${API_BASE}/realm/${REALM_ID}/members/${currentUser.id}/point-balance`, {
                    method: 'PATCH',
                    headers: {
                        'Authorization': `Bearer ${DRIP_API_KEY}`,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(requestBody)
                });
                
                if (!response.ok) {
                    const error = await response.json();
                    throw new Error(error.message || 'Failed to award points');
                }
                
                const currency = realmCurrencies.find(c => c.id === currencyId);
                const currencyName = currency ? `${currency.emoji || 'πŸ’°'} ${currency.name}` : 'points';
                
                showSuccess(`Successfully awarded ${amount} ${currencyName}${reason ? ` for: ${reason}` : ''}!`);
                
                // Clear form
                document.getElementById('award-amount').value = '';
                document.getElementById('award-reason').value = '';
                
                // Refresh user data
                setTimeout(() => {
                    searchUser();
                }, 1000);
                
            } catch (error) {
                console.error('Award error:', error);
                showError(error.message);
            }
        }
        
        // Deduct points from user
        async function deductPoints() {
            if (!currentUser) return;
            
            const currencyId = document.getElementById('deduct-currency').value;
            const amount = parseInt(document.getElementById('deduct-amount').value);
            const reason = document.getElementById('deduct-reason').value.trim();
            
            if (!currencyId || !amount || amount <= 0) {
                showError('Please select a currency and enter a valid amount');
                return;
            }
            
            try {
                const requestBody = {
                    tokens: -amount // Negative amount for deduction
                };
                
                if (currencyId) {
                    requestBody.realmPointId = currencyId;
                }
                
                const response = await fetch(`${API_BASE}/realm/${REALM_ID}/members/${currentUser.id}/point-balance`, {
                    method: 'PATCH',
                    headers: {
                        'Authorization': `Bearer ${DRIP_API_KEY}`,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(requestBody)
                });
                
                if (!response.ok) {
                    const error = await response.json();
                    throw new Error(error.message || 'Failed to deduct points');
                }
                
                const currency = realmCurrencies.find(c => c.id === currencyId);
                const currencyName = currency ? `${currency.emoji || 'πŸ’°'} ${currency.name}` : 'points';
                
                showSuccess(`Successfully deducted ${amount} ${currencyName}${reason ? ` for: ${reason}` : ''}!`);
                
                // Clear form
                document.getElementById('deduct-amount').value = '';
                document.getElementById('deduct-reason').value = '';
                
                // Refresh user data
                setTimeout(() => {
                    searchUser();
                }, 1000);
                
            } catch (error) {
                console.error('Deduct error:', error);
                showError(error.message);
            }
        }
        
        // UI Helper Functions
        function showLoading(show) {
            document.getElementById('loading').classList.toggle('hidden', !show);
        }
        
        function showError(message) {
            document.getElementById('error-message').textContent = message;
            document.getElementById('error').classList.remove('hidden');
            setTimeout(() => {
                document.getElementById('error').classList.add('hidden');
            }, 5000);
        }
        
        function showSuccess(message) {
            document.getElementById('success-message').textContent = message;
            document.getElementById('success').classList.remove('hidden');
            setTimeout(() => {
                document.getElementById('success').classList.add('hidden');
            }, 5000);
        }
        
        function hideMessages() {
            document.getElementById('error').classList.add('hidden');
            document.getElementById('success').classList.add('hidden');
        }
        
        function showUserSection() {
            document.getElementById('user-section').classList.remove('hidden');
        }
        
        function hideUserSection() {
            document.getElementById('user-section').classList.add('hidden');
        }
        
        // Allow Enter key to trigger search
        document.getElementById('discordId').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                searchUser();
            }
        });
    </script>
</body>
</html>

Key Features Explained

How to Get Discord IDs

1

Enable Developer Mode

In Discord: Settings β†’ Advanced β†’ Enable β€œDeveloper Mode”
2

Copy User ID

Right-click any user β†’ β€œCopy User ID”
3

Use in App

Paste the ID into the search field and click β€œSearch Member”

What’s Next? πŸš€

Congratulations! You just built your first DRIP app! πŸŽ‰ Here are some ideas for your next projects:

Points Dashboard

Show detailed analytics: points over time, top earners, activity trends

Member Directory

Searchable member list with profiles, badges, and achievements

Quest Tracker

Show active quests, completion rates, and member progress

Store Integration

Let members spend points on rewards directly from your app

Ready for More Advanced Stuff?

Share your creation! Built something cool? Show it off in our Discord community and get feedback from other developers! πŸš€