git:20260929.c1f17bd to git:20261014.8613433

106 added, 116 removed. Audit A to A.

---
name: meta-grey-ops
- description: "Launches and operates Meta (FB/IG) ads through the Marketing API / metaops CLI: bulk launch (apply creates ACTIVE and spends; create_status PAUSED opt-out), DLO language slots, edit/clone/rules, catalog, MCP vs API access, agency and verification handling, per-vertical playbooks, plus grey-market survival (antidetect/proxy hygiene, cloaking/review-layer). Triggers: 'launch FB ads via API', 'bulk launch on N accounts', 'ad rejected Online Gambling'. Clean theory is meta-ads; tracker metrics are tracker-ops."
+ description: "Launches, monitors and operates Meta (Facebook/Instagram) ads for any vertical and offer through the metaops CLI (workspace.json + JSON specs): doctor, plan, apply --confirm SPEND (creates ACTIVE, spends), verify, review/monitor, insights/stats pulls, pause, comment hiding, BM invites, pixels/CAPI, and tokens (System User, or DevTools EAAB/EAAG/EAAd/EAAH/EAAI with cookies + proxy). Grey-market survival: antidetect/proxy hygiene, rejects and disabled accounts, review layer and cloaking, catalog/PWA funnels, restricted-vertical gates, vertical playbooks (casino/iGaming, nutra, crypto/trading, news-to-Telegram). Triggers: 'launch FB ads', 'metaops', 'workspace.json', 'bulk launch on N accounts', 'ad rejected Online Gambling', 'account disabled', 'check spend/stats on FB'. Clean Ads Manager theory is meta-ads; tracker/postback metrics are tracker-ops; portfolio and kill/scale decisions are senior-buyer-ops."
---
# Meta Grey Ops
- Reviewed 2026-09-29 (docs synced to the 29/09 code fixes; a few sections still carry their own older dates); ACTIVE-default change 2026-09-25. Baseline 2026-09-03 (Sonnet 5 blind): bulk-launch task [K] — shared BM pixel still needs per-account attach (1815045). Launch/operate via API on your own token (= autolaunch SaaS without UI, `13`) + grey survival. "buy well" → `meta-ads` · "count & sync" → `tracker-ops` · "portfolio decisions" → `senior-buyer-ops`.
+ Launch and operate via API on your own tokens (= autolaunch SaaS without the UI, `13`) + grey survival.
+ Sibling skills (same `meta/` folder): "buy well" → `../meta-ads` · "count & sync" → `../tracker-ops` · "portfolio, kill and
+ scale decisions" → `../senior-buyer-ops` · "is this difference real" → `../measurement-experimentation-ops`. In references,
+ `meta-ads/14`, `tracker-ops/03`, `senior-buyer-ops/01` etc. mean `<skill>/references/NN-*.md` (link `../../<skill>/references/NN-*.md`
+ from a references file, `../<skill>/references/NN-*.md` from this file); bare numbers (`02`, `16`) are this skill's
+ `references/NN-*.md`.
## Start here
| You are… | Read | Then |
|---|---|---|
- | In a directory with `workspace.json` | `references/16-metaops-agent-cli.md` | use `metaops --workspace … --json`; before `apply --confirm SPEND` also read `00` §6–7.5 (§8 only for create_status: PAUSED) |
- | Handed a token / asked to launch anything via API | `references/00-launch-runbook.md` | create a workspace in a project dir outside the skill (`scripts/specs/example-workspace.json`), validate it, then use `metaops` |
- | Setting up access, deciding MCP vs API, minting/handing over a token | `references/02-access-tokens-and-mcp.md` | come back to `00` |
+ | In a directory with `workspace.json` | `references/16-metaops-agent-cli.md` | `metaops --workspace … --json` (or the project's wrapper); before `apply --confirm SPEND` read `00` §6–7.5 |
+ | Handed a token / asked to launch anything via API | `references/00-launch-runbook.md` | a workspace in a project dir outside the skill (`scripts/specs/example-workspace.json`), validate, then `metaops` |
+ | Setting up access, minting/handing over a token | `references/02-access-tokens-and-mcp.md` | back to `00` |
| Hit an error, a gate, a rejection, a dead token | `00` § "When a step fails" | the one file it names |
| Ad account disabled, or ads "Spam"-rejected right after approval | `references/23-disabled-account-triage.md` | `05`, `03` |
- | Launching by hand in Ads Manager (API path not usable) | `references/24-manual-launch-ads-manager.md` | — |
- | Own BM from scratch, or a new account after a ban | `references/22-own-bm-from-scratch.md` · `references/26-new-account-after-ban.md` | `02`, `03` |
+ | Launching by hand in Ads Manager | `references/24-manual-launch-ads-manager.md` | n/a |
+ | Own BM from scratch, new account after a ban, buying accounts | `22` · `26` · `27` | `02`, `03` |
- **Agent writes a JSON spec; `metaops` controls the lifecycle; `launch.py` writes Graph
- payloads.** Never hand-assemble a payload or invoke a low-level Graph mutation directly.
- New shape → extend `metaops`/the implementation and its tests.
+ **The agent writes a JSON spec; `metaops` controls the lifecycle; `launch.py` writes the Graph payloads.** Never
+ hand-assemble a payload or call a Graph mutation directly. A new shape → extend `metaops` and its tests.
- ## Launch defaults (what every ad set/ad gets unless the spec says otherwise)
+ ## Strategy belongs to the project
- | Setting | Default | Enforced by | Override |
- |---|---|---|---|
- | Object status at create | `ACTIVE`, all levels — `apply`/`bulk-apply` spend the moment the create succeeds, so both refuse without literal `--confirm SPEND` | internal launcher (`launch.py` `spec["create_status"]`) | `"create_status": "PAUSED"` at spec top level — paused build, activate separately via `metaops activate --confirm-ui REVIEWED --confirm SPEND`. Default path builds the campaign PAUSED internally and flips it ACTIVE only after the whole tree exists — a half-built tree never spends |
- | Attribution | 1d click / 1d engaged-view / 1d view on conversion goals; **1d click only** on LINK_CLICKS/REACH/etc. (Meta rejects view windows there, 1885501). Set at CREATE (immutable after, 1504040). **Vertical overrides: casino Purchase = 7d click / 1d view** (`playbooks/casino.md`); market defaults by vertical → `references/21` | `launch.py` `DEFAULT_ATTRIBUTION`; `verify.py` reads it back; `plan` WARNS on PURCHASE without explicit `attribution` (default 1/1/1 is not the casino window) | `attribution: {...}` or `"account_default"` |
- | Advantage+ creative enhancements | every feature `OPT_OUT` incl. `adapt_to_placement`; music via `audios: []`. **Ads Manager-built creatives are different** (field 2026-09-29, all five CF1 ads): `creative_sourcing_spec.featured_offering_spec` ("Show spotlights", pulls website text into the ad) reads back OPT_IN while `degrees_of_freedom_spec` shows nothing OPT_IN, and social-feedback preservation is on; API-built creatives read back OPT_OUT | `launch.py` `DEFAULT_OPT_OUT`; `verify.py` flags any `OPT_IN` in `degrees_of_freedom_spec` AND in `creative_sourcing_spec` unless `creative.opt_in_features` lists it | `creative.opt_out_features`; on a live/manual ad only `edit creative --enhancements-off` (`16`) |
- | Multi-advertiser ads | `contextual_multi_ads: OPT_OUT` on every creative (Meta Ads MCP cannot set it — its creatives inherit OPT_IN) | `launch.py`; readable on link/template creatives, **UI check before spend** (immediately after create by default, or while PAUSED under the `create_status` override) on FORMAT_AUTOMATION collections | `creative.multi_advertiser: true` |
- | Advantage+ audience | must be explicit `true/false` in `targeting`; **`true` caps `age_min` at 25 and forces `age_max` 65** — any hard age/gender requirement means `false` (`04`) | spec rejected without it (v23+ Graph rule) | — |
- | Placements | Ad set `placements`: `"fb_ig_all"` (Facebook + Instagram, explicit positions, `device_platforms: ["mobile"]` since story/reels positions are mobile-only; no Messenger / Audience Network / Threads) or `"fb_ig_feeds"` (FB feed + IG stream); explicit `publisher_platforms` + positions win. Omitted `publisher_platforms` = Meta's all-placements incl. AN / Messenger / Threads: `plan` WARNS, and `verify` WARNS on a spec that never pinned placements (FAILS only under `lint: "strict"`). Recipe and when to narrow → `playbooks/casino.md`, `references/25` | **Instagram is mandatory**: `launch.py` rejects any `publisher_platforms` without `instagram`; positions for an untargeted platform or an empty positions list are spec errors; `verify` FAILS `publisher_platforms` the spec pinned but the read-back lacks, AN / Messenger / Threads on the read-back that the spec did not list, and an ad without `instagram_user_id`; positions are required on the read-back only when the spec/preset listed them | positions may narrow, never drop IG |
- | Bid policy | conversion goals should carry a cap; `"bid_policy": "require_cap"` (campaign or ad set) makes a missing cap strategy / `bid_amount_minor` a load-time error; `campaign.bid_amount_minor` is rejected (cap lives on ad sets, 1815857) | `launch.py`; `plan` WARNS on an uncapped conversion goal without it | — |
- | Link-ad copy (longread) | `headline` = short NEUTRAL line, ≤40 chars (e.g. "Read before you judge"), never the casino/brand name; no offer wording in headline/description; `cta: "SEE_DETAILS"` (launch.py default is `LEARN_MORE`: set it); `display_link` empty or the root domain, never the casino subdomain; `description` empty or neutral. Names of campaign / ad set / ad carry no casino name (own BM, operator 29/09). Body = first-person story, may mention the bonus | top-level `"lint": "strict"\|"warn"`: headline >40 chars and offer/casino words (flagged: welcome bonus, get exclusive, offer valid, valid until, play online, free spins, deposit match, "$N … bonus", casino, jackpot; bare "exclusive" and "claim" are NOT flagged) in headline/description = error in strict, warning otherwise; strict also requires non-empty headline and message; `display_link` on `link_video` = error in strict. The message body is never linted. **Not linted: casino names in campaign/ad set/ad names, and the display_link value: check by hand** | `lint: "warn"` (default) |
- | Budget mode | CBO (`campaign.daily_budget_minor`) **or** ABO (every `adsets[].daily_budget_minor`) | spec rejected if both/neither; cap strategies (`COST_CAP`/`LOWEST_COST_WITH_BID_CAP`) need `bid_amount_minor` on every ad set — under ABO that's the ad set's own `bid_strategy`, under CBO it's `campaign.bid_strategy` but the cap amount still lives on each ad set (1815857) | — |
- | API pacing | throttle (17/613/4/32/80xxx) is **never retried**: account (or all, for 4/32) goes on a ≥30 min cooldown that every later process honours; one new campaign per ad account per 3 h (also `clone campaign --times N`: stops within the gap); `assets swap --interval` floor 300 s; transient errors are retried only for GETs and `idempotent=True` calls, a non-idempotent create raises once with `outcome_unknown=true`; `x-fb-ads-insights-throttle` is read like the other usage headers (pause at 85%) | `graph.py` (`~/.metaops/pacing.json`, per-process tmp + flock), `launch.py`; `metaops pace` shows state | `METAOPS_PACE_OVERRIDE=1` only to stop spend or when the operator asks |
- | Budget unit | integer minor units; **whole units on no-offset currencies (TWD, JPY, KRW, HUF…)** | `launch.py` prints every budget in major units and fails on `spec.currency` ≠ account currency | put `currency` in the spec |
- | `start_time` | conversions 06:00–08:00 geo-time, never 00:00; reach/traffic next 00:00 | spec required; `apply` refuses a past or offsetless `start_time` on ACTIVE creates (re-date spec, re-plan); `--refresh-start` exists only on `activate` | — |
- | Identity | Page + PBIA (`instagram_user_id: "auto"`) | launcher resolves it. No PBIA: `doctor` only WARNS, `launch.py` aborts (IG is mandatory). `POST /{page_id}/page_backed_instagram_accounts` is deprecated for all versions since 2025-04-21 (v22.0 changelog, verified 2026-09-29; returns #10), and the GET edge is dead: Ads Manager → ad draft → Identity → **Use Facebook Page**, discard draft (`18`) | explicit id |
- | EU/EEA geo | `dsa_beneficiary` + `dsa_payor` required | spec rejected without them | — |
- | Swap timing | swap each set the moment ITS ads are approved (ACTIVE); never wait for first delivery, white must not spend (operator 2026-09-26). Start `assets swap --watch` right after `apply` | `swapgate.py`: per-set verdicts, other sets' rejects/reviews and zero impressions never block; same gate on `feed swap` / `catalog products batch` / `set-products` | `--revert` (to white), `--paused-ok`, `--force` on feed/batch |
- | Catalog text model | texts live in the CATALOG ITEMS (white item = what review reads, target item = what runs); creative carries only `{{product.*}}` tags + `-----`; never target wording in the creative | `plan` checks white items (filled, no gambling words) and target SKUs; `verify` diffs the card texts | `allow_white_text` |
- | Catalog texts (swap-safe) | `message` `-----`; headline/description `{{product.name}}`/`{{product.description}}` (else Meta scrapes the link's `<title>`) | `launch.py` refuses wordy message / static headline; `assets swap` refuses ads without macros | `creative.allow_message` / `allow_static_text` |
- | Tracking | spec-level `url_tags` inherited by every ad; catalog cards need `template_url` | `launch.py`; `verify.py` diffs destination per creative kind | per-creative `url_tags` |
- | Special ad categories | must be declared explicitly (`[]` or the real one) | spec rejected if absent | — |
+ Structure (1-5-1, 5-3-1, ABO/CBO…), budgets, bid, optimization event (registration first or Purchase from day one), start
+ time, account tz and edit policy (in-place scaling vs set-and-forget) depend on offer and geo: there is no right answer
+ here. Read them from the project's notes/AGENTS file or ask the owner before a launch; this skill gives options (`04`
+ Structures, `playbooks/`, `senior-buyer-ops/01`), not a default. Mechanics that serve any strategy:
+ - Campaign creates on one account are spaced by `METAOPS_CREATE_GAP_HOURS` (default 0.1 = 6 min; raise it on weak/fresh accounts).
+ Several campaigns on one account at once → `METAOPS_CREATE_GAP_HOURS=0` for that batch (keeps the throttle cooldown;
+ `METAOPS_PACE_OVERRIDE=1` drops it, so keep it for stopping spend). Leave `start_time` out of the spec (start at create).
+ - New stories into a live campaign (after rejects): spec `"campaign": {"id": "<id>"}` adds ad sets; the campaign is read and checked,
+ never edited; no create gap; single-account only (`16` § Single-account lifecycle).
+ - A budget above the account's daily spend limit (`adtrust_dsl`) is a valid choice (the limit grows within hours): don't flag it.
+ - Judge on full days (deps lag); kill/scale figures → `senior-buyer-ops/01`.
- Not in the scripts and therefore on you: account-level "test new optimizations" enrollment
- (Advertising Settings, UI only), billing/card binding, appeals, BM and Page creation.
+ Standing rules that hold in every project:
+ - **Rejects:** never edit or resubmit a rejected ad, never re-upload the same creative/angle/PWA into that account (`23`).
+ Switching rejected ads / dead campaigns off is the owner's manual job unless the project says otherwise.
+ - **Comments: hide, never delete** (`comments delete` is disabled).
+ - **Backup admin** on every BM before work: an ADMIN invite to the owner's backup email (`business user invite --role
+ ADMIN`), re-issued before the 30-day expiry; PENDING_INTEGRITY_REVIEW = no email went out, the owner confirms in the UI
+ (`22`).
+ - **No brand/casino names** in campaign / ad set / ad names, headline, description or display link (`lint` checks only
+ headline/description; names and display link by hand). Longread copy: `playbooks/casino.md`.
- ## Non-negotiables (always apply)
+ ## Launch defaults (detail and enforcement: `16` § Launch defaults)
- 1. **One identity = one egress profile.** Browser, API, and token operations use the BM/operator's
- assigned IP or proxy. Token type does not waive this rule. Scripts refuse to run without
- `META_PROXY` unless `META_ALLOW_NO_PROXY=1` deliberately confirms that direct egress from the
- current IP is expected for this BM (`01`, `02`). **Under `metaops` the only way to run without a
- proxy is `workspace.json` `defaults.allow_no_proxy: true`; an exported `META_ALLOW_NO_PROXY=1` is
- dropped.** Field 2026-09-29: a scraped EAAB from a Mac with no proxy passed `metaops doctor`
- (14 ok, incl. `validate_only`): it works, which is not proof direct egress is safe; per-BM decision.
- 2. **Cookies gate only the scraped EAAB browser token** (`c_user`/`xs` + proxy/UA, `02` §5) — a
- dev-app or System User token needs none; supplying them elsewhere is inert, omitting them on
- EAAB fails every call with `code 1`. Rule 1's egress default still covers all three: an
- unfamiliar IP can checkpoint the BM mid-flight regardless of token type.
- 3. **A token rides a login session.** Logout / password change / security rotation kills every
- token minted from it, 60-day included. System User tokens are session-independent — prefer
- them when the BM is yours (`02`).
- 4. **Restriction / checkpoint = FREEZE.** No re-logins, token regen, profile edits (`01`).
+ | Setting | Default |
+ |---|---|
+ | Status at create | `ACTIVE`; `apply`/`bulk-apply` need the literal `--confirm SPEND`. Opt-out `"create_status": "PAUSED"` → `activate --confirm-ui REVIEWED --confirm SPEND` |
+ | Attribution | 1d click / 1d engaged-view / 1d view; **casino Purchase = 7d click / 1d view, set explicitly** (immutable after create) |
+ | Advantage+ creative | every enhancement `OPT_OUT`; `contextual_multi_ads: OPT_OUT`. Ads Manager-built creatives keep "Show spotlights" OPT_IN (`24`) |
+ | Advantage+ audience | explicit `true/false`; `true` caps `age_min` at 25 and forces `age_max` 65 → a hard age (US gambling 21-65) = `false` |
+ | Placements | `"fb_ig_all"` (FB + IG, mobile, no AN / Messenger / Threads) or `"fb_ig_feeds"`; Instagram mandatory |
+ | Bid | an uncapped conversion goal → `plan` WARNS; `"bid_policy": "require_cap"` makes a cap mandatory; the cap amount goes on ad sets, never on the campaign |
+ | Link-ad copy | headline neutral ≤40 chars ("Read before you judge"), `cta: "SEE_DETAILS"` (the launcher default is LEARN_MORE), display link empty/root, `lint: "strict"` |
+ | Budget | CBO or ABO, never both; integer minor units (whole units on no-offset currencies); `currency` in the spec |
+ | API pacing | a throttle (17/613/4/32/80xxx) is never retried, ≥30 min account cooldown; one campaign create per account per create gap (§ Strategy); no polling loops |
+ | Identity | Page + PBIA (`instagram_user_id: "auto"`); no PBIA → Ads Manager draft → Identity → Use Facebook Page (`18`) |
+ | Tracking | spec-level `url_tags` on every ad; PWA builders that pick the pixel from the URL need `fbp=<pixel>` (`11`) |
+ | Required | `special_ad_categories` explicit; EU/EEA `dsa_beneficiary` + `dsa_payor` |
+
+ Not in the scripts: Advertising Settings "test new optimizations", billing/card binding, appeals, BM and Page creation.
+
+ ## Non-negotiables
+
+ 1. **One identity = one egress profile, always through its proxy** (a persona reached by a script without its proxy or cookies goes
+ straight to a verification / selfie checkpoint). Scripts refuse without `META_PROXY` (only `defaults.allow_no_proxy: true`
+ waives it, for a persona whose own browser lives on that IP) and, before the first Facebook call, check the proxy answers and
+ exits in `META_PROXY_COUNTRY` / `defaults.proxy_country` when set. A dead or wrong-country proxy = nothing sent (`01`, `02`).
+ 2. **DevTools tokens need their session:** in `token_mode: devtools` every call needs the profile's `c_user` + `xs` cookies
+ and UA (else code 1). System User / own-app tokens need none, but rule 1 still applies.
+ 3. **A token rides a login session.** Logout / password change / checkpoint kills every token of it (`02`).
+ 4. **Restriction / checkpoint = FREEZE.** No re-logins, token regen, profile edits. Set `META_LOCKED='<reason>'` in the profile's
+ env file: every call refuses until the owner clears it (`01`).
5. **Never touch a work identity from a personal browser or IP.**
- 6. **Judge accounts after $30–50 spend, not the first hours.** Keep a replacement reserve (`03`).
- 7. **Bulk = one creative per account.** Identical creative across accounts overheats your own
- auction; `bulk.py` warns, you decide (`03`).
- 8. **A launch runs on a plan file, not memory.** Copy `19`'s template to `.notes/plan.md` before
- the first write; tick only against a read-back. A todo list forgets steps that fire days
- later — above all the post-approval swap (`00` 9.7) — so the white set keeps spending, no
- error anywhere.
- 9. **Pace the API like a human operator** (FIELD 2026-09-27: the heaviest-load account, with a
- code-17 retry storm, was disabled for Account Integrity "automation"; siblings on the same token
- lived). One campaign create per account per 3 h, one read-back after create (no polling loops — the only sanctioned poll is `assets swap --watch`
- at ≥300 s, one per account),
- stop on any throttle and leave that account alone ≥30 min, weak/new accounts (tiny lifetime
- spend, low billing threshold) start below target budget. Never re-upload a rejected creative /
- angle / PWA into the same account — switch the ad off. Enforced in code (`metaops pace`).
- 10. **Operator rule: on a disapproval do not edit or resubmit.** Switch the ad off, leave it, report
- (`references/23`). `edit tags|creative` skips DISAPPROVED / `ad_review_feedback` ads unless
- `--allow-disapproved`: do not pass it. Clean-lane advice to "fix, appeal, resubmit"
- (`meta-ads/07`, `14`, vendor recipes in `04`/`07`) does not apply on this BM.
- 11. **Own BM: no casino names in campaign / ad set / ad names, headline, description or display
- link** (operator 29/09; the tr-1 launch carried a casino name in campaign name and headline and
- the account was disabled: correlation, cause unproven). `lint` covers headline/description
- only; names and display link are checked by hand.
+ 6. **Bulk = one creative per account** (the same creative across accounts competes with itself; `bulk.py` warns).
+ 7. **A launch runs on a plan file, not memory.** The `19` template → your plan file; tick only against a read-back.
+ 8. **Pace like a human operator** (an account with a code-17 retry storm was disabled for "automation", siblings lived): one
+ read-back after create, stop on any throttle, leave that account ≥30 min (`16` § Pacing).
## References
| Need | Reference |
|---|---|
- | **Ordered launch path — START HERE** | `references/00-launch-runbook.md` |
- | **Launch plan file** — template copied to `.notes/plan.md`, ticked step by step, carries the deferred swap | `references/19-launch-plan.md` |
- | Antidetect, proxies, IP/session discipline, checkpoints, domain/pixel rotation | `references/01-infra-and-identity.md` |
- | **AdsPower Local API**: profile create/update payloads, v1/v2 `domain_name` vs `platform` trap, `open_urls` false-ban signal, desktop fingerprint pinning, why cookie liveness can't be scripted | `references/20-adspower-local-api.md` |
- | **Attribution market defaults**: window by vertical (casino Purchase vs reg, nutra, lead gen, e-com), All vs First conversion, Meta 2025-26 changes, tracker reconciliation | `references/21-attribution-market-defaults.md` |
- | **Access: app use cases, scopes, one app + token per BM (multi-BM rule), System User vs user token, token death, MCP vs API vs CLI, operator handoff checklist** | `references/02-access-tokens-and-mcp.md` |
- | Agency setups, BMs, asset sharing, BM-ban recovery, **bought aged "spend" accounts: pre-buy QC + card-before-BM binding order**, billing gotchas, naming, replacements | `references/03-agency-accounts-and-bm.md` |
- | Why the scripts do what they do: structures, params, bid strategies, scheduling, DLO, catalog quirks, media, warm-up, re-moderation | `references/04-mass-launch-api.md` |
- | API errors — grey survival response (freeze/replace/rotate); canonical code→fix is `meta-ads/14` | `references/05-api-error-catalog.md` |
- | Why accounts die, attributed: hazard-rate forensics, balanced infra tests | `references/06-portfolio-forensics.md` |
- | Review-layer filters, cloaking, DLO/catalog/unicode/CTM tricks, BM verification (vendor tool configs & unverified hypotheses → `references/07a-vendor-recipes.md`) | `references/07-review-layer-and-cloaking.md` |
- | Location fees, tz/currency 60d lock, sanctioned targeting, WABA | `references/08-geo-fees-and-waba.md` |
- | Verification gates: business / beneficial-owner / identity, gambling+crypto+financial authorization, order | `references/09-verification-gates.md` |
- | **Does this vertical have a path at all** — permission-before-spend and no-path lists | `references/10-no-path-and-permissions.md` |
- | PWA funnel builders: join macros, postbacks, CAPI, QA gates | `references/11-pwa-funnel-builders.md` |
- | **Meta Ads CLI** (`pip install meta-ads`): what it can do, flags, traps — read before reaching for it | `references/12-meta-ads-cli.md` |
- | **Autolaunch parity**: what Dolphin Cloud / FBTool / cabinet.partners do, how (EAAB tokens), feature → our script or UI-only | `references/13-autolaunch-parity.md` |
- | Canonical Graph error codes and fixes (not a local reference 14) | `meta-ads/14` |
- | Meta Ads MCP live tool inventory (106 tools, params) | `references/15-mcp-tools-live.md` |
- | **Agent launcher**: plan/apply/verify/activate contract, JSON output, locks. Also: `edit ... --validate-only` (Meta checks a change, nothing applied), `review --tree`, `activity`, `images list`, `insights pull --breakdown`, `doctor --risk`, `token import`, `keitaro push/report` (FB spend to Keitaro) | `references/16-metaops-agent-cli.md` |
- | **Catalog as one Google Sheet** — service-account setup, Commerce Manager scheduled feed, columns, `sheetfeed` | `references/17-catalog-via-google-sheets.md` |
- | **Fanpage by GEO/tier**: white vs mechanic vs empty (disputed), name/avatar freeze, one Page vs many, NPE partner-share, PBIA | `references/18-fanpage-lifecycle-and-shielding.md` |
- | **Own BM from scratch**: system user, app, pixel + CAPI, sharing, Page/IG, billing | `references/22-own-bm-from-scratch.md` |
- | **Disabled ad account / "Spam" right after approval**: what to read, what not to do | `references/23-disabled-account-triage.md` |
- | **Manual launch in Ads Manager**: 1-5-1 checklist, Advantage+ sales limits | `references/24-manual-launch-ads-manager.md` |
- | **Reading placement performance**: which placements to keep | `references/25-placement-performance.md` |
- | **New account after a ban**: minimal footprint | `references/26-new-account-after-ban.md` |
- | Per-vertical playbooks (casino, nutra, crypto-trading, news-tg) | `playbooks/` — numbers are dated vendor/team priors, replace with live data |
+ | **Ordered launch path: START HERE** · plan file template | `references/00-launch-runbook.md` · `19-launch-plan.md` |
+ | Proxies, IP/session discipline, checkpoints, domain/pixel rotation | `01-infra-and-identity.md` |
+ | Tokens: classes (EAAB/EAAG/EAAd/EAAH/EAAI), devtools mode, scopes, System User, token death | `02-access-tokens-and-mcp.md` |
+ | Agency setups, BMs, asset sharing, bought accounts, billing, naming, replacements | `03-agency-accounts-and-bm.md` |
+ | Why the scripts do what they do: bid strategies, objective traps, creative fields, media, warm-up, re-moderation, catalog quirks | `04-mass-launch-api.md` |
+ | API errors and the grey response (canonical code→fix: `meta-ads/14`) · why accounts die | `05-api-error-catalog.md` · `06-portfolio-forensics.md` |
+ | Review layer, cloaking, DLO/unicode/CTM tricks (vendor configs → `07a`) | `07-review-layer-and-cloaking.md` |
+ | Location fees, tz/currency lock, sanctioned targeting, WABA | `08-geo-fees-and-waba.md` |
+ | Verification gates, gambling/crypto/financial authorization · does this vertical have a path at all | `09-verification-gates.md` · `10-no-path-and-permissions.md` |
+ | PWA funnel builders (SKAK, pwa.bot, …): join macros, postbacks, CAPI, QA gates | `11-pwa-funnel-builders.md` |
+ | **metaops CLI**: commands, JSON contract, locks, pacing, launch-default enforcement | `16-metaops-agent-cli.md` |
+ | Fanpage lifecycle, PBIA · attribution windows by vertical | `18-fanpage-lifecycle-and-shielding.md` · `21-attribution-market-defaults.md` |
+ | Own BM from scratch · disabled account / "Spam" after approval · manual launch · placements | `22` · `23` · `24` · `25` |
+ | New account after a ban · buying accounts and keeping them alive | `26` · `27` |
+ | Vertical playbooks (casino, nutra, crypto-trading, news-tg): priors, replace with live data | `playbooks/` |
- Declared gaps: no dating or loans playbook; `APP_PROMOTION` only directional in `playbooks/casino.md`.
+ Optional (load only when the project uses it):
- ## Implementation boundary
+ | Lane | Where |
+ |---|---|
+ | **Catalog / swap funnels**: launch order in `17` § Field scenario (EAAH = the catalog token, white items, new white sets per campaign, `{{product.*}}` macros, `assets swap --watch` right after `apply`) | `17-catalog-via-google-sheets.md`, `00` §9.7, `16` § Catalog lifecycle, `playbooks/casino.md` § Review traps |
+ | **Keitaro** (cost push, per-ad join, campaign-name contract) | `16` § Keitaro, `../tracker-ops/references/01-keitaro.md` |
+ | AdsPower Local API · Meta Ads MCP (tool inventory) · Meta Ads CLI · autolaunch SaaS parity | `20` · `15` · `12` · `13` |
- `metaops` is the only agent write boundary: it rejects workspaces inside the skill directory and
- owns command syntax in `references/16-metaops-agent-cli.md`. Low-level scripts are internal; their
- POST/DELETE calls run only through a validated, workspace-bound `metaops` process. Generated files
- belong in the project workspace. Run `16`'s isolated checks after an implementation change.
+ Declared gaps: no dating or loans playbook; `APP_PROMOTION` only directional in `playbooks/casino.md`.
- Token classes (FB Helper extension, all five supported, `tokens.py`, capability matrix in `02` §5): `META_TOKEN` = ads (EAAB, or System User / own-app user token); optional `META_TOKEN_BUSINESS` (EAAG), `META_TOKEN_CATALOG` (EAAH), `META_TOKEN_EVENTS` (EAAd), `META_TOKEN_RULES` (EAAI), cookies shared in `META_COOKIES`. `metaops token import` files a pasted extension block by class; `doctor` prints the table. Field 29/09: all five read the ad account, EAAd cannot read an ad set (#10).
+ ## Implementation boundary and env
- Graph quirk (field 29/09, CF1): per-parent edges `{adset}/ads`, `{campaign}/ads` and nested `adsets{ads{}}` can answer EMPTY while `act_ID/ads` lists the ads; read the flat account edge (with `filtering` on `adset.id` / `campaign.id`).
+ `metaops` is the only agent write boundary: it rejects workspaces inside the skill directory, and the low-level scripts'
+ writes run only through a validated, workspace-bound `metaops` process. Generated files belong in the project. After a code
+ change run `16`'s isolated checks, then `uv tool install --reinstall ~/.claude/skills/meta/meta-grey-ops`: the `metaops` on
+ PATH is a copy, not a link (fixes sit unused until the reinstall).
- Env: `META_TOKEN` (required; any token kind — System User, your own app's user token, a
- third-party dev app's user token, or a scraped browser token, `02` — `workspace.json`'s
- `profiles.<p>.token_kind` tells `assets verify` which BM-ownership checks it can hard-gate vs.
- only warn on), `META_PROXY` or (direct scripts only) `META_ALLOW_NO_PROXY=1`; under `metaops` use workspace
- `defaults.allow_no_proxy`, optional `META_COOKIES`
- (scraped EAAB only), `META_USER_AGENT`, optional `META_APP_SECRET` (`appsecret_proof`),
- `META_API_VERSION` (pinned `v26.0`; must look like `vNN.N`, a warning outside v25/v26), `GSHEETS_JSON_KEY_FILE` (sheetfeed / feed swap),
- `TG_BOT_TOKEN`+`TG_CHAT_ID` (`monitor --telegram`). Never pass a token on argv.
+ Tokens (`token_mode: "devtools"`, default for new workspaces, `02` §5.1): `META_TOKEN` (EAAB ads), `META_TOKEN_BUSINESS`
+ (EAAG), `META_TOKEN_EVENTS` (EAAd pixel/CAPI), `META_TOKEN_CATALOG` (EAAH), `META_TOKEN_RULES` (EAAI), optional
+ `META_TOKEN_ANALYTICS` (BM System User token, GET-only: policy in `02` §5.1a, i.e. every scheduled read uses ONLY the per-BM
+ System User token with no persona-token fallback, and DevTools tokens serve on-demand reads), `META_COOKIES`, `META_USER_AGENT`,
+ `META_PROXY` (socks5h), `META_PROXY_COUNTRY` (ISO2), `META_LOCKED`. `metaops token import` files a pasted FB Helper block by
+ class; `doctor` prints the table. Also: `META_APP_SECRET`, `META_API_VERSION` (pinned `v26.0`), `GSHEETS_JSON_KEY_FILE`
+ (Google Sheet catalog feed), `TG_BOT_TOKEN`+`TG_CHAT_ID` (`monitor --telegram`), `METAOPS_CREATE_GAP_HOURS`. Never pass a
+ token on argv.
+
+ Graph quirk: `{adset}/ads`, `{campaign}/ads` and nested `adsets{ads{}}` can answer EMPTY while `act_ID/ads` lists the ads;
+ read the flat account edge with `filtering` on `adset.id` / `campaign.id`.