Ana içeriğe geç

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.

JavaScript
// 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ıca getProfiles() 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:

JavaScript
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:

AlanAnlamı
idprofil tanımlayıcısı — fb.profile(...)'a geçirin
nameprofil adı
groupprofil grubu
ownerBuyerIdprofilin sahibi olan alıcı
tokenAliveerişim belirtecinin canlı olup olmadığı
onCheckpointprofilin bir Facebook güvenlik kontrol noktasında (checkpoint) takılı olup olmadığı
hasProxyprofilde 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çenekAnlamı
urlisteğ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
methodGET (varsayılan), POST veya DELETE
paramsbir nesne olarak istek parametreleri; POST bunları gövdede taşır, diğer yöntemler — adreste
headerskendi başlıklarınız; bunlar bizimkilerin üzerine eklenir ve herhangi birini geçersiz kılabilir
timeoutMsyanı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:

JavaScript
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)
AlanAnlamı
okyanıtın geldiği ve kodun başarılı olduğu
statusFacebook'tan gelen HTTP yanıt kodu
contentTypeyanıtın içerik türü
headersyanı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ı
textgövde olduğu gibi, bir string olarak — JSON.parse(res.text)
bytesaynı gövde ikili biçimde — görseller ve indirmeler için
profileIdisteğ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/catch ile 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 >= 400 ile normal bir yanıt olarak döner ve sebep gövdededir: JSON.parse(res.text).error, message ve code taşır, error_user_title ve error_user_msg mevcut 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ı:

JavaScript
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
}
Bir değişiklikten hemen sonra tekrar okumayın

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:

JavaScript
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 kendi code'unda, ayrıştırılmış yanıtını da data'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, sql ve ctx.fetch gibi eşzamanlıdır.
  • Çağrı başına bekleme, SistemJavaScript 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

JavaScript
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

JavaScript
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:

AlanNe olduğu
amount_spenthesabın toplam harcaması, hesap para biriminin alt birimlerinde
adtrust_dslhesabın günlük harcama sınırı
adspaymentcyclefaturalama eşiği; tutar .data[0].threshold_amount içindedir ve 100'e bölünür
account_statushesabın durumu, bir sayı olarak
disable_reasondevre dışı bırakılma nedeni, bir sayı olarak
currencyhesap para birimi
timezone_namehesap 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:

JavaScript
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

JavaScript
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

JavaScript
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:

JavaScript
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:

JavaScript
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

JavaScript
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ı duraklatmak için SDK eylemlerini tercih edin

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

JavaScript
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:

JavaScript
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_clicks ve unique_outbound_clicks, bir sayı olarak değil, action_type/value nesnelerinden oluşan bir dizi olarak gelir — diğer tıklama alanları bir string içinde sayı olarak gelir;
  • üst düzeyde landing_page_views alanı hiç yokturactions'ı isteyin ve içinde action_type: 'landing_page_view''i arayın.

Kreatif görselleri — ikili gövdesine kadar

JavaScript
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

JavaScript
// 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:

JavaScript
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

JavaScript
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}` })
Qubix, yorumları kendi başına da temizleyebilir

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.

JavaScript
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.

JavaScript
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.

Sırada ne var