Facebook Qubix Bridge
Facebook Qubix Bridge, bir kullanıcı script'inin Facebook ile doğrudan konuşmasını sağlayan SDK'dır: script kendi isteğini gönderir — Graph API'ye veya başka herhangi bir *.facebook.com adresine — Qubix ise bu isteği daha önce bağladığınız bir tarayıcı profili üzerinden taşır: bu profilin erişim belirteci, çerezleri, proxy'si ve tarayıcı imzasıyla birlikte. Facebook'un yanıtı ikili gövdesine kadar olduğu gibi script'e geri döner. Profilin reklam yöneticisinde elle yapmasına izin verilen her şeyi artık bir script zamanlanmış olarak yapabilir: ayarları ve istatistikleri okuma, bütçeleri değiştirme, duraklatma ve başlatma, kreatifleri indirme, yorumları temizleme.
Kurulacak hiçbir şey yoktur: Qubix'e bağlı bir profil, köprünün zaten çalıştığı anlamına gelir. Bir isteğin ne yapabileceğine o profilin Facebook içindeki hakları karar verir: profil bir kampanyayı elle düzenleyemiyorsa, Facebook script'i de reddeder ve script bu reddi kelimesi kelimesine görür.
Bu bir kanaldır, sadece bir kanal: adresi, yöntemi, parametreleri ve başlıkları siz yazarsınız, Qubix ise anahtarı, çerezleri, proxy'yi ve tarayıcı imzasını sağlar. Burada bilerek hazır kampanya yönetimi işlevleri yoktur — bunlar Facebook'un bugünkü şeklini ürüne dondurur ve ilk değişiklikte bozulur. Üst düzey parçaları örneklerden alın ve kendinize göre uyarlayın: köprü bir isteğin şeklini bilmez, tam da bu yüzden eskimez.
Bir istek nasıl ilerler
fb komutu kullanıcı script'lerinde kullanılabilir — hem zamanlanmış çalıştırmalarda hem de ▶ Çalıştır düğmesiyle. Britva kurallarında kullanılamaz.
Yürütücü profili seçmenin iki yolu
Her isteği bir tarayıcı profili gönderir — yürütücü. Bunu ya kendiniz belirlersiniz ya da Qubix'in seçmesine izin verirsiniz.
// 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)— kampanya, script'inizin veri kümesine ait olmalıdır (rolünüzün görmesine izin verilenler). Qubix, bu kampanyayı gören profiller arasından yürütücüyü seçer ve reklamın sahibinin profillerini tercih eder. Bir aday teslimat düzeyinde devre dışı kalırsa — ölü bir proxy, kopuk bir bağlantı — sıradaki denenir, ama yalnızca okumalar için: bir değişiklik (POST,DELETE) başka bir profil üzerinden asla tekrar denenmez, böylece iki kez uygulanamaz. Facebook yanıt verir vermez — bir ret bile olsa — arama durur. Hedef ayrıcagetProfiles()taşır — seçiminizdeki bir kampanyanın sahip olduğu profil listesinin aynısı.fb.profile(profileId)— istek tam olarak bu profil üzerinden gider, yedek yoktur. Hedef, profilin kendi alanlarını da taşır (name,tokenAlive,onCheckpoint, …), böylece birden fazlası üzerinde döngü kurduğunuzda hedefleri birbirinden ayırt etmek kolaydır.
Hangi profiller bir nesneyi görür
Herhangi bir şey göndermeden önce, bir script hangi profillerin bir kampanya veya reklam üzerinde işlem yapabileceğini sorabilir — Qubix'in durumları hakkında bildiği her şeyle birlikte:
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)
}
Bir dizi döndürür: bir kampanya genellikle birden fazla profile görünür, bunların Facebook'taki erişim düzeyleri farklıdır ve seçim sizindir. Her giriş şunları taşır:
| Alan | Anlamı |
|---|---|
id | profil tanımlayıcısı — fb.profile(...)'a geçirin |
name | profil adı |
group | profil grubu |
ownerBuyerId | profilin sahibi olan alıcı |
tokenAlive | erişim belirtecinin canlı olup olmadığı |
onCheckpoint | profilin bir Facebook güvenlik kontrol noktasında (checkpoint) takılı olup olmadığı |
hasProxy | profilde bir proxy yapılandırılıp yapılandırılmadığı |
Burada bilerek bir «erişim düzeyi» alanı yoktur: Qubix bunu hiçbir yerde saklamaz. Bir profilin bir nesneyle ne yapabileceğinin yanıtını Facebook'un kendisi verir — isteği gönderin ve yanıtı okuyun. tokenAlive, onCheckpoint ve hasProxy birer durumdur, izin değil: ölü bir belirteci olan bir profil listede kalır, böylece onu görür ve kendiniz karar verirsiniz. Veri kümenizin dışındaki bir nesne, bir hata değil, boş bir dizi döndürür.
İstek
request(options), tek bir nesne alır:
| Seçenek | Anlamı |
|---|---|
url | isteğin mutlak adresi: https://graph.facebook.com/${campaignId}, https://graph.facebook.com/act_123/campaigns, https://graph.facebook.com/me/adaccounts. Herhangi bir *.facebook.com ana bilgisayarı çalışır — köprü profilin çerezlerini ve anahtarını ekler ve bunlar hiçbir zaman yabancı bir ana bilgisayara gitmez |
method | GET (varsayılan), POST veya DELETE |
params | bir nesne olarak istek parametreleri; POST bunları gövdede taşır, diğer yöntemler — adreste |
headers | kendi başlıklarınız; bunlar bizimkilerin üzerine eklenir ve herhangi birini geçersiz kılabilir |
timeoutMs | yanıtın kaç milisaniye bekleneceği; boş bırakılırsa operatörün varsayılanı kullanılır, üst sınırın üzerine çıkamazsınız |
Erişim belirteci, çerezler, proxy ve tarayıcı imzası, profilin kendi taşıma katmanı tarafından sağlanır.
Yanıt — ve iki tür ret
Facebook'un yanıtı olduğu gibi gelir; Qubix onu ayrıştırmaz — bunu siz yaparsınız:
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)
| Alan | Anlamı |
|---|---|
ok | yanıtın geldiği ve kodun başarılı olduğu |
status | Facebook'tan gelen HTTP yanıt kodu |
contentType | yanıtın içerik türü |
headers | yanıt başlıkları — Set-Cookie hariç hepsi; gövdede bulunmayan şeyler burada yaşar: kalan hız sınırları, Facebook ile bir destek talebi için istek tanımlayıcısı |
text | gövde olduğu gibi, bir string olarak — JSON.parse(res.text) |
bytes | aynı gövde ikili biçimde — görseller ve indirmeler için |
profileId | isteği hangi tarayıcı profilinin gönderdiği |
İki tür ret vardır ve bunlar farklı şekillerde gelir:
- Qubix teslim edemedi — ölü bir proxy, kopuk bir bağlantı, veri kümenizin dışında bir kampanya — bir hata olarak fırlatılır: döngünün devam etmesini istiyorsanız
try/catchile yakalayın.fb.campaign(...)üzerinden yapılan okumalarda, bu noktaya kadar yedek profiller zaten denenmiştir. - Facebook bir ret ile yanıt verdi — hak yok, yanlış bir alan, süresi dolmuş bir oturum —
res.status >= 400ile normal bir yanıt olarak döner ve sebep gövdededir:JSON.parse(res.text).error,messagevecodetaşır,error_user_titleveerror_user_msgmevcut olduğunda ise bu, hazır bir insan dili ifadesidir.
Her tarifte ayrıştırmayı tekrarlamamanız için üç satırlık bir yardımcı:
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 okumaları bir gecikmeyle sunar: başarılı bir değişiklikten hemen sonra okunan bir değer hâlâ eski değer olabilir. Bunu başarısız bir yazma olarak değerlendirmeyin. Yeni değeri bir sonraki çalıştırmada kontrol edin veya ne ayarladığınızı ctx.state içinde hatırlayın.
Toplu istek: tek seferde birçok istek
Profil başına kuyruk, her aralıkta yalnızca bir isteğin geçmesine izin verir — yüz tekil okuma, çalıştırmanın zaman sınırına sığmaz. Bir toplu istek, kuyruğa bir kez öder:
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 })— alt istekleri tek seferde gönderir; 50'şer parçaya bölünür — bu Facebook'un sınırıdır. Tüm toplu isteğin reddi bir hata olarak fırlatılır; her alt isteğin sonucunu kendicode'unda, ayrıştırılmış yanıtını dadata'da kontrol edin.fbReads(ids, fields)— bir tanımlayıcı listesinden okuma alt istekleri oluşturur;fields'i siz yazarsınız. Bir alt istek elle de oluşturulabilir:{ method: 'GET', relativeUrl: 'act_123/ads?fields=name' }.
Sınırlar ve kayıt
- Script çalışma süresi, kodun geri kalanı gibi
fbçağrılarını da keser — komut,sqlvectx.fetchgibi eşzamanlıdır. - Çağrı başına bekleme, Sistem → JavaScript içinde, Facebook (own request via fb.*) bloğunda yönetici tarafından ayarlanır: bir varsayılan ve bir üst sınır.
timeoutMs'iniz üst sınır içinde uygulanır. - Profil başına bir kuyruk. Bir tarayıcı profiline yapılan istekler sistem genelinde aralıklandırılır — Britva, istatistik toplama ve script'ler tek bir kuyruğu paylaşır, böylece bir script sık çağrılarla bir profili yakıp bitiremez. Çok sayıda okumaya mı ihtiyacınız var — bir toplu istek kullanın.
- Her istek kaydedilir sunucunuzdaki istek kaydında — okumalar dahil: zaman, script, yürütücü profil, yöntem, yol, sonuç ve Facebook'un yanıtı. «Script kampanyalarımı mahvetti» gibi bir şikayeti kayıttan çözmek bir dakika sürer. Erişim belirteçleri hiçbir zaman kayda ulaşmaz — saklanan metinlerden temizlenir.
Yapı taşları kataloğu
Aşağıdaki her istek, Qubix tarafından canlı ortamda gönderilmiş ve yanıtlanmıştır (27 Ağustos 2026'da doğrulanmıştır) — bunlara temel küme olarak güvenebilirsiniz. Bunun ötesindeki her şey de geçer: köprü herhangi bir *.facebook.com adresini kabul eder — Graph API ve ötesi — ve gerisine profilinizin hakları karar verir. Script düzenleyicisindeki AI asistanı bu yapı taşlarını bilir ve düz sözcüklerle tarif edilmiş bir görevden bir script oluşturur.
Bu yapı taşlarından oluşturulmuş hazır script'ler Script örnekleri sayfasındadır.
Bu profil altında ben kimim
const res = fb.profile(profileId).request({ url: 'https://graph.facebook.com/me', params: { fields: 'id,name,email' } })
Buradaki bir ret, profilin oturumunun öldüğü anlamına gelir. Bu, oturum sağlığını zamanlanmış olarak kontrol etmenin en ucuz yoludur — medya alım ekibi fark etmeden önce.
Reklam hesapları — para dahil
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',
},
})
Para burada yaşar — Graph API'nin ayrı bir «bakiye» uç noktası yoktur:
| Alan | Ne olduğu |
|---|---|
amount_spent | hesabın toplam harcaması, hesap para biriminin alt birimlerinde |
adtrust_dsl | hesabın günlük harcama sınırı |
adspaymentcycle | faturalama eşiği; tutar .data[0].threshold_amount içindedir ve 100'e bölünür |
account_status | hesabın durumu, bir sayı olarak |
disable_reason | devre dışı bırakılma nedeni, bir sayı olarak |
currency | hesap para birimi |
timezone_name | hesap saat dilimi; «bugün» buna göre sayılır |
Hesapların ödeme kartları
Aynı adres, farklı bir alan kümesi — display_string, bağlı kartın maskesini taşır:
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}}' },
})
Kampanya durumu ve bütçesi
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${campaignId}`,
params: { fields: 'id,name,status,effective_status,daily_budget' },
})
daily_budget, hesap para biriminin alt birimlerinde, bir string olarak döner. Bütçe kampanya yerine reklam setlerinde ayarlanmışsa, alan basitçe yoktur — bu, bir hata değil, normal bir durumdur.
Bütçeyi değiştirme
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
})
Başarı durumunda Facebook {"success":true} yanıtı verir. Güvenlik eşikleriyle birlikte tam senaryo, örnekler içindeki «Kârlı kampanyaların bütçesini artırma» tarifidir.
Reklam seti ayarları: hedefleme, bütçeler, optimizasyon
Bir reklam seti, raporlarımızda hiç bulunmayan şeyleri ortaya çıkarır:
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${adsetId}`,
params: { fields: 'name,targeting,daily_budget,lifetime_budget,optimization_goal,billing_event' },
})
Aynı şekilde, bir reklam üzerindeki tek bir istek, iç içe nesnelerini — kampanyayı, reklam setini ve kreatifi — tek seferde çeker:
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}' },
})
Duraklatma ve başlatma
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' } })
Aynı biçim bir reklam, bir reklam seti ve bir kampanya için çalışır — Facebook bunları tanımlayıcıya göre birbirinden ayırt eder. Bir kampanyayı duraklatmak zincirleme etki yapar: reklamları effective_status: 'CAMPAIGN_PAUSED' bildirirken kendi status'ları değişmeden kalır. Bu yüzden bir şeyin gerçekten çalışıp çalışmadığına effective_status'a göre karar verin, asla status'a göre değil.
Reklamları, reklam setlerini ve kampanyaları duraklatmak ve geri döndürmek, yerleşik .pause() / .activate() ile daha iyi yapılır: bunlar Britva ile aynı kuyruktan geçer ve tam duraklatma kayıtlarını tutar. fb'yi, SDK'nın komutu olmayan işler için kullanın.
Bir reklam neden yayınlanmıyor
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, sorunu Facebook'un kendi ifadeleriyle taşır; adset.end_time, süresi dolmuş bir reklam setini yakalar — «reklam aktif ama hiçbir şey harcanmıyor» sorusunun her zamanki yanıtıdır.
Doğrudan reklam yöneticisinden istatistikler
Qubix raporlarında bulunmayan bir kesit gerektiğinde, doğrudan Facebook'a sorun:
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,
},
})
Herkesin düştüğü iki tuzak:
outbound_clicksveunique_outbound_clicks, bir sayı olarak değil,action_type/valuenesnelerinden oluşan bir dizi olarak gelir — diğer tıklama alanları bir string içinde sayı olarak gelir;- üst düzeyde
landing_page_viewsalanı hiç yoktur —actions'ı isteyin ve içindeaction_type: 'landing_page_view''i arayın.
Kreatif görselleri — ikili gövdesine kadar
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/act_${accountId}/adimages`,
params: { hashes: JSON.stringify([imageHash]), fields: 'url,permalink_url,hash' },
})
Bir fb yanıtı ikili de olabilir: res.bytes, gövdeyi olduğu gibi taşır — dosyaların kendisini, yalnızca onlara bağlantıları değil, işte böyle alırsınız.
Video kaynağı ve gönderi görseli
// the source file of a video creative
fb.campaign(campaignId).request({ url: `https://graph.facebook.com/${videoId}`, params: { fields: 'source' } })
«Reklam → görseli» zinciri üç istek gerektirir ve başka türlü kurulamaz:
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)
Sayfalar, işletme yöneticileri, gönderiler ve yorumlar
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}` })
Yerleştirilmiş bağlantıların otomatik temizliği, bir kayıtla birlikte, zaten yerleşiktir — Yorum temizliği bölümü. Kendi mantığınızı bunun üzerine kurmak için fb'yi kullanın: kendi kelime listeleriniz, kendi istisnalarınız, kendi yazar kontrolleriniz.
Sayfalama
Facebook uzun listeleri imleçlerle (cursor) sunar: yanıt paging.next taşıdığı sürece, paging.cursors.after'ı alın ve isteği params içinde after ile tekrarlayın. paging yoksa son demektir.
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» (istediğiniz veri miktarını azaltın) yanıtını verirse, bu bir ret değil bir ricadır: limit'i düşürün ve baştan başlayın.
Asıl güç, SDK'nın geri kalanıyla birleşiminde
Köprü, script'in diğer komutlarıyla birlikte gerçek gücüne kavuşur: withCondition nesneleri istatistiklerinize göre seçer, sql veritabanından herhangi bir kesiti çeker, fb reklam yöneticisini kontrol eder ve değiştirir, ctx.state çalıştırmalar arasında hafızayı tutar, ctx.fetch sonucu mesajlaşma uygulamanıza gönderir.
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)
}
}
Nesneler SQL olmadan da seçilebilir — withCondition ile: nesnenin tüm metrikleri üzerinde SQL düzeyinde bir ifade, parantezlerle, AND/OR/NOT ile, aritmetik ve alan-alan karşılaştırmasıyla — spend_24h > 2 * geo_avg_payout, roas_24h < 0.5 * prev_roas_24h. Tam alan listesi düzenleyicideki makrolar sekmesinde ve Metrikler sayfasındadır.
Bir tedarikçinin asla ele alamayacağı senaryoları böyle kurarsınız: kararı huniniz verir, yürütmeyi profiliniz yapar.