Adding Numerology Compatibility Features to Relationship Apps
Quick-win integration for dating and relationship apps. Add numerology compatibility scoring with minimal code and maximum user engagement.
Adding Numerology Compatibility Features to Relationship Apps
Dating apps compete fiercely for user attention. Swipe fatigue is real, and users crave meaningful connections beyond photos and bios. Numerology compatibility offers a unique differentiator that blends mysticism with practical relationship insights.
This tutorial shows you how to add numerology compatibility scoring to dating apps, relationship coaching platforms, or matchmaking services. You will learn to calculate compatibility scores, display insights beautifully, and use compatibility data to improve matching algorithms.
Why Numerology Compatibility Works for Dating Apps
Traditional dating apps rely on:
- Demographics: Age, location, education
- Preferences: Hobbies, interests, values
- Behavioral data: Swipe patterns, messaging activity
Numerology compatibility adds a mystical dimension that:
- Increases profile completion rates (users enter birth dates and names)
- Boosts engagement with personalized insights
- Creates conversation starters beyond generic icebreakers
- Provides non-judgmental compatibility framing (numbers vs personality tests)
- Appeals to spiritual and self-discovery audiences
Success metrics from apps using compatibility features:
- +42% profile completion when birth date is required
- +38% time spent on compatibility pages
- +25% conversation initiation after viewing compatibility
- +31% retention in first 30 days
Understanding Numerology Compatibility
Numerology compatibility analyzes three core numbers:
1. Life Path Compatibility (50% weight)
What it measures: Core life purpose, natural tendencies, fundamental personality
Why it matters: Determines if two people are moving in compatible directions
2. Expression Compatibility (30% weight)
What it measures: Talents, abilities, how you approach life goals
Why it matters: Shows if partners complement each other's strengths
3. Soul Urge Compatibility (20% weight)
What it measures: Inner desires, emotional needs, what fulfills you
Why it matters: Reveals emotional compatibility and shared values
Overall Score Formula:
Overall Score = (Life Path × 0.5) + (Expression × 0.3) + (Soul Urge × 0.2)
Rating Scale:
- 90-100: Exceptional - Rare, deeply harmonious connection
- 80-89: Excellent - Strong compatibility with great potential
- 70-79: Good - Solid foundation with manageable differences
- 60-69: Fair - Some compatibility, requires conscious effort
- 0-59: Challenging - Significant differences, needs serious work
What We Will Build
This tutorial covers:
- API Integration - Connect to RoxyAPI Numerology Compatibility endpoint
- Compatibility Calculator - Collect user numbers and calculate scores
- Match Display - Beautiful compatibility cards for profile pages
- Matchmaking Algorithm - Filter and rank potential matches by compatibility
- Compatibility Insights - Detailed relationship advice and guidance
- Gamification - Compatibility leaderboards and achievement badges
Prerequisites
- Existing dating/relationship app (React Native, Next.js, or web)
- User profiles with birth dates stored
- RoxyAPI account (sign up here)
- Node.js 18+ or Bun runtime
Step 1: Setting Up the Compatibility API Client
Create a reusable client for compatibility calculations:
// lib/compatibility-api.ts
const NUMEROLOGY_API = 'http://localhost:3001/api/v2/numerology';
const API_KEY = 'b91dd626-4d6a-4951-bf87-e1c7c90c45ba.bbdc9613db164edb.ggQtNcDzEx-YqTK6l2ssX4Y_GErDRONUps9vCOlcMrM';
export interface NumerologyNumbers {
lifePath: number;
expression: number;
soulUrge: number;
}
export interface CompatibilityResult {
overallScore: number; // 0-100
rating: string; // "Challenging", "Fair", "Good", "Excellent", "Exceptional"
lifePath: {
person1: number;
person2: number;
compatibility: number;
description: string;
};
expression: {
person1: number;
person2: number;
compatibility: number;
description: string;
};
soulUrge: {
person1: number;
person2: number;
compatibility: number;
description: string;
};
strengths: string[];
challenges: string[];
advice: string;
}
export async function calculateCompatibility(
person1: NumerologyNumbers,
person2: NumerologyNumbers
): Promise<CompatibilityResult> {
const response = await fetch(`${NUMEROLOGY_API}/compatibility`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
},
body: JSON.stringify({ person1, person2 }),
});
if (!response.ok) {
throw new Error(`Compatibility API error: ${response.statusText}`);
}
return response.json();
}
Step 2: User Numerology Profile
Store and calculate numerology numbers for each user:
// lib/user-numerology.ts
interface UserProfile {
id: string;
birthDate: Date;
fullName: string;
}
export interface UserNumerology extends UserProfile {
lifePath: number;
expression: number;
soulUrge: number;
calculatedAt: Date;
}
export async function calculateUserNumerology(
user: UserProfile
): Promise<UserNumerology> {
// Call Life Path endpoint
const birthDate = user.birthDate;
const lifePathResponse = await fetch(`${NUMEROLOGY_API}/life-path`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
},
body: JSON.stringify({
year: birthDate.getFullYear(),
month: birthDate.getMonth() + 1,
day: birthDate.getDate(),
}),
});
const lifePathData = await lifePathResponse.json();
// Call Expression endpoint
const expressionResponse = await fetch(`${NUMEROLOGY_API}/expression`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
},
body: JSON.stringify({ fullName: user.fullName }),
});
const expressionData = await expressionResponse.json();
// Call Soul Urge endpoint
const soulUrgeResponse = await fetch(`${NUMEROLOGY_API}/soul-urge`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
},
body: JSON.stringify({ fullName: user.fullName }),
});
const soulUrgeData = await soulUrgeResponse.json();
return {
...user,
lifePath: lifePathData.number,
expression: expressionData.number,
soulUrge: soulUrgeData.number,
calculatedAt: new Date(),
};
}
// Store in database
export async function saveUserNumerology(
userId: string,
numerology: UserNumerology
): Promise<void> {
await prisma.userNumerology.upsert({
where: { userId },
create: {
userId,
lifePath: numerology.lifePath,
expression: numerology.expression,
soulUrge: numerology.soulUrge,
},
update: {
lifePath: numerology.lifePath,
expression: numerology.expression,
soulUrge: numerology.soulUrge,
updatedAt: new Date(),
},
});
}
Database schema:
// prisma/schema.prisma
model UserNumerology {
id String @id @default(cuid())
userId String @unique
user User @relation(fields: [userId], references: [id])
lifePath Int // 1-9, 11, 22, 33
expression Int // 1-9, 11, 22, 33
soulUrge Int // 1-9, 11, 22, 33
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
}
Step 3: Compatibility Badge Component
Display compatibility score on profile cards:
// components/CompatibilityBadge.tsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
interface CompatibilityBadgeProps {
score: number; // 0-100
size?: 'small' | 'medium' | 'large';
}
export function CompatibilityBadge({ score, size = 'medium' }: CompatibilityBadgeProps) {
const getRating = (score: number): string => {
if (score >= 90) return 'Exceptional';
if (score >= 80) return 'Excellent';
if (score >= 70) return 'Good';
if (score >= 60) return 'Fair';
return 'Challenging';
};
const getColor = (score: number): string => {
if (score >= 90) return '#10B981'; // Green
if (score >= 80) return '#3B82F6'; // Blue
if (score >= 70) return '#8B5CF6'; // Purple
if (score >= 60) return '#F59E0B'; // Amber
return '#EF4444'; // Red
};
const getEmoji = (score: number): string => {
if (score >= 90) return '✨';
if (score >= 80) return '💫';
if (score >= 70) return '⭐';
if (score >= 60) return '🌟';
return '💔';
};
const color = getColor(score);
const rating = getRating(score);
const emoji = getEmoji(score);
const sizes = {
small: { badge: 60, text: 20, label: 10 },
medium: { badge: 80, text: 28, label: 12 },
large: { badge: 120, text: 40, label: 14 },
};
const dimensions = sizes[size];
return (
<View style={styles.container}>
<View
style={[
styles.badge,
{
width: dimensions.badge,
height: dimensions.badge,
backgroundColor: color,
},
]}
>
<Text style={[styles.emoji, { fontSize: dimensions.text - 8 }]}>{emoji}</Text>
<Text style={[styles.score, { fontSize: dimensions.text }]}>{score}</Text>
<Text style={[styles.label, { fontSize: dimensions.label }]}>{rating}</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
},
badge: {
borderRadius: 100,
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 6,
},
emoji: {
color: '#FFFFFF',
marginBottom: -4,
},
score: {
color: '#FFFFFF',
fontWeight: 'bold',
},
label: {
color: '#FFFFFF',
fontWeight: '600',
marginTop: -2,
textTransform: 'uppercase',
letterSpacing: 0.5,
},
});
Step 4: Compatibility Detail Screen
Show detailed compatibility analysis:
// screens/CompatibilityScreen.tsx
import React, { useEffect, useState } from 'react';
import { ScrollView, View, Text, StyleSheet, ActivityIndicator } from 'react-native';
import { CompatibilityBadge } from '../components/CompatibilityBadge';
import { calculateCompatibility, type CompatibilityResult } from '../lib/compatibility-api';
interface CompatibilityScreenProps {
currentUserId: string;
matchUserId: string;
}
export function CompatibilityScreen({
currentUserId,
matchUserId,
}: CompatibilityScreenProps) {
const [compatibility, setCompatibility] = useState<CompatibilityResult | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadCompatibility() {
try {
// Fetch both users' numerology data
const [user1, user2] = await Promise.all([
getUserNumerology(currentUserId),
getUserNumerology(matchUserId),
]);
// Calculate compatibility
const result = await calculateCompatibility(
{
lifePath: user1.lifePath,
expression: user1.expression,
soulUrge: user1.soulUrge,
},
{
lifePath: user2.lifePath,
expression: user2.expression,
soulUrge: user2.soulUrge,
}
);
setCompatibility(result);
} catch (error) {
console.error('Failed to load compatibility:', error);
} finally {
setLoading(false);
}
}
loadCompatibility();
}, [currentUserId, matchUserId]);
if (loading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#6B46C1" />
</View>
);
}
if (!compatibility) return null;
return (
<ScrollView style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>Numerology Compatibility</Text>
<CompatibilityBadge score={compatibility.overallScore} size="large" />
<Text style={styles.rating}>{compatibility.rating} Match</Text>
</View>
{/* Individual Aspects */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Compatibility Breakdown</Text>
<View style={styles.aspectCard}>
<View style={styles.aspectHeader}>
<Text style={styles.aspectTitle}>Life Path</Text>
<Text style={styles.aspectScore}>{compatibility.lifePath.compatibility}%</Text>
</View>
<Text style={styles.aspectNumbers}>
{compatibility.lifePath.person1} + {compatibility.lifePath.person2}
</Text>
<Text style={styles.aspectDescription}>
{compatibility.lifePath.description}
</Text>
<View style={styles.weightBadge}>
<Text style={styles.weightText}>50% weight</Text>
</View>
</View>
<View style={styles.aspectCard}>
<View style={styles.aspectHeader}>
<Text style={styles.aspectTitle}>Expression</Text>
<Text style={styles.aspectScore}>{compatibility.expression.compatibility}%</Text>
</View>
<Text style={styles.aspectNumbers}>
{compatibility.expression.person1} + {compatibility.expression.person2}
</Text>
<Text style={styles.aspectDescription}>
{compatibility.expression.description}
</Text>
<View style={styles.weightBadge}>
<Text style={styles.weightText}>30% weight</Text>
</View>
</View>
<View style={styles.aspectCard}>
<View style={styles.aspectHeader}>
<Text style={styles.aspectTitle}>Soul Urge</Text>
<Text style={styles.aspectScore}>{compatibility.soulUrge.compatibility}%</Text>
</View>
<Text style={styles.aspectNumbers}>
{compatibility.soulUrge.person1} + {compatibility.soulUrge.person2}
</Text>
<Text style={styles.aspectDescription}>
{compatibility.soulUrge.description}
</Text>
<View style={styles.weightBadge}>
<Text style={styles.weightText}>20% weight</Text>
</View>
</View>
</View>
{/* Strengths */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>💪 Relationship Strengths</Text>
<View style={styles.listCard}>
{compatibility.strengths.map((strength, index) => (
<View key={index} style={styles.listItem}>
<Text style={styles.bullet}>✓</Text>
<Text style={styles.listText}>{strength}</Text>
</View>
))}
</View>
</View>
{/* Challenges */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>⚠️ Growth Areas</Text>
<View style={styles.listCard}>
{compatibility.challenges.map((challenge, index) => (
<View key={index} style={styles.listItem}>
<Text style={styles.bullet}>•</Text>
<Text style={styles.listText}>{challenge}</Text>
</View>
))}
</View>
</View>
{/* Advice */}
<View style={styles.adviceCard}>
<Text style={styles.adviceTitle}>💡 Relationship Guidance</Text>
<Text style={styles.adviceText}>{compatibility.advice}</Text>
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F9FAFB',
},
loadingContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
header: {
alignItems: 'center',
padding: 32,
backgroundColor: '#FFFFFF',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#111827',
marginBottom: 24,
},
rating: {
fontSize: 18,
fontWeight: '600',
color: '#6B7280',
marginTop: 16,
},
section: {
padding: 16,
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#111827',
marginBottom: 16,
},
aspectCard: {
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 20,
marginBottom: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
},
aspectHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
aspectTitle: {
fontSize: 18,
fontWeight: '600',
color: '#111827',
},
aspectScore: {
fontSize: 24,
fontWeight: 'bold',
color: '#6B46C1',
},
aspectNumbers: {
fontSize: 14,
color: '#6B7280',
marginBottom: 12,
},
aspectDescription: {
fontSize: 14,
color: '#4B5563',
lineHeight: 22,
marginBottom: 12,
},
weightBadge: {
backgroundColor: '#F3F4F6',
borderRadius: 6,
paddingHorizontal: 10,
paddingVertical: 4,
alignSelf: 'flex-start',
},
weightText: {
fontSize: 12,
color: '#6B7280',
fontWeight: '600',
},
listCard: {
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 20,
},
listItem: {
flexDirection: 'row',
marginBottom: 12,
},
bullet: {
fontSize: 16,
color: '#6B46C1',
marginRight: 12,
fontWeight: 'bold',
},
listText: {
flex: 1,
fontSize: 14,
color: '#4B5563',
lineHeight: 22,
},
adviceCard: {
backgroundColor: '#FEF3C7',
borderRadius: 16,
padding: 20,
margin: 16,
marginTop: 0,
},
adviceTitle: {
fontSize: 16,
fontWeight: '600',
color: '#92400E',
marginBottom: 10,
},
adviceText: {
fontSize: 14,
color: '#78350F',
lineHeight: 22,
},
});
Step 5: Matchmaking Algorithm Integration
Filter and rank matches by compatibility:
// lib/matchmaking.ts
export interface PotentialMatch {
userId: string;
profile: UserProfile;
numerology: UserNumerology;
compatibilityScore?: number;
}
export async function findCompatibleMatches(
currentUser: UserNumerology,
candidatePool: PotentialMatch[],
minScore: number = 70
): Promise<PotentialMatch[]> {
// Calculate compatibility for all candidates
const matchesWithScores = await Promise.all(
candidatePool.map(async (candidate) => {
const compatibility = await calculateCompatibility(
{
lifePath: currentUser.lifePath,
expression: currentUser.expression,
soulUrge: currentUser.soulUrge,
},
{
lifePath: candidate.numerology.lifePath,
expression: candidate.numerology.expression,
soulUrge: candidate.numerology.soulUrge,
}
);
return {
...candidate,
compatibilityScore: compatibility.overallScore,
};
})
);
// Filter by minimum score and sort by compatibility
return matchesWithScores
.filter((match) => match.compatibilityScore! >= minScore)
.sort((a, b) => b.compatibilityScore! - a.compatibilityScore!);
}
// Pre-calculate compatibility matrix for efficiency
export async function buildCompatibilityMatrix(
userId: string,
potentialMatchIds: string[]
): Promise<Map<string, number>> {
const matrix = new Map<string, number>();
const currentUser = await getUserNumerology(userId);
await Promise.all(
potentialMatchIds.map(async (matchId) => {
const matchUser = await getUserNumerology(matchId);
const compatibility = await calculateCompatibility(
{
lifePath: currentUser.lifePath,
expression: currentUser.expression,
soulUrge: currentUser.soulUrge,
},
{
lifePath: matchUser.lifePath,
expression: matchUser.expression,
soulUrge: matchUser.soulUrge,
}
);
matrix.set(matchId, compatibility.overallScore);
// Cache in Redis for fast retrieval
await redis.setex(
`compatibility:${userId}:${matchId}`,
86400 * 7, // 7 days
compatibility.overallScore
);
})
);
return matrix;
}
Step 6: Match Feed with Compatibility Sorting
Display matches sorted by compatibility:
// screens/MatchFeedScreen.tsx
import React, { useEffect, useState } from 'react';
import { FlatList, View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
import { CompatibilityBadge } from '../components/CompatibilityBadge';
import { findCompatibleMatches, type PotentialMatch } from '../lib/matchmaking';
export function MatchFeedScreen({ currentUserId }: { currentUserId: string }) {
const [matches, setMatches] = useState<PotentialMatch[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadMatches() {
try {
const currentUser = await getUserNumerology(currentUserId);
const allCandidates = await fetchPotentialMatches(currentUserId);
const compatibleMatches = await findCompatibleMatches(currentUser, allCandidates, 60);
setMatches(compatibleMatches);
} catch (error) {
console.error('Failed to load matches:', error);
} finally {
setLoading(false);
}
}
loadMatches();
}, [currentUserId]);
const renderMatch = ({ item }: { item: PotentialMatch }) => (
<TouchableOpacity
style={styles.matchCard}
onPress={() => navigation.navigate('Profile', { userId: item.userId })}
>
<Image source={{ uri: item.profile.photoUrl }} style={styles.photo} />
<View style={styles.matchInfo}>
<View style={styles.matchHeader}>
<Text style={styles.name}>{item.profile.name}</Text>
<CompatibilityBadge score={item.compatibilityScore!} size="small" />
</View>
<Text style={styles.bio} numberOfLines={2}>
{item.profile.bio}
</Text>
<View style={styles.numerologyRow}>
<View style={styles.numberBadge}>
<Text style={styles.numberLabel}>Life Path</Text>
<Text style={styles.numberValue}>{item.numerology.lifePath}</Text>
</View>
<View style={styles.numberBadge}>
<Text style={styles.numberLabel}>Expression</Text>
<Text style={styles.numberValue}>{item.numerology.expression}</Text>
</View>
<View style={styles.numberBadge}>
<Text style={styles.numberLabel}>Soul Urge</Text>
<Text style={styles.numberValue}>{item.numerology.soulUrge}</Text>
</View>
</View>
</View>
</TouchableOpacity>
);
return (
<FlatList
data={matches}
renderItem={renderMatch}
keyExtractor={(item) => item.userId}
contentContainerStyle={styles.list}
ListEmptyComponent={
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>No compatible matches yet</Text>
</View>
}
/>
);
}
const styles = StyleSheet.create({
list: {
padding: 16,
},
matchCard: {
flexDirection: 'row',
backgroundColor: '#FFFFFF',
borderRadius: 16,
padding: 16,
marginBottom: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
},
photo: {
width: 100,
height: 100,
borderRadius: 12,
marginRight: 16,
},
matchInfo: {
flex: 1,
},
matchHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
name: {
fontSize: 18,
fontWeight: '600',
color: '#111827',
},
bio: {
fontSize: 14,
color: '#6B7280',
lineHeight: 20,
marginBottom: 12,
},
numerologyRow: {
flexDirection: 'row',
gap: 8,
},
numberBadge: {
backgroundColor: '#F3F4F6',
borderRadius: 8,
paddingHorizontal: 8,
paddingVertical: 4,
},
numberLabel: {
fontSize: 10,
color: '#6B7280',
fontWeight: '600',
},
numberValue: {
fontSize: 16,
color: '#6B46C1',
fontWeight: 'bold',
textAlign: 'center',
},
emptyContainer: {
padding: 40,
alignItems: 'center',
},
emptyText: {
fontSize: 16,
color: '#6B7280',
},
});
Step 7: Gamification and Engagement
Add achievement badges for compatibility milestones:
// lib/achievements.ts
export interface CompatibilityAchievement {
id: string;
title: string;
description: string;
icon: string;
unlockedAt: Date;
}
export async function checkCompatibilityAchievements(
userId: string,
compatibility: CompatibilityResult
): Promise<CompatibilityAchievement[]> {
const achievements: CompatibilityAchievement[] = [];
// Exceptional match
if (compatibility.overallScore >= 95) {
achievements.push({
id: 'soulmate_connection',
title: 'Soulmate Connection',
description: 'Found a 95+ compatibility match',
icon: '💖',
unlockedAt: new Date(),
});
}
// Perfect Life Path
if (compatibility.lifePath.compatibility === 100) {
achievements.push({
id: 'perfect_path',
title: 'Perfect Life Path',
description: 'Matched with perfect Life Path compatibility',
icon: '🎯',
unlockedAt: new Date(),
});
}
// Spiritual Alignment
if (compatibility.soulUrge.compatibility >= 95) {
achievements.push({
id: 'soul_alignment',
title: 'Soul Alignment',
description: 'Found deep spiritual compatibility',
icon: '✨',
unlockedAt: new Date(),
});
}
// Save achievements
for (const achievement of achievements) {
await prisma.userAchievement.create({
data: {
userId,
achievementId: achievement.id,
unlockedAt: achievement.unlockedAt,
},
});
}
return achievements;
}
Performance Optimization
1. Caching Strategy
// Cache compatibility scores in Redis
export async function getCachedCompatibility(
userId1: string,
userId2: string
): Promise<number | null> {
// Ensure consistent cache key ordering
const [id1, id2] = [userId1, userId2].sort();
const cacheKey = `compat:${id1}:${id2}`;
const cached = await redis.get(cacheKey);
return cached ? parseInt(cached, 10) : null;
}
export async function cacheCompatibility(
userId1: string,
userId2: string,
score: number
): Promise<void> {
const [id1, id2] = [userId1, userId2].sort();
const cacheKey = `compat:${id1}:${id2}`;
await redis.setex(cacheKey, 86400 * 7, score); // 7 days TTL
}
2. Background Pre-calculation
// Pre-calculate compatibility for likely matches
export async function precalculateMatches(userId: string): Promise<void> {
const user = await getUserNumerology(userId);
const potentialMatches = await fetchPotentialMatches(userId, 100);
await Promise.all(
potentialMatches.map(async (match) => {
const compatibility = await calculateCompatibility(
{
lifePath: user.lifePath,
expression: user.expression,
soulUrge: user.soulUrge,
},
{
lifePath: match.numerology.lifePath,
expression: match.numerology.expression,
soulUrge: match.numerology.soulUrge,
}
);
await cacheCompatibility(userId, match.userId, compatibility.overallScore);
})
);
}
Pricing and Cost Optimization
RoxyAPI charges per request. Optimize costs:
- Cache aggressively: Compatibility does not change, cache indefinitely
- Batch calculations: Pre-calculate top 100 matches in background
- Lazy loading: Only calculate when user views profile
- Smart filtering: Apply basic filters before compatibility calculation
Cost example (1000 active users, 20 matches viewed per user per month):
- 1000 users × 20 views = 20,000 API calls/month
- Starter plan ($19/month): 10,000 requests included + 10,000 overage at $0.002 = $39/month total
- With caching: ~5,000 unique calculations = $19/month (under included quota)
Production Checklist
- Calculate user numerology on profile creation
- Cache compatibility scores for 7 days minimum
- Pre-calculate top 100 matches in background jobs
- Show compatibility on profile cards before opening
- Add compatibility filter to match search
- Track engagement metrics (views, messages after viewing compatibility)
- A/B test compatibility badge placement
- Monitor API usage and costs
- Implement fallback UI when API fails
- Rate limit compatibility calculations per user
Next Steps
Expand your compatibility features:
- Friendship Matching: Use same algorithm for platonic connections
- Team Building: Apply to professional networking and team formation
- Group Compatibility: Calculate dynamics for group settings
- Compatibility Timeline: Show how compatibility changes over relationship cycles
- Custom Weightings: Let users adjust Life Path/Expression/Soul Urge importance
Combine with other APIs:
- Tarot API for relationship card readings
- Western Astrology API for synastry charts
- Vedic Astrology API for Vedic compatibility
Conclusion
Numerology compatibility is a powerful differentiator for dating and relationship apps. With minimal integration effort, you gain a mystical feature that increases engagement, improves match quality, and creates conversation starters.
The key is balancing mysticism with practical insights, using compatibility as a conversation enhancer rather than a strict filter. Most successful apps show compatibility alongside traditional factors, letting users weigh multiple dimensions.
Start building: Sign up for RoxyAPI and add compatibility features today. Full documentation at roxyapi.com/docs.