---
title: "Facebook Ads"
description: "Stream Meta ad objects, lead-form leads and insights reports into Streamkap with a system-user access token from your own Meta Business."
---

The Facebook Ads source reads one or more Meta ad accounts through the Marketing API: ad objects, the leads of their lead forms, and insights reports. Each resource you select becomes a topic named `source_<id>.facebook_ads.<resource>`.

import Callout from 'blume/components/content/Callout.astro';

:::info
This connector is in alpha. See [API sources](/api-sources) for how API sources work and what alpha means.
:::

## Prerequisites

* A Meta Business that owns the ad account, with admin access to its **Business settings**.
* Access to the [Meta App Dashboard](https://developers.facebook.com/apps/) to create an app in that Business.
* A Streamkap workspace with permission to create source connectors.

The token needs the **`ads_read`** permission, and its system user needs the ad account assigned with at least **View performance**. To sync `leads` it also needs:

* The **`leads_retrieval`**, `pages_show_list`, `pages_read_engagement` and `pages_manage_ads` permissions.
* Each Page the lead ads run on assigned to the system user with **Leads** access.

Streamkap never requests the ad account fields that need `business_management` or finance access.

## Meta Setup

### 1. Create an App

In the [Meta App Dashboard](https://developers.facebook.com/apps/), create an app of type **Business** owned by the same Business as the ad account, and add the **Marketing API** product. A new app starts on the **Limited access** tier, which is enough to sync; see [Access Tier and Rate Limits](#access-tier-and-rate-limits).

### 2. Add a System User

In **Business settings → Users → System users**, add a system user or pick an existing one. Under **Assign assets**, give it the ad account with at least **View performance**, and the app from step 1. Meta generates a token only for an app the system user has installed.

### 3. Generate the Token

On the system user, choose **Generate token**, select the app, tick **`ads_read`** (plus the leads permissions above if you need `leads`), and pick an expiry: 60 days or never. Meta recommends expiring tokens. Copy the token.

:::warning
Streamkap never renews the token. Replace an expiring token on the source before its expiry date, or the source stops syncing. With the **App ID** and **App secret** set, Streamkap warns you 14 days ahead.
:::

### 4. Find the Ad Account ID

The ad account ID is the number shown after the account name in Ads Manager's account menu, such as `123456789012345`. A pasted `act_123456789012345` also works.

### 5. Optional: Copy the App ID and App Secret

In the App Dashboard, open **App settings → Basic** and copy the **App ID** and **App secret**. Set both or neither. With them:

* Every request carries an `appsecret_proof`. An app with **Require App Secret** on refuses requests without it, so for such an app the pair is required.
* **Test connection** and every save warn when the token or Meta's data access for it ends within 14 days, when it lacks `ads_read`, or when it is not a system-user or user token.

## Streamkap Setup

### 1. Create the Source

* Navigate to [Add Connectors](https://app.streamkap.com/connectors/add?tab=Sources).
* Choose **Facebook Ads**.

### 2. Connection Settings (Auth Tab)

* **Source name**: A unique name for this source, for example `facebook-ads-prod`.
* **Access token**: The system-user token from [step 3](#3-generate-the-token). Stored encrypted and never shown again.
* **App ID** *(optional)*: The ID of the app the token was generated for. Set together with **App secret**.
* **App secret** *(optional)*: The app's secret. Required when the app has **Require App Secret** on.
* **Ad account ID**: The numeric ad account ID; an `act_` prefix is fine. Once the token is entered, the field lists the ad accounts it reaches by name.
* **Additional ad account IDs** *(optional)*: More ad accounts to sync with the same token; assign each to the token's system user. See [Several Ad Accounts](#several-ad-accounts).

**Test connection** names the ad account the token reads and checks every additional one. It warns, without failing, when an ad account's status is not `ACTIVE`, and with the app pair set it adds the token warnings from [step 5](#5-optional-copy-the-app-id-and-app-secret). **Test access** reads each selected resource on up to the first ten ad accounts and names each account that refused it. A report that returns no rows passes. See [Test connection and Test access](/api-sources#test-connection-and-test-access).

### 3. Settings Tab

* **Resources**: The objects and insights reports to sync. The form starts with the six defaults marked in [Supported Resources](#supported-resources). Select `custom_<name>` for each report in **Custom insights**. **Add a preset** adds:
  * **Performance overview**: the six defaults: `campaigns`, `adsets`, `ads`, `ad_account`, `account_insights`, `campaign_insights`.
  * **Ad-level and creative**: `campaigns`, `adsets`, `ads`, `ad_account`, `ad_creatives`, `ad_images`, `ad_videos`, `adset_insights`, `ads_insights`.
  * **Audience breakdowns**: `campaigns`, `adsets`, `ads`, `ad_account`, `ads_insights`, `ads_insights_age_and_gender`, `ads_insights_country`, `ads_insights_delivery_platform_and_device_platform`. Breakdowns multiply the rows.
* **Custom insights** *(optional)*: Your own insights reports, as a JSON list. See [Custom Insights](#custom-insights).
* **Include deleted objects** *(off by default)*: Also sync deleted campaigns, ad sets, ads and creatives, with the status `DELETED`. Turned on later, it applies from then on. See [Deleted Objects](#deleted-objects).
* **Keep creative thumbnails** *(off by default)*: Store each creative's thumbnail in `thumbnail_data_url`, because Meta's `thumbnail_url` link expires. See [Creative Thumbnails](#creative-thumbnails).
* **API version**: `v26.0` (default) or `v25.0`. If Meta refuses a field under one version, switch to the other.
* **Backfill start date**: **Insights from about 13 months back, full object history**, or **Specific start date**. See [Backfill](#backfill) and [Backfill start date](/api-sources#backfill-start-date).

### 4. Review and Create

Check the summary and click **Create**. Streamkap tests the token and every selected resource again before it creates the source. An ad account backs one source per workspace, keyed on **Ad account ID**; see [One source per vendor account](/api-sources#one-source-per-vendor-account). Then send the topics to a destination: see [Send topics to a destination](/api-sources#send-topics-to-a-destination).

## Editing the Source

* **Replacing the token**: click **Replace** on the **Auth** tab, paste a new token for the same system user and app, and save. Syncing continues where it stopped, and an authentication failure clears on save.
* **Adding an ad account**: an account added to **Additional ad account IDs** after a resource has synced is read from that resource's current position. Reset the source to backfill its history.
* **Changing a custom insight**: a synced definition cannot change, because its level and breakdowns form every row's `id` and its fields form the columns. A changed definition under the same name fails its poll with "Custom insight custom_`<name>` changed since it was first synced". Add the new definition under a new name and select it.

For adding and removing resources, see [Editing an API source](/api-sources#editing-an-api-source).

## Supported Resources

There are 14 objects and 20 insights presets, plus any custom insights you define. **Default** marks the form's starting selection.

### Objects

| Resource | What it holds | How it is read | Default |
| --- | --- | --- | --- |
| `campaigns` | Objective, statuses, budgets, bid strategy, schedule | By update time, every 5 minutes | Yes |
| `adsets` | Targeting, optimization goal, budgets, attribution, schedule | By update time, every 5 minutes | Yes |
| `ads` | Statuses, creative reference, tracking specs | By update time, every 5 minutes | Yes |
| `ad_account` | Name, status, currency, time zone, amount spent, balance | Read whole, hourly | Yes |
| `ad_creatives` | Creatives used by ads: body, title, links, media, call to action | Through the ads that use them, every 5 minutes | |
| `custom_audiences` | Subtype, approximate size, status, lookalike spec | Read whole, hourly | |
| `custom_conversions` | Event type, rule, default value, fire times | Read whole, hourly | |
| `ad_images` | Hash, URLs, dimensions, status | Newest first, every 5 minutes | |
| `ad_videos` | Title, length, format, status | Newest first, every 5 minutes | |
| `activities` | The account's activity log | Newest first, every 5 minutes | |
| `ad_labels` | Name and times | Read whole, hourly | |
| `leads` | Lead-form answers (`field_data`), form, ad, campaign, platform | Every ad's new leads, hourly | |
| `ad_studies` | Lift and split studies | Read whole, hourly | |
| `all_ad_creatives` | Every creative of the account, used by an ad or not | Read whole, hourly | |

The ad account's `owner` and `funding_source_details` and a study's business fields are left out, because they need `business_management` or finance access.

**`leads`** are read per ad, because Meta lists leads by ad or form, never by account. Each hour Streamkap reads the leads created since the last read under every ad that is not archived, and every ad archived within the last day. Leads reach back 90 days at most. Each ad costs one request an hour, so an account with many ads needs the **Full access** tier. Organic leads, submitted from a Page rather than an ad, are not read. `field_data` holds personal data your customers submitted.

### Insights Presets

Every row is one day, one object at the preset's level, and one value of each breakdown. **Breakdowns** add rows; **action breakdowns** add none and only change how lists such as `actions` are split.

| Resource | Level | Breakdowns | Action breakdowns | Default |
| --- | --- | --- | --- | --- |
| `account_insights` | account | none | `action_type` | Yes |
| `campaign_insights` | campaign | none | `action_type` | Yes |
| `adset_insights` | ad set | none | `action_type` | |
| `ads_insights` | ad | none | `action_type` | |
| `ads_insights_age_and_gender` | ad | `age`, `gender` | `action_type` | |
| `ads_insights_country` | ad | `country` | `action_type` | |
| `ads_insights_region` | ad | `region` | `action_type` | |
| `ads_insights_comscore_market` | ad | `comscore_market` | `action_type` | |
| `ads_insights_demographics_age` | ad | `age` | `action_type` | |
| `ads_insights_demographics_gender` | ad | `gender` | `action_type` | |
| `ads_insights_delivery_platform` | ad | `publisher_platform` | `action_type` | |
| `ads_insights_delivery_platform_and_device_platform` | ad | `publisher_platform`, `device_platform` | `action_type` | |
| `ads_insights_action_conversion_device` | ad | `device_platform` | `action_type` | |
| `ads_insights_action_product_id` | ad | `product_id` | none | |
| `ads_insights_platform_and_device` | ad | `publisher_platform`, `platform_position`, `impression_device` | `action_type` | |
| `ads_insights_action_carousel_card` | ad | none | `action_carousel_card_id`, `action_carousel_card_name` | |
| `ads_insights_action_reaction` | ad | none | `action_reaction` | |
| `ads_insights_action_video_sound` | ad | none | `action_video_sound` | |
| `ads_insights_action_video_type` | ad | none | `action_video_type` | |
| `ads_insights_hourly_advertiser` | ad | `hourly_stats_aggregated_by_advertiser_time_zone` | `action_type` | |

Meta serves a report without breakdowns for 37 months and one with breakdowns for 13 months.

Every preset requests the ids and names of its level and the levels above it, 90 metrics (impressions, reach, clicks, spend, CPC, CPM, CTR, the `actions`, `action_values`, `conversions` and cost-per lists, video and ROAS lists), and at ad level `quality_ranking`, `engagement_rate_ranking` and `conversion_rate_ranking`. Fields Meta refuses beside a breakdown are left out: the hourly preset has no reach, frequency, unique or video fields. Every preset reads the attribution windows `1d_click`, `7d_click`, `28d_click` and `1d_view`; Meta has returned no data for `7d_view` and `28d_view` since 12 January 2026.

`ads_insights` also stands in for other connectors' `ads_insights_action_type`. `ads_insights_country`, `ads_insights_comscore_market` and `ads_insights_action_conversion_device` stand in for `demographics_country`, `demographics_comscore_market_region` and `delivery_device`. A preset never changes under its name.

:::warning
**`impression_device` is quota-limited by Meta.** Since 6 August 2026 it needs an Ads Manager opt-in on accounts Meta does not support through sales, and Meta allows about 10 of its report jobs a day. A report with it, `ads_insights_platform_and_device` or a custom insight, polls every three hours, re-reads the last 29 days in one job, and spreads its backfill over days. Together these reports submit at most 8 jobs in any 24 hours, so you can select at most 8 of them. Without the opt-in Meta may return no rows, which **Test access** passes.
:::

## Custom Insights

**Custom insights** holds your own reports as a JSON list. Each one becomes the resource `custom_<name>`, which you select in **Resources**:

```json
[
  {
    "name": "campaign_by_country",
    "level": "campaign",
    "breakdowns": ["country"],
    "fields": ["campaign_id", "spend", "impressions", "clicks", "actions"]
  },
  {
    "name": "adset_by_platform",
    "level": "adset",
    "breakdowns": ["publisher_platform"],
    "attribution_windows": ["1d_click", "7d_click"]
  }
]
```

A definition takes only these keys, all Marketing API names:

* **`name`** *(required)*: lowercase letters, digits and underscores, starting with a letter, at most 40 characters, unique. `audiences` and `conversions` are taken by object resources.
* **`level`** *(required)*: `account`, `campaign`, `adset` or `ad`.
* **`breakdowns`**: Meta's breakdown names.
* **`action_breakdowns`**: default `["action_type"]`; an empty list requests none.
* **`attribution_windows`**: default `1d_click`, `7d_click`, `28d_click`, `1d_view`.
* **`action_report_time`**: `conversion`, `impression`, `lifetime` or `mixed`. Sent, though Meta has ignored it since 10 June 2025.
* **`fields`**: insights fields to read; the level's ids and names, `date_start` and `date_stop` are always added. Omitted, the level's preset fields apply. A field must exist at the level, must not also be a breakdown, and must be one Meta reports beside the breakdowns.
* **`time_increment`**: `1` (default, daily); a number of days up to `90`, grouped from Mondays, so `7` is Monday-to-Sunday weeks; `monthly`; or `all_days`. See [Weekly, Monthly and Whole-Range Reports](#weekly-monthly-and-whole-range-reports).
* **`start_date`**, **`end_date`**: `YYYY-MM-DD`. The report starts at `start_date` instead of the **Backfill start date**, and stops reading once Meta stops revising `end_date`.
* **`insights_lookback_window`**: days each poll re-reads, 1 to 28, default 28. Shorter costs fewer requests and misses Meta's later revisions.

An **asset breakdown** (`body_asset`, `title_asset`, `image_asset`, `video_asset`, `call_to_action_asset`, `description_asset`, `link_url_asset` or `ad_format_asset`) splits a Dynamic Creative ad's rows by the asset shown. One per report, `image_asset` and `video_asset` not at account level, and only `impressions`, `clicks`, `spend`, `reach`, `actions` and `action_values`, which are its default fields. The asset arrives as JSON text in the breakdown's column, and its id in `<breakdown>_id` and in the row's `id`. `ad_format_asset` is a plain name.

Refused at save:

| Refused | Why |
| --- | --- |
| Breakdown `frequency_value` | Meta returns it for 6 months at most, only asynchronously on opted-in accounts. |
| Breakdown `mmm` | Meta serves it only as a separate export. |
| Breakdown `hourly_stats_aggregated_by_audience_time_zone` | It needs an opt-in and is quota-limited. Use `hourly_stats_aggregated_by_advertiser_time_zone`. |
| Windows `7d_view`, `28d_view` | Meta has returned no data for them since 12 January 2026. |
| Window `default` | Meta's reference defines it two ways. List the windows you want. |
| Key `time_increment_period` | Use `time_increment`. |
| Key `filtering` | A filter would hide rows the level's status filter keeps. |
| An hourly breakdown with `time_increment` other than 1 | Meta reports hourly breakdowns per day. |
| A value listed twice, an unknown key, or a field Meta no longer serves | |

Every selected `custom_<name>` needs a definition, and every definition must be selected. Streamkap runs each definition against Meta when you test or save, and reports a combination Meta refuses on that resource.

## What Each Record Contains

**Object records** hold the resource's fixed field list, with `null` for a field Meta did not send, plus:

* **`id`**: Meta's id, a string, and the Kafka key. An `activities` row's `id` is `object_id|actor_id|application_id|event_time|event_type`.
* **Times** such as `updated_time` in UTC (`2024-03-12T22:02:47Z`), converted from the ad account's offset so they sort across daylight-saving changes.
* **`account_id`**, added where the object has none.
* **`streamkap_changed_at`**: the object's update time (`event_time` for `activities`), or the time of the poll that saw the change for `ad_account`, `custom_conversions` and `all_ad_creatives`, or the time of the read that found a lead.
* Nested values such as `targeting` or `creative` as JSON text with sorted keys.

**Insights records** look like this `campaign_insights` row, abridged (values illustrative):

```json
{
  "id": "123456789012345|120210000000000001|2026-09-24",
  "account_id": "123456789012345",
  "campaign_id": "120210000000000001",
  "campaign_name": "Autumn sale",
  "date_start": "2026-09-24",
  "date_stop": "2026-09-24",
  "impressions": 18342,
  "spend": 231.57,
  "actions": "[{\"1d_click\":\"380\",\"action_type\":\"link_click\",\"value\":\"398\"}]"
}
```

* **`id`**: the account id, the level's id (none at account level), the day and each breakdown value, joined with `|`. A `|` or `%` inside a value is escaped as `%7C` or `%25`; an empty breakdown value is an empty part.
* **`date_start`**: the report day in the ad account's time zone. At a coarser grain, the first day of the period.
* **Breakdown values** in their own columns, named as the breakdown.

**Column types** come from Meta's API specification pinned per version, so a column keeps one type:

* Every id and `*_id` field is a string.
* On insights, `impressions`, `clicks`, `reach`, `unique_clicks`, `inline_link_clicks`, `unique_inline_link_clicks`, `inline_post_engagement`, `full_view_impressions` and `full_view_reach` are integers; every other metric is a double.
* On objects, budgets, spend caps, `amount_spent` and `balance` are integers in the currency's minor units.
* Lists and objects are JSON text.

## Behavior & Limits

### Backfill

The **Backfill start date** bounds each resource's first sync only.

* **Insights** start on that day in the ad account's time zone, or 393 days back by default, clamped to 37 months without breakdowns and 13 months with them.
* **Objects read by update time or newest first** read changes on or after the date, or back to November 2007 by default.
* **`leads`** read leads created on or after the date, never more than 90 days back.
* **Objects read whole** ignore the date.
* A custom insight with `start_date` starts there.

### How Object Changes Arrive

* **`campaigns`, `adsets`, `ads`** are read by update time up to a minute ago, each changed object sent whole. Archived objects keep updating.
* **`ad_creatives`** are read through their ads and stamped with the ad's update time. An edit to a creative that does not touch its ad is not read.
* **`ad_images`, `ad_videos`, `activities`** cannot be filtered by time, so each poll reads newest first back to the last poll and sends the new rows oldest first. A large backlog is sent over several polls.
* **Objects read whole** are compared with the last read every hour; only new or changed rows are sent, and a row gone from a complete read is sent as a delete marker. `all_ad_creatives` has no size limit. When more than about 1,500 of its creatives are added or deleted in an hour, or about 750 edited, that hour re-sends every creative and may miss a delete.

### Deleted Objects

Meta does not list deleted campaigns, ad sets or ads, and refuses a filter on the `DELETED` status. With **Include deleted objects** off, a deleted campaign, ad set or ad keeps its last record and status in the destination, as do its child ad sets and ads and the creatives of deleted ads.

With **Include deleted objects** on, `campaigns`, `adsets`, `ads` and `ad_creatives` ask for every delivery state, and a deletion arrives as an upsert with `effective_status` `DELETED`, since Meta still reports its spend. To read deletions from before you turned it on, reset the resource.

Objects read whole send a delete marker (`__deleted = true`) when a row is gone from a complete read. The marker carries only `id`, `streamkap_changed_at` and `__deleted`. See [Deletes](/api-sources#deletes).

### Creative Thumbnails

`thumbnail_url` is a signed link that stops working within days. With **Keep creative thumbnails** on, every `ad_creatives` and `all_ad_creatives` record also carries:

* **`thumbnail_data_url`**: the image as a `data:` URL, fetched when the record is sent.
* **`thumbnail_url_expires_at`**: when the link expires, in UTC.

The fetch goes to Meta's CDN without your token and does not count against your API budget. `thumbnail_data_url` is empty when the link has expired or errors, the image is over 256 KB, or it would take the record past about 900 KB.

### Weekly, Monthly and Whole-Range Reports

At a coarser `time_increment`, Streamkap always asks for whole periods, because reach, frequency and unique counts do not add up across days. Each row is keyed on the period's first day, and the current period is updated in place until it ends. The re-read reaches back 28 days plus one period.

`all_days` reads one range from the report's start to today every poll, one row per object and breakdown value, updated in place. A range Meta refuses as too large fails the poll; set a later `start_date` or a coarser level.

### Several Ad Accounts

Every resource reads all accounts into the same topic, each row carrying its `account_id`. An object two accounts share arrives once. Meta meters each account separately, so the pace scales with the number of accounts, and each insights job runs in its account's own time zone.

When Meta holds one account for usage, objects read whole and `leads` read the other accounts, insights stop at the first day the held account has not read, and objects read by update time wait for it. Nothing is skipped.

When Meta refuses one account to the token, each resource skips it with a warning and the others keep syncing; its rows already in the destination stay. Insights catch up within the 28-day re-read once access returns; objects changed meanwhile arrive on their next change or after a reset. Only when the source reads a single account, or every account is refused, does the resource stop. `leads` skips an ad whose Page the token cannot read the same way.

### Insights Restatement

Meta revises insights for up to 28 days, so every insights poll re-reads the last 28 days and today. A row whose values changed is sent again with the same `id`; an unchanged row is not. **Set the destination to upsert on `id`**: an append-only destination keeps every version, and summing it counts a day several times. A row Meta stops serving is not retracted. Campaign, ad set and ad reports include rows of objects in every status, archived and deleted included.

Streamkap remembers 10,000 rows per insights resource across the 29-day window, about 345 rows a day. A larger report re-sends its oldest days' rows every poll as duplicate upserts. `ads_insights` passes that at about 345 delivering ads; `ads_insights_age_and_gender` at about 25.

### Insights Report Jobs

Meta builds insights asynchronously, in jobs of 7 days (29 for `impression_device`, about four weeks at a coarser grain). A poll waits a short while for a job, and the next poll picks up one still running. A failed job is resubmitted once; if it fails again, or Meta says it holds too much data, the window is split, down to single days. A single day Meta still cannot report fails the poll with Meta's message, and the next poll retries. A window with too many rows is split the same way; a single day is always read in full.

### Access Tier and Rate Limits

A new app is on the **Limited access** tier: 60 points every 5 minutes per ad account, one point per read, and a 5-minute block when exceeded. The connector paces itself at that rate per ad account whatever your tier, one request at a time. An app qualifies for **Full access** after 500 Marketing API calls in 15 days; request it in the App Dashboard.

When any usage budget Meta reports reaches 90%, the resource holds what that budget covers (an ad account, a business, or the app) until Meta says it recovers, at least a minute. When every account a resource needs is held, the whole source shows **Throttled** and resumes on its own. A Meta block is retried within a minute when Meta says it clears that soon; otherwise the source is throttled until it does. See [Connector status](/api-sources#connector-status).

### API Version

Streamkap pins the field list and types of `v26.0` and `v25.0`. Meta upgrades a call on a deprecated version, so an old version works until a field it requests is gone. If Meta refuses a field, the poll fails naming it; switch to the other version.

### Errors

A rejected token (Meta error 190 or 102), or a missing `appsecret_proof` on an app that requires one, stops all syncing until you save a fixed credential. A permission error on one resource pauses only that resource; see [Connector status](/api-sources#connector-status). A Meta server error is retried once, and a page too large for Meta is re-requested with half as many rows, down to one.

## Troubleshooting

**"Ad account ID must be a number"**

Enter the number shown after the account name in Ads Manager's account menu, with or without `act_`.

**"App secret is missing" or "App ID is missing"**

Set both from the app's **App settings → Basic** page, or clear both.

**"Meta rejected the access token"**

The token expired, was revoked, or its system user lost the app or the ad account. Check the system user's assets in **Business settings → Users → System users**, generate a new token with `ads_read`, and paste it with **Replace**.

**"Your Meta app has Require App Secret turned on"**

Set the **App ID** and **App secret** of the app the token was generated for, and save.

**"Meta rejected the App secret"**

The pair belongs to a different app than the token, or the secret was reset. Copy the App ID and App secret of the app you generated the token for.

**"Meta cannot find ad account act_… for this token"**

Check **Ad account ID**, and assign the ad account to the system user under **Assign assets**.

**"Meta did not let the access token read ad account act_…" or "Meta refused access to ad account act_…"**

The token lacks `ads_read`, or its system user is not assigned the ad account. The second message names an additional ad account: assign it to the system user, or remove it from **Additional ad account IDs**.

**Warning: the access token expires, or Meta's data access for it ends**

Generate a new token and paste it with **Replace** before that date. Saving is not blocked.

**Warning: "The access token lacks the ads_read permission"**

Generate a new token with `ads_read` ticked and paste it with **Replace**.

**Warning: "The access token is a … token, not a system-user token"**

Generate a system-user token (see [Meta Setup](#meta-setup)) and paste it with **Replace**.

**Warning: "Meta could not check when the access token expires"**

Check that the App ID and App secret belong to the app the token was generated for. The token itself works.

**Warning: "Ad account act_… is …, not ACTIVE"**

The ad account is disabled, unsettled, under review or closing. The source syncs its history either way; resolve the status in Ads Manager.

**"Meta refused to list …" or "The access token cannot read …"**

Give the system user at least **View performance** on the ad account, accept any terms Meta names, and save; or deselect the resource. For `leads`, assign each ad's Page with **Leads** access and generate the token with the leads permissions in [Prerequisites](#prerequisites).

**"Meta refused custom insight …"**

Meta does not report that level, breakdowns and fields together. Change the definition, under a new name if the old one has synced.

**"Custom insight custom_… changed since it was first synced"**

Restore the original definition, or add the new one under a new name. See [Editing the Source](#editing-the-source).

**"Meta is throttling the ad account"**

The source resumes on its own. If it happens often, request **Full access** for the app or deselect resources you do not need.

**"Meta refused … as too much data, even for one day"**

Select a coarser level or fewer breakdowns, or define a custom insight with fewer fields.

**"Meta could not report … for …"**

Meta failed a one-day job twice; the message carries Meta's error. The next poll retries. If the same day keeps failing, act on Meta's message or deselect the resource.

**"Meta does not report the breakdowns of … together"**

Add a custom insight with a combination Meta reports, under a new name.

**"Meta refused a field … requests under …"**

Switch **API version**, or remove the field from the custom insight.

**"Meta keeps insights for 37 months"**

Set the **Backfill start date** within the last 37 months.

**A deleted campaign, ad set or ad is still in my destination**

Meta does not serve deleted objects. Turn on **Include deleted objects** to receive deletions as `DELETED` upserts, or archive objects instead of deleting them. See [Deleted Objects](#deleted-objects).

**Totals look too high, or a day appears with different values**

Meta revised the day and the revision arrived under the same `id`. Upsert on `id` and do not sum the topic.

**Numbers differ from Ads Manager**

Ads Manager may use other attribution windows, and recent days are still being revised. Compare a day older than 28 days with the same attribution setting. Reach is not additive across days.

## Related Documentation

- [API sources](/api-sources) - how API sources work, Test access, deletes, status and sending topics to a destination
- [Google Ads](/google-ads-source) - Google Ads objects and performance reports
- [Google Analytics 4](/google-analytics-source) - daily GA4 reports
