Marketing (Meta ads)
Neike talks to Meta’s Marketing API: the part of Meta that creates and
manages ads on Facebook and Instagram. Every endpoint lives under
/api/marketing/*, authenticates with the same API key as the rest of the
product, and works on ad accounts your users connect through Facebook Login.
What it is, and what it is not
Section titled “What it is, and what it is not”Neike does not replace Ads Manager, and it is not a nicer wrapper around Meta’s own Marketing API. If you run one ad account for one business, keep using Meta’s tools.
Neike is for products that operate ads for many tenants and already run
their messaging through Neike. Two things come out of that. First, one
integration serves every customer: their ad accounts sit next to their
WhatsApp, Instagram and Messenger channels, under the same client, key and
webhook. Second — and this is the real reason the section exists — the ad and
the conversation it started land in the same place. When someone taps a
Click-to-WhatsApp ad and writes, the inbound message carries a referral with
the ad’s id and click id. You get the ad, the click and the reply in one
webhook, without stitching two systems together. See
Attribution.
If you have never touched Meta ads, four boxes are enough:
| Object | What it is | Where it is decided |
|---|---|---|
Ad account (act_123456) | The wallet. Currency, timezone and a payment method loaded at Meta. All money leaves from here. | Meta Ads Manager |
| Campaign | The objective: “traffic to my site”, “WhatsApp conversations”. | Neike |
| Ad set | Who, where, when, and how much per day. | Neike |
| Ad | What people see. Points at a creative — the text, the image, the button. | Neike |
An ad needs all four: no account, no money; no campaign, no objective; no ad set, no audience; no creative, nothing to show.
Before you start
Section titled “Before you start”Permissions. The Facebook Login asks for four Meta permissions in one go:
| Permission | Used for | Optional? |
|---|---|---|
ads_management | Create, edit and pause campaigns, ad sets, ads and creatives. | No. Without it the OAuth exchange fails with 400 and error: "missing_permission". |
ads_read | Read insights. | Yes, but you get no numbers. |
business_management | Resolve which business owns the account. | Yes; the account has no business_name. |
pages_show_list | List Facebook pages — where a creative’s page_id comes from. | Yes, but you cannot create creatives. |
App Review. Operating ad accounts that belong to other people requires
Meta’s Advanced Access for ads_management and ads_read, which in turn
requires App Review and Business Verification. Until that is granted, the
permissions only work for accounts of people with a role on the app: a customer
going through the OAuth flow sees the permission screen and gets an empty list
of ad accounts back.
Payment. The payment method lives in Meta Ads Manager, on the ad account. It is not your Neike wallet: the wallet pays for messages, Meta bills the ads to the advertiser directly. If the account has no payment method at Meta, Neike creates the objects without error and nothing is ever delivered.
Accepting responsibility for the spend
Section titled “Accepting responsibility for the spend”Marketing is the one part of Neike that spends the advertiser’s money. Two locks guard it, and both have to be opened on purpose.
The scope is not inherited
Section titled “The scope is not inherited”marketing:manage is not in the default preset of an API key. A key
created without an explicit scope list gets every operational scope except two:
keys:manage and marketing:manage. To let a key create ads, name it when you
create the key in the console:
{ "label": "backend production", "scopes": ["messages:send", "marketing:manage"] }marketing:read is inherited: looking at campaigns and metrics spends
nothing. In console sessions the scope comes from the role — owner and
admin can manage, member and readonly can only read.
The terms
Section titled “The terms”Even with the scope, nothing that can increase spend runs until someone on your team accepts Neike’s terms of responsibility for ad spend. The text is short: you decide and pay for the advertising, Meta bills you directly, Neike does what you ask and tells you what happens — and you can pause or disconnect at any time.
| Method | Path | Scope | What it does |
|---|---|---|---|
GET | /api/marketing/terms | marketing:read | Current version and whether this client accepted it. |
POST | /api/marketing/terms/accept — { "version": "2026-08-30" } | marketing:manage | Records the acceptance. |
Both return the same shape (POST answers 200 with the updated state):
{ "version": "2026-08-30", "accepted": true, "accepted_at": "2026-08-30T14:32:11.000Z", "current_version_accepted": true}accepted and current_version_accepted say the same thing — the version in
force is accepted. accepted_at and accepted_by describe the latest
acceptance, whatever its version: accepted: false next to a populated
accepted_at means “an older version was accepted, someone has to accept the
new one”. Publishing a new version invalidates every previous acceptance.
Without a current acceptance, every call that can increase spend answers 403
with a stable error you can branch on:
{ "error": "marketing_terms_not_accepted", "message": "Antes de operar anuncios, alguien con rol dueño o administrador tiene que aceptar la responsabilidad por el gasto publicitario.", "terms_version": "2026-08-30"}That covers POST, PATCH and DELETE on campaigns, ad sets, ads, creatives,
images and videos; POST /insights/reports; and ad accounts, including the
OAuth exchange and the PATCH that activates an account.
Reads are never blocked. A GET costs nothing, and locking a customer out
of the metrics of campaigns already running would blind them exactly when they
need to see what is happening to their money.
A brake is never blocked
Section titled “A brake is never blocked”Anything that can only lower spend also passes, acceptance or not:
| Method | Path | Body | Why |
|---|---|---|---|
DELETE | /ad-accounts/{id} | — | Disconnecting deletes the token, the only thing Neike can spend with. |
PATCH | /ad-accounts/{id} | exactly { "status": "disabled" } | Turns the account off. "active" does not pass: that enables spending. |
PATCH | /campaigns/{id}, /adsets/{id}, /ads/{id} | status set to PAUSED or ARCHIVED, and nothing else | Pausing and archiving stop spend; activating starts it. |
The reason is the day a new version of the terms is published: every client
with running campaigns falls back to 403 at once. If pausing were behind the
gate, they would watch their ads keep spending until an owner logged in. The
gate exists so nobody spends by accident, not to stop anyone from stopping.
A PATCH passes only if the status is the only change (ad_account_id is
always required and does not count). { "status": "PAUSED", "daily_budget": 500000 }
is blocked like any other edit; so is status: "DELETED" and the DELETE of a
campaign — those need acceptance.
Connect an ad account
Section titled “Connect an ad account”There is no endpoint that takes a token. The only way in is Facebook Login, so the credential that spends an advertiser’s budget is never pasted into a request body. The console does this under Marketing → Connect account; the steps below are what happens underneath.
-
Get the login URL
Section titled “Get the login URL”Terminal window curl "https://api.neike.dev/api/marketing/oauth/url?redirect_uri=https://app.neike.dev/marketing/callback" \-H "x-neike-api-key: whk_live_…"{"url": "https://www.facebook.com/v25.0/dialog/oauth?client_id=…&state=…","state": "eyJ…"}The
stateis signed, expires in 10 minutes and is bound to the client that asked for it. Send the person tourl. -
Exchange the code
Section titled “Exchange the code”Meta redirects back with a
code. Send it, the sameredirect_uriand thestate:Terminal window curl -X POST https://api.neike.dev/api/marketing/oauth/exchange \-H "x-neike-api-key: whk_live_…" \-H "Content-Type: application/json" \-d '{"code": "AQD…","redirect_uri": "https://app.neike.dev/marketing/callback","state": "eyJ…"}'Neike exchanges the code, extends the token to a long-lived one right away (a one-hour token turns every connected account into a broken one the next day), reads
/me/adaccountsand registers one row per ad account the person can see — all in statusavailable:{"data": [{"id": "7c1b2e9a-4f3d-4a8e-9b6c-2d5e8f1a3b4c","ad_account_id": "act_123456","account_id": "123456","name": "Aurora Store","currency": "ARS","timezone_name": "America/Argentina/Buenos_Aires","account_status": 1,"account_status_label": "active","business_id": "987654321","business_name": "Aurora S.A.","status": "available","last_synced_at": "2026-08-30T14:40:02.000Z","created_at": "2026-08-30T14:40:02.000Z"}],"paging": { "after": null }}The token is stored encrypted (AES-256-GCM) and never leaves the server: not in a response, not in a log, not in a webhook.
-
Activate the ones you want to operate
Section titled “Activate the ones you want to operate”Connecting is not enabling. Meta letting a person see eight accounts does not mean you want Neike operating all eight.
{id}here is Neike’sid(the uuid), not theact_id:Terminal window curl -X PATCH https://api.neike.dev/api/marketing/ad-accounts/7c1b2e9a-4f3d-4a8e-9b6c-2d5e8f1a3b4c \-H "x-neike-api-key: whk_live_…" \-H "Content-Type: application/json" \-d '{ "status": "active" }'This is the call that emits
ad_account.connected— once per account.disabledturns it back off without disconnecting;DELETE /api/marketing/ad-accounts/{id}disconnects and deletes the token.
Two helpers you will need later: GET /api/marketing/pages?ad_account_id=act_123456
lists the Facebook pages the connected user can publish from (the page_id of
a creative), and GET /api/marketing/enums returns the catalogue of valid
objectives, optimisation goals, calls to action and so on — read it instead of
hard-coding values that Meta adds to every quarter.
Publish an ad
Section titled “Publish an ad”Every request says which account it works on: ad_account_id as a query
parameter on GET and DELETE, as a body field on POST and PATCH. The
value is Meta’s act_<id>. An account that is not yours answers 404, exactly
like one that does not exist; an object that hangs from another account also
answers 404. Every Meta id must be numeric (with the act_ prefix for
accounts) — anything else is 400 before it reaches Meta.
Three things that bite if you do not know them:
- Amounts are in cents of the account’s currency.
daily_budget: 100000on an ARS account is $1,000.daily_budgetandlifetime_budgetare mutually exclusive (400if both come), andlifetime_budgetrequiresend_time. special_ad_categoriesis mandatory at Meta. If the campaign falls in none, send[]— Neike does that for you when the field is missing.- Everything is born
PAUSED. Campaigns, ad sets and ads default toPAUSEDunless you say otherwise, so nothing spends without being asked. To go live you activate the campaign, the ad set and the ad.
-
Campaign
Section titled “Campaign”Terminal window curl -X POST https://api.neike.dev/api/marketing/campaigns \-H "x-neike-api-key: whk_live_…" \-H "Content-Type: application/json" \-d '{"ad_account_id": "act_123456","name": "Traffic · September","objective": "OUTCOME_TRAFFIC","special_ad_categories": []}'The response is the campaign as Meta stores it, with a numeric
idsuch as"120210000000000000".OUTCOME_*objectives are the ones Meta recommends today; the full list is in/enums. -
Ad set
Section titled “Ad set”Who sees it, where, from when and with how much per day.
targetingtravels as an object — countries, age range, platforms and placements. Saved audiences are not supported.Terminal window curl -X POST https://api.neike.dev/api/marketing/adsets \-H "x-neike-api-key: whk_live_…" \-H "Content-Type: application/json" \-d '{"ad_account_id": "act_123456","campaign_id": "120210000000000000","name": "AR 25-45 · feed","daily_budget": 50000,"billing_event": "IMPRESSIONS","optimization_goal": "LINK_CLICKS","start_time": "2026-09-01T00:00:00-0300","targeting": {"geo_locations": { "countries": ["AR"] },"age_min": 25,"age_max": 45,"publisher_platforms": ["facebook", "instagram"]}}'Which objective works with which optimisation goal and billing event is validated by Meta, not by Neike: an incompatible combination comes back as a
400with Meta’s message (see Errors). -
Upload the picture once and reference it by hash. The body is base64 without the
data:prefix; the file itself can be up to 7 MB.Terminal window curl -X POST https://api.neike.dev/api/marketing/images \-H "x-neike-api-key: whk_live_…" \-H "Content-Type: application/json" \-d '{"ad_account_id": "act_123456","name": "spring-banner.jpg","bytes_base64": "/9j/4AAQSkZJRg…"}'{ "hash": "a1b2c3d4e5f6…", "url": "https://scontent…", "width": 1200, "height": 628, "name": "spring-banner.jpg" }Video goes through
POST /api/marketing/videoswith a publicfile_urlthat Meta downloads; the responseidgoes invideo_data.video_id. -
Creative
Section titled “Creative”The text, the image, the button, and the Facebook page that publishes it (
page_idfromGET /pages).link_dataandvideo_dataare mutually exclusive.Terminal window curl -X POST https://api.neike.dev/api/marketing/creatives \-H "x-neike-api-key: whk_live_…" \-H "Content-Type: application/json" \-d '{"ad_account_id": "act_123456","name": "Traffic creative","object_story_spec": {"page_id": "1234567890","link_data": {"message": "New season, new prices.","link": "https://mystore.com/promo","name": "Spring sale","image_hash": "a1b2c3d4e5f6…","call_to_action": { "type": "SHOP_NOW", "value": { "link": "https://mystore.com/promo" } }}}}'For a Click-to-WhatsApp ad the call to action is
WHATSAPP_MESSAGE; for Messenger,MESSAGE_PAGE. -
Terminal window curl -X POST https://api.neike.dev/api/marketing/ads \-H "x-neike-api-key: whk_live_…" \-H "Content-Type: application/json" \-d '{"ad_account_id": "act_123456","name": "Traffic ad · image","adset_id": "120210000000000001","creative": { "creative_id": "120210000000000002" }}'An ad cannot be moved to another ad set afterwards —
adset_idon aPATCHis400. Create a new one instead. -
Go live
Section titled “Go live”Meta reviews the ad first;
effective_statustells you where it is. When you are ready, activate the three levels. Each of these needs the terms accepted:Terminal window curl -X PATCH https://api.neike.dev/api/marketing/ads/120210000000000003 \-H "x-neike-api-key: whk_live_…" \-H "Content-Type: application/json" \-d '{ "ad_account_id": "act_123456", "status": "ACTIVE" }'Same call, same body, on
/campaigns/{id}and/adsets/{id}. Pausing is the samePATCHwith"PAUSED"— and that one always works.
Lists (GET /campaigns, /adsets, /ads, /creatives) always return
{ "data": [...], "paging": { "after": string | null } }. limit is a decimal
integer from 1 to 100; 1e3, 1.5 or 12abc are 400, not silently
clamped. Filter by status, and by campaign_id (ad sets and ads) or
adset_id (ads). The fields of every request and response are in the
API reference.
Insights
Section titled “Insights”GET /api/marketing/insights answers synchronously and is what you want for a
dashboard: the last 7 days of a campaign, today’s spend of the account.
curl "https://api.neike.dev/api/marketing/insights?ad_account_id=act_123456&object_id=120210000000000000&level=campaign&date_preset=last_7d&time_increment=1" \ -H "x-neike-api-key: whk_live_…"| Parameter | Notes |
|---|---|
object_id | Campaign, ad set or ad. Without it, the whole account. |
level | account, campaign, adset or ad. |
date_preset | A named range: today, yesterday, last_7d, last_28d, this_month, last_month, maximum… Free string; Meta validates it. /enums lists the usual ones. |
time_range | Explicit range, as JSON in the query string: {"since":"2026-08-01","until":"2026-08-31"}. |
time_increment | Row granularity: a number of days (1, 7, 28), monthly or all_days. Without it Meta aggregates the whole range into one row. |
fields | Comma-separated metrics. Default: impressions,clicks,spend,reach,frequency,cpc,cpm,ctr,actions,cost_per_action_type,date_start,date_stop. |
breakdowns, action_breakdowns | Comma-separated, e.g. age,gender. |
Meta only looks back 37 months. A range that starts earlier is rejected
with Meta code 3018, which Neike passes through as a meta_error.
For big ranges — a year of daily rows broken down by age and gender — ask for an asynchronous report and poll it:
# 1. Request the report (needs the terms accepted)curl -X POST https://api.neike.dev/api/marketing/insights/reports \ -H "x-neike-api-key: whk_live_…" \ -H "Content-Type: application/json" \ -d '{ "ad_account_id": "act_123456", "level": "ad", "time_range": { "since": "2026-01-01", "until": "2026-08-31" }, "time_increment": "1", "breakdowns": "age,gender" }'
# 2. Poll until async_status is "Job Completed"curl "https://api.neike.dev/api/marketing/insights/reports/6200000000000000?ad_account_id=act_123456" \ -H "x-neike-api-key: whk_live_…"
# 3. Page through the rowscurl "https://api.neike.dev/api/marketing/insights/reports/6200000000000000/results?ad_account_id=act_123456&limit=100" \ -H "x-neike-api-key: whk_live_…"The status call returns report_run_id, async_status (Job Not Started,
Job Started, Job Running, Job Completed, Job Failed…) and
async_percent_completion. Reports expire after 30 days at Meta: a
report_run_id from last month returns nothing.
Webhooks
Section titled “Webhooks”When something happens to your ads, Neike POSTs to your backend. Same
mechanism as the messaging webhooks — your webhook_url plus a path, your
shared token in x-webhook-token — with its own path:
{webhook_url}/ads/eventsWithout a webhook_url configured nothing is dispatched, but the event is
still recorded as failed with error_code=no_webhook_url, so it can be
replayed once you set one.
Events
Section titled “Events”event | When |
|---|---|
ad_account.connected | The account became operable: it went active. Once per account. |
ad_account.sync_completed | Finished pulling the account’s data from Meta. |
ad_account.disconnected | Disconnected, or Meta revoked the token. |
campaign.created · campaign.updated · campaign.status_changed · campaign.deleted | Mutations made through the Neike API. |
adset.created · adset.updated · adset.status_changed · adset.deleted | Same. |
ad.created · ad.updated · ad.status_changed · ad.deleted | Same. |
adcreative.created · adcreative.deleted | Same. |
ad.review_failed | Meta rejected an ad in review. Comes from Meta’s webhook. |
ad.issues_detected | Meta flagged objects with problems. Same. |
ad.creative_fatigue | The creative is wearing out: the same people saw it too often. Same. |
The OAuth exchange does not emit ad_account.connected: accounts come back
available, which means Meta lets you see them, not that Neike operates them.
What the exchange emits is one ad_account.sync_completed per account — that
is how you learn which accounts appeared. connected fires once, on the
PATCH that makes an account active.
*.created, *.updated, *.status_changed and *.deleted are emitted
after Meta confirmed the operation: if the notification fails, the campaign
still exists and the endpoint still answered 200. A PATCH that moves
status emits status_changed with data.status = { from, to }; one that
changes anything else emits updated with data.fields, the list of fields
touched. A PATCH that changes the status and something else emits a single
status_changed.
Payload
Section titled “Payload”Always the same envelope:
{ "event": "campaign.status_changed", "event_id": "9b0e6b6c-6d8f-4b3a-9a1c-0f5d2c7e4a11", "occurred_at": "2026-08-29T12:00:00.000Z", "ad_account_id": "act_123456", "object": { "type": "campaign", "id": "120210000000000000", "name": "Traffic · September" }, "data": { "status": { "from": "PAUSED", "to": "ACTIVE" } }}| Field | Detail |
|---|---|
event | One of the table above. Closed list. |
event_id | uuid of the event. Use it to deduplicate: a replay resends the same event_id. |
occurred_at | ISO 8601, UTC. |
ad_account_id | Always with the act_ prefix. |
object | What changed. type is ad_account, campaign, adset, ad, adcreative or product_set. name can be null. |
data | Event-specific. Its shape depends on the event. |
The ad account token never travels in the payload: before sending, Neike strips
any key in data that looks like a credential (*token*, *secret*,
*password*, *signature*), at any depth.
Replays
Section titled “Replays”Every dispatch is recorded in the event store with direction: "marketing" and
platform: "meta_ads", visible in the console under Event history. There
is no automatic retry with backoff for these notifications: a failed one is
replayed from the console, or with POST /api/events/{id}/retry. The replay
repeats the same POST with the same envelope and re-resolves your current
webhook_url.
What Meta sends to Neike
Section titled “What Meta sends to Neike”Meta notifies on its own when something happens on its side — a rejected ad, a burnt-out creative. That webhook reaches Neike, not you; Neike translates it and forwards it with the envelope above.
The callback is https://api.neike.dev/webhook/meta-ads, verified with Meta’s
hub.challenge handshake and signed with X-Hub-Signature-256 like the
messaging webhook. The subscription lives on Neike’s Meta app (product
Webhooks, object ad_account); there is nothing to configure on your
side. Neike answers 200 immediately and processes afterwards, and a webhook
for an account nobody connected is ignored with 200.
How each subscribed ad_account field becomes a Neike event:
ad_account field | Neike event |
|---|---|
with_issues_ad_objects | ad.review_failed if it carries a review rejection; otherwise ad.issues_detected. |
creative_fatigue | ad.creative_fatigue |
in_process_ad_objects | ad.issues_detected |
product_set_issue | ad.issues_detected (with object.type: "product_set") |
ads_async_creation_request | ad.updated |
ad_recommendations | ad.updated |
The last four have no event of their own in the contract. They are sent with
the closest one and the original field name travels in data.meta_field;
the rest of Meta’s value is forwarded as-is inside data. If the same Meta
account is connected by two Neike clients (an agency and its customer), the
webhook produces one event for each, with its own event_id.
Attribution: from ad to conversation
Section titled “Attribution: from ad to conversation”This is the piece that joins the two halves of Neike. When a person opens a
conversation from a Click-to-WhatsApp, Click-to-Messenger or
Click-to-Instagram ad, Meta attaches a referral object to the first
message. Neike forwards it verbatim as message.referral on the same
message.received webhook you already handle:
{ "event": "message.received", "channel": { "id": "nk_dvzobn7m4h4y", "platform": "whatsapp" }, "contact": { "id": "5491112345678", "name": "Ana", "phone": "+5491112345678", "username": null, "avatar_url": null }, "message": { "id": "wamid.HBgNNTQ5MTEy…", "direction": "inbound", "timestamp": "2026-08-30T15:02:11.000Z", "type": "text", "content": { "text": "Hi, I saw the spring sale — is it still on?" }, "reply_to": null, "referral": { "source_url": "https://fb.me/2abc…", "source_id": "120210000000000003", "source_type": "ad", "headline": "Spring sale", "body": "New season, new prices.", "media_type": "image", "ctwa_clid": "ARAkLkA8rmlFeiCC…" } }}referral is additive: it only appears when the conversation came through
an ad, and only on the first message. A handler that ignores it keeps working
exactly as before.
What it contains
Section titled “What it contains”The shape depends on the platform.
WhatsApp. Meta does not document this object officially — the field names come from the community and from partners — so Neike forwards it raw, with nothing filtered: a field Meta adds or renames tomorrow still reaches you. What has been seen:
| Field | Meaning |
|---|---|
source_id | Id of the ad (or post) that was clicked. Matches the id of an ad you created through /api/marketing/ads. |
source_type | ad. |
source_url | URL of the ad. |
headline, body | The ad’s title and text. |
media_type | image or video. |
ctwa_clid | The Click-to-WhatsApp click id. Can legitimately be missing — a click on the post rather than the ad, desktop traffic, a WABA not linked to the ad account. |
Messenger and Instagram. This one Meta does document (messaging_referrals). Same forwarding rule — verbatim — and these fields:
| Field | Meaning |
|---|---|
ref | The ref parameter of the ad or the m.me link. |
source | ADS or SHORTLINK. |
type | OPEN_THREAD. |
ad_id | Id of the ad. |
referer_uri | URL the thread was opened from. |
ads_context_data | ad_title, photo_url, video_url, post_id, product_id, flow_id. |
So a full loop looks like this: you create an ad with a WHATSAPP_MESSAGE
call to action, a customer taps it and writes, the message.received webhook
arrives with referral.source_id equal to that ad’s id, and every message
after that belongs to a conversation you can attribute — and later measure
against the ad’s spend from /insights.
Errors
Section titled “Errors”Anything that goes wrong at Meta comes back with a stable body, whatever the
endpoint. message is Meta’s advertiser-facing text (error_user_msg) when
there is one, otherwise the technical one:
{ "error": "meta_error", "message": "Your ad set budget is below the minimum allowed.", "meta": { "code": 1487872, "subcode": null, "type": "OAuthException", "title": "Budget too low", "fbtrace_id": "AbCdEf123" }}The HTTP status follows Meta’s code:
| Meta code | HTTP | Meaning | What to do |
|---|---|---|---|
190, 102, 463 | 401 | The account’s token expired, was revoked or belongs to another session. | Reconnect the ad account through OAuth. |
200, 10, 803 | 403 | The user has no permission on that object, or a scope is missing. | Check the account’s roles at Meta. |
4, 17, 613, 80000, 80004 | 429 | Throttling — app-level or ad-account-level. | Back off and retry. |
100, 1487000–1487999 | 400 | Invalid parameter, or one of the Marketing API’s own validations (budget, targeting, incompatible objective). | Fix the request. |
| anything else, or no code | 502 | Meta failed in a way Neike does not interpret, or did not answer at all. | Retry with backoff. |
Neike’s own checks come first and use the usual codes: 400 for a non-numeric
id or an invalid limit, 403 with marketing_terms_not_accepted for the
terms gate, 404 for an ad account or object that is not yours.
Out of scope
Section titled “Out of scope”What Neike does not do today, so nobody finds out halfway through an integration:
- Product catalogues (
product_catalog,product_set): not created or managed. Only Meta’sproduct_set_issuenotice is forwarded. - Audiences (Custom Audiences, Lookalike): not created or listed. An ad
set’s
targetingis built from geography, age, gender, platforms and placements, not from saved audiences. - Experiments / A-B tests (
ad_studies). - Pixel and conversion events: Neike neither creates pixels nor sends
events through the Conversions API. You can reference an existing
pixel_idin an ad set’spromoted_object, nothing more. - Chunked video upload:
POST /videoshands Meta a URL. Meta’s resumable upload protocol for large files is not implemented. - Automated rules, shared campaign budgets and bulk edits.
- Meta billing: the payment method and the balance live in Meta Ads Manager. Neike’s wallet pays for messages, not for ads, and Neike never advances, intermediates or sees the ad spend.