Building a Real Estate Agent Directory with BayutAPI
Real estate in the UAE is a relationship-driven business. Buyers and renters want to find agents who specialize in their preferred neighborhoods, handle their property type, and have a track record of active listings. An agent directory serves this need — and BayutAPI provides the data to build one.
In this tutorial, we will build an agent directory backend using Node.js that fetches agent and agency data, organizes it, and serves it through an Express API.
Project Setup
Initialize a new Node.js project:
mkdir agent-directory
cd agent-directory
npm init -y
npm install express node-fetch
Create the main file server.js:
import express from 'express';
const app = express();
const PORT = 3000;
const API_BASE = 'https://bayut14.p.rapidapi.com/v2';
const HEADERS = {
'x-rapidapi-host': 'bayut14.p.rapidapi.com',
'x-rapidapi-key': 'YOUR_API_KEY'
};
app.listen(PORT, () => {
console.log(`Agent directory running on port ${PORT}`);
});
Make sure your package.json includes "type": "module" for ES module imports.
Fetching Agency Listings
The first step is to fetch listings associated with a specific agency. This gives us both the agency’s property portfolio and the individual agents who work there:
async function fetchAgencyListings(agencyExternalId, page = 1) {
const url = new URL(`${API_BASE}/agency-listings`);
url.searchParams.set('agency_external_id', agencyExternalId);
url.searchParams.set('page', String(page));
const response = await fetch(url, { headers: HEADERS });
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return response.json();
}
The response includes listings with embedded agent information:
{
"success": true,
"data": {
"total": 342,
"properties": [
{
"externalID": "7891234",
"title": { "en": "Luxurious 3BR with Burj Khalifa View" },
"price": 3200000,
"rooms": 3,
"baths": 4,
"area": 2100,
"purpose": "for-sale",
"contactName": "Ahmed Hassan",
"phoneNumber": { "phone": "+971501234567", "mobile": "+971501234567" },
"agency": {
"name": "Premier Properties Real Estate",
"slug": "premier-properties-real-estate",
"logo": { "url": "https://..." }
},
"location": [
{ "name": "UAE" },
{ "name": "Dubai" },
{ "name": "Downtown Dubai" }
]
}
]
}
}
Extracting Agent Profiles
From the listings, we can build agent profiles by grouping listings by agent name:
function extractAgents(listings) {
const agentMap = new Map();
for (const listing of listings) {
const agentName = listing.contactName || 'Unknown Agent';
if (!agentMap.has(agentName)) {
agentMap.set(agentName, {
name: agentName,
phone: listing.phoneNumber?.phone || null,
mobile: listing.phoneNumber?.mobile || null,
agency: listing.agency?.name || 'Independent',
agencySlug: listing.agency?.slug || null,
listings: [],
areas: new Set(),
propertyTypes: new Set(),
purposes: new Set()
});
}
const agent = agentMap.get(agentName);
agent.listings.push({
id: listing.externalID,
title: listing.title?.en,
price: listing.price,
rooms: listing.rooms,
baths: listing.baths,
area: listing.area,
purpose: listing.purpose,
location: listing.location?.map(l => l.name).join(' > ')
});
// Track the areas and types this agent handles
const neighborhood = listing.location?.[2]?.name;
if (neighborhood) agent.areas.add(neighborhood);
const category = listing.category?.[1]?.name;
if (category) agent.propertyTypes.add(category);
agent.purposes.add(listing.purpose);
}
// Convert Sets to Arrays for JSON serialization
return Array.from(agentMap.values()).map(agent => ({
...agent,
areas: Array.from(agent.areas),
propertyTypes: Array.from(agent.propertyTypes),
purposes: Array.from(agent.purposes),
listingCount: agent.listings.length,
avgPrice: agent.listings.reduce((sum, l) => sum + (l.price || 0), 0) / agent.listings.length
}));
}
Building the API Routes
Now let’s create Express routes to serve agent data:
// Get all agents for an agency
app.get('/api/agency/:slug/agents', async (req, res) => {
try {
const { slug } = req.params;
const allListings = [];
let page = 1;
let totalHits = Infinity;
// Fetch all pages (up to a limit)
while (allListings.length < totalHits && page <= 10) {
const data = await fetchAgencyListings(slug, page);
totalHits = data.data?.total || 0;
const properties = data.data?.properties || [];
if (properties.length === 0) break;
allListings.push(...properties);
page++;
}
const agents = extractAgents(allListings);
// Sort by listing count (most active first)
agents.sort((a, b) => b.listingCount - a.listingCount);
res.json({
agency: slug,
totalListings: allListings.length,
agentCount: agents.length,
agents: agents.map(({ listings, ...profile }) => profile)
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Get a specific agent's listings
app.get('/api/agency/:slug/agents/:agentName', async (req, res) => {
try {
const { slug, agentName } = req.params;
const decodedName = decodeURIComponent(agentName);
const allListings = [];
let page = 1;
// Fetch pages until we find the agent
while (page <= 10) {
const data = await fetchAgencyListings(slug, page);
const properties = data.data?.properties || [];
if (properties.length === 0) break;
allListings.push(...properties);
page++;
}
const agents = extractAgents(allListings);
const agent = agents.find(
a => a.name.toLowerCase() === decodedName.toLowerCase()
);
if (!agent) {
return res.status(404).json({ error: 'Agent not found' });
}
res.json(agent);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Adding Search and Filtering
Let users search for agents by area or specialty:
// Search agents across agencies
app.get('/api/agents/search', async (req, res) => {
try {
const { area, purpose } = req.query;
if (!area) {
return res.status(400).json({ error: 'Area parameter is required' });
}
// First, find the location ID
const autoUrl = new URL(`${API_BASE}/autocomplete`);
autoUrl.searchParams.set('query', area);
autoUrl.searchParams.set('purpose', purpose || 'for-sale');
const autoResponse = await fetch(autoUrl, { headers: HEADERS });
const autoData = await autoResponse.json();
const locationId = autoData.data?.locations?.[0]?.externalID;
if (!locationId) {
return res.json({ agents: [], message: 'Location not found' });
}
// Search properties in that area to find active agents
const propUrl = new URL(`${API_BASE}/properties`);
propUrl.searchParams.set('location_ids', locationId);
propUrl.searchParams.set('purpose', purpose || 'for-sale');
propUrl.searchParams.set('page', '1');
const propResponse = await fetch(propUrl, { headers: HEADERS });
const propData = await propResponse.json();
const agents = extractAgents(propData.data?.properties || []);
agents.sort((a, b) => b.listingCount - a.listingCount);
res.json({
area,
totalListingsInArea: propData.data?.total || 0,
agents: agents.map(({ listings, ...profile }) => profile)
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Adding a Simple Cache
To avoid hitting the API on every request, add in-memory caching:
const cache = new Map();
const CACHE_TTL = 15 * 60 * 1000; // 15 minutes
function getCached(key) {
const entry = cache.get(key);
if (entry && Date.now() - entry.timestamp < CACHE_TTL) {
return entry.data;
}
cache.delete(key);
return null;
}
function setCache(key, data) {
cache.set(key, { data, timestamp: Date.now() });
}
// Use in routes:
async function fetchAgencyListingsCached(agencyExternalId, page = 1) {
const cacheKey = `agency:${agencyExternalId}:${page}`;
const cached = getCached(cacheKey);
if (cached) return cached;
const data = await fetchAgencyListings(agencyExternalId, page);
setCache(cacheKey, data);
return data;
}
Building the Frontend
For the frontend, here is a simple HTML page that displays agent cards. You can use any framework, but plain HTML works for demonstration:
<!DOCTYPE html>
<html lang="en">
<head>
<title>UAE Real Estate Agent Directory</title>
</head>
<body>
<h1>Agent Directory</h1>
<input type="text" id="areaSearch" placeholder="Search by area (e.g., Dubai Marina)">
<button onclick="searchAgents()">Search</button>
<div id="results"></div>
<script>
async function searchAgents() {
const area = document.getElementById('areaSearch').value;
const response = await fetch(`/api/agents/search?area=${encodeURIComponent(area)}`);
const data = await response.json();
const html = data.agents.map(agent => `
<div style="border:1px solid #333; padding:16px; margin:8px 0; border-radius:8px;">
<h3>${agent.name}</h3>
<p>Agency: ${agent.agency}</p>
<p>Active Listings: ${agent.listingCount}</p>
<p>Areas: ${agent.areas.join(', ')}</p>
<p>Specialties: ${agent.purposes.join(', ')}</p>
${agent.phone ? `<p>Phone: ${agent.phone}</p>` : ''}
</div>
`).join('');
document.getElementById('results').innerHTML = html || '<p>No agents found</p>';
}
</script>
</body>
</html>
Running the Directory
Start the server:
node server.js
Test the endpoints:
# Get agents from a specific agency
curl http://localhost:3000/api/agency/premier-properties-real-estate/agents
# Search agents by area
curl "http://localhost:3000/api/agents/search?area=Dubai%20Marina&purpose=for-sale"
Production Considerations
Before deploying an agent directory to production, consider these factors:
Data freshness. Agent listings change frequently. Cache for 15-30 minutes at most, or set up a background sync that refreshes agent data periodically.
Agent deduplication. The same agent might have slightly different name spellings across listings (e.g., “Ahmed Hassan” vs “Ahmed M. Hassan”). Implement fuzzy matching or normalize names before grouping.
Rate limiting. If your directory gains traction, many users will hit your API simultaneously. Cache aggressively and consider implementing a queue for API calls.
Contact information handling. Agent phone numbers are personal data. Follow relevant privacy regulations and consider showing a “Request Contact” button instead of displaying numbers directly.
Search experience. As your directory grows, add filters for property type, price range, language spoken, and years of experience (if available). The more specific users can be, the more useful the directory becomes.
Next Steps
This tutorial gives you a working agent directory backend. To turn it into a complete product:
- Add a polished frontend with agent profile pages
- Implement user reviews and ratings
- Add agent comparison features
- Build email/WhatsApp integration for contacting agents
- Set up analytics to track which agents get the most inquiries
The agent data from BayutAPI is a starting point. The directory you build around it — with reviews, search, and comparison features — is what creates lasting value for your users.
Ready to Build with UAE Real Estate Data?
Get your API key and start making requests in minutes. Free tier available with 900 requests per month.