Skip to main content
Glama

Facebook Ads Management Control Panel

by codprocess
facebookApiService.js16.3 kB
/** * Facebook API Service * Handles interactions with the Facebook Marketing API */ const axios = require('axios'); const { URLSearchParams } = require('url'); const { FacebookApiError } = require('../utils/errorTypes'); const tokenManager = require('../utils/tokenManager'); const facebookConfig = require('../config/facebook'); const logger = require('../utils/logger'); /** * Facebook API Service class */ class FacebookApiService { /** * Create a new Facebook API Service instance * @param {string} accessToken - Facebook access token */ constructor(accessToken) { this.accessToken = accessToken; this.baseUrl = `${facebookConfig.endpoints.base}/${facebookConfig.apiVersion}`; this.requestDefaults = facebookConfig.requestDefaults; } /** * Make a GET request to the Facebook API * @param {string} endpoint - API endpoint * @param {Object} params - Query parameters * @param {Object} options - Request options * @returns {Promise<Object>} - API response */ async get(endpoint, params = {}, options = {}) { try { const url = this._buildUrl(endpoint); const queryParams = { access_token: this.accessToken, ...params }; const config = { params: queryParams, timeout: options.timeout || this.requestDefaults.timeout }; const response = await this._makeRequest(() => axios.get(url, config)); return response.data; } catch (error) { throw this._handleApiError(error); } } /** * Make a POST request to the Facebook API * @param {string} endpoint - API endpoint * @param {Object} data - Request body * @param {Object} params - Query parameters * @param {Object} options - Request options * @returns {Promise<Object>} - API response */ async post(endpoint, data = {}, params = {}, options = {}) { try { const url = this._buildUrl(endpoint); const queryParams = { access_token: this.accessToken, ...params }; const config = { params: queryParams, timeout: options.timeout || this.requestDefaults.timeout }; const response = await this._makeRequest(() => axios.post(url, data, config)); return response.data; } catch (error) { throw this._handleApiError(error); } } /** * Make a DELETE request to the Facebook API * @param {string} endpoint - API endpoint * @param {Object} params - Query parameters * @param {Object} options - Request options * @returns {Promise<Object>} - API response */ async delete(endpoint, params = {}, options = {}) { try { const url = this._buildUrl(endpoint); const queryParams = { access_token: this.accessToken, ...params }; const config = { params: queryParams, timeout: options.timeout || this.requestDefaults.timeout }; const response = await this._makeRequest(() => axios.delete(url, config)); return response.data; } catch (error) { throw this._handleApiError(error); } } /** * Make a batch request to the Facebook API * @param {Array} requests - Array of request objects * @param {Object} options - Request options * @returns {Promise<Array>} - Array of API responses */ async batch(requests, options = {}) { try { const url = `${this.baseUrl}/`; const batchSize = options.batchSize || this.requestDefaults.maxBatchSize; // Split requests into batches const batches = []; for (let i = 0; i < requests.length; i += batchSize) { batches.push(requests.slice(i, i + batchSize)); } // Process each batch const results = []; for (const batch of batches) { const batchRequests = batch.map(request => ({ method: request.method || 'GET', relative_url: request.relative_url, body: request.body, name: request.name })); const params = new URLSearchParams(); params.append('access_token', this.accessToken); params.append('batch', JSON.stringify(batchRequests)); const response = await this._makeRequest(() => axios.post(url, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: options.timeout || this.requestDefaults.timeout }) ); // Process batch response const batchResults = response.data.map(result => { if (result === null) { return { error: { message: 'Batch request failed' } }; } if (result.code !== 200) { return { error: { message: result.body, code: result.code } }; } try { return JSON.parse(result.body); } catch (e) { return { body: result.body }; } }); results.push(...batchResults); } return results; } catch (error) { throw this._handleApiError(error); } } /** * Get user ad accounts * @returns {Promise<Array>} - Array of ad accounts */ async getAdAccounts() { const fields = [ 'id', 'name', 'account_id', 'account_status', 'age', 'business_city', 'business_country_code', 'business_name', 'business_street', 'business_street2', 'business_zip', 'currency', 'timezone_name', 'timezone_offset_hours_utc', 'capabilities', 'spend_cap', 'amount_spent', 'balance' ].join(','); const response = await this.get(facebookConfig.endpoints.adAccounts, { fields, limit: 100 }); return response.data || []; } /** * Get campaigns for an ad account * @param {string} adAccountId - Ad account ID * @param {Object} params - Query parameters * @returns {Promise<Array>} - Array of campaigns */ async getCampaigns(adAccountId, params = {}) { const fields = [ 'id', 'name', 'objective', 'status', 'special_ad_categories', 'spend_cap', 'daily_budget', 'lifetime_budget', 'start_time', 'stop_time', 'created_time', 'updated_time' ].join(','); const response = await this.get(`act_${adAccountId}/campaigns`, { fields, limit: params.limit || 100, ...params }); return response.data || []; } /** * Get ad sets for a campaign * @param {string} campaignId - Campaign ID * @param {Object} params - Query parameters * @returns {Promise<Array>} - Array of ad sets */ async getAdSets(campaignId, params = {}) { const fields = [ 'id', 'name', 'campaign_id', 'status', 'targeting', 'optimization_goal', 'bid_strategy', 'bid_amount', 'budget_remaining', 'daily_budget', 'lifetime_budget', 'start_time', 'end_time', 'created_time', 'updated_time' ].join(','); const response = await this.get(`${campaignId}/adsets`, { fields, limit: params.limit || 100, ...params }); return response.data || []; } /** * Get ads for an ad set * @param {string} adSetId - Ad set ID * @param {Object} params - Query parameters * @returns {Promise<Array>} - Array of ads */ async getAds(adSetId, params = {}) { const fields = [ 'id', 'name', 'adset_id', 'campaign_id', 'status', 'creative', 'preview_url', 'created_time', 'updated_time' ].join(','); const response = await this.get(`${adSetId}/ads`, { fields, limit: params.limit || 100, ...params }); return response.data || []; } /** * Get insights for an entity (ad account, campaign, ad set, or ad) * @param {string} entityId - Entity ID * @param {string} timeRange - Time range (e.g., 'today', 'yesterday', 'last_7_days') * @param {Array} metrics - Array of metrics to retrieve * @param {Object} params - Additional parameters * @returns {Promise<Array>} - Array of insights */ async getInsights(entityId, timeRange, metrics = [], params = {}) { const defaultMetrics = [ 'impressions', 'reach', 'clicks', 'ctr', 'cpc', 'cpm', 'spend', 'frequency', 'conversions', 'cost_per_conversion', 'conversion_rate_ranking', 'quality_ranking', 'engagement_rate_ranking' ]; const fields = metrics.length > 0 ? metrics : defaultMetrics; const response = await this.get(`${entityId}/insights`, { fields: fields.join(','), time_range: this._formatTimeRange(timeRange), level: params.level || 'ad', limit: params.limit || 100, ...params }); return response.data || []; } /** * Create a campaign * @param {string} adAccountId - Ad account ID * @param {Object} campaignData - Campaign data * @returns {Promise<Object>} - Created campaign */ async createCampaign(adAccountId, campaignData) { const response = await this.post(`act_${adAccountId}/campaigns`, campaignData); return response; } /** * Update a campaign * @param {string} campaignId - Campaign ID * @param {Object} campaignData - Campaign data * @returns {Promise<Object>} - Updated campaign */ async updateCampaign(campaignId, campaignData) { const response = await this.post(campaignId, campaignData); return response; } /** * Delete a campaign * @param {string} campaignId - Campaign ID * @returns {Promise<Object>} - Deletion response */ async deleteCampaign(campaignId) { const response = await this.delete(campaignId); return response; } /** * Create an ad set * @param {string} adAccountId - Ad account ID * @param {Object} adSetData - Ad set data * @returns {Promise<Object>} - Created ad set */ async createAdSet(adAccountId, adSetData) { const response = await this.post(`act_${adAccountId}/adsets`, adSetData); return response; } /** * Update an ad set * @param {string} adSetId - Ad set ID * @param {Object} adSetData - Ad set data * @returns {Promise<Object>} - Updated ad set */ async updateAdSet(adSetId, adSetData) { const response = await this.post(adSetId, adSetData); return response; } /** * Delete an ad set * @param {string} adSetId - Ad set ID * @returns {Promise<Object>} - Deletion response */ async deleteAdSet(adSetId) { const response = await this.delete(adSetId); return response; } /** * Create an ad * @param {string} adAccountId - Ad account ID * @param {Object} adData - Ad data * @returns {Promise<Object>} - Created ad */ async createAd(adAccountId, adData) { const response = await this.post(`act_${adAccountId}/ads`, adData); return response; } /** * Update an ad * @param {string} adId - Ad ID * @param {Object} adData - Ad data * @returns {Promise<Object>} - Updated ad */ async updateAd(adId, adData) { const response = await this.post(adId, adData); return response; } /** * Delete an ad * @param {string} adId - Ad ID * @returns {Promise<Object>} - Deletion response */ async deleteAd(adId) { const response = await this.delete(adId); return response; } /** * Build a URL for the Facebook API * @param {string} endpoint - API endpoint * @returns {string} - Full URL * @private */ _buildUrl(endpoint) { // Remove leading slash if present const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; return `${this.baseUrl}/${cleanEndpoint}`; } /** * Make a request with retry logic * @param {Function} requestFn - Request function * @returns {Promise<Object>} - API response * @private */ async _makeRequest(requestFn) { let retries = 0; const maxRetries = this.requestDefaults.retries; const retryDelay = this.requestDefaults.retryDelay; while (true) { try { return await requestFn(); } catch (error) { // Don't retry if we've reached the maximum number of retries if (retries >= maxRetries) { throw error; } // Don't retry for certain error types if (error.response) { const status = error.response.status; // Don't retry for client errors (except rate limiting) if (status >= 400 && status < 500 && status !== 429) { throw error; } } // Increment retry count retries++; // Calculate delay with exponential backoff const delay = retryDelay * Math.pow(2, retries - 1); // Log retry attempt logger.warn(`Retrying Facebook API request (${retries}/${maxRetries}) after ${delay}ms`); // Wait before retrying await new Promise(resolve => setTimeout(resolve, delay)); } } } /** * Handle API errors * @param {Error} error - API error * @returns {Error} - Formatted error * @private */ _handleApiError(error) { let message = 'Facebook API error'; let statusCode = 500; let fbErrorCode = null; let fbErrorSubcode = null; if (error.response) { statusCode = error.response.status; if (error.response.data && error.response.data.error) { const fbError = error.response.data.error; message = fbError.message || message; fbErrorCode = fbError.code; fbErrorSubcode = fbError.error_subcode; logger.error(`Facebook API error: ${message} (Code: ${fbErrorCode}, Subcode: ${fbErrorSubcode})`); } } else if (error.request) { message = 'No response received from Facebook API'; logger.error(`Facebook API request error: ${message}`); } else { message = error.message; logger.error(`Facebook API error: ${message}`); } return new FacebookApiError(message, statusCode, fbErrorCode, fbErrorSubcode); } /** * Format time range for insights API * @param {string|Object} timeRange - Time range string or object * @returns {Object} - Formatted time range * @private */ _formatTimeRange(timeRange) { if (typeof timeRange === 'object') { return timeRange; } const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); switch (timeRange) { case 'today': return { since: today.toISOString().split('T')[0], until: now.toISOString().split('T')[0] }; case 'yesterday': { const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); return { since: yesterday.toISOString().split('T')[0], until: yesterday.toISOString().split('T')[0] }; } case 'last_7_days': { const lastWeek = new Date(today); lastWeek.setDate(lastWeek.getDate() - 7); return { since: lastWeek.toISOString().split('T')[0], until: today.toISOString().split('T')[0] }; } case 'last_30_days': { const lastMonth = new Date(today); lastMonth.setDate(lastMonth.getDate() - 30); return { since: lastMonth.toISOString().split('T')[0], until: today.toISOString().split('T')[0] }; } default: return { since: today.toISOString().split('T')[0], until: today.toISOString().split('T')[0] }; } } } /** * Create a Facebook API Service instance for a user * @param {Object} user - User object * @returns {Promise<FacebookApiService>} - Facebook API Service instance */ const createForUser = async (user) => { if (!user || !user.accessToken) { throw new Error('User access token is required'); } const accessToken = await tokenManager.decryptToken(user.accessToken); return new FacebookApiService(accessToken); }; module.exports = { FacebookApiService, createForUser };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/codprocess/facebook-ads-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server