|
| 1 | +# Meta Ads Integration |
| 2 | + |
| 3 | +The buyer agent integrates with the Meta Marketing API to book and report on social channel campaigns across Facebook and Instagram. This page covers authentication setup, the booking flow, and reporting endpoints. |
| 4 | + |
| 5 | +The buyer agent calls `graph.facebook.com` directly using a system user access token. This is the same endpoint used by the official Meta Marketing API SDKs — no browser OAuth is required. |
| 6 | + |
| 7 | +## Configuration |
| 8 | + |
| 9 | +| Variable | Type | Default | Description | |
| 10 | +|---|---|---|---| |
| 11 | +| `META_ACCESS_TOKEN` | `str` | `""` | System user access token from Meta Business Manager | |
| 12 | +| `META_AD_ACCOUNT_ID` | `str` | `""` | Ad account ID — format `act_XXXXXXXXX` | |
| 13 | +| `META_PAGE_ID` | `str` | `""` | Facebook Page ID — required for ad creative creation | |
| 14 | +| `META_API_VERSION` | `str` | `v21.0` | Meta Graph API version | |
| 15 | + |
| 16 | +Add these to your `.env` file: |
| 17 | + |
| 18 | +```bash |
| 19 | +META_ACCESS_TOKEN=your-system-user-token |
| 20 | +META_AD_ACCOUNT_ID=act_XXXXXXXXX |
| 21 | +META_PAGE_ID=XXXXXXXXX |
| 22 | +META_API_VERSION=v21.0 |
| 23 | +``` |
| 24 | + |
| 25 | +### Generating a System User Token |
| 26 | + |
| 27 | +1. Open **Meta Business Manager → Business Settings → System Users** |
| 28 | +2. Create or select a system user |
| 29 | +3. Click **Generate Token** → select your app |
| 30 | +4. Required scopes: `ads_management`, `ads_read`, `business_management` |
| 31 | +5. Assign the system user to your ad account: **Business Settings → System Users → Add Assets → Ad Accounts** |
| 32 | + |
| 33 | +### Installation |
| 34 | + |
| 35 | +```bash |
| 36 | +pip install -e ".[meta]" |
| 37 | +``` |
| 38 | + |
| 39 | +!!! note "Sandbox accounts" |
| 40 | + Meta provides sandbox ad accounts for development. Campaigns created in a sandbox account are API-only and not visible in the Ads Manager UI. They are created in `PAUSED` state and never serve impressions. |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## Booking Flow |
| 45 | + |
| 46 | +When a booking brief includes `"channels": ["social"]` or `"channels": ["meta"]`, the buyer agent routes through the Meta booking path. |
| 47 | + |
| 48 | +```mermaid |
| 49 | +sequenceDiagram |
| 50 | + participant Buyer as Buyer Agent |
| 51 | + participant Meta as graph.facebook.com |
| 52 | +
|
| 53 | + Buyer->>Meta: GET /{account}/reachestimate |
| 54 | + Meta-->>Buyer: Reach + CPM estimates |
| 55 | +
|
| 56 | + Note over Buyer: Awaiting approval |
| 57 | +
|
| 58 | + loop Per placement (Instagram Reels, Facebook Feed, etc.) |
| 59 | + Buyer->>Meta: POST /act_{id}/campaigns |
| 60 | + Meta-->>Buyer: campaign_id (PAUSED) |
| 61 | +
|
| 62 | + Buyer->>Meta: POST /act_{id}/adsets |
| 63 | + Meta-->>Buyer: ad_set_id (PAUSED) |
| 64 | + end |
| 65 | +``` |
| 66 | + |
| 67 | +### Research Phase |
| 68 | + |
| 69 | +The `SocialCrew` uses `MetaInventoryTool` to call `GET /{account}/reachestimate` and estimate reach and CPM for four placements: |
| 70 | + |
| 71 | +- Instagram Reels |
| 72 | +- Facebook Video Feeds |
| 73 | +- Instagram Feed |
| 74 | +- Facebook Feed |
| 75 | + |
| 76 | +If the reach estimate API returns an error, the tool falls back to static estimates. |
| 77 | + |
| 78 | +### Booking Phase |
| 79 | + |
| 80 | +After the user approves recommendations, the buyer agent creates two resources per placement: |
| 81 | + |
| 82 | +| Step | API Call | Status | |
| 83 | +|---|---|---| |
| 84 | +| 1 | `POST /act_{id}/campaigns` | PAUSED | |
| 85 | +| 2 | `POST /act_{id}/adsets` | PAUSED | |
| 86 | + |
| 87 | +!!! note "Creative step" |
| 88 | + Ad creative creation (step 3) requires an uploaded image asset. This step is skipped — campaign and ad set creation is sufficient to confirm booking. |
| 89 | + |
| 90 | +### Objective Mapping |
| 91 | + |
| 92 | +| IAB Objective | Meta Objective | |
| 93 | +|---|---| |
| 94 | +| `brand_awareness`, `reach` | `OUTCOME_AWARENESS` | |
| 95 | +| `traffic` | `OUTCOME_TRAFFIC` | |
| 96 | +| `conversions` | `OUTCOME_SALES` | |
| 97 | +| `video_views` | `OUTCOME_ENGAGEMENT` | |
| 98 | +| `lead_generation` | `OUTCOME_LEADS` | |
| 99 | + |
| 100 | +### Budget Allocation |
| 101 | + |
| 102 | +The `PortfolioCrew` LLM allocates budget across channels based on campaign objectives, audience fit, and KPIs. If `channels` is specified in the brief, the LLM is instructed to allocate only to those channels: |
| 103 | + |
| 104 | +```json |
| 105 | +{ |
| 106 | + "channels": ["branding", "ctv", "social"], |
| 107 | + "budget": 15000 |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +The LLM will distribute the `$15,000` across `branding`, `ctv`, and `social` based on which best fits the campaign objectives. |
| 112 | + |
| 113 | +### Example Booking Request |
| 114 | + |
| 115 | +``` |
| 116 | +POST /bookings |
| 117 | +Content-Type: application/json |
| 118 | +``` |
| 119 | + |
| 120 | +```json |
| 121 | +{ |
| 122 | + "brief": { |
| 123 | + "name": "Summer Campaign 2026", |
| 124 | + "objectives": ["brand_awareness", "reach"], |
| 125 | + "budget": 5000, |
| 126 | + "start_date": "2026-06-01", |
| 127 | + "end_date": "2026-06-30", |
| 128 | + "channels": ["social"], |
| 129 | + "target_audience": { |
| 130 | + "demographics": {"age": "18-45"}, |
| 131 | + "interests": ["technology", "gaming"] |
| 132 | + } |
| 133 | + }, |
| 134 | + "auto_approve": false |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +Poll `GET /bookings/{job_id}` until `status: awaiting_approval`, then approve: |
| 139 | + |
| 140 | +``` |
| 141 | +POST /bookings/{job_id}/approve-all |
| 142 | +``` |
| 143 | + |
| 144 | +Booked lines for the social channel will have `booking_status: "paused"`. |
| 145 | + |
| 146 | +--- |
| 147 | + |
| 148 | +## Reporting |
| 149 | + |
| 150 | +### List Campaigns |
| 151 | + |
| 152 | +Returns all campaigns in the ad account — no booking job ID required. |
| 153 | + |
| 154 | +``` |
| 155 | +GET /meta/campaigns?limit=10 |
| 156 | +``` |
| 157 | + |
| 158 | +| Parameter | Type | Default | Description | |
| 159 | +|---|---|---|---| |
| 160 | +| `limit` | `int` | `10` | Number of campaigns to return | |
| 161 | + |
| 162 | +```json |
| 163 | +{ |
| 164 | + "ad_account_id": "act_XXXXXXXXX", |
| 165 | + "campaigns": [ |
| 166 | + { |
| 167 | + "id": "23856xxxxxxxxx", |
| 168 | + "name": "Summer Campaign 2026 — Instagram Reels", |
| 169 | + "effective_status": "PAUSED", |
| 170 | + "objective": "OUTCOME_AWARENESS", |
| 171 | + "daily_budget": "125000", |
| 172 | + "created_time": "2026-05-11T15:35:59+0530" |
| 173 | + } |
| 174 | + ], |
| 175 | + "count": 10 |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +### Campaign Report |
| 180 | + |
| 181 | +Returns campaign details combined with delivery insights for one or more campaign IDs. |
| 182 | + |
| 183 | +``` |
| 184 | +GET /meta/report?campaign_ids=CAMPAIGN_ID_1,CAMPAIGN_ID_2&date_preset=last_30d |
| 185 | +``` |
| 186 | + |
| 187 | +| Parameter | Type | Default | Description | |
| 188 | +|---|---|---|---| |
| 189 | +| `campaign_ids` | `string` | required | Comma-separated Meta campaign IDs | |
| 190 | +| `date_preset` | `string` | `last_30d` | `last_7d` / `last_14d` / `last_30d` / `last_90d` | |
| 191 | + |
| 192 | +```json |
| 193 | +{ |
| 194 | + "ad_account_id": "act_XXXXXXXXX", |
| 195 | + "date_preset": "last_30d", |
| 196 | + "campaigns": [ |
| 197 | + { |
| 198 | + "campaign_id": "23856xxxxxxxxx", |
| 199 | + "campaign_name": "Summer Campaign 2026 — Instagram Reels", |
| 200 | + "status": "PAUSED", |
| 201 | + "objective": "OUTCOME_AWARENESS", |
| 202 | + "daily_budget": "125000", |
| 203 | + "created_time": "2026-05-11T15:35:59+0530", |
| 204 | + "spend": 0.0, |
| 205 | + "impressions": 0, |
| 206 | + "reach": 0, |
| 207 | + "frequency": 0.0, |
| 208 | + "clicks": 0, |
| 209 | + "ctr": 0.0, |
| 210 | + "cpm": 0.0 |
| 211 | + } |
| 212 | + ], |
| 213 | + "summary": { |
| 214 | + "total_spend": 0.0, |
| 215 | + "total_impressions": 0, |
| 216 | + "total_clicks": 0, |
| 217 | + "total_reach": 0 |
| 218 | + } |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +### Job-Scoped Report |
| 223 | + |
| 224 | +To report on all campaigns booked within a specific job: |
| 225 | + |
| 226 | +``` |
| 227 | +GET /reports/{job_id}?date_range=last_30d |
| 228 | +``` |
| 229 | + |
| 230 | +The buyer agent automatically identifies Meta campaign IDs from `booked_lines` and pulls insights for each. IAB OpenDirect order IDs in the same job are routed to the seller agent's delivery performance endpoint. See [Bookings API](../api/bookings.md) for details. |
| 231 | + |
| 232 | +!!! tip "Access token security" |
| 233 | + The Meta access token is never exposed in API error responses. It is automatically redacted to `***` before any error message reaches the HTTP response. |
| 234 | + |
| 235 | +--- |
| 236 | + |
| 237 | +## Related |
| 238 | + |
| 239 | +- [Seller Agent Integration](seller-agent.md) --- How buyer and seller agents communicate |
| 240 | +- [Bookings API](../api/bookings.md) --- Full booking flow reference |
| 241 | +- [Configuration Reference](../guides/configuration.md) --- All environment variables |
0 commit comments