Facebook Qubix Bridge
Facebook Qubix Bridge は、ユーザースクリプトが Facebook と直接やり取りできるようにする SDK です。スクリプト自身が Graph API や他の任意の *.facebook.com アドレスへリクエストを送信し、Qubix はそれを、あなたがすでに接続済みのブラウザプロフィール — そのアクセストークン、Cookie、プロキシ、ブラウザ署名 — を通じて運びます。Facebook からの応答は、バイナリ本体に至るまで、そのままスクリプトへ返されます。プロフィールが広告アカウントで手動で行える操作であれば、スクリプトはスケジュールに沿って同じことを行えます:設定と統計の読み取り、予算の変更、一時停止と再開、クリエイティブのダウンロード、コメントの整理。
設定は何も必要ありません。プロフィールが Qubix に接続済みであれば、ブリッジはすでに機能しています。リクエストが何を行えるかは、そのプロフィールが Facebook 内で持つ権限によって決まります。プロフィールが手動でキャンペーンを編集できない場合は、Facebook はスクリプトからのリクエストも同様に拒否し、スクリプトはその拒否内容をそのまま受け取ります。
これはパイプであり、パイプでしかありません。アドレス、メソッド、パラメータ、ヘッダーはあなたが書き、鍵、Cookie、プロキシ、ブラウザ署名は Qubix が用意します。すぐに使えるキャンペーン管理用の関数は、意図的に用意されていません — そうした関数を用意すると、今日の Facebook の形を製品に固定してしまい、Facebook 側の最初の変更で壊れてしまうからです。高レベルの部品はサンプルから取り、自分の用途に合わせて調整してください。ブリッジはリクエストの形について何も知らないからこそ、古びることがないのです。
リクエストがたどる経路
fb コマンドはユーザースクリプトの中で使用できます — スケジュール実行でも、▶ 実行 ボタンからでも同様です。Britva ルールの中では使用できません。
実行プロフィールを選ぶ二つの方法
すべてのリクエストは、いずれかのブラウザプロフィール — 実行プロフィール — から送信されます。自分で指定することも、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、…)も含まれるため、複数のターゲットを反復処理する際にも区別しやすくなっています。
どのプロフィールがオブジェクトを閲覧できるか
何かを送信する前に、スクリプトはどのプロフィールがそのキャンペーンや広告に対して操作を行えるかを問い合わせることができます — 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 上でのアクセスレベルもそれぞれ異なるため、どれを使うかはあなた次第です。各要素には次の項目が含まれます。
| Field | Meaning |
|---|---|
id | プロフィールの識別子 — fb.profile(...) に渡します |
name | プロフィール名 |
group | プロフィールグループ |
ownerBuyerId | プロフィールを所有するバイヤー |
tokenAlive | アクセストークンが有効かどうか |
onCheckpoint | プロフィールが Facebook のセキュリティチェックポイントで止まっているかどうか |
hasProxy | プロフィールにプロキシが設定されているかどうか |
「アクセスレベル」というフィールドは意図的に存在しません — Qubix はそれをどこにも保存していないからです。プロフィールがそのオブジェクトに対して何を行えるかは、Facebook 自身が答えます。リクエストを送って応答を読んでください。tokenAlive、onCheckpoint、hasProxy は権限ではなく状態です。トークンが無効なプロフィールも一覧に残るので、あなた自身の目で確認して判断できます。あなたのデータセット外のオブジェクトは、エラーではなく空の配列を返します。
リクエスト
request(options) は一つのオブジェクトを受け取ります。
| Option | Meaning |
|---|---|
url | リクエストの絶対アドレス:https://graph.facebook.com/${campaignId}、https://graph.facebook.com/act_123/campaigns、https://graph.facebook.com/me/adaccounts。*.facebook.com であればどのホストでも使用できます — ブリッジがプロフィールの Cookie と鍵を付与し、それらが他のホストに渡ることは決してありません |
method | GET(デフォルト)、POST、DELETE のいずれか |
params | オブジェクト形式のリクエストパラメータ。POST の場合は本文に、それ以外のメソッドではアドレスに含まれます |
headers | 独自のヘッダー。こちらが設定したヘッダーの上に重なり、どれでも上書きできます |
timeoutMs | 応答を待つ時間(ミリ秒)。空の場合は運用側のデフォルト値が使われ、上限を超えることはできません |
アクセストークン、Cookie、プロキシ、ブラウザ署名は、プロフィールのトランスポート自体が供給します。
応答 — そして二種類の拒否
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)
| Field | Meaning |
|---|---|
ok | 応答が届き、コードが成功であること |
status | Facebook から返された HTTP レスポンスコード |
contentType | 応答のコンテンツタイプ |
headers | 応答ヘッダー — Set-Cookie を除くすべて。本文にはない情報(残りのレート制限、Facebook へのサポート問い合わせ用のリクエスト識別子)はここにあります |
text | 本文をそのまま文字列として — JSON.parse(res.text) |
bytes | 同じ本文をバイナリで — 画像やダウンロード用 |
profileId | どのブラウザプロフィールがリクエストを送信したか |
拒否には二種類あり、それぞれ届き方が異なります。
- Qubix がリクエストを送信できなかった場合 — プロキシが死んでいる、接続が切れている、キャンペーンがあなたのデータセット外にあるなど — はエラーとしてスローされます。ループを続けたい場合は
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 に記憶しておいてください。
バッチ:一回の通信で複数のリクエストを送る
プロフィールごとのキューは、一定間隔につき一件のリクエストしか通しません — 単発の読み取りを百件送ろうとすると、実行の時間制限に収まりません。バッチであれば、キューへの支払いは一回だけで済みます。
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件ずつに分割します — これは Facebook の上限です。バッチ全体の拒否はスローされます。各サブリクエストの結果はcodeで、パース済みの応答はdataで確認してください。fbReads(ids, fields)— 識別子のリストから読み取り用のサブリクエストを組み立てます。fieldsは自分で指定します。サブリクエストは手動でも組み立てられます:{ method: 'GET', relativeUrl: 'act_123/ads?fields=name' }。
制限とログ
- スクリプトの実行時間は、他のコードと同様に
fbの呼び出しも打ち切ります — このコマンドはsqlやctx.fetchと同じく同期処理です。 - 呼び出しごとの待機時間は、管理者が システム → JavaScript の Facebook (own request via fb.*) ブロックで設定します:デフォルト値と上限があります。あなたの
timeoutMsはこの上限の範囲内で適用されます。 - プロフィールごとに一つのキュー。 一つのブラウザプロフィールへのリクエストは、ボックス全体で間隔が空けられます — Britva、統計収集、スクリプトが一つのキューを共有するため、スクリプトが頻繁な呼び出しでプロフィールを消耗させることはできません。読み取りを大量に行う必要がある場合はバッチを使ってください。
- すべてのリクエストが記録されます — 読み取りも含め、あなたのサーバー上のリクエストログに、時刻、スクリプト、実行プロフィール、メソッド、パス、結果、Facebook の応答が残ります。「スクリプトがキャンペーンを台無しにした」といった訴えも、ログを見れば数分で解決します。アクセストークンがログに残ることは決してありません — 保存されるテキストからは取り除かれます。
部品のカタログ
以下のリクエストはすべて、Qubix が実運用で送信し、応答を得たものです(2026年8月27日に確認済み)— 基本セットとして信頼して使用できます。これ以外のものも問題なく通ります。ブリッジは *.facebook.com の任意のアドレス — Graph API はもちろんそれ以外も — を受け付け、残りはあなたのプロフィールの権限が決めます。スクリプトエディタの AI アシスタントはこれらの部品を把握しており、平易な言葉で書かれたタスクからスクリプトを組み立てます。
これらの部品から組み立てられた既製のスクリプトは スクリプトの例 にあります。
このプロフィールでは自分は誰か
const res = fb.profile(profileId).request({ url: 'https://graph.facebook.com/me', params: { fields: 'id,name,email' } })
ここで拒否が返ってきた場合、プロフィールのセッションが切れているということです。これは、バイヤーが気づくよりも早く、セッションの健全性をスケジュールで確認する最も手軽な方法です。
広告アカウント — 資金も含めて
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 には別途「残高」用のエンドポイントはありません。
| Field | What it is |
|---|---|
amount_spent | アカウントの総消化額。アカウント通貨の最小単位で表されます |
adtrust_dsl | アカウントの1日あたりの消化上限額 |
adspaymentcycle | 請求のしきい値。金額は .data[0].threshold_amount にあり、100 で割った値です |
account_status | アカウントの状態(数値) |
disable_reason | 無効化された理由(数値) |
currency | アカウントの通貨 |
timezone_name | アカウントのタイムゾーン。「今日」はこの基準で数えられます |
アカウントの支払いカード
同じアドレスに、異なるフィールドの組み合わせを指定します — 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}}' },
})
キャンペーンの状態と予算
const res = fb.campaign(campaignId).request({
url: `https://graph.facebook.com/${campaignId}`,
params: { fields: 'id,name,status,effective_status,daily_budget' },
})
daily_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} と応答します。安全のためのしきい値を含む完全なシナリオは、スクリプトの例 の「収益性の高いキャンペーンの予算を上げる」レシピにあります。
広告セットの設定:ターゲティング、予算、最適化
広告セットには、当社のレポートにはまったくない情報が含まれています。
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}' },
})
一時停止と再開
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 と同じキューを通り、一時停止の記録も完全に保たれます。SDK にコマンドがない操作にだけ fb を使ってください。
広告が配信されない理由
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 は終了した広告セットを検出します — 「広告はアクティブなのに何も消化されない」に対する典型的な答えです。
アカウントから直接取得する統計
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'を探してください。
クリエイティブ画像 — バイナリ本体まで
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 は本文をそのまま運びます — こうして、リンクだけでなくファイル自体を取得できます。
動画のソースと投稿の画像
// 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)
ページ、ビジネスマネージャー、投稿、コメント
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 を使ってください:独自の単語リスト、独自の例外、独自の投稿者チェックなど。
ページネーション
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 を下げてやり直してください。
真価はほかの 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 のように。全フィールドの一覧はエディタのマクロタブと 指標 にあります。
こうして、既製のベンダーでは決して手が届かないシナリオを組み立てられます:あなたのファネルが判断し、あなたのプロフィールが実行します。