Skip to content

Meta Instagram Ads ​

The Meta Instagram Ads source reads the same daily Meta Marketing API insights as Meta Ads, restricted to delivery on Instagram, and lands them in their own meta_instagram_ads_* tables. Use it when you report Instagram as a separate channel, or alongside Meta Ads when you want both the all-placement totals and the Instagram slice.

Before you begin ​

Setup is the same as for Meta Ads: a Meta app, a token with ads_read for the ad account, the ad account ID, and a destination that can rewrite daily partitions.

  1. Create an app. In Meta for Developers, create a Business app and add the Marketing API product.
  2. Create a system user token. In Business Settings, open Users → System users, add a system user, and under Assign assets give it access to the ad account. Then choose Generate new token, pick your app, select the ads_read permission, and set the token to never expire. You can reuse the Meta Ads token.
  3. Find the ad account ID. In Ads Manager, the account dropdown shows the ID. Both 1234567890 and act_1234567890 work.
  4. Set up a destination. Every stream is rewritten one day at a time, so the destination must support partition replacement: Athena, Athena Iceberg, or SkipprLake. The examples below assume a data sink named lake already exists in skippr.yml.
bash
export META_INSTAGRAM_ADS_ACCESS_TOKEN="your-system-user-token"

Configure ​

python
from skippr import Config, DataSourceMetaInstagramAds, EnvRef, Pipeline

cfg = Config.discover()
src = cfg.data_source(
    "meta_instagram_ads",
    DataSourceMetaInstagramAds(
        ad_account_id="act_1234567890",
        start_date="2026-01-01",
        access_token=EnvRef("META_INSTAGRAM_ADS_ACCESS_TOKEN"),
    ),
)
cfg.pipeline(
    "meta_instagram_ads",
    Pipeline(data_source=src, data_sink=cfg.get_data_sink("lake")),
)
cfg.save()
bash
skipprd connect data-source meta-instagram-ads \
  --pipeline meta_instagram_ads \
  --name meta_instagram_ads \
  --ad-account-id act_1234567890 \
  --start-date 2026-01-01 \
  --access-token '${META_INSTAGRAM_ADS_ACCESS_TOKEN}'
yaml
pipelines:
  meta_instagram_ads:
    data_source: data_sources.meta_instagram_ads
    data_sink: data_sinks.lake

data_sources:
  meta_instagram_ads:
    MetaInstagramAds:
      ad_account_id: "act_1234567890"
      start_date: "2026-01-01"
      access_token: ${META_INSTAGRAM_ADS_ACCESS_TOKEN}

With the CLI, wire the lake destination into the same pipeline with skipprd connect data-sink … --pipeline meta_instagram_ads if it is not there already.

Check it worked ​

bash
skipprd discover --pipeline meta_instagram_ads
skipprd sync --pipeline meta_instagram_ads --once

Then query meta_instagram_ads_account_daily in your destination. You should see one row per day from start_date through yesterday on which the account delivered on Instagram.

Options ​

KeyTypeDefaultDescription
ad_account_idstringRequiredAd account ID, with or without the act_ prefix.
start_datestringRequiredFirst report day, YYYY-MM-DD.
access_tokensecret—Token with ads_read. Used before OAuth refresh.
oauth_token_urlstring—Token endpoint for your own OAuth refresh flow.
oauth_client_idstring—App ID.
oauth_client_secretsecret—App secret.
oauth_refresh_tokensecret—Refresh token. Used only when access_token is not set, and only when all four oauth_* fields are set.
api_versionstringv21.0Graph API version, such as v23.0.
end_datestring—Last report day, YYYY-MM-DD. Without it, Skipprd syncs through yesterday.
lookback_daysinteger3Days before the last completed day to fetch again on every sync, so late conversions land.
processing_lag_daysinteger1Never sync days more recent than this many days ago.
instagram_filterbooleantrueOnly report Instagram delivery. Set to false to get all placements in these tables.
stream_profilestringfullWhich streams to sync: minimal, standard, or full.
streamslist of strings—Exact stream names to sync. Overrides stream_profile.

Secret fields only accept ${NAME} environment references (EnvRef("NAME") in Python).

What gets synced ​

Each stream lands as its own table named after the stream, with the dot replaced by an underscore.

StreamTableOne row perProfiles
meta_instagram_ads.account_dailymeta_instagram_ads_account_dailydayminimal, standard, full
meta_instagram_ads.campaign_dailymeta_instagram_ads_campaign_dailycampaign per daystandard, full
meta_instagram_ads.adset_dailymeta_instagram_ads_adset_dailyad set per daystandard, full
meta_instagram_ads.ad_dailymeta_instagram_ads_ad_dailyad per dayfull
meta_instagram_ads.campaign_placement_dailymeta_instagram_ads_campaign_placement_dailycampaign per Instagram placement (feed, stories, reels, …) per dayfull

The columns match Meta Ads: ad_account_id, date, grain IDs and names, impressions, reach, frequency, clicks, inline_link_clicks, spend, cpm, cpc, ctr, and the actions conversion list. The placement stream adds publisher_platform and platform_position.

Instagram filter. With instagram_filter: true, every request asks Meta for Instagram delivery only, and any placement row whose publisher_platform is not instagram is dropped. Campaigns that only ran on Facebook do not appear at all.

Incremental window. The first sync requests each day from start_date to yesterday, one request per day per stream. Each later sync starts again at the last completed day minus lookback_days and rewrites those days in full. Conversions follow the ad set's attribution setting (by default, 7-day click and 1-day view), so set lookback_days to at least 7 if you need them to settle.

Discover. skipprd discover reads only account_daily for the last 3 days and does not save progress.

Rate limits. This source shares Meta's per-ad-account rate limit with any Meta Ads pipeline on the same account. Skipprd retries rate-limit (429) and server (5xx) responses with exponential backoff, up to 8 attempts per request. It honours Retry-After, waits 30 seconds on a 429 without one, and never waits more than 2 minutes between attempts.

Troubleshooting ​

SymptomCauseFix
Meta Instagram Ads requires access_token, META_INSTAGRAM_ADS_ACCESS_TOKEN, or oauth_token_url + …No token is set, its environment variable is empty, or only some oauth_* fields are set.Export the token, or set all four OAuth fields.
ad_account_id is requiredad_account_id is empty.Set the ad account ID.
Tables are empty but Meta Ads has dataThe account had no Instagram delivery in the window.Check placements in Ads Manager, or set instagram_filter: false.
Error code 190 (invalid or expired token)The token expired, was revoked, or its user lost access.Generate a new system user token.
Error code 200 or 10 (permission)The token lacks ads_read, or the system user was not assigned the ad account.Regenerate the token with ads_read and assign the ad account.
Error about an unsupported or deprecated versionMeta retires each Graph API version about two years after release.Set api_version to a current version.
sink '…' does not support write policy ReplacePartitionThe destination cannot rewrite daily partitions.Use Athena, Athena Iceberg, or SkipprLake as the destination.

Next steps ​