Meta Ads (Facebook/Instagram)
Meta Marketing API deklarativ via api-Adapter — Kampagnen, AdSets, Ads, Budgets, Insights; für jeden Workspace, Geld ausgeben nur mit Freigabe.
Meta Ads laufen über die Marketing API (Graph API v22.0+). TaskMonkey bindet sie über den deklarativen api-Adapter mit Auth-Typ meta_long_lived_token ein — kein \Facebook\SDK, kein OAuth-Refresh. Die Tools liegen in config/tenants/_shared/tools/meta_ads/ und stehen damit jedem Workspace zur Verfügung — er braucht nur den apis.meta_ads-Block und seinen eigenen Token in der .env (seit 28.09.2026; vorher nur meco).
Was es gibt
| Tool | Art | Geld? | Freigabe |
|---|---|---|---|
metaListCampaigns |
lesen: Kampagnen des Werbekontos | – | – |
metaGetInsights |
lesen: CTR, CPC, Klicks, Conversions, Ausgaben je Kampagne/AdSet/Ad | – | – |
metaSearchInterests |
lesen: Interessen fürs Targeting | – | – |
metaCreateCampaign |
Kampagne anlegen, optional mit Kampagnenbudget (CBO) | nein — immer PAUSED | – (Budget ≤ Obergrenze) |
metaCreateAdSet |
AdSet (Zielgruppe + Tagesbudget) | nein — immer PAUSED | – (Budget ≤ Obergrenze, DSA-Pflichtfelder) |
metaCreateAdCreative |
Bild + Text + CTA + Ziel-URL | – | – |
metaCreateAd |
Creative mit AdSet verknüpfen | nein — immer PAUSED | – |
metaUpdateStatus |
PAUSED sofort (Notaus), ACTIVE = live, DELETED = endgültig |
ja | ACTIVE/DELETED: Ja im Chat (ACTIVE in autonomen Workspaces ohne, bis zum Deckel) |
metaUpdateBudget |
Tagesbudget von Kampagne (CBO) oder AdSet | ja | Erhöhen: Ja im Chat (autonome Workspaces: ohne, bis zum Deckel); Senken sofort |
metaUpdateObject |
Roh-Schreibzugriff (status/daily_budget) |
ja | nur über die zwei Hüllen oben (only_via) |
metaDescribeObject, metaGetObject |
intern: Typ + Budgets eines Objekts (für die Prüfungen) | – | – |
Regeln für Geld (gleiches Muster wie Google Ads):
- Alles wird PAUSED angelegt. Live geht etwas nur über
metaUpdateStatus(status=ACTIVE). ACTIVE,DELETEDund Budget-Erhöhungen laufen überconfirmChatAction→ApprovalGate: der erste Aufruf liefertneeds_confirmation+ Vorschau + Ja/Nein-Buttons, erst ein klares Ja in der nächsten Nutzer-Nachricht plus derselbe Aufruf mitconfirm=trueschreibt. Ohne Menschen im Chat (test_tool/CLI, Scheduled Task) → Freigabe-Anfrage in TaskMonkey (status: pending_approval, freigeben unter/manage/approvals, siehe Google Ads → Freigabe-Anfragen); Public API, Inbox, Webhook → abgelehnt.- Obergrenze
apis.meta_ads.max_daily_budget_eur(Default 30) pro Workspace, gilt pro Kampagne: Kampagnenbudget (CBO) bzw. Summe aller AdSet-Tagesbudgets. Darüber schreibt TaskMonkey nie — auch nicht mit Bestätigung. Beim Anlegen gilt sie je Budget, beim Aktivieren und Erhöhen für die ganze Kampagne danach. - Konto-Deckel der Plattform
Platform.ads.max_account_daily_budget_eur(Default 50, pro Workspace inconfig/platform.phpunterPlatform.ads.workspaces.<code>anpassbar — nicht in der Workspace-Config, der Workspace kann ihn nicht selbst anheben): Summe der Tagesbudgets aller aktiven Kampagnen des Kontos inklusive der neuen bzw. geänderten. Geprüft beim Anlegen, Aktivieren und Erhöhen, vor der Rückfrage; nicht bestimmbar (z. B. Laufzeitbudgets) = abgelehnt. - Autonom ohne Rückfrage (29.09., nur per Plattform-Freigabe): Steht in
config/platform.phpfür den WorkspacePlatform.ads.workspaces.<code>.autonomous = trueplusautonomous_max_daily_eur(meco:true, 50), dann laufenmetaUpdateStatus(ACTIVE)undmetaUpdateBudget(Erhöhen) ohneconfirmChatAction— solange alle aktiven Kampagnen des Kontos danach zusammen ≤min(autonomous_max_daily_eur, max_account_daily_budget_eur)liegen. Darüber: Rückfrage wie oben bzw. Konto-Deckel.DELETEDbleibt immer hinter der Rückfrage (nicht umkehrbar). Die Obergrenze je Kampagne (apis.meta_ads.max_daily_budget_eur) gilt weiter. Kern:App\Tooling\AdsAutonomyhinter dem internen ToolcheckAdsAutonomy(only_via). Weitere Bedingungen:- Nur vertrauenswürdige Aufrufer (Prompt-Injection):
test_tool/CLI, Scheduled Task des Workspaces, Chat eines angemeldeten Mitglieds (Assistant ohnepublic, Unified, Builder-Terminal,chat_<user>). Nie Inbox/Kanal-Chat, Public-Widget/-Assistant, Webhook/Hook, API (Tool-/Public-/External-API), Widget-Vorschau, Sub-Agent oder Headless-Runner ohne Nutzer, nie read-only. - Tageslimit
Platform.ads.autonomous_max_actions_per_day(Default 50, pro Workspace überschreibbar) — danach wieder mit Rückfrage. - Protokoll: jede autonome Aktion schreibt eine eigene
task_executions-Zeile (entry_type = 'ads_autonomy',tool_name= Tool,input_args= wer/was/vorher→nachher/Konto-Summe/Deckel); daraus zählt auch das Tageslimit. - Meldung danach per Mail (
sendAdminEmail) anPlatform.ads.workspaces.<code>.notify_emails, sonst an dieadmin_emailsdes Workspaces: „Kampagne X aktiviert, Konto jetzt Y €/Tag“. - Das Tool-Ergebnis trägt
autonomous: true,account_daily_eur,audit_id. - Der Workspace kann sich die Freigabe nicht selbst geben (nicht in der Tenant-Config). Abschalten: Eintrag in
config/platform.phpentfernen bzw.autonomous => false.
- Nur vertrauenswürdige Aufrufer (Prompt-Injection):
- Ist das Tagesbudget der Kampagne nicht bestimmbar (Laufzeitbudget, mehr als 200 AdSets), wird nicht aktiviert und nicht erhöht — dann im Werbeanzeigenmanager.
metaUpdateObjectträgtonly_via— der ToolRunner lehnt jeden Aufruf ab, der nicht direkt ausmetaUpdateStatusodermetaUpdateBudgetkommt (auch nicht überrunTooloder einen Assistant, der das Tool freischaltet).- Pausieren und Budget senken gehen sofort, auch aus Scheduled Tasks (Notaus). Pausieren liest vorher nichts, damit es auch bei Lese-Fehlern greift.
- Budgets rechnen in der Konto-Währung (Cent ↔ EUR); das Werbekonto sollte auf EUR stehen.
Setup-Reihenfolge (einmalig pro Tenant)
Der Setup läuft komplett im Meta Business Manager + Meta for Developers. Kein Code, aber Reihenfolge zählt — falsche Reihenfolge und du siehst "Keine Berechtigungen verfügbar" beim Token generieren.
1. Neue App im Business Manager anlegen
business.facebook.com/settings/apps→ Hinzufügen → Neue App-ID erstellen- Name: z.B.
TaskMonkey Ads - Anwendungsfall zunächst: Andere (Marketing-Use-Case wird gleich manuell dazu)
- App-Typ: Business
Warum eine dedizierte Marketing-App und nicht die bestehende WhatsApp-App erweitern:
- Saubere Trennung der Blast-Radien (Ads-App-Suspension bricht kein WhatsApp)
- Token-Lifecycles unabhängig rotierbar
- Meta-App-Reviews für Marketing sind strenger als für WhatsApp — Reviews nicht mischen
2. Anwendungsfall "Marketing" hinzufügen
Im App-Dashboard von TaskMonkey Ads:
- Linke Sidebar: Anwendungsfälle → Anwendungsfall hinzufügen
- Filter: Werbung und Monetarisierung
- Diese zwei anhaken:
- ✅ Werbeanzeigen mit Marketing API erstellen und verwalten (gibt
ads_management,ads_read,business_management) - ✅ Anzeigenperformance-Daten mit Marketing API messen (gibt
read_insights)
- ✅ Werbeanzeigen mit Marketing API erstellen und verwalten (gibt
- Alle anderen (Anzeigen-Leads, App Ads, MCP-Server, Audience Network, Catalog API) nicht anhaken — falscher Use-Case, macht das Token unnötig breit
3. App-Rolle für System-User setzen
Das ist der Schritt, den man leicht verpasst — ohne ihn geht Schritt 4 mit Fehlermeldung "Weise dem Systemnutzer eine App-Rolle zu".
- In
TaskMonkey Ads-Dashboard: App-Rollen in der Sidebar - System-Nutzer hinzufügen — deinen bestehenden System-User (z.B.
Employee, den ihr für WhatsApp nutzt) auswählen - Rolle: Administrator
Alternativ: business.facebook.com/settings/system-users → System-User klicken → App zuweisen → TaskMonkey Ads wählen → Vollzugriff.
4. Token generieren
business.facebook.com/settings/system-users→ System-User klicken → Token generieren- App: TaskMonkey Ads
- Ablaufdatum: Nie
- Berechtigungen (aus der Liste anhaken):
- ✅
ads_management - ✅
ads_read - ✅
business_management - ✅
read_insights(falls in der Liste)
- ✅
- NICHT anhaken:
ads_mcp_management,pages_manage_ads,catalog_management,instagram_basic,leads_retrieval - Stand 04.10.: Für Lead-Formulare und Reels braucht meco zusätzliche Rechte, aktuelle Liste in Reels veröffentlichen bzw. unten unter Lead-Formulare.
- Token generieren → sofort kopieren (wird nur einmal angezeigt)
5. Werbekonto dem System-User zuweisen
business.facebook.com/settings/ad-accounts→ dein Werbekonto klicken- Personen hinzufügen oder Zugriff hinzufügen → System-User
Employee→ Rolle Verwalten
Ohne diesen Schritt hat der Token zwar ads_management-Permission, aber keinen Zugriff auf ein konkretes Konto — Fehler bei jedem Call.
6. Werbekonto-ID kopieren
Auf derselben Seite (oben in der Detail-Ansicht) siehst du die Konto-ID im Format 2281091339403984 (16-stellige Zahl).
Für die Konfig brauchst du sie mit act_-Prefix: act_2281091339403984.
7. Konfiguration in TaskMonkey
In config/tenants/<tenant>/apis.php (Workspaces ohne Repo: gleicher Block, rein deklarativ):
'apis' => [
'meta_ads' => [
'base_url' => 'https://graph.facebook.com/v22.0/',
'auth' => [
'type' => 'meta_long_lived_token',
'token' => '{{secret:META_ADS_ACCESS_TOKEN}}',
],
'headers' => [
'Accept' => 'application/json',
],
// Nicht sensitiv — direkt hier oder als {{secret:META_ADS_ACT_ID}}:
'act_id' => 'act_2281091339403984',
// EU-DSA: Werbekunde + Zahler (Pflicht bei EU-Zielgruppen).
'dsa_beneficiary' => 'Firma GmbH',
// 'dsa_payor' => 'Firma GmbH', // Default = dsa_beneficiary
// Harte Obergrenze Tagesbudget pro Kampagne (Default 30).
'max_daily_budget_eur' => 30,
],
],
In der .env des Workspaces (getrackt: tenants_untracked/<tenant>/.env, Server shared/tenants_untracked/<tenant>/.env):
META_ADS_ACCESS_TOKEN=EAA... # der Token aus Schritt 4
Dann die Tools im Assistant freischalten (assistants.<name>.tools): die Lese-Tools, metaCreate*, metaUpdateStatus, metaUpdateBudget. metaUpdateObject, metaGetObject, metaDescribeObject nicht freischalten — die laufen nur als Sub-Tools.
8. Test
bin/cake test_tool --tenant <tenant> --tool metaListCampaigns --args='{"status":"ALL"}'
Erfolgreich = data-Array mit Kampagnen (leer, wenn noch keine angelegt). Fehler Object with ID '{{secret:...}}' does not exist = Placeholder wurde nicht aufgelöst → Deploy nach apis.php-Änderung nicht durch.
Long-Lived Token — Ablauf und Rotation
Der System-User-Token mit "Ablauf: Nie" läuft technisch nicht ab, aber:
- Meta kann ihn invalidieren, wenn die App-Compliance sich ändert (z.B. Business-Verifizierung abläuft, App-Review-Status wechselt).
- Rotation empfohlen: jährlich, oder wenn ein Ex-Mitarbeiter im System-User-Admin war.
- Bei Rotation: neuen Token generieren (Schritt 4),
META_ADS_ACCESS_TOKENin.envupdaten, kein Redeploy nötig (Cake liest.envbei jedem Request).
Monitoring: Der Token verrät sich beim Ausfallen erst bei Aufruf — kein Vorwarn-Signal. Empfehlung: einmal pro Woche metaListCampaigns als Health-Check via Cron, bei 401/403 sofort neue Token-Generation.
Auth-Layer im ToolRunner
Der Adapter nutzt den Auth-Type meta_long_lived_token im Auth-Block von src/Tooling/ToolRunner.php. Ablauf pro Call:
base_urlmusshttps://*.facebook.comsein — sonst gibt der Runner keinen Token heraus (Meta-Auth nicht erlaubt). Eine Workspace-Config kann den Token so nicht an einen fremden Host schicken lassen.- ToolRunner liest
apis.<name>.auth.token expandSecretslöst{{secret:META_ADS_ACCESS_TOKEN}}gegen die Workspace-.envauf- Token wird als Query-Param
access_tokenan jeden Meta-Call gehängt (nicht als Bearer-Header — Meta will es so)
IDs aus dem Chat (object_id, act_id) prüfen die Tools auf reine Ziffern bzw. act_<Ziffern>, bevor sie im Pfad landen.
Auth-Layer ist generisch: jede Meta-API (Ads, WhatsApp Business, Instagram Business) funktioniert gleich, nur mit anderem Token und anderen Endpoints.
Business-Manager-Verifizierung
Werbekonten in einem verifizierten Business haben höhere Limits (Ausgaben, gleichzeitige aktive Kampagnen). Verifizierung dauert 1-5 Werktage, braucht Handelsregister-Auszug oder Steuer-ID.
Nicht kritisch für den Start — TaskMonkey Ads funktionieren auch mit unverifiziertem Business, nur mit engeren Ausgaben-Limits (~€10.000 lifetime spend cap).
Häufige Fehler
| Fehler | Ursache | Fix |
|---|---|---|
Object with ID '{{secret:...}}' does not exist |
Placeholder nicht aufgelöst | .env prüfen, Deploy neu |
Unsupported get request... missing permissions |
System-User hat Werbekonto nicht zugewiesen | Schritt 5 nachziehen |
(#100) Invalid parameter |
Meist Budget in EUR statt Cent | body_transform prüft (in Tools schon drin) |
Application does not have the capability |
Berechtigung fehlt im Token | Schritt 4 wiederholen mit vollständiger Auswahl |
(#803) Some of the aliases you requested do not exist |
act_id falsch | act_<Ziffern> in apis.php (reine Ziffern ergänzen die Tools selbst) |
apis.meta_ads.act_id fehlt oder ist ungueltig |
act_id leer |
Werbekonto-ID eintragen |
dsa_beneficiary fehlt |
EU-DSA-Pflichtfeld | apis.meta_ads.dsa_beneficiary setzen oder als Argument mitgeben |
… über der Obergrenze … |
Budget > max_daily_budget_eur |
Budget senken oder Obergrenze bewusst anheben (Config-Änderung, apis ist in ApprovalGate sensitiv) |
error_code: only_via |
metaUpdateObject direkt aufgerufen |
über metaUpdateStatus / metaUpdateBudget |
(#2635) You are calling a deprecated version |
Graph-API-Version zu alt | base_url auf aktuelle Version updaten |
Lead-Formulare (Instant Forms) — meco-Workspace
Seit 02.10.2026. Jeder Lead aus einem Facebook-/Instagram-Lead-Formular landet innerhalb weniger Minuten in TaskMonkey und wird Marcus sofort gemeldet.
| Teil | Wo |
|---|---|
| Abruf (Polling, Webhooks in der EU blockiert) | pollMetaLeads, scheduled.php → poll_meta_leads (läuft in jedem meco-Cron-Tick zuerst, Cron alle 5 min) |
| Graph-Calls (deklarativ, read-only) | metaListLeadForms (/{page_id}/leadgen_forms), metaGetFormLeads (/{form_id}/leads) |
| Auth | apis.meta_leads, Auth-Type meta_page_token: Runner leitet aus META_ADS_ACCESS_TOKEN den Seiten-Token ab, nur für page_ids |
| Ablage | Supabase leads: campaign = source = meta_lead_form, outreach_status = warm_inbound, Dedupe (source, external_id); Schema db/supabase/meco_2026-10-02_meta_leads.sql |
| Meldung | notifyMetaLead (Core MetaLeadAlertTools): Mail immer, WhatsApp über die Plattform-Nummer (24h-Fenster oder Template), Zeile Anrufen: tel:+49… = Anruf-Link in der Mail. Empfänger Onboarding.alert.meta_lead_recipients (leer = recipients) |
| Auto-Antwort an den Lead | sendMetaLeadAutoReply, apis.meta_leads.auto_reply.enabled — Standard aus |
| Aufbewahrung | purgeMetaLeads täglich, retention_days 180 (= Datenschutzerklärung Abschnitt 19), outreach_status = 'kunde' bleibt |
Kaltmail-Strecke: meta_lead_form ist keine lead_campaigns-Kampagne,
generateOutreach überspringt die Quelle zusätzlich. Warme Leads bekommen
keine Outreach-Mail.
Klickliste Marcus (Stand 02.10.)
Ist-Stand Token (debug_token, 02.10.): Systemnutzer „Taskmonkey“
(122112517077482065), App „TaskMonkey Ads“ (1047000088377962), Rechte nur
ads_management, ads_read, business_management. Keine Seite zugewiesen
(me/accounts leer), /{page}/leadgen_forms → „Requires pages_manage_ads“.
- developers.facebook.com → App „TaskMonkey Ads“ → Anwendungsfälle →
Hinzufügen „Leads aus Werbeanzeigen erfassen und verwalten“ (Lead Ads).
Darin
leads_retrieval,pages_manage_ads,pages_read_engagement,pages_show_listhinzufügen. Standard-Zugriff reicht (nur eigene Seite, kein App Review). - business.facebook.com → Einstellungen → Nutzer → Systemnutzer → „Taskmonkey“ → Assets zuweisen → Seiten → meco-Seite (108629875903438), später zusätzlich die neue TaskMonkey-Seite: Vollzugriff (mindestens „Werbeanzeigen“ + „Leads“).
- Einstellungen → Integrationen → Leads-Zugriff → Seite wählen → Systemnutzer „Taskmonkey“ Zugriff geben (nur nötig, wenn dort schon Einschränkungen gesetzt sind; schadet nicht).
- Systemnutzer „Taskmonkey“ → Token generieren → App „TaskMonkey Ads“,
Ablauf „Nie“, Häkchen:
ads_management,ads_read,business_management,leads_retrieval,pages_show_list,pages_read_engagement,pages_manage_ads→ Token an den Orchestrator (ersetztMETA_ADS_ACCESS_TOKENintenants_untracked/meco/.env, lokal und auf Prod). - Lead-Ads-Bedingungen der Seite akzeptieren:
https://www.facebook.com/ads/leadgen/tos(einmal pro Seite, sonst lässt Meta keine Lead-Anzeige zu). - Im Formular als Datenschutz-Link:
https://taskmonkey.de/datenschutz#lead-formulare.
Danach (Orchestrator): Deploy, Supabase-Migration ausführen, dann
bin/cake test_tool -t meco --tool pollMetaLeads --args='{"dry_run":true}'
und einen Test-Lead über das Lead Ads Testing Tool
(developers.facebook.com/tools/lead-ads-testing) anlegen. Neue Seite:
ID in apis.meta_leads.page_ids ergänzen.
Fehlen Rechte, schreibt pollMetaLeads höchstens eine Hinweis-Mail pro Tag
an die meco-Admins und liefert error_code: meta_permissions_missing.
Verwandt
google-ads.md— Google Ads, gleiches Freigabe-Mustergoogle-workspace.md— der Auth-Layer für Google APIs (google_service_account) — analoges Musteroauth-flow.md— allgemeiner OAuth-Flow (falls du eine App für User-Delegation baust, nicht System-User)