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
- Local development and testing
- Learning DRIP API concepts
- Prototyping (with test API keys only)
HTML + JavaScript Version
Copy
<!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
Discord ID Search
Discord ID Search
The app searches for members using their Discord user ID:
- Uses the
/realm/{realmId}/members/search
endpoint - Searches by
type=discord
and the provided Discord ID - Returns member details including current point balances
Multi-Currency Support
Multi-Currency Support
Handles multiple point currencies in your realm:
- Fetches available currencies from
/realm/{realmId}/points
- Populates dropdown menus for currency selection
- Displays all current balances with currency names and emojis
Balance Management
Balance Management
Award or deduct points with full control:
- Uses
/realm/{realmId}/members/{memberId}/point-balance
endpoint - Positive values for awards, negative for deductions
- Optional reason tracking for audit purposes
- Real-time balance updates after each operation
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?
Multi-Realm Apps
Build apps that work across multiple communities
API Reference
Explore all available endpoints and advanced features
Share your creation! Built something cool? Show it off in our Discord community and get feedback from other developers! 🚀