Meta Marketing API Python SDK reference
The Ads client in the Socialift Python SDK wraps Meta's Marketing API: ad accounts, targeting search and reach estimates, campaigns, ad sets, creatives and ads, custom and lookalike audiences, and insights. Campaigns, ad sets and ads are created PAUSED, so nothing spends until you call set_status(..., "ACTIVE"). All methods are async and take a user access token.
from socialift import Ads35 methodsas of socialift 1.3.0Updated:
How do I create an Ads client?
Ads takes your Meta app secret as app_secret; with it, every request carries appsecret_proof. Ads.from_env() reads it from FACEBOOK_APP_SECRET (the same variable Facebook.from_env() uses) and the API version from META_GRAPH_VERSION. Unlike the other clients, a missing secret is not an error: it stays empty and requests go out without appsecret_proof. The license key comes from SOCIALIFT_LICENSE_KEY.
Methods take a user access token (not a Page token): ads_read for reading, ads_management for creating and changing objects. For production use, Meta requires App Review for both permissions.
Money safety: campaigns, ad sets, ads and boost_post() objects are created PAUSED, and delivery_estimate() spends nothing. Budget arguments ending in _major are in the ad account's currency and are multiplied by CURRENCY_OFFSET (100) before sending. Meta uses offset 1 for some currencies (JPY, KRW, IDR, VND, HUF and others), so for those accounts divide the amount by 100 first. UZS is not in Meta's list of ad account currencies.
The default API version is DEFAULT_GRAPH_VERSION (v26.0). Meta supports each version for at least two years from release, and an older version expires two years after the next one launches; calls to an expired version are automatically redirected to the oldest version still supported. Pass graph_version= if you need a different one, and check the date on Meta's versions page.
import asyncio
import os
from socialift import Ads
# From the environment: ads = Ads.from_env()
# (FACEBOOK_APP_SECRET, META_GRAPH_VERSION)
ads = Ads(
app_secret=os.environ["FACEBOOK_APP_SECRET"],
graph_version="v26.0", # check Meta's versions page
)
async def main():
# your own variable: a user token with ads_read
user_token = os.environ["META_USER_TOKEN"]
async with ads:
for account in await ads.ad_accounts(user_token):
print(account.id, account.name, account.currency, account.is_active)
asyncio.run(main())Client and keys
Creating the client and reading the Meta app secret from environment variables.
from_env
classmethodCreates the client from environment variables: FACEBOOK_APP_SECRET (for appsecret_proof) and META_GRAPH_VERSION. Extra keyword arguments (timeout, retry, session, license_key) are passed to the constructor.
Ads.from_env(**kwargs) -> Ads- Unlike the other clients it does not fail on an empty key: without
FACEBOOK_APP_SECRETtheapp_secretstays empty and noappsecret_proofis attached. If your Meta app requires the app secret, such requests are rejected. - Without
META_GRAPH_VERSIONthe SDK default version is used; override it inline withAds.from_env(graph_version="..."). - No access token is read here — it is passed as an argument to every method.
Ad accounts and pixels
Find the ad account to work in and its pixels.
ad_accounts
asyncLists the ad accounts the user's token can access as AdAccount objects with id, name, currency and status. Usually the first call, since most other methods need an ad_account_id.
await ads.ad_accounts(token: str) -> list[socialift.ads.AdAccount]- Needs a user access token (not a Page token) with
ads_read. - Returns a single page of up to 50 accounts.
is_activeisTruewhen Meta'saccount_statusequals 1.- Budgets are in the account's
currency; UZS is not in Meta's list of ad account currencies.
Required permissions:ads_read
for account in await ads.ad_accounts(user_token):
print(account.id, account.currency)pixels
asyncLists the Meta pixels attached to the ad account, with last_fired_time. You need a pixel ID for conversion ad sets (promoted_object) and website audiences.
await ads.pixels(token: str, *, ad_account_id: str) -> list[dict]- Single page of up to 50 pixels; needs
ads_read.
Required permissions:ads_read
pixels = await ads.pixels(user_token, ad_account_id="act_123")Targeting and estimates
Search targeting options, build a spec and estimate reach — no money spent.
build_targetingdelivery_estimatesearch_interestssearch_localessearch_locationssearch_targetingtargeting_suggestionsvalidate_targeting
build_targeting
staticmethodBuilds a Meta targeting spec (a dict) from plain arguments: locations, age, gender, locales, interests, behaviors, custom audiences and placements. A pure function: no network call and no token.
Ads.build_targeting(
*,
countries: Optional[list[str]] = None,
regions: Optional[list[Any]] = None,
cities: Optional[list[Any]] = None,
zips: Optional[list[Any]] = None,
excluded_countries: Optional[list[str]] = None,
excluded_cities: Optional[list[Any]] = None,
age_min: Optional[int] = None,
age_max: Optional[int] = None,
genders: Optional[list[Any]] = None,
locales: Optional[list[int]] = None,
interests: Optional[list[Any]] = None,
behaviors: Optional[list[Any]] = None,
demographics: Optional[list[Any]] = None,
life_events: Optional[list[Any]] = None,
excluded_interests: Optional[list[Any]] = None,
custom_audiences: Optional[list[str]] = None,
excluded_custom_audiences: Optional[list[str]] = None,
platforms: Optional[list[str]] = None,
facebook_positions: Optional[list[str]] = None,
instagram_positions: Optional[list[str]] = None,
devices: Optional[list[str]] = None,
connections: Optional[list[str]] = None,
excluded_connections: Optional[list[str]] = None,
flexible_spec: Optional[list[dict]] = None,
advantage_audience: bool = True,
) -> dict[str, Any]- Items inside
interestsare combined with OR; for AND, useflexible_spec, where each element is a separate AND condition. gendersaccepts1/2or"male"/"female"; omit it to target everyone. Ages are clamped to 13–65.advantage_audiencedefaults toTrue. With Advantage+ audience on, Meta fixesage_maxat 65 and acceptsage_minonly from 18 to 25, so passadvantage_audience=Falsefor hard age limits.- Meta requires a location in ad set targeting; this helper doesn't enforce it, so pass
countries,regions,citiesorzips.
targeting = ads.build_targeting(countries=["UZ"], age_min=18, age_max=45, interests=[{"id": "6003107902433"}], advantage_audience=False)delivery_estimate
asyncEstimates audience size for a targeting spec and optimization goal without creating anything or spending money. Use it before creating an ad set to catch audiences that are too small or too broad.
await ads.delivery_estimate(
token: str,
*,
ad_account_id: str,
targeting: dict[str, Any],
optimization_goal: str = 'REACH',
promoted_object: Optional[dict] = None,
) -> dict- Read-only: nothing is created and nothing is billed.
- Returns the first estimate row from Meta's response as a dict (an empty dict if there is none).
optimization_goaldefaults toREACH; pass the goal you plan for the ad set.- Needs
ads_read.
Required permissions:ads_read
estimate = await ads.delivery_estimate(user_token, ad_account_id="act_123", targeting=targeting)search_interests
asyncSearches Meta's interest targeting options by name and returns id, name, audience size bounds and path. Use the IDs in build_targeting(interests=...).
await ads.search_interests(token: str, query: str, *, limit: int = 25) -> list[dict]limitdefaults to 25; needsads_read.- Meta deprecates interests over time, so check saved IDs with
validate_targeting().
Required permissions:ads_read
interests = await ads.search_interests(user_token, "fitness", limit=25)
cities = await ads.search_locations(user_token, "Tosh", country_code="UZ")search_locales
asyncSearches Meta's locale (language) targeting options and returns numeric keys for build_targeting(locales=...). Use it when you split an audience by language rather than by location.
await ads.search_locales(token: str, query: str, *, limit: int = 25) -> list[dict]search_locations
asyncSearches Meta locations (countries, regions, cities) by name; the key in the response is the identifier targeting expects. Narrow results with country_code.
await ads.search_locations(
token: str,
query: str,
*,
location_types: tuple[str, ...] = ('city', 'region', 'country'),
country_code: Optional[str] = None,
limit: int = 25,
) -> list[dict]location_typesdefaults tocity,region,country; addzipfor postal codes.- A city can take a radius:
{"key": "...", "radius": 25, "distance_unit": "kilometer"}; Meta accepts 17–80 km (10–50 miles). - Needs
ads_read.
Required permissions:ads_read
interests = await ads.search_interests(user_token, "fitness", limit=25)
cities = await ads.search_locations(user_token, "Tosh", country_code="UZ")search_targeting
asyncLists or searches the targeting categories of one class, such as behaviors, demographics, life_events, industries or income. Without query it returns the whole class, for example to build a picker.
await ads.search_targeting(
token: str,
*,
target_class: str = 'interests',
query: Optional[str] = None,
limit: int = 50,
) -> list[dict]target_classmust be one ofTARGETING_CLASSES, otherwiseConfigurationErroris raised before any request.
Suggests targeting options similar to the ones you pass, such as related interests. A quick way to widen an audience that came out too narrow.
await ads.targeting_suggestions(
token: str,
*,
ad_account_id: str,
targeting_list: list[dict],
limit: int = 25,
) -> list[dict]targeting_listformat:[{"type": "interests", "id": "..."}]; needsads_read.
Required permissions:ads_read
similar = await ads.targeting_suggestions(user_token, ad_account_id="act_123", targeting_list=[{"type": "interests", "id": "6003107902433"}])validate_targeting
asyncChecks whether saved targeting items (interest IDs and similar) are still valid for the ad account. Run it before reusing old targeting in a new ad set.
await ads.validate_targeting(
token: str,
*,
ad_account_id: str,
targeting_list: list[dict],
) -> list[dict]- Meta deprecates targeting options over time, so IDs saved months ago may stop working.
- Needs
ads_read.
Required permissions:ads_read
result = await ads.validate_targeting(user_token, ad_account_id="act_123", targeting_list=[{"type": "interests", "id": "6003107902433"}])Campaigns, ad sets and ads
Create and manage the three levels. By default everything is created PAUSED.
adsadsetadsetsboost_postcampaigncampaignscreate_adcreate_adsetcreate_campaigndeleteset_statusupdate_budgetupdate_targeting
ads
asyncLists ads with their creative, status and preview link, for one ad set, one campaign or the whole ad account.
await ads.ads(
token: str,
*,
ad_account_id: Optional[str] = None,
adset_id: Optional[str] = None,
campaign_id: Optional[str] = None,
limit: int = 100,
) -> list[dict]- Priority:
adset_id, thencampaign_id, thenad_account_id; with none,ConfigurationError. - One page of up to
limititems; needsads_read.
Required permissions:ads_read
items = await ads.ads(user_token, adset_id="120200000000001")adset
asyncFetches one ad set by ID with its targeting, budgets, bid, optimization goal and schedule.
await ads.adset(token: str, adset_id: str) -> dictadsets
asyncLists the ad sets of a campaign (campaign_id) or of the whole ad account (ad_account_id), with targeting, budget and optimization fields.
await ads.adsets(
token: str,
*,
ad_account_id: Optional[str] = None,
campaign_id: Optional[str] = None,
limit: int = 100,
) -> list[dict]- If both are given,
campaign_idwins; with neither,ConfigurationError. - One page of up to
limit(default 100) items; needsads_read.
Required permissions:ads_read
adsets = await ads.adsets(user_token, campaign_id="120200000000000")boost_post
asyncTurns an existing Instagram post into an ad in one call: creates a campaign, an ad set, a creative from the post and the ad, all PAUSED. Returns their IDs; nothing spends until you call set_status(..., "ACTIVE").
await ads.boost_post(
token: str,
*,
ad_account_id: str,
ig_user_id: str,
media_id: str,
name: str,
daily_budget_major: float,
goal: str = 'traffic',
page_id: Optional[str] = None,
link: Optional[str] = None,
targeting: Optional[dict[str, Any]] = None,
currency: Optional[str] = None,
cleanup_on_error: bool = True,
) -> dict[str, str]goal:traffic,engagement,reachormessages(mapped to anOUTCOME_*objective and optimization goal);messagesrequirespage_id. The campaign gets an emptyspecial_ad_categories, so don't use it for housing, employment, financial or political ads.daily_budget_majoris in the account's currency and is multiplied byCURRENCY_OFFSET(100); for currencies Meta handles with offset 1 (JPY, KRW, IDR, VND, HUF, ...) divide by 100 first.- Without
targetingit usesdefault_targeting(): UZ, ages 18–55, Instagram placements, Advantage+ audience on. - The four objects are created one after another: if a later step fails, the earlier ones stay in the account (PAUSED) and should be deleted. Posts with copyrighted music or interactive elements such as filters can't be boosted.
Required permissions:ads_managementinstagram_basic
result = await ads.boost_post(user_token, ad_account_id="act_123", ig_user_id="1784...", media_id="1790...", name="Reels boost", daily_budget_major=10, goal="traffic")campaign
asyncFetches one campaign by ID with the same fields as campaigns(): objective, status, budgets, bid strategy and special ad categories.
await ads.campaign(token: str, campaign_id: str) -> dictcampaigns
asyncLists campaigns in an ad account with budget, objective and status fields. Filter with statuses, for example ["ACTIVE", "PAUSED"].
await ads.campaigns(
token: str,
*,
ad_account_id: str,
statuses: Optional[list[str]] = None,
limit: int = 100,
) -> list[dict]statusesis sent to Meta as theeffective_statusfilter.- Returns one page of up to
limit(default 100) items; there is no automatic paging. - Needs
ads_read.
Required permissions:ads_read
campaigns = await ads.campaigns(user_token, ad_account_id="act_123", statuses=["ACTIVE", "PAUSED"])create_ad
asyncCreates an ad in an ad set from an existing creative_id or an inline creative spec. Created PAUSED by default.
await ads.create_ad(
token: str,
*,
ad_account_id: str,
name: str,
adset_id: str,
creative_id: Optional[str] = None,
creative: Optional[dict] = None,
status: str = 'PAUSED',
**extra: Any,
) -> dict- Pass
creative_idorcreative; if both are given,creative_idis used. - Extra Meta fields go through
**extraas strings or numbers (JSON-encode dicts yourself).
Required permissions:ads_management
ad = await ads.create_ad(user_token, ad_account_id="act_123", name="Banner A", adset_id="120200000000001", creative_id="120200000000002")create_adset
asyncCreates an ad set: who sees the ads (targeting), how delivery is optimized and, unless the campaign has a budget, how much it spends. Created PAUSED by default.
await ads.create_adset(
token: str,
*,
ad_account_id: str,
name: str,
campaign_id: str,
targeting: dict[str, Any],
optimization_goal: str = 'LINK_CLICKS',
billing_event: str = 'IMPRESSIONS',
status: str = 'PAUSED',
daily_budget_major: Optional[float] = None,
lifetime_budget_major: Optional[float] = None,
bid_amount_major: Optional[float] = None,
start_time: Optional[str] = None,
end_time: Optional[str] = None,
promoted_object: Optional[dict] = None,
destination_type: Optional[str] = None,
currency: Optional[str] = None,
**extra: Any,
) -> dicttargetingcan't be empty; build it withbuild_targeting()ordefault_targeting().optimization_goalandbilling_eventare checked againstOPTIMIZATION_GOALSandBILLING_EVENTS.daily_budget_major,lifetime_budget_majorandbid_amount_majorare multiplied byCURRENCY_OFFSET(100); for offset-1 currencies (JPY, KRW, IDR, VND, HUF, ...) divide by 100 first.lifetime_budget_majorrequiresend_time. At creation Meta accepts onlyACTIVEorPAUSEDstatus.- Some objectives need
promoted_object, for example{"pixel_id": "...", "custom_event_type": "PURCHASE"}for conversions.
Required permissions:ads_management
adset = await ads.create_adset(user_token, ad_account_id="act_123", name="Toshkent 18-45", campaign_id="120200000000000", targeting=targeting, daily_budget_major=15)create_campaign
asyncCreates a campaign, the top level that sets the objective and, optionally, a campaign budget. Created PAUSED by default.
await ads.create_campaign(
token: str,
*,
ad_account_id: str,
name: str,
objective: str = 'OUTCOME_TRAFFIC',
status: str = 'PAUSED',
daily_budget_major: Optional[float] = None,
lifetime_budget_major: Optional[float] = None,
bid_strategy: Optional[str] = None,
special_ad_categories: Optional[list[str]] = None,
special_ad_category_country: Optional[list[str]] = None,
is_adset_budget_sharing_enabled: Optional[bool] = None,
currency: Optional[str] = None,
**extra: Any,
) -> dictobjectivemust be one of theOUTCOME_*values inOBJECTIVES; legacy names raiseConfigurationError.special_ad_categoriesis always sent (an empty list by default): Meta requires every campaign to declare its category, such asHOUSING,EMPLOYMENT,FINANCIAL_PRODUCTS_SERVICESorISSUES_ELECTIONS_POLITICS.- Set the budget either here (
daily_budget_majororlifetime_budget_major, one of them) or on the ad sets, not both. Amounts are multiplied byCURRENCY_OFFSET(100); for offset-1 currencies such as JPY or KRW, divide by 100 first. - From Marketing API v24.0, a campaign without its own budget needs
is_adset_budget_sharing_enabled; pass it through**extraas a string ("false"or"true").
Required permissions:ads_management
campaign = await ads.create_campaign(user_token, ad_account_id="act_123", name="Kuz aksiyasi", objective="OUTCOME_TRAFFIC", daily_budget_major=20)delete
asyncDeletes a campaign, ad set, ad or other Marketing API object by ID with an HTTP DELETE. To stop spending but keep the object, use set_status(..., "PAUSED") instead.
await ads.delete(token: str, node_id: str) -> bool- Returns Meta's
successflag; needsads_management.
Required permissions:ads_management
await ads.delete(user_token, node_id)set_status
asyncChanges the status of a campaign, ad set or ad. ACTIVE starts spending; PAUSED stops delivery and is the safe way to turn ads off.
await ads.set_status(token: str, node_id: str, status: str) -> bool- Accepted values:
ACTIVE,PAUSED,ARCHIVED,DELETED; anything else raisesConfigurationError. - While a campaign or ad set is paused, the effective status of its ads is
CAMPAIGN_PAUSEDorADSET_PAUSED. - Needs
ads_management.
Required permissions:ads_management
await ads.set_status(user_token, campaign_id, "ACTIVE")update_budget
asyncSets a new daily budget on an ad set. It affects ads that are already running, so double-check the amount.
await ads.update_budget(
token: str,
adset_id: str,
daily_budget_major: float,
*,
currency: Optional[str] = None,
ad_account_id: Optional[str] = None,
) -> booldaily_budget_majormust be greater than 0 and is multiplied byCURRENCY_OFFSET(100); for offset-1 currencies divide by 100 first.- Needs
ads_management.
Required permissions:ads_management
await ads.update_budget(user_token, adset_id, daily_budget_major=25)update_targeting
asyncReplaces the targeting of an existing ad set with the spec you pass. Send the full new spec, not just the changes.
await ads.update_targeting(token: str, adset_id: str, targeting: dict[str, Any]) -> bool- An empty
targetingraisesConfigurationError; needsads_management. - Advantage+ audience age rules apply here too (see
build_targeting()).
Required permissions:ads_management
await ads.update_targeting(user_token, adset_id, targeting)Creatives and images
What the ad looks like: image upload, creatives and previews.
create_creativepreviewupload_image
create_creative
asyncCreates a link ad creative from a Page, a URL, text and an uploaded image (image_hash) or video (video_id). Use it when the ad needs its own creative rather than an existing post (boost_post()).
await ads.create_creative(
token: str,
*,
ad_account_id: str,
name: str,
page_id: str,
link: str,
message: Optional[str] = None,
headline: Optional[str] = None,
description: Optional[str] = None,
image_hash: Optional[str] = None,
video_id: Optional[str] = None,
call_to_action: str = 'LEARN_MORE',
instagram_user_id: Optional[str] = None,
**extra: Any,
) -> dict- The SDK builds
object_story_spec.link_data:headlinegoes toname,call_to_action(defaultLEARN_MORE) totype. - Pass
instagram_user_idto run the ad under the Instagram account;page_idis always required. - Get
image_hashfromupload_image(). Needsads_management.
Required permissions:ads_management
creative = await ads.create_creative(user_token, ad_account_id="act_123", name="Kuz", page_id="1111", link="https://siz.uz", message="Chegirma 30%", image_hash=image_hash)preview
asyncReturns preview HTML (an iframe in body) showing how an ad looks in a given placement. Show it to a person before activating the ad.
await ads.preview(
token: str,
ad_id: str,
*,
ad_format: str = 'MOBILE_FEED_STANDARD',
) -> list[dict]ad_formatdefaults toMOBILE_FEED_STANDARD;DESKTOP_FEED_STANDARDand other formats are listed in Meta's ad preview docs.- Needs
ads_read.
Required permissions:ads_read
previews = await ads.preview(user_token, ad_id, ad_format="INSTAGRAM_STANDARD")upload_image
asyncUploads image bytes to the ad account's image library and returns {"hash", "url", "raw"}. Creatives reference the image by this hash.
await ads.upload_image(
token: str,
*,
ad_account_id: str,
image: bytes,
filename: str = 'ad.jpg',
) -> dictimageis rawbytes, not a path;filenameonly names the file in the upload.- Needs
ads_management.
Required permissions:ads_management
with open("banner.jpg", "rb") as f:
image = await ads.upload_image(user_token, ad_account_id="act_123", image=f.read(), filename="banner.jpg")Audiences
Custom, lookalike and saved audiences.
add_audience_userscreate_custom_audiencecreate_lookalikecustom_audiencessaved_audiences
add_audience_users
asyncAdds people to a customer-list audience by email and/or phone. Values are normalized and SHA-256-hashed on your server, so raw contact data never leaves it.
await ads.add_audience_users(
token: str,
audience_id: str,
*,
emails: Optional[list[str]] = None,
phones: Optional[list[str]] = None,
phone_country: Optional[str] = None,
batch_size: int = 10000,
) -> dict- Normalization: emails are trimmed and lowercased; phones keep digits only. Leading zeros aren't removed and no country code is added, so pass numbers in international format such as
+998901234567. - Meta accepts up to 10,000 records per request; split larger lists into several calls.
- When you pass both lists, they are zipped row by row as the same person: keep them the same length and order (extra items are dropped).
- Needs
ads_management.
Required permissions:ads_management
await ads.add_audience_users(user_token, audience_id, emails=["ali@example.com"], phones=["+998901234567"])Creates an empty custom audience, by default a customer list (subtype="CUSTOM") that you then fill with add_audience_users().
await ads.create_custom_audience(
token: str,
*,
ad_account_id: str,
name: str,
subtype: str = 'CUSTOM',
description: Optional[str] = None,
customer_file_source: str = 'USER_PROVIDED_ONLY',
rule: Optional[dict] = None,
**extra: Any,
) -> dict- For customer lists, the ad account must have accepted Meta's Custom Audience Terms; otherwise Meta returns a terms-not-accepted error.
customer_file_sourcedeclares where the data came from:USER_PROVIDED_ONLY(default),PARTNER_PROVIDED_ONLYorBOTH_USER_AND_PARTNER_PROVIDED.ruleis sent JSON-encoded; other Meta fields go through**extra. Needsads_management.
Required permissions:ads_management
audience = await ads.create_custom_audience(user_token, ad_account_id="act_123", name="Mijozlar")create_lookalike
asyncCreates a lookalike audience in one country from a source audience. A smaller ratio means closer similarity and a smaller audience.
await ads.create_lookalike(
token: str,
*,
ad_account_id: str,
name: str,
origin_audience_id: str,
country: str = 'UZ',
ratio: float = 0.01,
) -> dictratioranges from 0.01 to 0.20 (the most similar 1–20% of the country); default 0.01,countrydefaults toUZ.- The source audience needs at least 100 people from a single country.
- A lookalike takes 1–6 hours to populate; you can create ad sets that use it in the meantime.
- Needs
ads_management.
Required permissions:ads_management
lookalike = await ads.create_lookalike(user_token, ad_account_id="act_123", name="1% UZ", origin_audience_id="1202...", country="UZ", ratio=0.01)custom_audiences
asyncLists custom audiences in the ad account with their subtype, approximate size bounds and delivery and operation status.
await ads.custom_audiences(
token: str,
*,
ad_account_id: str,
limit: int = 100,
) -> list[dict]- One page of up to
limit(default 100) items; needsads_read.
Required permissions:ads_read
custom = await ads.custom_audiences(user_token, ad_account_id="act_123")
saved = await ads.saved_audiences(user_token, ad_account_id="act_123")saved_audiences
asyncLists saved audiences (targeting presets saved in Ads Manager) with their targeting spec and approximate size.
await ads.saved_audiences(
token: str,
*,
ad_account_id: str,
limit: int = 100,
) -> list[dict]- One page of up to
limititems; needsads_read.
Required permissions:ads_read
custom = await ads.custom_audiences(user_token, ad_account_id="act_123")
saved = await ads.saved_audiences(user_token, ad_account_id="act_123")Insights
Spend and results, as totals or by breakdown.
insights
asyncReturns spend and results for a campaign, ad set, ad or account as AdInsights (spend, impressions, reach, clicks, link_clicks, cpc, ctr, frequency). Use it for a quick totals card.
await ads.insights(
token: str,
node_id: str,
*,
since: Optional[str] = None,
until: Optional[str] = None,
) -> socialift.ads.AdInsights- Give both
sinceanduntil(YYYY-MM-DD); otherwise the whole lifetime (date_preset=maximum) is used. - Only the first row is read; for breakdowns use
insights_rows(). - Needs
ads_read.
insights_rows
asyncReturns raw insight rows at the chosen level with optional breakdowns such as age, gender, country or publisher_platform. Use it to find the segment where results are cheaper.
await ads.insights_rows(
token: str,
node_id: str,
*,
level: str = 'campaign',
date_preset: Optional[str] = 'last_30d',
since: Optional[str] = None,
until: Optional[str] = None,
breakdowns: Optional[list[str]] = None,
action_breakdowns: Optional[list[str]] = None,
fields: Optional[str] = None,
limit: int = 200,
) -> list[dict]- Meta supports only some breakdown combinations:
age+genderworks,age+countrydoes not. since+untiltake precedence overdate_preset(defaultlast_30d).- One page of up to
limit(default 200) rows; needsads_read.
Required permissions:ads_read
rows = await ads.insights_rows(user_token, "act_123", level="campaign", date_preset="last_7d", breakdowns=["age", "gender"])Low-level calls
Direct access to Marketing API paths the SDK doesn't wrap.
call
asyncCalls any Marketing API path with the SDK's signing (access_token, appsecret_proof), retries and error mapping. Use it for endpoints without a wrapper, such as automated rules.
await ads.call(token: str, path: str, *, method: str = 'GET', **params: Any) -> Any- Argument order is
token, path;methoddefaults toGET. Anything other thanGET/DELETEis sent asPOSTand changes real objects. dict/listvalues are JSON-encoded for you; pass booleans as strings ("true"/"false").
Required permissions:ads_read
data = await ads.call(user_token, "act_123/adcreatives", method="GET", fields="id,name")Methods shared by all clients
Every HTTP client (Telegram, Facebook, Instagram, LinkedIn, TikTok, Ads) has these low-level methods — for calling an endpoint the SDK doesn't wrap yet or for reading pages of results. The connection is closed automatically when the async with block ends.
request
asyncSends an arbitrary HTTP request and returns the JSON response — for endpoints the SDK doesn't wrap with a dedicated method yet. You pass the token and signing parameters yourself in params or headers (access_token, and for Meta appsecret_proof via socialift.crypto.appsecret_proof).
await ads.request(
method: str,
url: str,
*,
params: Optional[Mapping[str, Any]] = None,
data: Optional[Any] = None,
json: Optional[Any] = None,
headers: Optional[Mapping[str, str]] = None,
) -> AnyNonevalues inparamsare not sent.- Rate limits (
RateLimitError) and transient server errors (HTTP 429, 500, 502, 503, 504) are retried according toRetryPolicy; aRetry-Afterheader is honoured. Network failures are raised asTransportError.
get
asyncShorthand for request("GET", ...).
await ads.get(url: str, **kw) -> Anypost
asyncShorthand for request("POST", ...).
await ads.post(url: str, **kw) -> Anydelete
asyncShorthand for request("DELETE", ...).
await ads.delete(url: str, **kw) -> Anypaginate
asyncFollows Meta's cursor pagination (paging.next) and returns every data item as a single list.
await ads.paginate(
url: str,
*,
params: Optional[Mapping[str, Any]] = None,
max_items: int = 500,
page_size: int = 100,
) -> list[dict]max_items(default 500) is a safety cap — a large account can have tens of thousands of records.page_sizesets thelimitof each request.
close
asyncCloses the HTTP session the SDK created itself (a session you passed in is left open). Called automatically when you use async with Client(...).
await ads.close() -> NoneFrequently asked questions
Does creating a campaign through the SDK spend money?
Not by itself. create_campaign, create_adset and create_ad default to status="PAUSED", and boost_post() creates all four objects paused. While the campaign or ad set is paused, the ad's effective status is CAMPAIGN_PAUSED or ADSET_PAUSED, so set the campaign, ad set and ad to ACTIVE with set_status() when you are ready.
What currency are Meta ad budgets in?
The ad account's currency (AdAccount.currency). Arguments ending in _major are multiplied by CURRENCY_OFFSET (100) before sending. Meta uses offset 1 for some currencies (JPY, KRW, IDR, VND, HUF and others), so for those accounts divide by 100 first. UZS is not in Meta's list of ad account currencies.
Which permissions does the Meta Marketing API need?
A user access token with ads_read to read accounts, targeting and insights, and ads_management to create or change campaigns and audiences. boost_post() also lists instagram_basic. For production use, Meta requires App Review for these permissions.
How do I check audience size before launching Facebook ads?
Build the spec with build_targeting() and pass it to delivery_estimate(). It is read-only: nothing is created and nothing is billed. Use the same optimization_goal you plan for the ad set.
Is customer data sent to Meta in plain text when I upload an audience?
No. add_audience_users() normalizes emails and phone numbers and hashes them with SHA-256 on your server before the request. Meta accepts up to 10,000 records per request, so split larger lists across calls.