Lifecycle · Draft
Meta 2-Tier Onboarding — Dev Runbook (Errors, Graph API, Ads Readiness)
Audience: Engineering starting the product worker. Captures everything we hit in the Jun 2026 dev spike — errors, fixes, access quirks, pixel fields from Graph API, and what still blocks live ads.
Related: Implementation pack · ADR 0003 · meta-dev/ · specs/provisioning/
1. Ads readiness (dev smoke)
Infrastructure verified in Jun 2026 dev spike. Run audit-readiness.ps1 for a snapshot, run-2tier-phase5-verify.ps1 for spend_cap + Page linkage, probe-smoke-campaign.ps1 -Create for PAUSED campaign+adset.
| Requirement | Dev spike status | Blocks live ads? |
|---|---|---|
| Child BM + system user | ✅ Verified | — |
Ad account (account_status=1 ACTIVE) |
✅ Verified | — |
| Pixel created | ✅ Verified | — |
| Page for ads | ✅ GET /{child_bm}/client_pages + PROFILE_PLUS_ADVERTISE |
No — use promoted_object.page_id |
spend_cap |
✅ POST works — see §1b | Guardrail only |
| Pixel firing | ❌ No site install until client IT | Yes (A2 gate) |
| Campaign create (PAUSED smoke) | ✅ Verified | — |
| Production billing (PRE-2) | ⬜ Deferred | Yes (production) |
1b. Page linkage via client_pages
When the child BM is created with shared_page_id in owned_businesses, Meta attaches the Page to the child BM as a client page.
| Check | Dev result |
|---|---|
GET /{child_bm}/client_pages |
✅ Page present with PROFILE_PLUS_ADVERTISE |
POST /act_{id}/campaigns + ad set with promoted_object.page_id |
✅ PAUSED smoke campaign created |
Onboarding worker (step 6): verify client_pages contains the OAuth Page with ADVERTISE task; store meta_page_id; campaigns use promoted_object.page_id.
1c. spend_cap — dev smoke vs production
API: POST /act_{id} with spend_cap={minor_units} (TRY → kuruş, 100 minor = 1.00 TRY).
| Request | Stored (dev) | Meaning |
|---|---|---|
100 (1.00 TRY) |
10000 |
Meta raises to account minimum (~100 TRY) |
10000 |
10000 |
100.00 TRY — use as dev smoke default until billing module sends plan cap |
Production: billing/plan module passes credit_sub_limit_try × 100 minor units. Dev script default: -SpendCapTryMinor 100 (attempts 1 TRY, accepts Meta floor).
./run-2tier-phase5-verify.ps1 -SpendCapTryMinor 100
2. Pixel — fetch from Graph API (do not hand-write in prod)
After POST /act_{id}/adspixels, always GET the pixel for install code and health probes.
2.1 Recommended GET fields
GET /{pixel-id}?fields=id,name,code,last_fired_time,creation_time,data_use_setting,is_created_by_business,is_unavailable,owner_business
Token: child BM system user · appsecret_proof required on all calls when app setting enabled.
Alternate (list on account):
GET /act_{id}/adspixels?fields=id,name,last_fired_time,is_created_by_business
2.2 Verified dev response (Jun 2026)
| Field | Example value | Notes |
|---|---|---|
id |
1654665905839965 |
Store on tenant_registry.meta_pixel_id |
name |
Kobico Test Pixel 20260614-1003 |
|
code |
Full HTML <script>…fbq('init','1654665905839965')… |
Use this in implementation_pack.meta.base_pixel_snippet — Meta-generated, includes noscript |
creation_time |
2026-06-14T07:03:21+0000 |
|
data_use_setting |
advertising_and_analytics |
|
is_created_by_business |
false |
|
is_unavailable |
false |
|
owner_business |
{ id: child_bm_id, name: "Kobico Test Client BM …" } |
Confirms pixel owned by child BM |
last_fired_time |
Omitted when never fired | Health probe: absent = yellow; present = green |
Product rule: buildImplementationPack() calls fetchPixelFromGraph() and stores code in the pack. Fallback to template only if GET fails (log + HITL).
Reference: specs/provisioning/examples/tourism-onboarding.worker.ts · Zod: implementationPack.ts
2.3 Ad account fields for readiness audit
GET /act_{id}?fields=name,account_status,currency,spend_cap,amount_spent,balance,disable_reason
GET /{child_bm}/client_pages?fields=id,name,permitted_tasks
GET /act_{id}/campaigns?fields=id,name,status&limit=5
| Dev value | Meaning |
|---|---|
account_status: 1 |
ACTIVE |
spend_cap: "0" |
Fix in worker — set from credit_sub_limit (minor units) |
client_pages contains OAuth Page + ADVERTISE |
Page linkage OK |
campaigns.data: [] |
Expected until campaign execution |
Scripts: meta-dev/fetch-pixel-details.ps1, meta-dev/audit-readiness.ps1
3. Graph API payload reference (verified)
All POST requests need access_token + appsecret_proof (HMAC-SHA256 of token with app secret).
Step 1 — Child BM
POST /{parent_bm_id}/owned_businesses
| Field | Value |
|---|---|
| Token | Client user token (OAuth) |
name |
kobi_{tenant_slug}_meta |
vertical |
OTHER |
shared_page_id |
Client Page from OAuth |
page_permitted_tasks |
["ADVERTISE","ANALYZE"] |
timezone_id |
134 (Europe/Istanbul) |
Step 2 — Child system user token
POST /{child_bm_id}/access_token
| Field | Value |
|---|---|
| Token | Parent system user |
app_id |
Kobico app ID |
scope |
ads_management,ads_read,business_management,catalog_management (see §2b) |
2b. Child system user scopes
Always request the full set at POST /{child_bm}/access_token:
ads_management,ads_read,business_management,catalog_management
| Scope | Purpose |
|---|---|
ads_management |
Ad account, pixel, campaigns, ad sets |
ads_read |
Insights / reporting |
business_management |
BM asset assignment |
catalog_management |
POST /{child_bm}/owned_product_catalogs, feeds, product sets on child-owned catalog |
Catalog API use case on the app ≠ catalog_management on system user token. With Catalog API enabled in App Dashboard → Use cases, minting still returns OAuth 100 / subcode 1690239 until App Review approves catalog_management.
| State | Child token | Catalog onboarding |
|---|---|---|
| Before App Review | ads_management, ads_read, business_management |
Ecommerce SKU blocked at catalog step — complete review first |
| After App Review + BM verification | All four scopes incl. catalog_management |
POST /{child_bm}/owned_product_catalogs on child BM (only path) |
Scripts: meta-dev/lib/child-system-user-scopes.ps1, run-2tier-phase1.ps1, refresh-child-token-catalog.ps1, probe-child-token-scopes.ps1.
2c. Catalog App Review gate (Kobico verified BM + test catalog)
Production rule: catalog is owned by the child BM — never created on parent and shared down.
Meta App Review for the Catalog API use case typically requires:
- Business verification (PRE-8) on Kobico parent BM
- Test catalog — create and manage a sample catalog via the app on the verified BM (App Review wizard)
catalog_managementapproved on system user tokens
Until all three are done, child token mint returns OAuth 1690239 for catalog_management and phase 3–4 scripts exit with instructions.
After approval: re-run refresh-child-token-catalog.ps1 → run-2tier-phase3-catalog.ps1 → run-2tier-phase4-feed.ps1 on a child BM under Kobico verified parent BM.
Step 3 — Ad account
POST /{child_bm_id}/adaccount
| Field | Dev value |
|---|---|
| Token | Child system user |
currency |
TRY |
timezone_id |
134 |
end_advertiser |
Page ID |
media_agency / partner |
NONE |
funding_id |
Omit when card on child BM |
Step 4 — Spend cap
POST /act_{id}
Body: spend_cap={minor_units}
Plan credit_sub_limit_try × 100 for TRY kuruş.
Step 5 — Pixel
POST /act_{id}/adspixels
Body: name=...
Then GET pixel (§2) for code.
Step 6 — Verify Page on child BM
GET /{child_bm}/client_pages?fields=id,name,permitted_tasks
Confirm OAuth Page ID is present with PROFILE_PLUS_ADVERTISE. Script: run-2tier-phase5-verify.ps1
Step 7 — System user on ad account
POST /act_{id}/assigned_users
Body: user={child_system_user_id}&tasks=MANAGE,ADVERTISE,ANALYZE&business={child_bm_id}
Step 8 — Catalog on child BM (ecommerce / tourism + feed)
Token: child system user with catalog_management (App Review + PRE-8).
POST /{child_bm}/owned_product_catalogs
POST /{catalog_id}/product_sets
POST /{catalog_id}/product_feeds
POST /{feed_id}/uploads
POST /act_{id}/catalogs
Verify: GET /{child_bm}/owned_product_catalogs contains the catalog.
Script: run-2tier-phase3-catalog.ps1, run-2tier-phase4-feed.ps1
4. Errors we hit — and fixes
| # | Symptom | Cause | Fix |
|---|---|---|---|
| 1 | Graph POST returns generic error / invalid signature | Missing appsecret_proof |
HMAC-SHA256(app_secret, access_token) on every POST and GET |
| 2 | owned_businesses fails with parent system user |
Meta requires client user token for child BM create | OAuth in portal first; pass user token to worker step 1 only |
| 3 | Catalog step blocked — no catalog_management on child token |
App Review / BM verification incomplete (OAuth 1690239 at mint) | Complete Catalog API review on Kobico verified BM; re-mint token; POST /{child_bm}/owned_product_catalogs |
| 4 | Ad account create fails without funding_id |
No payment method on BM | Dev: add card on child BM; prod: PRE-2 parent LOC share |
| 5 | Child BM not visible in Business Settings UI | Agency-managed 2-Tier — expected | Ops: POST /{child_bm}/business_users ADMIN invite, or API-only |
| 6 | Email ADMIN invite fails | User not on Meta / wrong email | Fallback: assign by Meta user ID (grant-child-admin.ps1) |
| 7 | Feed URL fetch fails locally | Cloudflare bot protection | Meta server-side fetch succeeded; allowlist Meta crawlers for client feeds |
| 8 | product_feeds upload empty |
Wrong API sequence | Use POST /{feed}/uploads with url=, not only scheduled feed |
| 9 | Graph GET returns nonsense object ID | PowerShell $path?$qs parsed as ternary |
Use ${path}?${qs} in URL builders |
| 10 | last_fired_time missing on pixel |
Pixel never installed on website | Expected pre-client-install; use for health yellow/green |
| 11 | spend_cap: 0 |
Not set in dev phase 2 | POST /act_{id} — prod value from billing module; dev: -SpendCapTryMinor 100 |
| 12 | spend_cap POST 100 stores 10000 |
Meta minimum for TRY (~100 TRY) | Accept floor in dev; billing module sends real cap in prod |
5. Access & permission issues
| Topic | Detail |
|---|---|
| Child token scopes | Request ads_management,ads_read,business_management,catalog_management at mint. catalog_management requires App Review + PRE-8 BM verification — ecommerce catalog step blocked until granted |
| User token scopes | Need business_management + Page scopes for OAuth / owned_businesses |
| Dev mode child BM limit | ~2 child BMs until App Review — enough to prove flow |
| PRE-10 | 2-Tier API at production scale — Meta rep |
| PRE-2 | Extended credit on parent BM — production billing |
| Human BM access | Optional; business_users with role ADMIN on child BM |
| Client never needs | BM UI, ad account ownership, platform billing |
6. Code artifacts (copy to product monorepo)
| Path | Purpose |
|---|---|
specs/provisioning/schemas/implementationPack.ts |
Zod schemas — pack, pixel Graph response, vertical template |
specs/provisioning/templates/*.meta.json |
Vertical event templates (health, school, tourism, ecommerce) |
specs/provisioning/examples/tourism-onboarding.worker.ts |
Full tourism example — Graph client, onboarding, buildImplementationPack, pixel GET |
meta-dev/run-2tier-phase*.ps1 |
Verified Graph sequences (PowerShell reference) |
7. Tourism — example implementation direction
Vertical: tourism (e.g. Kalkan Villa–style property listings)
Flow:
- Client completes portal → OAuth Page + feed URL (
https://…/feed.php) - Worker runs steps §3 (1–8 if catalog SKU)
fetchPixelFromGraph(pixelId)→ storecodein packbuildImplementationPack('tourism')→ Lead, ViewContent, InitiateCheckout, Purchase snippets- Portal Screen 7 → client IT installs pixel + events
- Health job polls
last_fired_time→ green → A2 launch allowed - Campaign execution (separate module) creates paused DPA / lead gen campaign
Primary optimize event: Lead (inquiry) or Purchase (online booking)
See full TypeScript: tourism-onboarding.worker.ts
Client install example (after pack generated):
// From implementation_pack.meta — base pixel already in <head> via pixel.code from Graph
fbq('track', 'Lead'); // inquiry form success
fbq('track', 'ViewContent', {
content_type: 'product',
content_ids: ['VILLA-123'],
});
fbq('track', 'Purchase', {
value: 15000.00,
currency: 'TRY',
content_ids: ['VILLA-123'],
content_type: 'product',
});