Facebook Qubix Bridge
Facebook Qubix Bridge वह SDK है जो उपयोगकर्ता की स्क्रिप्ट को सीधे Facebook से बात करने देता है: स्क्रिप्ट अपना अनुरोध ख़ुद भेजती है — Graph API को या किसी भी अन्य *.facebook.com पते पर — और Qubix उसे पहले से जोड़ी गई ब्राउज़र प्रोफ़ाइल के ज़रिए आगे पहुँचाता है: उस प्रोफ़ाइल के एक्सेस टोकन, कुकीज़, प्रॉक्सी और ब्राउज़र सिग्नेचर के साथ। Facebook का जवाब स्क्रिप्ट तक वैसे का वैसा वापस आता है — बाइनरी बॉडी तक। जो कुछ प्रोफ़ाइल विज्ञापन कैबिनेट में हाथ से कर सकती है, वही अब स्क्रिप्ट शेड्यूल पर कर सकती है: सेटिंग्स और आँकड़े पढ़ना, बजट बदलना, रोकना और शुरू करना, क्रिएटिव डाउनलोड करना, टिप्पणियाँ साफ़ करना।
यहाँ कुछ भी सेट अप करने की ज़रूरत नहीं है: Qubix से जुड़ी प्रोफ़ाइल का मतलब है कि ब्रिज पहले से काम कर रहा है। कोई अनुरोध क्या कर सकता है, यह उस प्रोफ़ाइल के Facebook के भीतर अधिकारों से तय होता है: अगर प्रोफ़ाइल किसी कैंपेन को हाथ से संपादित नहीं कर सकती, तो Facebook स्क्रिप्ट को भी मना कर देगा, और स्क्रिप्ट को यह इनकार शब्दशः दिखेगा।
यह एक पाइप है, और सिर्फ़ एक पाइप: पता, मेथड, पैरामीटर और हेडर आप ख़ुद लिखते हैं, जबकि Qubix कुंजी, कुकीज़, प्रॉक्सी और ब्राउज़र सिग्नेचर उपलब्ध कराता है। यहाँ जान-बूझकर कैंपेन-प्रबंधन के लिए कोई तैयार फ़ंक्शन नहीं दिए गए हैं — वे Facebook के आज के स्वरूप को उत्पाद में हमेशा के लिए जमा देते और उसके पहले ही बदलाव पर टूट जाते। सैंपल से बड़े-स्तर के हिस्से लें और उन्हें अपने हिसाब से ढालें: ब्रिज को अनुरोध के स्वरूप के बारे में कुछ भी पता नहीं होता, और ठीक इसी वजह से यह पुराना नहीं पड़ता।
How a request travels
fb कमांड उपयोगकर्ता स्क्रिप्ट में उपलब्ध है — शेड्यूल पर भी और ▶ Run बटन से भी। यह Britva नियमों में उपलब्ध नहीं है।
Two ways to choose the executor profile
हर अनुरोध किसी न किसी ब्राउज़र प्रोफ़ाइल से भेजा जाता है — निष्पादक। या तो आप इसे ख़ुद नाम देते हैं, या Qubix को चुनने देते हैं।
// a) let Qubix pick: any profile that sees this campaign
const res = fb.campaign(campaignId).request({
method: 'GET',
url: `https://graph.facebook.com/${campaignId}`,
params: { fields: 'name,daily_budget' },
})
// b) a named profile: no picking, no fallbacks
const res2 = fb.profile(profileId).request({
method: 'GET',
url: 'https://graph.facebook.com/me',
params: { fields: 'id,name' },
})
fb.campaign(campaignId)— यह कैंपेन आपकी स्क्रिप्ट के डेटा सेट में होना चाहिए (जो आपकी भूमिका को देखने की अनुमति है)। Qubix उन प्रोफ़ाइलों में से निष्पादक चुनता है जो इस कैंपेन को देख सकती हैं, विज्ञापन के मालिक की प्रोफ़ाइलों को प्राथमिकता देते हुए। अगर कोई उम्मीदवार डिलीवरी स्तर पर बाहर हो जाता है — मरा हुआ प्रॉक्सी, टूटा हुआ कनेक्शन — तो अगली प्रोफ़ाइल आज़माई जाती है, लेकिन केवल रीड के लिए: बदलाव (POST,DELETE) को किसी दूसरी प्रोफ़ाइल के ज़रिए कभी दोबारा नहीं आज़माया जाता, ताकि वह दो बार लागू न हो सके। जैसे ही Facebook जवाब देता है — भले ही इनकार के साथ — खोज रुक जाती है। इस टारगेट मेंgetProfiles()भी होता है — वही प्रोफ़ाइल सूची जो आपके चुनाव के किसी कैंपेन के पास होती है।fb.profile(profileId)— अनुरोध ठीक इसी प्रोफ़ाइल से भेजा जाता है, बिना किसी फ़ॉलबैक के। इस टारगेट में प्रोफ़ाइल के अपने फ़ील्ड भी होते हैं (name,tokenAlive,onCheckpoint, …), इसलिए जब आप कई टारगेट पर लूप चलाते हैं तो उन्हें पहचानना आसान होता है।
Which profiles see an object
कुछ भी भेजने से पहले, स्क्रिप्ट यह पूछ सकती है कि कौन-सी प्रोफ़ाइलें किसी कैंपेन या विज्ञापन पर कार्रवाई कर सकती हैं — साथ में वह सब कुछ जो Qubix उनकी स्थिति के बारे में जानता है:
const profiles = campaign.getProfiles() // also: ad.getProfiles() and fb.campaign(id).getProfiles()
for (const p of profiles) {
console.log(p.name, 'token alive:', p.tokenAlive, 'checkpoint:', p.onCheckpoint)
}
यह एक ऐरे लौटाता है: एक कैंपेन अक्सर कई प्रोफ़ाइलों को दिखता है, Facebook में उनके एक्सेस लेवल अलग-अलग होते हैं, और चुनाव आपका है। हर एंट्री में यह होता है:
| फ़ील्ड | अर्थ |
|---|---|
id | प्रोफ़ाइल पहचानकर्ता — इसे fb.profile(...) में दें |
name | प्रोफ़ाइल का नाम |
group | प्रोफ़ाइल समूह |
ownerBuyerId | वह बायर जिसकी यह प्रोफ़ाइल है |
tokenAlive | क्या एक्सेस टोकन ज़िंदा है |
onCheckpoint | क्या प्रोफ़ाइल Facebook सुरक्षा checkpoint पर अटकी है |
hasProxy | क्या प्रोफ़ाइल में प्रॉक्सी कॉन्फ़िगर की गई है |
यहाँ जान-बूझकर कोई «एक्सेस लेवल» फ़ील्ड नहीं है: Qubix इसे कहीं भी स्टोर नहीं करता। कोई प्रोफ़ाइल किसी ऑब्जेक्ट के साथ क्या कर सकती है, इसका जवाब Facebook ख़ुद देता है — अनुरोध भेजें और जवाब पढ़ें। tokenAlive, onCheckpoint और hasProxy स्थिति (state) हैं, अनुमति नहीं: मरे हुए टोकन वाली प्रोफ़ाइल भी सूची में बनी रहती है ताकि आप उसे देखें और ख़ुद तय करें। आपके डेटा सेट से बाहर का ऑब्जेक्ट खाली ऐरे लौटाता है, त्रुटि नहीं।
The request
request(options) एक ऑब्जेक्ट लेता है:
| विकल्प | अर्थ |
|---|---|
url | अनुरोध का पूरा (absolute) पता: https://graph.facebook.com/${campaignId}, https://graph.facebook.com/act_123/campaigns, https://graph.facebook.com/me/adaccounts. कोई भी *.facebook.com होस्ट काम करता है — ब्रिज प्रोफ़ाइल की कुकीज़ और कुंजी जोड़ देता है, और वे किसी बाहरी होस्ट तक कभी नहीं जातीं |
method | GET (डिफ़ॉल्ट), POST या DELETE |
params | अनुरोध के पैरामीटर एक ऑब्जेक्ट के रूप में; POST में ये बॉडी में जाते हैं, बाक़ी मेथड में — पते में |
headers | आपके अपने हेडर; ये हमारे ऊपर जुड़ते हैं और किसी को भी ओवरराइड कर सकते हैं |
timeoutMs | जवाब का इंतज़ार कितनी देर करना है, मिलीसेकंड में; खाली — ऑपरेटर का डिफ़ॉल्ट, और आप ऊपरी सीमा से ऊपर नहीं जा सकते |
एक्सेस टोकन, कुकीज़, प्रॉक्सी और ब्राउज़र सिग्नेचर प्रोफ़ाइल के अपने ट्रांसपोर्ट द्वारा दिए जाते हैं।
The answer — and the two kinds of refusal
Facebook का जवाब वैसे का वैसा आता है; Qubix इसे पार्स नहीं करता — यह आप करते हैं:
const res = fb.campaign(campaignId).request({
method: 'GET',
url: `https://graph.facebook.com/${campaignId}`,
params: { fields: 'name,daily_budget' },
timeoutMs: 5000,
})
const data = JSON.parse(res.text)
if (res.status >= 400) { // Facebook's refusal — a normal answer with the reason in the body
console.log('Facebook refused:', data.error.message, 'code', data.error.code)
return
}
console.log(data.name, 'daily budget:', data.daily_budget)
| फ़ील्ड | अर्थ |
|---|---|
ok | जवाब आ गया और कोड सफलता का है |
status | Facebook से आया HTTP रिस्पॉन्स कोड |
contentType | जवाब का content type |
headers | रिस्पॉन्स के हेडर — Set-Cookie को छोड़कर सभी; यहीं वह सब कुछ रहता है जो बॉडी में नहीं होता: बची हुई rate limit, Facebook के साथ सपोर्ट केस के लिए अनुरोध पहचानकर्ता |
text | बॉडी वैसे की वैसी, स्ट्रिंग के रूप में — JSON.parse(res.text) |
bytes | वही बॉडी बाइनरी में — इमेज और डाउनलोड के लिए |
profileId | किस ब्राउज़र प्रोफ़ाइल ने अनुरोध भेजा |
दो तरह के इनकार होते हैं, और वे अलग-अलग तरह से आते हैं:
- Qubix डिलीवर नहीं कर सका — मरा हुआ प्रॉक्सी, टूटा हुआ कनेक्शन, आपके डेटा सेट से बाहर का कैंपेन — त्रुटि के रूप में throw होता है: अगर आप चाहते हैं कि लूप चलता रहे तो इसे
try/catchसे पकड़ें।fb.campaign(...)के ज़रिए रीड के लिए, इस बिंदु तक फ़ॉलबैक प्रोफ़ाइलें पहले ही आज़माई जा चुकी होती हैं। - Facebook ने इनकार के साथ जवाब दिया — अधिकार नहीं, ग़लत फ़ील्ड, समाप्त हो चुका सेशन — एक सामान्य जवाब के रूप में लौटता है,
res.status >= 400के साथ, और वजह बॉडी में होती है:JSON.parse(res.text).errorमेंmessageऔरcodeहोते हैं, और जबerror_user_titleऔरerror_user_msgमौजूद हों, तो यह पहले से तैयार इंसानी शब्दों में वजह होती है।
एक तीन-पंक्ति का हेल्पर, ताकि हर रेसिपी में पार्सिंग दोहरानी न पड़े:
function graph(res) {
const data = res.text ? JSON.parse(res.text) : {}
if (res.status >= 400) throw new Error('Facebook: ' + ((data.error && data.error.message) || res.status))
return data
}
Facebook रीड में देरी के साथ जवाब देता है: सफल बदलाव के तुरंत बाद पढ़ा गया मान अब भी पुराना हो सकता है। इसे विफल राइट न समझें। अगले रन में नया मान जाँचें, या जो आपने सेट किया था उसे ctx.state में याद रखें।
Batch: many requests in one exchange
हर प्रोफ़ाइल की कतार एक अंतराल में एक ही अनुरोध जाने देती है — सौ अलग-अलग रीड रन की समय-सीमा में नहीं समाएँगे। एक बैच कतार की क़ीमत सिर्फ़ एक बार चुकाता है:
const rows = fbBatch(fb.profile(profileId), fbReads(ids, 'name,daily_budget'))
for (const r of rows) {
if (r.code !== 200) { console.log('code', r.code); continue }
console.log(r.data.name, r.data.daily_budget)
}
fbBatch(target, subrequests, { timeoutMs })— सब-रिक्वेस्ट को एक ही आदान-प्रदान में भेजता है; 50-50 के हिस्सों में बाँटकर — यह Facebook की सीमा है। पूरे बैच का इनकार throw होता है; हर सब-रिक्वेस्ट का नतीजा उसकेcodeमें और उसका पार्स किया हुआ जवाबdataमें जाँचें।fbReads(ids, fields)— पहचानकर्ताओं की सूची से रीड सब-रिक्वेस्ट बनाता है;fieldsआप ख़ुद लिखते हैं। सब-रिक्वेस्ट हाथ से भी बनाई जा सकती है:{ method: 'GET', relativeUrl: 'act_123/ads?fields=name' }।
Limits and the log
- स्क्रिप्ट रनटाइम बाक़ी कोड की तरह ही
fbकॉल को भी काटता है — यह कमांडsqlऔरctx.fetchकी तरह ही सिंक्रोनस (synchronous) है। - हर कॉल का इंतज़ार एडमिनिस्ट्रेटर सिस्टम → JavaScript में, Facebook (own request via fb.*) ब्लॉक में सेट करता है: एक डिफ़ॉल्ट और एक ऊपरी सीमा। आपका
timeoutMsइसी सीमा के भीतर लागू होता है। - हर प्रोफ़ाइल के लिए एक कतार। एक ब्राउज़र प्रोफ़ाइल को भेजे गए अनुरोध पूरे बॉक्स में समान रूप से बाँटे जाते हैं — Britva, आँकड़े इकट्ठा करना और स्क्रिप्ट एक ही कतार साझा करते हैं, इसलिए कोई स्क्रिप्ट बार-बार कॉल करके किसी प्रोफ़ाइल को «जला» नहीं सकती। बहुत सारे रीड चाहिए — बैच का इस्तेमाल करें।
- हर अनुरोध दर्ज होता है आपके सर्वर के अनुरोध लॉग में — रीड भी शामिल हैं: समय, स्क्रिप्ट, निष्पादक प्रोफ़ाइल, मेथड, पथ, नतीजा और Facebook का जवाब। «स्क्रिप्ट ने मेरे कैंपेन बिगाड़ दिए» जैसी शिकायत को लॉग से सुलझाने में एक मिनट लगता है। एक्सेस टोकन कभी लॉग तक नहीं पहुँचते — उन्हें सहेजे गए टेक्स्ट से मिटा दिया जाता है।
The catalog of building blocks
नीचे दिया हर अनुरोध Qubix द्वारा असल में भेजा जा चुका है और उसका जवाब मिल चुका है (27 अगस्त 2026 को जाँचा गया) — आप इन्हें आधार सेट के रूप में भरोसे से इस्तेमाल कर सकते हैं। इनसे आगे भी सब कुछ काम करता है: ब्रिज कोई भी *.facebook.com पता स्वीकार करता है — Graph API और उससे आगे भी — और बाक़ी सब आपकी प्रोफ़ाइल के अधिकार तय करते हैं। स्क्रिप्ट एडिटर में मौजूद AI असिस्टेंट इन ब्लॉक्स को जानता है और सामान्य भाषा में बताए गए काम से स्क्रिप्ट बना देगा।
इन ब्लॉक्स से बनी तैयार स्क्रिप्ट स्क्रिप्ट के उदाहरण में मिलेंगी।
Who am I under this profile
const res = fb.profile(profileId).request({ url: 'https://graph.facebook.com/me', params: { fields: 'id,name,email' } })
यहाँ इनकार का मतलब है कि प्रोफ़ाइल का सेशन ख़त्म हो चुका है। शेड्यूल पर सेशन की सेहत जाँचने का यह सबसे सस्ता तरीक़ा है — मीडिया बाइंग टीम के नोटिस करने से भी पहले।
Ad accounts — including money
const res = fb.profile(profileId).request({
url: 'https://graph.facebook.com/me/adaccounts',
params: {
limit: 50,
fields: 'name,account_id,account_status,disable_reason,currency,' +
'adspaymentcycle,adtrust_dsl,amount_spent,timezone_name',
},
})
पैसा यहीं मिलता है — Graph API में अलग से कोई «बैलेंस» endpoint नहीं है:
| फ़ील्ड | यह क्या है |
|---|---|
amount_spent | अकाउंट का कुल ख़र्च, अकाउंट की मुद्रा की छोटी इकाइयों (minor units) में |
adtrust_dsl | अकाउंट की दैनिक ख़र्च सीमा |
adspaymentcycle | बिलिंग सीमा; राशि .data[0].threshold_amount में होती है और उसे 100 से भाग दिया जाता है |
account_status | अकाउंट की स्थिति, संख्या के रूप में |
disable_reason | ब्लॉक होने की वजह, संख्या के रूप में |
currency | अकाउंट की मुद्रा |
timezone_name | अकाउंट का टाइम ज़ोन; «आज» इसी में गिना जाता है |
Payment cards of the accounts
वही पता, बस फ़ील्ड अलग — display_string में जुड़े हुए कार्ड का मास्क होता है:
const res = fb.profile(profileId).request({
url: 'https://graph.facebook.com/me/adaccounts',
params: { limit: 100, fields: 'account_id,all_payment_methods{pm_credit_card{display_string}}' },
})
Campaign state and budget
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${campaignId}`,
params: { fields: 'id,name,status,effective_status,daily_budget' },
})
daily_budget अकाउंट की मुद्रा की छोटी इकाइयों में, स्ट्रिंग के रूप में लौटाया जाता है। अगर बजट कैंपेन की बजाय ऐड सेट पर सेट है, तो यह फ़ील्ड बस मौजूद नहीं होती — यह एक सामान्य स्थिति है, त्रुटि नहीं।
Changing the budget
const res = fb.campaign(campaignId).request({
method: 'POST',
url: `https://graph.facebook.com/${campaignId}`,
params: { daily_budget: '5000' }, // minor units: 5000 = 50.00 in the account currency
})
सफल होने पर Facebook {"success":true} जवाब देता है। सुरक्षा सीमाओं के साथ पूरा परिदृश्य उदाहरण में «लाभदायक कैंपेन का बजट बढ़ाना» रेसिपी में है।
Ad set settings: targeting, budgets, optimization
ऐड सेट में वह सब कुछ मिलता है जो हमारी रिपोर्ट में बिल्कुल नहीं होता:
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${adsetId}`,
params: { fields: 'name,targeting,daily_budget,lifetime_budget,optimization_goal,billing_event' },
})
उसी तरह, विज्ञापन पर एक अनुरोध उसके नेस्टेड ऑब्जेक्ट को एक साथ खींच लेता है — कैंपेन, ऐड सेट और क्रिएटिव, सबको एक साथ:
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${adId}`,
params: { fields: 'name,status,effective_status,campaign{objective,daily_budget},adset{targeting,optimization_goal},creative{id}' },
})
Pausing and starting
fb.campaign(campaignId).request({ method: 'POST', url: `https://graph.facebook.com/${someId}`, params: { status: 'PAUSED' } })
fb.campaign(campaignId).request({ method: 'POST', url: `https://graph.facebook.com/${someId}`, params: { status: 'ACTIVE' } })
यही तरीक़ा विज्ञापन, ऐड सेट और कैंपेन तीनों के लिए काम करता है — Facebook उन्हें पहचानकर्ता से अलग पहचानता है। कैंपेन को रोकने का असर आगे तक फैलता है: उसके विज्ञापन effective_status: 'CAMPAIGN_PAUSED' दिखाते हैं जबकि उनका अपना status नहीं बदलता। इसलिए यह तय करने के लिए कि कुछ असल में चल रहा है या नहीं, हमेशा effective_status देखें, कभी status नहीं।
विज्ञापन, ऐड सेट और कैंपेन को रोकना और वापस चालू करना बेहतर है बिल्ट-इन .pause() / .activate() से करें: ये Britva जैसी ही कतार से गुज़रते हैं और रोकने का पूरा हिसाब-किताब रखते हैं। fb का इस्तेमाल वहाँ करें जहाँ SDK के पास कोई कमांड नहीं है।
Why an ad is not delivering
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${adId}`,
params: { fields: 'effective_status,issues_info{error_summary,error_message,level},adset{effective_status,end_time}' },
})
issues_info में समस्या Facebook के अपने शब्दों में होती है; adset.end_time समाप्त हो चुके ऐड सेट को पकड़ता है — «विज्ञापन सक्रिय है लेकिन कुछ भी ख़र्च नहीं हो रहा» का सामान्य जवाब यही है।
Statistics straight from the cabinet
जब आपको ऐसा हिस्सा चाहिए जो Qubix की रिपोर्ट में नहीं है, तो Facebook से ही पूछें:
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/act_${accountId}/insights`,
params: {
level: 'ad',
fields: 'ad_id,ad_name,spend,impressions,clicks',
time_range: JSON.stringify({ since: '2026-08-20', until: '2026-08-27' }),
time_increment: 1, // by day
limit: 500,
},
})
दो जालें जिनमें हर कोई फँसता है:
outbound_clicksऔरunique_outbound_clicks,action_type/valueऑब्जेक्ट के ऐरे के रूप में आते हैं, संख्या के रूप में नहीं — बाक़ी क्लिक फ़ील्ड स्ट्रिंग में संख्या के रूप में आते हैं;- टॉप-लेवल पर
landing_page_viewsफ़ील्ड बिल्कुल नहीं है —actionsका अनुरोध करें और उसके अंदरaction_type: 'landing_page_view'खोजें।
Creative images — down to the binary body
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/act_${accountId}/adimages`,
params: { hashes: JSON.stringify([imageHash]), fields: 'url,permalink_url,hash' },
})
fb का जवाब बाइनरी भी हो सकता है: res.bytes में बॉडी वैसे की वैसी होती है — इसी तरह आप फ़ाइलें ख़ुद हासिल करते हैं, न कि सिर्फ़ उनके लिंक।
Video source and the post picture
// the source file of a video creative
fb.campaign(campaignId).request({ url: `https://graph.facebook.com/${videoId}`, params: { fields: 'source' } })
«विज्ञापन → उसकी तस्वीर» की चेन में तीन अनुरोध लगते हैं और इसे किसी और तरीक़े से नहीं जोड़ा जा सकता:
const cr = JSON.parse(fb.campaign(campaignId).request({ url: `https://graph.facebook.com/${adId}`, params: { fields: 'creative{id}' } }).text)
const post = JSON.parse(fb.campaign(campaignId).request({ url: `https://graph.facebook.com/${cr.creative.id}`, params: { fields: 'effective_object_story_id' } }).text)
const pic = JSON.parse(fb.campaign(campaignId).request({ url: `https://graph.facebook.com/${post.effective_object_story_id}`, params: { fields: 'full_picture' } }).text)
console.log(pic.full_picture)
Pages, business managers, posts and comments
fb.profile(profileId).request({ url: 'https://graph.facebook.com/me/accounts' }) // fan pages
fb.profile(profileId).request({ url: 'https://graph.facebook.com/me/businesses' }) // business managers
fb.profile(profileId).request({ url: `https://graph.facebook.com/${pageId}/published_posts` }) // page posts
// post comments
const res = fb.profile(profileId).request({
url: `https://graph.facebook.com/${postId}/comments`,
params: { fields: 'id,message,from,created_time', limit: 100 },
})
// delete a comment
fb.profile(profileId).request({ method: 'DELETE', url: `https://graph.facebook.com/${commentId}` })
लगाए गए लिंक की स्वचालित सफ़ाई, लॉग के साथ, पहले से ही बनी हुई है — टिप्पणी सफ़ाई सेक्शन देखें। अपने ऊपर अपना लॉजिक बनाने के लिए fb का इस्तेमाल करें: अपनी शब्द सूचियाँ, अपने अपवाद, अपनी लेखक जाँच।
Paging
Facebook लंबी सूचियाँ कर्सर के साथ देता है: जब तक जवाब में paging.next हो, paging.cursors.after लें और params में after के साथ अनुरोध दोहराएँ। paging न होने का मतलब है कि सूची ख़त्म हो गई।
let after = null
do {
const params = { limit: 50, fields: 'name,account_id' }
if (after) params.after = after
const data = JSON.parse(fb.profile(profileId).request({ url: 'https://graph.facebook.com/me/adaccounts', params: params }).text)
if (data.error) break
for (const acc of data.data || []) console.log(acc.name)
after = data.paging && data.paging.next ? data.paging.cursors.after : null
} while (after)
अगर Facebook «Please reduce the amount of data you're asking for» जवाब देता है, तो यह एक अनुरोध है, इनकार नहीं: limit कम करें और फिर से शुरू करें।
The power is in combining with the rest of the SDK
ब्रिज असल में तभी ताक़तवर बनता है जब इसे स्क्रिप्ट के बाक़ी कमांड के साथ जोड़ा जाए: withCondition आपके आँकड़ों के आधार पर ऑब्जेक्ट चुनता है, sql डेटाबेस से कोई भी हिस्सा खींचता है, fb कैबिनेट जाँचता और बदलता है, ctx.state रन के बीच याददाश्त रखता है, ctx.fetch नतीजा आपके मैसेंजर तक भेजता है।
function main() {
// 1. Your own statistics — a direct ClickHouse query: campaign profitability by FUNNEL revenue
const rows = sql`
SELECT campaign_id, sum(spend_24h) AS spend, sum(revenue_24h) AS revenue
FROM v_ads_stats
GROUP BY campaign_id
HAVING spend > 0 AND revenue / spend >= 1.5
ORDER BY revenue / spend DESC`
// 2. Live budgets of all candidates — in one exchange with Facebook
const ids = rows.map((r) => r.campaign_id)
if (!ids.length) return
const budgets = fbBatch(fb.campaign(ids[0]), fbReads(ids, 'name,daily_budget'))
for (let i = 0; i < ids.length; i++) {
if (budgets[i].code !== 200) continue
console.log(budgets[i].data.name,
'funnel ROAS:', (rows[i].revenue / rows[i].spend).toFixed(2),
'budget in the cabinet:', budgets[i].data.daily_budget)
}
}
ऑब्जेक्ट बिना SQL के भी चुने जा सकते हैं — withCondition से: यह ऑब्जेक्ट के सभी मेट्रिक्स पर एक SQL-स्तर की एक्सप्रेशन है, कोष्ठक, AND/OR/NOT, गणित और फ़ील्ड-से-फ़ील्ड तुलना के साथ — spend_24h > 2 * geo_avg_payout, roas_24h < 0.5 * prev_roas_24h। पूरी फ़ील्ड सूची एडिटर के मैक्रो टैब पर और मेट्रिक्स में है।
इसी तरह आप ऐसे परिदृश्य बनाते हैं जिन तक कोई वेंडर कभी नहीं पहुँच पाता: आपका फ़नल तय करता है, आपकी प्रोफ़ाइल उसे अंजाम देती है।