AdAdvisor MCP Server
What this MCP does
Manages Meta advertising campaigns, creatives, audiences, lead forms, budgets, statuses, and performance reporting.
Tools
Input schema
{'type': 'object', 'required': ['account_id', 'entity_type', 'entity_id'], 'properties': {'force': {'type': 'boolean', 'default': False, 'description': 'Bypass the >100% delta guardrail. By default, budget changes of more than 2× the current value (in either direction) are rejected to prevent accidental runaway edits. Set True to intentionally apply a large change.'}, 'entity_id': {'type': 'string', 'minLength': 1, 'description': 'The campaign or ad set ID (from list_campaigns, list_adsets, or get_performance — never invent IDs)'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': "Ad account ID. Must be the account where entity_id lives — Meta returns code 100 (parameter error) on a mismatch. Check via list_campaigns or list_adsets if you're not sure."}, 'entity_type': {'enum': ['campaign', 'adset'], 'type': 'string', 'description': "'campaign' or 'adset'. Ads do not have budgets."}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is changing this budget (e.g. 'doubling the winning ad set after 3-day soak'). Logged for telemetry."}, 'daily_budget': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'New daily budget in account currency (e.g. 50.00 = $50/day). Provide this OR lifetime_budget, not both.'}, 'lifetime_budget': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'New lifetime budget in account currency (e.g. 500.00 = $500 total). Provide this OR daily_budget, not both.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['entity_type', 'entity_id', 'account_id', 'currency'], 'properties': {'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'currency': {'type': 'string', 'description': 'Account currency (ISO 4217).'}, 'entity_id': {'type': 'string'}, 'update_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Audit-record ID — the entity_updates row created for this change.'}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'entity_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Human-readable name of the entity.'}, 'entity_type': {'enum': ['campaign', 'adset'], 'type': 'string'}, 'state_after': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot after the change (shape varies by entity type).'}, 'daily_budget': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'New daily budget in account currency.'}, 'state_before': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot before the change (shape varies by entity type).'}, 'lifetime_budget': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'New lifetime budget in account currency.'}}, 'description': 'Result of updating a campaign or adset budget.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id', 'entity_type', 'entity_ids', 'action'], 'properties': {'action': {'enum': ['pause', 'resume'], 'type': 'string', 'description': "'pause' stops delivery (sets PAUSED), 'resume' reactivates (sets ACTIVE). For ARCHIVED/DELETED, use update_entity."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID. Must match the account each entity_id lives under — passing an entity_id from a different account causes Meta to return code 100 (parameter error). When unsure, look the IDs up via list_campaigns / list_adsets / list_ads first.'}, 'entity_ids': {'type': 'array', 'items': {'type': 'string'}, 'minItems': 1, 'description': 'One or more entity IDs to update (from list_campaigns, list_adsets, list_ads, or get_performance — never invent IDs). All IDs must be of the same entity_type. Pass a list with one element for a single update, or many elements to apply the same action to all of them in one call.'}, 'entity_type': {'enum': ['campaign', 'adset', 'ad'], 'type': 'string', 'description': "Entity type: 'campaign', 'adset', or 'ad'"}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is making this change (e.g. 'pausing fatigued ads with CTR < 0.5%'). Logged for telemetry."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['status', 'action', 'entity_type', 'account_id', 'all_succeeded', 'success_count', 'error_count', 'results'], 'properties': {'action': {'enum': ['pause', 'resume'], 'type': 'string'}, 'status': {'enum': ['success', 'partial'], 'type': 'string'}, 'results': {'type': 'array', 'items': {'type': 'object', 'required': ['entity_id', 'status'], 'properties': {'error': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Populated when status='error' — human-readable failure reason."}, 'status': {'enum': ['success', 'error'], 'type': 'string'}, 'entity_id': {'type': 'string'}, 'update_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'entity_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'state_after': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None}, 'state_before': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None}}, 'description': 'Per-entity result inside a batch mutation response.', 'additionalProperties': True}, 'description': 'Per-entity results — one entry per requested entity_id.'}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'entity_type': {'enum': ['campaign', 'adset', 'ad'], 'type': 'string'}, 'error_count': {'type': 'integer'}, 'all_succeeded': {'type': 'boolean', 'description': 'True if every item succeeded.'}, 'success_count': {'type': 'integer'}}, 'description': "Result of pausing or resuming entities.\n\n`status='success'` means every item succeeded. `status='partial'` means\nsome failed (inspect each `results[i].status`).", 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'page_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': "Page to check. Omit to use the account's default promote_page."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['account_id'], 'properties': {'note': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Advisory caveats about interpreting this verdict.'}, 'error': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Degraded-read detail from the Page listing, if any.'}, 'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'blockers': {'type': 'array', 'items': {'type': 'object', 'required': ['reason', 'detail', 'user_action'], 'properties': {'detail': {'type': 'string', 'description': 'What is wrong, in plain English.'}, 'reason': {'type': 'string', 'description': 'Machine key: no_page | no_page_token | no_advertise_task | no_manage_leads_task | missing_scopes | leadgen_tos_not_accepted | lead_retrieval_not_available'}, 'user_action': {'type': 'string', 'description': 'What the human must do to clear it.'}}, 'description': 'One prerequisite currently failing, with what to do about it.'}}, 'page_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Suggested next tool calls when fully ready.'}, 'can_advertise': {'type': 'boolean', 'default': False}, 'granted_scopes': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'null means the permission lookup failed — unknown, not empty.'}, 'missing_scopes': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None}, 'required_scopes': {'type': 'array', 'items': {'type': 'string'}}, 'can_create_forms': {'type': 'boolean', 'default': False}, 'can_retrieve_leads': {'type': 'boolean', 'default': False}, 'has_advertise_task': {'type': 'boolean', 'default': False}, 'leadgen_tos_accepted': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': "Whether the Page has accepted Meta's Lead Generation Terms of Service. null = could not be determined (treat as unknown, NOT as false). False blocks every lead-ads write for the Page until a Page admin accepts at https://www.facebook.com/ads/leadgen/tos."}, 'page_token_available': {'type': 'boolean', 'default': False}, 'has_manage_leads_task': {'type': 'boolean', 'default': False}, 'lead_retrieval_available': {'type': 'boolean', 'default': False}}, 'description': 'Readiness verdict for lead generation on one ad account + Page.'}
Input schema
{'type': 'object', 'required': ['account_id', 'files'], 'properties': {'files': {'type': 'array', 'items': {'type': 'object', 'required': ['s3_key', 'filename', 'content_type'], 'properties': {'s3_key': {'type': 'string', 'minLength': 1, 'description': "S3 object key returned by init_upload (e.g. 'tmp/<acct>/<task>/<id>.jpg')."}, 'filename': {'type': 'string', 'minLength': 1, 'description': 'Original file name (used as the Meta asset name).'}, 'content_type': {'type': 'string', 'minLength': 1, 'description': 'MIME type (must match what was sent to S3).'}}, 'description': 'A file already uploaded to S3, ready to be pushed to Meta.'}, 'minItems': 1, 'description': "Files already uploaded to S3 by the widget — each is downloaded and pushed to Meta's ad images/videos API."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Meta ad account ID'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['results'], 'properties': {'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'results': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}, 'description': 'Per-file Meta-registration outcome. Each carries image_hash or video_id plus name/dimensions/duration where applicable. Use the ID fields directly in create_creative.'}}, 'description': 'Result of complete_upload — registers uploaded S3 bytes with Meta.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'ads'], 'properties': {'ads': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'adset_id', 'creative_id'], 'properties': {'name': {'type': 'string', 'minLength': 1, 'description': 'Display name for the ad (shown in Ads Manager).'}, 'status': {'type': 'string', 'const': 'PAUSED', 'default': 'PAUSED', 'description': "Initial status. Locked to PAUSED to enforce the AdAdvisor policy that all newly-created ads must be reviewed before going live. Activate explicitly after review with change_entity_status(action='resume')."}, 'adset_id': {'type': 'string', 'minLength': 1, 'description': 'Ad set ID to attach this ad to (from list_adsets).'}, 'creative_id': {'type': 'string', 'minLength': 1, 'description': 'Creative ID to use for this ad. Obtain via create_creative or list_creatives.'}, 'conversion_domain': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Second-level domain (e.g. 'example.com', NOT 'https://shop.example.com/path') where conversions are recorded. REQUIRED by Meta for any ad in a campaign that shares data with a pixel — Meta returns subcode 2490408 ('Missing conversion domain') if omitted. Set to the registrable domain of the creative's destination URL: e.g. for a creative linking to https://shop.example.com/checkout, pass conversion_domain='example.com'. Optional for awareness/engagement campaigns and lead-form ads (which use Meta's hosted form, not a tracked website)."}}, 'description': 'Specification for creating a single Meta ad.'}, 'minItems': 1, 'description': 'One or more ad specs to create. Each attaches a creative to an ad set.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is creating these ads (e.g. 'wiring 5 new creatives into the BOFU retargeting ad set'). Logged for telemetry."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['status', 'all_succeeded', 'account_id', 'error_count', 'created_count', 'ads'], 'properties': {'ads': {'type': 'array', 'items': {'type': 'object', 'required': ['ad_id', 'name', 'adset_id', 'creative_id', 'status', 'update_id'], 'properties': {'name': {'type': 'string'}, 'ad_id': {'type': 'string'}, 'status': {'type': 'string', 'description': 'Initial status — PAUSED unless caller overrode.'}, 'adset_id': {'type': 'string'}, 'update_id': {'type': 'string'}, 'creative_id': {'type': 'string'}, 'conversion_domain': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'The verified domain attached to this ad (required by Meta on conversion-tracked ads).'}}, 'description': 'Successfully-created ad in a create_ad batch response.', 'additionalProperties': True}, 'description': 'Per-ad records on success. Note: field name is `ads` (not `created`).'}, 'errors': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'Per-item failure records. Each carries `name` plus the keys from format_tool_error() (`error`, `exception_type`, `meta_error?`, `hint?`). Present when error_count > 0.'}, 'status': {'enum': ['success', 'partial'], 'type': 'string'}, 'warnings': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Non-fatal configuration problems found before the write — most often a lead-gen mismatch between the creative and its ad set. The ads WERE created (PAUSED); resolve these before resuming them, or they will run without collecting leads.'}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'error_count': {'type': 'integer'}, 'all_succeeded': {'type': 'boolean', 'description': 'True if every item succeeded (no entries in `errors`).'}, 'created_count': {'type': 'integer'}}, 'description': 'Result of create_ad — N ads created (all PAUSED unless overridden).', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'adsets'], 'properties': {'adsets': {'type': 'array', 'items': {'type': 'object', 'required': ['campaign_id'], 'properties': {'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Ad set name. Omit to auto-generate from targeting: ADADVISOR || {audience_type} || {summary} || {geo} || {demographics} || AUTO.'}, 'zips': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': "Zip key from search_targeting geolocation results, e.g. 'US:94304'"}}, 'description': "A zip code geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': "Zip code targets from search_targeting(search_type='geolocation')"}, 'cities': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'City key from search_targeting geolocation results'}, 'radius': {'anyOf': [{'type': 'integer', 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Radius around city center (10-50 mi / 17-80 km)'}, 'distance_unit': {'anyOf': [{'enum': ['mile', 'kilometer'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Unit for radius'}}, 'description': "A city geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': "City targets from search_targeting(search_type='geolocation')"}, 'income': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Income targets from search_targeting(search_type='income')"}, 'age_max': {'anyOf': [{'type': 'integer', 'maximum': 65, 'minimum': 18}, {'type': 'null'}], 'default': None, 'description': 'Maximum age (18-65). NOTE: Meta rejects age_max<65 when advantage_audience=True (the default). To cap age below 65, also pass advantage_audience=False.'}, 'age_min': {'anyOf': [{'type': 'integer', 'maximum': 65, 'minimum': 18}, {'type': 'null'}], 'default': None, 'description': 'Minimum age (18-65)'}, 'genders': {'anyOf': [{'type': 'array', 'items': {'type': 'integer'}}, {'type': 'null'}], 'default': None, 'description': '[1]=male only, [2]=female only, [1,2]=both. Omit for all genders.'}, 'regions': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'Region key from search_targeting geolocation results'}}, 'description': "A region geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': "Region targets from search_targeting(search_type='geolocation')"}, 'end_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO datetime for ad set end (required with lifetime_budget)'}, 'behaviors': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Behavior targets from search_targeting(search_type='behaviors')"}, 'countries': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "ISO country codes, e.g. ['US', 'CA']"}, 'dsa_payor': {'anyOf': [{'type': 'string', 'maxLength': 512}, {'type': 'null'}], 'default': None, 'description': "The payor (entity paying for the ads) of all ads in this ad set. REQUIRED for ad sets targeting the EU or associated territories — Meta rejects with subcode 3858081 ('Missing DSA payor') otherwise. Auto-populated from the ad account's default_dsa_payor when omitted. Max 512 chars."}, 'interests': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Interest targets from search_targeting(search_type='interests')"}, 'bid_amount': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'For COST_CAP: the cost-per-result goal in account currency (e.g. 10.0 = $10/result). For LOWEST_COST_WITH_BID_CAP: the max bid per auction in account currency. Do NOT set with LOWEST_COST_WITH_MIN_ROAS (use roas_average_floor instead).'}, 'industries': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Industry targets from search_targeting(search_type='industries')"}, 'start_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO datetime for ad set start'}, 'campaign_id': {'type': 'string', 'minLength': 1, 'description': 'Parent campaign ID (from list_campaigns or create_campaign)'}, 'life_events': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Life event targets from search_targeting(search_type='life_events')"}, 'bid_strategy': {'anyOf': [{'enum': ['LOWEST_COST_WITHOUT_CAP', 'LOWEST_COST_WITH_BID_CAP', 'COST_CAP', 'LOWEST_COST_WITH_MIN_ROAS'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Bid strategy. Defaults to LOWEST_COST_WITHOUT_CAP (auto-bid). COST_CAP requires bid_amount (cost-per-result goal). LOWEST_COST_WITH_BID_CAP requires bid_amount (max bid per auction). LOWEST_COST_WITH_MIN_ROAS requires roas_average_floor and optimization_goal=VALUE.'}, 'daily_budget': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'Daily budget in account currency. Only if campaign has NO campaign-level budget.'}, 'billing_event': {'anyOf': [{'enum': ['IMPRESSIONS', 'LINK_CLICKS', 'THRUPLAY'], 'type': 'string'}, {'type': 'null'}], 'default': 'IMPRESSIONS', 'description': 'Billing event. Defaults to IMPRESSIONS.'}, 'work_employers': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Work employer targets from search_targeting(search_type='work_employers')"}, 'work_positions': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Work position targets from search_targeting(search_type='work_positions')"}, 'daily_spend_cap': {'anyOf': [{'type': 'number', 'minimum': 0}, {'type': 'null'}], 'default': None, 'description': "CBO only: daily spend ceiling for this ad set, in account currency. Requires the parent campaign to own a daily_budget. Meta enforces a minimum of $1 — passing 0 fails with subcode 1885056 ('maximum spend limit must be at least $1.00'). To remove an existing cap, clear it in Ads Manager."}, 'dsa_beneficiary': {'anyOf': [{'type': 'string', 'maxLength': 512}, {'type': 'null'}], 'default': None, 'description': "The beneficiary (entity benefiting from the ads) of all ads in this ad set. REQUIRED for ad sets targeting the EU or associated territories — Meta rejects with subcode 3858079 ('Missing DSA beneficiary') otherwise. Auto-populated from the ad account's default_dsa_beneficiary when omitted. Max 512 chars."}, 'lifetime_budget': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'Lifetime budget in account currency. Requires end_time.'}, 'promoted_object': {'anyOf': [{'type': 'object', 'properties': {'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Facebook page ID (for engagement/message objectives)'}, 'pixel_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Meta pixel ID for conversion tracking'}, 'application_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'App ID (for app promotion objectives)'}, 'object_store_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'App Store / Google Play URL (required with application_id for app promotion)'}, 'custom_event_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Conversion event, e.g. 'PURCHASE', 'LEAD', 'ADD_TO_CART'"}}, 'description': 'Conversion tracking config for an ad set.\n\nAuto-resolved for OUTCOME_SALES campaigns if omitted (picks account pixel + PURCHASE).', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Conversion tracking config. Auto-resolved for OUTCOME_SALES campaigns if omitted.'}, 'custom_audiences': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Custom audiences to target (from list_custom_audiences). Includes lookalike audiences, website custom audiences, customer lists, etc. When advantage_audience=True these are applied as Advantage+ *signals* (Meta may deliver beyond them), not hard filters; set advantage_audience=False to restrict delivery strictly to them.'}, 'destination_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'WEBSITE, APP, MESSENGER, etc.'}, 'device_platforms': {'anyOf': [{'type': 'array', 'items': {'enum': ['mobile', 'desktop'], 'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Device types. Omit for both. Call ads REQUIRE ['mobile'] — a desktop user cannot place a phone call from an ad."}, 'optimization_goal': {'anyOf': [{'enum': ['OFFSITE_CONVERSIONS', 'LINK_CLICKS', 'IMPRESSIONS', 'REACH', 'LEAD_GENERATION', 'QUALITY_LEAD', 'QUALITY_CALL', 'LANDING_PAGE_VIEWS', 'VALUE', 'CONVERSATIONS'], 'type': 'string'}, {'type': 'null'}], 'default': 'OFFSITE_CONVERSIONS', 'description': 'Optimization goal. Defaults to OFFSITE_CONVERSIONS. Use CONVERSATIONS for messaging-destination ad sets (destination_type=WHATSAPP / MESSENGER / INSTAGRAM_DIRECT) — Meta requires it on Click-to-Messenger / Click-to-WhatsApp / Click-to-Instagram / Click-to-MultiDestination ad sets.\nLead generation:\n- LEAD_GENERATION optimizes for lead VOLUME. Pair with destination_type=ON_AD and promoted_object={page_id}.\n- QUALITY_LEAD optimizes for lead QUALITY (Conversion Leads). Needs destination_type=ON_AD or MESSENGER and promoted_object={page_id}, and only works once the advertiser sends lead-stage events back via the Conversions API — without that CRM feedback Meta has no quality signal to optimize on.\n- QUALITY_CALL optimizes for call quality on call ads (destination_type=PHONE_CALL, promoted_object={page_id}).\nMeta does NOT allow changing optimization_goal on a campaign that has already published; duplicate the ad set and change it on the copy instead.'}, 'advantage_audience': {'type': 'boolean', 'default': True, 'description': "Enable Advantage+ audience expansion (Meta's ML broadens targeting beyond your spec). DEFAULTS TO TRUE — recommended for most cases. Set to False when you need a hard demographic cap (age_max<65, narrow gender) or any constraint Meta must respect exactly. Meta rejects advantage_audience=True combined with age_max<65. When True, any custom audiences / lookalikes / detailed targeting you pass are automatically submitted as Advantage+ *signals* (the 'relaxed setup'); they cannot be hard controls while Advantage+ is on (Meta rejects that as subcode 1359202) — set False to use them as controls."}, 'excluded_behaviors': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Behaviors to exclude from targeting'}, 'excluded_interests': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Interests to exclude from targeting'}, 'facebook_positions': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Facebook surfaces, e.g. ['feed','video_feeds','story','reels', 'marketplace','search']. Requires 'facebook' in publisher_platforms."}, 'lifetime_spend_cap': {'anyOf': [{'type': 'number', 'minimum': 0}, {'type': 'null'}], 'default': None, 'description': 'CBO only: lifetime spend ceiling for this ad set, in account currency. Requires the parent campaign to own a lifetime_budget. Meta enforces a minimum (subcode 1885056) — passing 0 will be rejected. To remove an existing cap, clear it in Ads Manager.'}, 'roas_average_floor': {'anyOf': [{'type': 'number', 'maximum': 1000.0, 'minimum': 0.01}, {'type': 'null'}], 'default': None, 'description': 'Minimum ROAS floor for LOWEST_COST_WITH_MIN_ROAS strategy. Expressed as a multiplier: 1.5 = 150% ROAS (get $1.50 back per $1 spent). Valid range: 0.01 to 1000.0. Requires optimization_goal=VALUE.'}, 'instagram_positions': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Instagram surfaces, e.g. ['stream','story','reels','explore']. Requires 'instagram' in publisher_platforms."}, 'is_dynamic_creative': {'type': 'boolean', 'default': False, 'description': "When true, the ad set runs as a Dynamic Creative ad set: Meta auto-combines variants from each ad's asset_feed_spec to find the best-performing combination per viewer. REQUIRED on the parent ad set for any creative built with multi-variant copy (messages / headlines / descriptions arrays in CreativeSpec). Note: dynamic-creative ad sets accept only ONE ad. Defaults to false."}, 'publisher_platforms': {'anyOf': [{'type': 'array', 'items': {'enum': ['facebook', 'instagram', 'audience_network', 'messenger', 'threads'], 'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Restrict which platforms the ad set may deliver on. Omit for Advantage+ placements (recommended — Meta optimizes across all of them). Call ads REQUIRE ['facebook']."}, 'daily_min_spend_target': {'anyOf': [{'type': 'number', 'minimum': 0}, {'type': 'null'}], 'default': None, 'description': 'CBO only: minimum daily spend Meta will try to allocate to this ad set, in account currency. Requires the parent campaign to own a daily_budget (do not pair with an ad-set-level daily_budget). Best-effort, not a hard floor. Note: duplicate_adset(deep_copy=True) carries this over from the source ad set — if the sum of sibling floors exceeds the campaign budget, Meta blocks activation with subcode 1885648. Pass 0 to clear the inherited floor.'}, 'excluded_geo_locations': {'anyOf': [{'type': 'object', 'properties': {'zips': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': "Zip key from search_targeting geolocation results, e.g. 'US:94304'"}}, 'description': "A zip code geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': 'Zip codes to exclude'}, 'cities': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'City key from search_targeting geolocation results'}, 'radius': {'anyOf': [{'type': 'integer', 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Radius around city center (10-50 mi / 17-80 km)'}, 'distance_unit': {'anyOf': [{'enum': ['mile', 'kilometer'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Unit for radius'}}, 'description': "A city geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': 'Cities to exclude'}, 'regions': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'Region key from search_targeting geolocation results'}}, 'description': "A region geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': 'Regions to exclude'}, 'countries': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "ISO country codes to exclude, e.g. ['MX']"}}, 'description': 'Geo locations to exclude from targeting.'}, {'type': 'null'}], 'default': None, 'description': 'Geo locations to exclude from targeting'}, 'excluded_custom_audiences': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Custom audiences to EXCLUDE from targeting (from list_custom_audiences).'}, 'lifetime_min_spend_target': {'anyOf': [{'type': 'number', 'minimum': 0}, {'type': 'null'}], 'default': None, 'description': 'CBO only: minimum lifetime spend Meta will try to allocate to this ad set, in account currency. Requires the parent campaign to own a lifetime_budget. Best-effort, not a hard floor. Pass 0 to clear.'}, 'audience_network_positions': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Audience Network surfaces, e.g. ['classic','rewarded_video']. Requires 'audience_network' in publisher_platforms."}}, 'description': 'Specification for creating a single Meta ad set.'}, 'minItems': 1, 'description': 'Array of ad set specs to create. Required: name, campaign_id, at least one geo field. Optional: targeting, budgets, config.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is creating these ad sets (e.g. 'Overnight launch: 3-tier ABO for new lead-gen campaign'). Logged for telemetry."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['status', 'all_succeeded', 'account_id', 'error_count', 'currency', 'created_count', 'created'], 'properties': {'errors': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'Per-item failure records. Each carries `name` plus the keys from format_tool_error() (`error`, `exception_type`, `meta_error?`, `hint?`). Present when error_count > 0.'}, 'status': {'enum': ['success', 'partial'], 'type': 'string'}, 'created': {'type': 'array', 'items': {'type': 'object', 'required': ['index', 'adset_id', 'name', 'campaign_id', 'status', 'update_id', 'state_after'], 'properties': {'name': {'type': 'string'}, 'index': {'type': 'integer'}, 'status': {'type': 'string', 'const': 'PAUSED'}, 'adset_id': {'type': 'string'}, 'update_id': {'type': 'string'}, 'campaign_id': {'type': 'string'}, 'state_after': {'type': 'object', 'additionalProperties': True}}, 'description': 'Successfully-created ad set in a create_adset batch response.', 'additionalProperties': True}}, 'currency': {'type': 'string'}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'error_count': {'type': 'integer'}, 'all_succeeded': {'type': 'boolean', 'description': 'True if every item succeeded (no entries in `errors`).'}, 'created_count': {'type': 'integer'}}, 'description': 'Result of create_adset — N ad sets created (all PAUSED).', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'campaigns'], 'properties': {'campaigns': {'type': 'array', 'items': {'type': 'object', 'required': ['objective'], 'properties': {'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Campaign name. Omit to auto-generate a structured name: ADADVISOR || CBO/ABO || BOF/MOF/TOF || {strategy} || {business} || {date}.'}, 'adlabels': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': "Optional Meta ad labels for cross-campaign tagging/analytics rollups. JSON array of label specs, e.g. [{'name': 'BlackFriday2026'}]."}, 'objective': {'enum': ['OUTCOME_SALES', 'OUTCOME_LEADS', 'OUTCOME_ENGAGEMENT', 'OUTCOME_AWARENESS', 'OUTCOME_TRAFFIC', 'OUTCOME_APP_PROMOTION'], 'type': 'string', 'description': 'Campaign objective'}, 'spend_cap': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'Lifetime spend ceiling in account currency (e.g. 5000.0 = $5,000 max total). Hard cap — Meta stops delivery when reached. Acts as a safety rail; recommended whenever an automated agent is creating campaigns.'}, 'stop_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO datetime for campaign end. Required with lifetime_budget.'}, 'start_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "ISO datetime for campaign start, e.g. '2025-01-15T00:00:00-0500'"}, 'buying_type': {'anyOf': [{'enum': ['AUCTION', 'RESERVED'], 'type': 'string'}, {'type': 'null'}], 'default': 'AUCTION', 'description': 'Buying type (default AUCTION)'}, 'bid_strategy': {'anyOf': [{'enum': ['LOWEST_COST_WITHOUT_CAP', 'LOWEST_COST_WITH_BID_CAP', 'COST_CAP', 'LOWEST_COST_WITH_MIN_ROAS'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Bid strategy. Defaults to LOWEST_COST_WITHOUT_CAP (auto-bid). Cap values (bid_amount, roas_average_floor) are set at the ad set level, not here.'}, 'daily_budget': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'Daily budget in account currency (e.g. 50.0 = $50/day). Mutually exclusive with lifetime_budget. Budget can be set at campaign OR adset level, not both.'}, 'strategy_label': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Short 1-2 word label for the campaign strategy, used in auto-generated names. Examples: 'Prospecting', 'Retargeting', 'Scaling', 'Conversions'. Defaults to 'Prospecting' if name is omitted."}, 'lifetime_budget': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'Lifetime budget in account currency. Requires stop_time. Mutually exclusive with daily_budget.'}, 'promoted_object': {'anyOf': [{'type': 'object', 'properties': {'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Facebook page ID (for engagement/message objectives)'}, 'pixel_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Meta pixel ID for conversion tracking'}, 'application_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'App ID (for app promotion objectives)'}, 'object_store_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'App Store / Google Play URL (required with application_id for app promotion)'}, 'custom_event_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Conversion event, e.g. 'PURCHASE', 'LEAD', 'ADD_TO_CART'"}}, 'description': 'Conversion tracking config for an ad set.\n\nAuto-resolved for OUTCOME_SALES campaigns if omitted (picks account pixel + PURCHASE).', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Conversion tracking config at the CAMPAIGN level. Meta requires this only for iOS 14+ app promotion — set application_id + object_store_url on OUTCOME_APP_PROMOTION.\nDo NOT set it for OUTCOME_LEADS or OUTCOME_SALES: those belong on the AD SET (AdSetSpec.promoted_object), and putting one here is rejected with subcode 1815023.'}, 'source_campaign_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Existing campaign ID whose settings to copy as the template for the new campaign. Different from duplicate_campaign — that copies the live entity AND its children; this only seeds the campaign-level config.'}, 'special_ad_categories': {'anyOf': [{'type': 'array', 'items': {'enum': ['NONE', 'HOUSING', 'EMPLOYMENT', 'FINANCIAL_PRODUCTS_SERVICES', 'ISSUES_ELECTIONS_POLITICS'], 'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Special ad categories. Allowed values: NONE, EMPLOYMENT, HOUSING, FINANCIAL_PRODUCTS_SERVICES (replaces the legacy CREDIT input from Jan 14 2025), ISSUES_ELECTIONS_POLITICS. Use ['NONE'] or omit for standard ads. When any value other than NONE is supplied, special_ad_category_country becomes required."}, 'is_skadnetwork_attribution': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': "Set True for iOS app campaigns that must use Apple's SKAdNetwork attribution (post-iOS 14.5). Required for OUTCOME_APP_PROMOTION targeting iOS."}, 'special_ad_category_country': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "ISO country codes scoping the special_ad_categories. Required by Meta for international housing/credit/employment campaigns outside the US (e.g. ['CA', 'GB']). Omit for US-only or non-special campaigns."}, 'is_adset_budget_sharing_enabled': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': "Ad set budget sharing (ABS). Only meaningful when budget is at the ad set level (no daily_budget / lifetime_budget on this spec). Required by Meta v24+ for ABO campaigns: pass True to let ad sets share up to 20% of budget with siblings, False to keep each ad set's budget isolated. Omit when setting a campaign-level budget (CBO)."}}, 'description': 'Specification for creating a single Meta campaign.'}, 'minItems': 1, 'description': 'Array of campaign specs to create. Required: objective. Optional: name (auto-generated if omitted), budgets, bid_strategy, schedule, special_ad_categories, promoted_object (ONLY for OUTCOME_APP_PROMOTION — for OUTCOME_LEADS and OUTCOME_SALES it belongs on the ad set), spend_cap (lifetime ceiling), is_skadnetwork_attribution (iOS), source_campaign_id, adlabels.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID where the new campaign(s) will live. Verify the account is reachable via list_ad_accounts first.'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is creating these campaigns (e.g. 'Q2 retargeting test for cart abandoners'). Logged for telemetry."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['status', 'account_id', 'currency', 'all_succeeded', 'created_count', 'error_count', 'created'], 'properties': {'errors': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'Per-item failure records (name + format_tool_error keys). Present when error_count > 0.'}, 'status': {'enum': ['success', 'partial'], 'type': 'string'}, 'created': {'type': 'array', 'items': {'type': 'object', 'required': ['index', 'campaign_id', 'name', 'objective', 'status', 'update_id', 'state_after'], 'properties': {'name': {'type': 'string'}, 'index': {'type': 'integer', 'description': 'Position of this spec in the input array.'}, 'status': {'type': 'string', 'const': 'PAUSED', 'description': 'Always PAUSED — Meta requires explicit activation via change_entity_status.'}, 'objective': {'type': 'string'}, 'update_id': {'type': 'string'}, 'campaign_id': {'type': 'string'}, 'state_after': {'type': 'object', 'description': 'Snapshot of the new campaign as Meta returned it.', 'additionalProperties': True}}, 'description': 'Successfully-created campaign in a create_campaign batch response.', 'additionalProperties': True}}, 'currency': {'type': 'string'}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'error_count': {'type': 'integer'}, 'all_succeeded': {'type': 'boolean', 'description': 'True if every spec succeeded.'}, 'created_count': {'type': 'integer'}}, 'description': "Result of create_campaign — N campaigns created (all PAUSED).\n\n`status='success'` means every spec succeeded. `status='partial'` means\nsome specs failed (inspect the per-item entries in `errors`).", 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'creatives'], 'properties': {'creatives': {'type': 'array', 'items': {'type': 'object', 'required': ['format'], 'properties': {'link': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Destination URL for the ad (e.g. 'https://example.com/shop'). Required for image_link format unless lead_gen_form_id is set (lead-form creatives use 'http://fb.me/' automatically). Optional for video (omit for awareness-only video)."}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Creative name in the account's creative library. Strongly recommended: list_creatives can only be searched by name, so an unnamed creative is effectively unfindable afterwards."}, 'format': {'enum': ['image_link', 'video', 'carousel', 'existing_post'], 'type': 'string', 'description': "Creative format: 'image_link' (static image ad), 'video' (video ad), 'carousel' (2-10 swipeable cards, each with its own asset and destination), or 'existing_post' (promote an existing Facebook post)."}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Primary ad text (body copy) shown above the image/video. Mutually exclusive with 'messages' (multi-variant)."}, 'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Facebook Page ID to post the ad from. Omit to auto-resolve from existing creatives on this account.'}, 'headline': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Headline text. For image_link: shown below the image in the link preview. For video: mapped to video_data.title. Mutually exclusive with 'headlines' (multi-variant)."}, 'messages': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}, 'maxItems': 5}, {'type': 'null'}], 'default': None, 'description': "Multiple body copy variants (up to 5). Meta optimizes which variant to show per viewer. Produces asset_feed_spec instead of inline copy. Requires a dynamic creative ad set (is_dynamic_creative=true). Mutually exclusive with 'message'."}, 'url_tags': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "UTM tracking parameters appended to the link URL, e.g. 'utm_source=facebook&utm_medium=paid'."}, 'video_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Video ID from search_ad_videos. Required for video format.'}, 'headlines': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}, 'maxItems': 5}, {'type': 'null'}], 'default': None, 'description': "Multiple headline variants (up to 5). Meta optimizes which to show per viewer. Mutually exclusive with 'headline'."}, 'image_hash': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Image hash from upload_ad_image or search_ad_images. Required for image_link format.'}, 'description': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Description text shown below the headline in the link preview. Mutually exclusive with 'descriptions' (multi-variant)."}, 'descriptions': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}, 'maxItems': 5}, {'type': 'null'}], 'default': None, 'description': "Multiple description variants (up to 5). Meta optimizes which to show per viewer. Mutually exclusive with 'description'."}, 'phone_number': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Business phone number in E.164 form, e.g. '+15551234567'. REQUIRED when call_to_action_type='CALL_NOW' (call ads) and rejected otherwise. The MCP emits Meta's documented call_to_action.value = {'link': 'tel:<number>'} shape. Pair with an ad set using destination_type='PHONE_CALL', optimization_goal='QUALITY_CALL', device_platforms=['mobile'] and publisher_platforms=['facebook']."}, 'thumbnail_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL custom thumbnail URL — only for a stable, publicly hosted image (e.g. your own CDN). Do NOT copy thumbnail_url values from search_ad_videos results: those are signed Meta CDN links that expire within minutes, so they are discarded and re-resolved server-side anyway. Omit to let the server resolve the video's processed thumbnail at create time (recommended). Provide this OR thumbnail_image_hash (not both)."}, 'carousel_cards': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'properties': {'link': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Destination for this card. Required unless the creative sets lead_gen_form_id (lead carousels use Meta's fb.me placeholder)."}, 'headline': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Card headline (Meta's link_data `name`)."}, 'video_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Video ID from upload_ad_video / search_ad_videos. A video card must ALSO set image_hash as its thumbnail — Meta requires one alongside child_attachments.video_id.'}, 'image_hash': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Image hash from upload_ad_image / search_ad_images.'}, 'description': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Card sub-headline.'}, 'call_to_action_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Per-card CTA. Defaults to the creative-level call_to_action_type.'}}, 'description': 'One card in a carousel creative.\n\nEach card is its own image or video with its own headline, description and\ndestination — that is what makes carousels worth using over a single image.', 'additionalProperties': False}, 'maxItems': 10, 'minItems': 2}, {'type': 'null'}], 'default': None, 'description': "Cards for format='carousel'. Meta allows 2-10. With lead_gen_form_id set, every card points at that same form — Meta does not support different forms per card."}, 'object_story_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Existing Facebook post ID in the format '<PAGE_ID>_<POST_ID>'. For existing_post format. Provide this OR instagram_post_url OR facebook_post_url."}, 'lead_gen_form_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Meta Lead Form ID to attach to the creative — get one from list_lead_forms, create one with create_lead_form, or create it in Meta Ads Manager. Required when the parent ad set is configured for lead generation (optimization_goal=LEAD_GENERATION/QUALITY_LEAD with destination_type=ON_AD). When set: the link is auto-overridden to 'http://fb.me/', and call_to_action.value embeds the form ID. The CTA type must be one of APPLY_NOW, DOWNLOAD, GET_QUOTE, LEARN_MORE, SIGN_UP, or SUBSCRIBE (defaults to SIGN_UP). Works with image_link, video, carousel (every card gets the same form — Meta allows only one per carousel) and existing_post (the form rides on a top-level call_to_action, since a boosted post has no object_story_spec)."}, 'placement_images': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['image_hash', 'placement_groups'], 'properties': {'image_hash': {'type': 'string', 'minLength': 1, 'description': 'Image hash from upload_ad_image / search_ad_images.'}, 'placement_groups': {'type': 'array', 'items': {'enum': ['FEED', 'STORY', 'RIGHT_COLUMN', 'MARKETPLACE', 'VIDEO_FEEDS', 'SEARCH', 'EXPLORE', 'AUDIENCE_NETWORK', 'DEFAULT'], 'type': 'string'}, 'minItems': 1, 'description': "Placement group(s) this image serves. Match the asset's aspect ratio to the placement: FEED for 1:1/4:5, STORY for 9:16. Use DEFAULT on exactly one asset as the catch-all for every placement not claimed by another asset."}}, 'description': 'One image mapped to the placement groups it should serve.'}, 'maxItems': 10, 'minItems': 2}, {'type': 'null'}], 'default': None, 'description': "PLACEMENT ASSET CUSTOMIZATION (format='image_link' only): serve a different image per placement from ONE creative. Pass 2-10 entries, each {image_hash, placement_groups}. Mark exactly one entry DEFAULT as the catch-all. Mutually exclusive with image_hash and multi-variant copy. Requires 'link'. The parent ad set must have is_dynamic_creative=false (the default). When a user has uploaded multiple aspect-ratio cuts of the SAME concept, use this — do NOT create one creative per cut."}, 'placement_videos': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['video_id', 'placement_groups'], 'properties': {'video_id': {'type': 'string', 'minLength': 1, 'description': 'Video ID from search_ad_videos / complete_upload.'}, 'placement_groups': {'type': 'array', 'items': {'enum': ['FEED', 'STORY', 'RIGHT_COLUMN', 'MARKETPLACE', 'VIDEO_FEEDS', 'SEARCH', 'EXPLORE', 'AUDIENCE_NETWORK', 'DEFAULT'], 'type': 'string'}, 'minItems': 1, 'description': "Placement group(s) this video serves. Match the asset's aspect ratio to the placement: FEED for 1:1/4:5, STORY for 9:16, VIDEO_FEEDS for 16:9. Use DEFAULT on exactly one asset as the catch-all for every placement not claimed by another asset (including Reels)."}}, 'description': 'One video mapped to the placement groups it should serve.'}, 'maxItems': 10, 'minItems': 2}, {'type': 'null'}], 'default': None, 'description': "PLACEMENT ASSET CUSTOMIZATION (format='video' only): serve a different video per placement from ONE creative — e.g. the 1:1 cut in Feed, the 9:16 cut in Stories, the 16:9 cut in video feeds — instead of creating one ad per aspect ratio. Pass 2-10 entries, each {video_id, placement_groups}. Mark exactly one entry DEFAULT as the catch-all. Mutually exclusive with video_id, thumbnail fields, and multi-variant copy (messages/headlines/descriptions). Requires 'link'. The parent ad set must have is_dynamic_creative=false (the default). Thumbnails are resolved server-side per video. When a user has uploaded multiple aspect-ratio cuts of the SAME concept, use this — do NOT create one creative per cut."}, 'facebook_post_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Facebook post URL (e.g. share link, permalink, or /posts/ URL). For existing_post format. Auto-resolved to object_story_id. Provide this OR object_story_id OR instagram_post_url.'}, 'instagram_user_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Instagram account ID for Instagram placement (optional).'}, 'instagram_post_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Instagram post URL (e.g. 'https://www.instagram.com/p/DVb6nP5Cbm7/'). For existing_post format. Auto-resolved to the Instagram media ID. Provide this OR object_story_id OR facebook_post_url."}, 'call_to_action_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Call-to-action button label, e.g. 'SHOP_NOW', 'LEARN_MORE', 'SIGN_UP', 'GET_OFFER'. Omit to use Meta's default."}, 'thumbnail_image_hash': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL image hash to pin as the video thumbnail (from upload_ad_image or search_ad_images). Omit to let the server resolve the video's processed thumbnail at create time (recommended). Provide this OR thumbnail_url (not both)."}}, 'description': 'Specification for creating a Meta ad creative.\n\nSupports image_link, video, carousel, and existing_post formats.\nMulti-variant copy (asset_feed_spec) is supported via messages/headlines/descriptions arrays.'}, 'minItems': 1, 'description': "One or more creative specifications. Pass a list with one element for a single creative, or many for a batch.\n\nALWAYS BATCH when the user picks multiple assets in the same picker turn. The asset picker (search_ad_images / search_ad_videos) supports multi-select up to 10 — when the user selects N hashes/video_ids, build N CreativeSpecs and pass them all in a single create_creative call. Do NOT call create_creative once per hash; that wastes the user's tool quota.\n\nEach item is processed independently; a failure on one does not abort the others (inspect per-item ``status`` in the returned ``results``).\n\nThree formats supported per item:\n- format='video': requires video_id only. The thumbnail is resolved server-side from the video at create time — do NOT copy thumbnail_url values from search_ad_videos results (signed CDN links that expire; they are discarded and re-resolved anyway). To pin a custom thumbnail, pass thumbnail_image_hash.\n- format='image_link': requires image_hash and link\n- format='existing_post': requires object_story_id OR instagram_post_url OR facebook_post_url\n\nMULTIPLE ASPECT RATIOS OF THE SAME CONCEPT (e.g. the user uploaded 1:1 + 9:16 + 16:9 cuts of one ad): build ONE creative with placement_videos (or placement_images) mapping each cut to its placements — do NOT create one creative per cut, that fragments delivery into separate ads. Example: placement_videos=[{video_id: '<1x1>', placement_groups: ['FEED']}, {video_id: '<9x16>', placement_groups: ['STORY']}, {video_id: '<16x9>', placement_groups: ['DEFAULT']}]. Requires link; exactly one asset must carry DEFAULT (the catch-all, also covers Reels). Distinct concepts/hooks still get separate creatives — placement assets are for cuts of the SAME concept.\nOptional copy fields: message, headline, description, call_to_action_type, url_tags.\nFor multi-variant copy on a single creative: use messages[], headlines[], descriptions[] (max 5 each).\nFor lead-generation ads: set lead_gen_form_id (create the form in Meta Ads Manager, or via list_lead_forms / create_lead_form when enabled). The tool auto-substitutes the placeholder link 'http://fb.me/' and embeds the form ID in call_to_action.value — required for any ad set whose optimization_goal is LEAD_GENERATION/QUALITY_LEAD with destination_type=ON_AD.\npage_id is auto-resolved — do NOT ask the user for it."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is creating these creatives (e.g. '5-variant hook test for new BFCM angle'). Logged for telemetry."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['status', 'all_succeeded', 'account_id', 'success_count', 'error_count', 'creative_ids', 'results'], 'properties': {'status': {'enum': ['success', 'partial'], 'type': 'string'}, 'results': {'type': 'array', 'items': {'type': 'object', 'required': ['index', 'status'], 'properties': {'error': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'index': {'type': 'integer'}, 'detail': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Raw exception string for debugging. May leak stack-trace context.'}, 'format': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Creative format that was built (image_link, video, existing_post, etc.).'}, 'status': {'enum': ['success', 'error'], 'type': 'string'}, 'update_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'creative_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'page_id_used': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Per-creative result inside create_creative.results.\n\nUnlike other batch creation tools, create_creative interleaves successes\nand errors in a single `results` list. Inspect each item's `status`.", 'additionalProperties': True}, 'description': 'Per-creative results — one entry per input spec.'}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'error_count': {'type': 'integer'}, 'creative_ids': {'type': 'array', 'items': {'type': 'string'}, 'description': 'IDs of successfully-created creatives. Pass these to create_ad.'}, 'all_succeeded': {'type': 'boolean'}, 'success_count': {'type': 'integer'}}, 'description': 'Result of create_creative — N creatives created (interleaved results).', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'forms'], 'properties': {'forms': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'questions', 'privacy_policy', 'follow_up_action_url'], 'properties': {'name': {'type': 'string', 'minLength': 1, 'description': 'Form name shown in the Forms Library.'}, 'locale': {'type': 'string', 'default': 'en_US', 'description': "Form locale, e.g. en_US, fr_FR, ar_AR, pt_BR. Case-insensitive. Must be one of Meta's 32 supported locales — an unsupported value is rejected before the form is created."}, 'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Facebook Page ID to create the form on. If omitted, the service auto-resolves the first promote_pages entry for the ad account (use list_pages to inspect candidates and pin a specific one).'}, 'questions': {'type': 'array', 'items': {'type': 'object', 'required': ['type'], 'properties': {'key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Stable lowercase question key (used in the lead webhook payload). Auto-generated for standard types. Recommended for CUSTOM so downstream systems can map answers reliably.'}, 'type': {'enum': ['FULL_NAME', 'FIRST_NAME', 'LAST_NAME', 'EMAIL', 'PHONE', 'CUSTOM', 'DATE_TIME', 'STORE_LOOKUP', 'ID_AR_DNI', 'ID_CPF', 'ID_CL_RUT', 'ID_CO_CC', 'ID_EC_CI', 'ID_PE_DNI'], 'type': 'string', 'description': 'Question type. Standard types (FULL_NAME / EMAIL / PHONE / FIRST_NAME / LAST_NAME) auto-fill the label and key. Use CUSTOM for free-text or dropdown questions, DATE_TIME for appointment scheduling, STORE_LOOKUP for store-locator (requires context_provider_type=LOCATION_MANAGER), and ID_* for the country-specific national ID inputs.'}, 'label': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Question text shown to the user. Required for CUSTOM, DATE_TIME, and STORE_LOOKUP.'}, 'options': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['value'], 'properties': {'key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Stable option key — this is what comes back in the lead payload. Derived from `value` when omitted; pass it explicitly when a downstream CRM expects a particular key.'}, 'value': {'type': 'string', 'description': 'Display label shown to the user.'}}, 'description': 'A single dropdown option on a CUSTOM lead form question.'}}, {'type': 'null'}], 'default': None, 'description': 'Dropdown options for CUSTOM. Omit to render a free-text input.'}, 'inline_context': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Helper text shown directly below the field.'}, 'context_provider_type': {'anyOf': [{'type': 'string', 'const': 'LOCATION_MANAGER'}, {'type': 'null'}], 'default': None, 'description': 'Required for STORE_LOOKUP. Set to LOCATION_MANAGER.'}, 'conditional_questions_choices': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "On a DEPENDENT question: which of the parent's option keys must be selected for this question to appear."}, 'conditional_questions_group_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Groups a parent question with the follow-ups that depend on it. Use the same id on the parent and its dependents.'}, 'dependent_conditional_questions': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'On a PARENT question: the follow-up questions it can reveal, each shaped like a normal question object.'}}, 'description': 'A single question on a Meta Lead Form (instant form).'}, 'minItems': 1, 'description': 'Ordered list of questions shown to the user. Common starting set: FULL_NAME, EMAIL, PHONE, plus 1-3 CUSTOM qualifier questions. Every extra question costs volume and buys intent — 3-5 is the usual sweet spot.'}, 'context_card': {'anyOf': [{'type': 'object', 'required': ['title', 'content'], 'properties': {'style': {'enum': ['PARAGRAPH_STYLE', 'LIST_STYLE'], 'type': 'string', 'default': 'PARAGRAPH_STYLE', 'description': 'PARAGRAPH_STYLE renders `content` as prose; LIST_STYLE renders it as bullet points.'}, 'title': {'type': 'string', 'minLength': 1, 'description': 'Headline on the intro screen.'}, 'content': {'type': 'array', 'items': {'type': 'string'}, 'minItems': 1, 'description': 'Body copy. One entry for PARAGRAPH_STYLE; one entry per bullet for LIST_STYLE.'}, 'button_text': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Label on the button that advances to the questions.'}, 'cover_photo_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Photo ID for the intro screen image. Defaults to the ad's creative."}}, 'description': "The intro screen shown before a lead form's questions.\n\nOptional, but the single biggest lever on lead quality that costs nothing:\nit sets expectations before someone starts filling in the form.", 'additionalProperties': False}, {'type': 'null'}], 'default': None, 'description': 'Intro screen shown before the questions — headline, body copy and button label. Omit for a form that opens straight on the questions. Setting expectations here is the cheapest lead-quality lever available.'}, 'privacy_policy': {'type': 'object', 'required': ['url', 'link_text'], 'properties': {'url': {'type': 'string', 'minLength': 1, 'description': "HTTPS URL to the advertiser's privacy policy."}, 'link_text': {'type': 'string', 'minLength': 1, 'description': "Anchor text shown in the form (e.g. 'Privacy Policy')."}}, 'description': 'Privacy policy URL + link text. Required by Meta.'}, 'thank_you_page': {'anyOf': [{'type': 'object', 'required': ['button_type'], 'properties': {'body': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Subheading / supporting text.'}, 'title': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Headline shown on the thank-you page.'}, 'button_text': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Button label, e.g. 'Visit website'. REQUIRED for every button_type except NONE — Meta does not supply a default and rejects the form with '(#100) Button text is missing for Thank You Page'."}, 'button_type': {'enum': ['VIEW_WEBSITE', 'CALL_BUSINESS', 'DOWNLOAD', 'MESSAGE_BUSINESS', 'VIEW_ON_FACEBOOK', 'NONE'], 'type': 'string', 'description': 'CTA on the thank-you page. VIEW_WEBSITE requires website_url; CALL_BUSINESS requires business_phone_number (plus country_code); VIEW_ON_FACEBOOK is the button type gated-file downloads require; NONE shows no button.'}, 'website_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Required when button_type is VIEW_WEBSITE.'}, 'country_code': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "ISO country code for business_phone_number, e.g. 'US'. Meta rejects CALL_BUSINESS without it in most locales."}, 'enable_messenger': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'Offer a Messenger thread from the thank-you page.'}, 'business_phone_number': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Required when button_type is CALL_BUSINESS (E.164 format).'}}, 'description': 'Thank-you page shown after the user submits the form.\n\nThis is the last moment a lead is warm, so the button matters: sending\nthem to a booking page or a phone call converts far better than leaving\nthem on the default confirmation.', 'additionalProperties': False}, {'type': 'null'}], 'default': None, 'description': 'Thank-you page config. If omitted, Meta shows the default copy.'}, 'custom_disclaimer': {'anyOf': [{'type': 'object', 'required': ['title'], 'properties': {'title': {'type': 'string', 'description': 'Heading for the disclaimer block.'}, 'body_text': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Body copy shown under the title. Sent to Meta as the nested body.text it expects.'}, 'checkboxes': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key', 'text'], 'properties': {'key': {'type': 'string', 'minLength': 1, 'description': "Stable checkbox key. This is what comes back in a lead's custom_disclaimer_responses, so it is the consent record — keep it meaningful and stable."}, 'text': {'type': 'string', 'description': 'Consent text shown next to the checkbox.'}, 'is_required': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'When true the form cannot be submitted without ticking it.'}, 'is_checked_by_default': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'Pre-ticks the box. Many jurisdictions do not treat a pre-ticked box as valid consent — check before using this.'}}, 'description': 'One consent checkbox inside a custom disclaimer.', 'additionalProperties': False}}, {'type': 'null'}], 'default': None, 'description': 'Consent checkboxes. Omit for a disclaimer that is informational only (no explicit opt-in captured).'}}, 'description': "Advertiser's own consent / legal text, shown before submission.\n\nRequired in practice for regulated verticals (finance, insurance,\nhealthcare, automotive in several markets) where a plain privacy-policy\nlink is not sufficient consent. Answers come back on each lead as\ncustom_disclaimer_responses — see list_leads.", 'additionalProperties': False}, {'type': 'null'}], 'default': None, 'description': "Advertiser's own consent text and opt-in checkboxes, shown before submission. Needed by regulated verticals where a privacy-policy link alone is not adequate consent. Responses come back per lead as custom_disclaimer_responses (list_leads)."}, 'tracking_parameters': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Flat {key: value} object passed back on every lead webhook. Useful for source attribution. Note: Meta returns this as an array on read, but it MUST be sent as an object on create.'}, 'follow_up_action_url': {'type': 'string', 'minLength': 1, 'description': "HTTPS URL surfaced after submission (e.g. calendar booking link or advertiser homepage). Required by Meta even though the public Marketing API guide does not list it — omitting raises OAuthException code 100 / subcode 1892085 'Missing field(s): FollowUpActionURL'. If unsure, pass the same URL as privacy_policy.url's domain root (e.g. https://example.com)."}, 'is_optimized_for_quality': {'type': 'boolean', 'default': False, 'description': 'Adds a review-and-confirm step before submission. Recommended for high-intent funnels (test drives, demos) where lead quality matters more than volume.'}, 'should_enforce_work_email': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'Reject free consumer email domains (gmail.com, etc.) on the EMAIL question. The standard B2B lead-quality filter — cuts volume sharply, so pair it with a volume check.'}, 'is_phone_sms_verify_enabled': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'Require the user to confirm their phone number by SMS. Strong filter against fake numbers where phone follow-up is the point.'}, 'allow_organic_lead_retrieval': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'Allow leads submitted from organic (non-ad) reach of the post to be retrieved alongside paid leads.'}, 'question_page_custom_headline': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Custom headline above the question list.'}, 'block_display_for_non_targeted_viewer': {'type': 'boolean', 'default': False, 'description': "When true, only people inside the ad's targeting can see / submit the form (filters organic and out-of-target views)."}}, 'description': 'Specification for creating a single Meta Lead Form (instant form).', 'additionalProperties': False}, 'minItems': 1, 'description': 'Array of lead form specs to create. Each form requires name, questions, and privacy_policy. page_id is optional (auto-resolved from account if omitted — use list_pages to inspect candidates).'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is creating these lead forms (e.g. 'PodoStart Bundle + 2 fallback offers for BUC lead-gen launch'). Logged for telemetry."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['status', 'all_succeeded', 'account_id', 'error_count', 'created_count', 'created'], 'properties': {'errors': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'Per-item failure records. Each carries `name` plus the keys from format_tool_error() (`error`, `exception_type`, `meta_error?`, `hint?`). Present when error_count > 0.'}, 'status': {'enum': ['success', 'partial'], 'type': 'string'}, 'created': {'type': 'array', 'items': {'type': 'object', 'required': ['index', 'lead_form_id', 'name', 'update_id', 'state_after'], 'properties': {'name': {'type': 'string'}, 'index': {'type': 'integer'}, 'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'page_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'update_id': {'type': 'string'}, 'state_after': {'type': 'object', 'additionalProperties': True}, 'lead_form_id': {'type': 'string'}}, 'description': 'Successfully-created lead form in a create_lead_form batch response.', 'additionalProperties': True}}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'error_count': {'type': 'integer'}, 'all_succeeded': {'type': 'boolean', 'description': 'True if every item succeeded (no entries in `errors`).'}, 'created_count': {'type': 'integer'}}, 'description': 'Result of create_lead_form — N forms created on a Facebook Page.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'name', 'form_ids', 'event_name'], 'properties': {'name': {'type': 'string', 'minLength': 1, 'description': "Audience name, e.g. 'Form openers - not submitted - 30d'."}, 'source': {'enum': ['facebook', 'instagram'], 'type': 'string', 'default': 'facebook', 'description': 'Which surface the form was filled on. Instagram lead forms are a separate event source from Facebook ones.'}, 'form_ids': {'type': 'array', 'items': {'type': 'string'}, 'minItems': 1, 'description': 'Lead form IDs to build the audience from (list_lead_forms).'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'event_name': {'enum': ['lead_generation_submitted', 'lead_generation_opened', 'lead_generation_dropoff'], 'type': 'string', 'description': 'Which interaction to capture. lead_generation_submitted = completed the form (EXCLUDE these from prospecting so you stop paying for leads you already have; use as a lookalike seed). lead_generation_opened = opened it. lead_generation_dropoff = opened but did not submit — the highest-intent retargeting pool a lead campaign produces.'}, 'description': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Optional note stored on the audience.'}, 'retention_days': {'type': 'integer', 'default': 90, 'maximum': 90, 'minimum': 1, 'description': 'How long someone stays in the audience. Meta caps lead-form engagement audiences at 90 days (unlike Page or site audiences).'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['next_steps', 'audience_id', 'name', 'update_id', 'event_name', 'form_ids', 'retention_days', 'source'], 'properties': {'name': {'type': 'string'}, 'source': {'type': 'string', 'description': "'facebook' or 'instagram' — which surface the form was filled on."}, 'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'form_ids': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Lead forms the audience was built from.'}, 'update_id': {'type': 'string'}, 'event_name': {'type': 'string', 'description': 'Which form interaction the audience captures.'}, 'next_steps': {'type': 'string', 'description': 'Recommended follow-up tool call.'}, 'audience_id': {'type': 'string'}, 'retention_days': {'type': 'integer', 'description': 'How long someone stays in the audience (Meta caps this at 90).'}, 'operation_status': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': "Meta's operation_status object for population state (None until ready)."}, 'approximate_count_lower_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Lower bound of approximate audience size. None until Meta finishes populating.'}, 'approximate_count_upper_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}}, 'description': "Result of create_lead_form_audience — lead-form engagement audience.\n\nDeliberately its own model rather than reusing the website one: a\nlead-form audience has no pixel and no prefill, so annotating the tool\nwith CreateWebsiteAudienceResponse made FastMCP's output validation fail\non the missing `event_name`/`pixel_id`/`prefill` — AFTER the audience had\nalready been created on Meta. The caller saw an error for a mutation that\nhad succeeded, which invites a retry and a duplicate audience.", 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'name', 'origin_audience_id', 'country'], 'properties': {'name': {'type': 'string', 'minLength': 1, 'description': "Audience name (e.g. '1% US Lookalike - Purchasers')"}, 'ratio': {'anyOf': [{'type': 'number', 'maximum': 0.2, 'minimum': 0.01}, {'type': 'null'}], 'default': None, 'description': 'Top percentage of population to target (0.01 = 1%, 0.05 = 5%, max 0.20 = 20%). Smaller = more similar. Defaults to 0.01 (1%).'}, 'country': {'type': 'string', 'maxLength': 2, 'minLength': 2, 'description': "ISO country code to find lookalike people in (e.g. 'US', 'CA', 'GB')."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is creating this lookalike (e.g. '1% LAL of high-LTV purchasers for prospecting'). Logged for telemetry."}, 'starting_ratio': {'anyOf': [{'type': 'number', 'maximum': 0.19, 'minimum': 0.0}, {'type': 'null'}], 'default': None, 'description': 'Start percentage for tiered lookalikes. E.g. starting_ratio=0.01, ratio=0.03 targets the 1%-3% band. Must be less than ratio.'}, 'origin_audience_id': {'type': 'string', 'minLength': 1, 'description': 'Seed audience ID from list_custom_audiences. Must have at least 100 members.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['next_steps', 'audience_id', 'name', 'update_id', 'origin_audience_id', 'country', 'ratio'], 'properties': {'band': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Human-readable size band (e.g. '1-2%') — convenience field."}, 'name': {'type': 'string'}, 'ratio': {'type': 'number', 'description': 'Similarity ratio (0.01 = top 1% most similar).'}, 'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'country': {'type': 'string', 'description': 'ISO country code the lookalike was built for.'}, 'update_id': {'type': 'string'}, 'next_steps': {'type': 'string', 'description': 'Recommended follow-up tool call.'}, 'audience_id': {'type': 'string'}, 'operation_status': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': "Meta's operation_status object for population state (None until ready)."}, 'origin_audience_id': {'type': 'string', 'description': 'ID of the seed custom audience.'}, 'approximate_count_lower_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Lower bound of approximate audience size. None until Meta finishes populating.'}, 'approximate_count_upper_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}}, 'description': 'Result of create_lookalike_audience — seed-derived prospecting audience.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'name', 'retention_days'], 'properties': {'name': {'type': 'string', 'minLength': 1, 'description': "Audience name (e.g. 'Purchasers - Last 90 Days', 'Cart Abandoners - 30 Days')"}, 'prefill': {'type': 'boolean', 'default': True, 'description': 'Include historical website activity before audience creation. Defaults to true.'}, 'pixel_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Pixel ID to use. Omit to auto-resolve from the account (most accounts have one pixel).'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'event_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Pixel event to match. Common events: Purchase, AddToCart, ViewContent, InitiateCheckout, Lead, CompleteRegistration, Search, AddPaymentInfo, AddToWishlist, PageView. Omit to match ALL website visitors.'}, 'description': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Optional description for the audience.'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is creating this audience (e.g. 'exclude 90-day purchasers from prospecting'). Logged for telemetry."}, 'retention_days': {'type': 'integer', 'maximum': 180, 'minimum': 1, 'description': 'How many days to keep people in the audience (1-180). E.g. 30, 60, 90, 180.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['next_steps', 'audience_id', 'name', 'update_id', 'event_name', 'retention_days', 'pixel_id', 'prefill'], 'properties': {'name': {'type': 'string'}, 'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'prefill': {'type': 'boolean', 'description': 'True if Meta backfilled the audience with historical pixel data.'}, 'pixel_id': {'type': 'string'}, 'update_id': {'type': 'string'}, 'event_name': {'type': 'string', 'description': "The pixel event the audience tracks (e.g. 'Purchase'). 'All website visitors' when unfiltered."}, 'next_steps': {'type': 'string', 'description': 'Recommended follow-up tool call.'}, 'audience_id': {'type': 'string'}, 'retention_days': {'type': 'integer', 'description': 'How long users stay in the audience after the matching event.'}, 'operation_status': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': "Meta's operation_status object for population state (None until ready)."}, 'approximate_count_lower_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Lower bound of approximate audience size. None until Meta finishes populating.'}, 'approximate_count_upper_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}}, 'description': 'Result of create_website_audience — pixel-event-based custom audience.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'ad_id'], 'properties': {'ad_id': {'type': 'string', 'minLength': 1, 'description': 'The ad ID to duplicate (from list_ads or get_performance)'}, 'reason': {'anyOf': [{'enum': ['COPY', 'SCALE', 'TEST', 'ISOLATE'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Why the ad is being duplicated. Appears in the auto-generated name. Defaults to COPY.'}, 'status': {'enum': ['PAUSED', 'ACTIVE'], 'type': 'string', 'default': 'PAUSED', 'description': 'Initial status for the new ad. Defaults to PAUSED.'}, 'new_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Name for the new ad. Defaults to '{original_name} || {REASON} || {date}'."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is duplicating this ad (e.g. 'A/B the winning creative under a new audience'). Logged for telemetry."}, 'target_adset_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': "Ad set to place the copy in. Can be any existing ad set (from list_adsets, create_adset, or duplicate_adset). Defaults to the original's ad set."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['next_steps', 'account_id', 'original_ad_id', 'ad_status'], 'properties': {'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'new_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'ad_status': {'enum': ['PAUSED', 'ACTIVE'], 'type': 'string'}, 'new_ad_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'update_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Audit-record ID — the entity_updates row created for this change.'}, 'account_id': {'type': 'string'}, 'next_steps': {'type': 'string', 'description': 'Recommended follow-up tool call.'}, 'entity_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Human-readable name of the entity.'}, 'state_after': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot after the change (shape varies by entity type).'}, 'state_before': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot before the change (shape varies by entity type).'}, 'original_ad_id': {'type': 'string'}, 'target_adset_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': 'Result of duplicating a Meta ad.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'adset_id'], 'properties': {'reason': {'anyOf': [{'enum': ['COPY', 'SCALE', 'TEST', 'ISOLATE'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Why the ad set is being duplicated. Appears in the auto-generated name. Defaults to COPY.'}, 'status': {'enum': ['PAUSED', 'ACTIVE'], 'type': 'string', 'default': 'PAUSED', 'description': 'Initial status for the new ad set. Defaults to PAUSED.'}, 'adset_id': {'type': 'string', 'minLength': 1, 'description': 'The ad set ID to duplicate (from list_adsets or get_performance — never invent IDs)'}, 'new_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Name for the new ad set. Defaults to '{original_name} || {REASON} || {date}'."}, 'deep_copy': {'type': 'boolean', 'default': True, 'description': 'If true (default), duplicate all ads under the ad set. If false, create an empty ad set shell.'}, 'dsa_payor': {'anyOf': [{'type': 'string', 'maxLength': 512}, {'type': 'null'}], 'default': None, 'description': "DSA payor for the new ad set. Required when copying into an EU-targeted context if the source ad set or destination campaign doesn't already carry one (Meta subcode 3858081). Max 512 chars."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': "Ad account ID where adset_id lives. Meta returns code 100 (parameter error) if the account doesn't own the ad set. Look it up via list_adsets when in doubt."}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is duplicating (e.g. 'cloning into geo-isolated campaign for incrementality test'). Logged for telemetry."}, 'dsa_beneficiary': {'anyOf': [{'type': 'string', 'maxLength': 512}, {'type': 'null'}], 'default': None, 'description': "DSA beneficiary for the new ad set. Required when copying into an EU-targeted context if the source ad set or destination campaign doesn't already carry one (Meta subcode 3858079). Max 512 chars."}, 'target_campaign_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': "Campaign to place the copy in. Can be any existing campaign (from list_campaigns, create_campaign, or duplicate_campaign). Defaults to the original's campaign."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['account_id', 'original_adset_id', 'deep_copy', 'adset_status'], 'properties': {'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'new_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'warnings': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None}, 'deep_copy': {'type': 'boolean'}, 'update_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Audit-record ID — the entity_updates row created for this change.'}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'entity_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Human-readable name of the entity.'}, 'state_after': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot after the change (shape varies by entity type).'}, 'adset_status': {'enum': ['PAUSED', 'ACTIVE'], 'type': 'string'}, 'new_adset_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'state_before': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot before the change (shape varies by entity type).'}, 'original_adset_id': {'type': 'string'}, 'target_campaign_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Campaign the new adset belongs to. None means same campaign as the original.'}}, 'description': 'Result of duplicating a Meta ad set.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'campaign_id'], 'properties': {'reason': {'anyOf': [{'enum': ['COPY', 'SCALE', 'TEST', 'ISOLATE'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Why the campaign is being duplicated. Appears in the auto-generated name. Defaults to COPY.'}, 'status': {'enum': ['PAUSED', 'ACTIVE'], 'type': 'string', 'default': 'PAUSED', 'description': 'Initial status for the new campaign. Defaults to PAUSED.'}, 'new_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Name for the new campaign. Defaults to '{original_name} || {REASON} || {date}'."}, 'deep_copy': {'type': 'boolean', 'default': True, 'description': 'If true (default), duplicate all ad sets and ads. If false, create an empty campaign shell.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': "Ad account ID where campaign_id lives. Meta returns code 100 (parameter error) if the account doesn't own the campaign. Look it up via list_campaigns when in doubt."}, 'campaign_id': {'type': 'string', 'minLength': 1, 'description': 'The campaign ID to duplicate (from list_campaigns or get_performance — never invent IDs)'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is duplicating (e.g. 'scaling top-performing creative into new geo'). Logged for telemetry."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['account_id', 'original_campaign_id', 'deep_copy', 'campaign_status'], 'properties': {'status': {'enum': ['success', 'partial_success'], 'type': 'string', 'default': 'success', 'description': "'partial_success' when deep_copy was requested but some ad sets or ads failed to copy (see adsets_failed / ads_failed and the *_copy_failures lists)."}, 'new_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'warnings': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Non-fatal issues during duplication (e.g. some children failed).'}, 'ads_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Ads on the source campaign (deep_copy only).'}, 'deep_copy': {'type': 'boolean', 'description': 'True if all child ad sets / ads were copied.'}, 'update_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Audit-record ID — the entity_updates row created for this change.'}, 'account_id': {'type': 'string'}, 'ads_copied': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Ads that actually landed in the new campaign (deep_copy only).'}, 'ads_failed': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Ads that failed to copy (deep_copy only).'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'entity_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Human-readable name of the entity.'}, 'state_after': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot after the change (shape varies by entity type).'}, 'adsets_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Ad sets on the source campaign (deep_copy only).'}, 'state_before': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot before the change (shape varies by entity type).'}, 'adsets_copied': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Ad sets that actually landed in the new campaign (deep_copy only).'}, 'adsets_failed': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Ad sets that failed to copy entirely — all their ads were lost too.'}, 'campaign_status': {'enum': ['PAUSED', 'ACTIVE'], 'type': 'string', 'description': 'Initial status of the new campaign (typically PAUSED).'}, 'new_campaign_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ID of the new campaign. None only if Meta returned an unparseable response.'}, 'ad_copy_failures': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'Per-ad structured failures (ad_id, ad_name, meta_error, hint) for individual ads that failed in the per-ad fallback path.'}, 'adset_copy_failures': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'Per-ad-set structured failures (adset_id, adset_name, meta_error, hint) for ad sets that failed to copy entirely.'}, 'original_campaign_id': {'type': 'string'}}, 'description': 'Result of duplicating a Meta campaign.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'zips': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': "Zip key from search_targeting geolocation results, e.g. 'US:94304'"}}, 'description': "A zip code geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': "Zip code targets from search_targeting(search_type='geolocation')."}, 'cities': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'City key from search_targeting geolocation results'}, 'radius': {'anyOf': [{'type': 'integer', 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Radius around city center (10-50 mi / 17-80 km)'}, 'distance_unit': {'anyOf': [{'enum': ['mile', 'kilometer'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Unit for radius'}}, 'description': "A city geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': "City targets from search_targeting(search_type='geolocation')."}, 'age_max': {'anyOf': [{'type': 'integer', 'maximum': 65, 'minimum': 18}, {'type': 'null'}], 'default': None, 'description': 'Maximum age (18-65).'}, 'age_min': {'anyOf': [{'type': 'integer', 'maximum': 65, 'minimum': 18}, {'type': 'null'}], 'default': None, 'description': 'Minimum age (18-65).'}, 'genders': {'anyOf': [{'type': 'array', 'items': {'type': 'integer'}}, {'type': 'null'}], 'default': None, 'description': '[1]=male only, [2]=female only, [1,2]=both. Omit for all.'}, 'regions': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'Region key from search_targeting geolocation results'}}, 'description': "A region geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': "Region targets from search_targeting(search_type='geolocation')."}, 'behaviors': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Behavior targets from search_targeting(search_type='behaviors')."}, 'countries': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "ISO country codes, e.g. ['US', 'CA']. At least one geo field required."}, 'interests': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Interest targets from search_targeting(search_type='interests')."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'custom_audiences': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Custom audiences to include (from list_custom_audiences).'}, 'excluded_custom_audiences': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Custom audiences to exclude (from list_custom_audiences).'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['account_id', 'estimate_ready', 'targeting_summary', 'targeting_spec'], 'properties': {'account_id': {'type': 'string'}, 'estimate_ready': {'type': 'boolean', 'description': 'True when Meta has a reliable estimate. False means try again with broader targeting.'}, 'targeting_spec': {'type': 'object', 'description': 'Final targeting_spec that was estimated.', 'additionalProperties': True}, 'targeting_summary': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'array', 'items': {}}], 'description': 'Human-readable summary of the resolved targeting spec. May be a dict of structured fields or a list of bullet strings depending on the version of the underlying targeting service.'}, 'users_lower_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Lower bound of estimated reach. None when Meta couldn't estimate."}, 'users_upper_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}}, 'description': 'Audience-size estimate for a targeting spec — no entity created.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'lead_form_id'], 'properties': {'page_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': "Facebook Page that owns the form. Omit to use the account's default promote_page. Only needed when the account has several Pages and the form lives on a non-default one (see list_pages)."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'lead_form_id': {'type': 'string', 'minLength': 1, 'description': "Lead form ID — from list_lead_forms, from create_lead_form's response, or from a creative's lead_gen_form_id (list_creatives)."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['id'], 'properties': {'id': {'type': 'string'}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'locale': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ACTIVE, ARCHIVED, DELETED, or DRAFT.'}, 'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'page_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'questions': {'type': 'array', 'items': {'type': 'object', 'properties': {'key': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Stable field key — this is the name that appears in the lead payload.'}, 'type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Question type, e.g. EMAIL, PHONE, FULL_NAME, CUSTOM. Non-CUSTOM types are prefilled by Meta from the user's profile."}, 'label': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Question text shown to the user.'}, 'options': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'Dropdown choices for CUSTOM questions, as [{key, value}].'}, 'inline_context': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Helper text shown beneath the question.'}}, 'description': 'One question as Meta stores it on an existing lead form.', 'additionalProperties': True}, 'description': 'Every question on the form, in display order.'}, 'leads_count': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Total leads collected to date (all time, not date-scoped).'}, 'context_card': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'The intro screen shown before the questions (title, style, content, button_text).'}, 'created_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'legal_content': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Custom disclaimers / consent checkboxes attached to the form.'}, 'thank_you_page': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'The completion screen shown after submission.'}, 'allow_organic_lead': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'True when the form can also be submitted from organic (non-ad) traffic.'}, 'privacy_policy_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'expired_leads_count': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Leads that have aged past Meta's 90-day retrieval window."}, 'organic_leads_count': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}, 'tracking_parameters': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Flat key/value map echoed back on every lead for this form.'}, 'follow_up_action_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'follow_up_action_text': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'is_optimized_for_quality': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'True for the higher-intent form type (adds a review step before submit). Trades volume for lead quality.'}, 'question_page_custom_headline': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'block_display_for_non_targeted_viewer': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None}}, 'description': "Full detail for a single lead form, from get_lead_form.\n\nA lead form's questions cannot be edited after creation, so this read is\nthe only way to inspect what an in-market form actually asks.", 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'level'], 'properties': {'level': {'enum': ['campaign', 'adset', 'ad'], 'type': 'string', 'description': "Hierarchy level: 'campaign', 'adset', or 'ad'"}, 'date_to': {'anyOf': [{'type': 'string', 'pattern': '^\\d{4}-\\d{2}-\\d{2}$'}, {'type': 'null'}], 'default': None, 'description': 'End date (YYYY-MM-DD). Defaults to yesterday.'}, 'adset_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': "Filter ads to this ad set. Only used when level is 'ad'."}, 'date_from': {'anyOf': [{'type': 'string', 'pattern': '^\\d{4}-\\d{2}-\\d{2}$'}, {'type': 'null'}], 'default': None, 'description': 'Start date (YYYY-MM-DD). Defaults to 45 days ago.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'campaign_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': "Filter ad sets or ads to this campaign. Ignored when level is 'campaign'."}, 'response_format': {'enum': ['full', 'concise'], 'type': 'string', 'default': 'full', 'description': "'concise' keeps key metrics only: id, name, status, spend, result_count, cost_per_result, conversion_result_name, result_action_type, objective_label, roas, revenue, cpa, cpl, leads, ctr, daily_budget. 'full' returns everything."}, 'include_lead_breakdown': {'type': 'boolean', 'default': False, 'description': "Add lead_action_breakdown to each row: leads split by the action type that produced them (instant form vs website pixel vs offline/CRM). Use when an account runs more than one lead destination and you need to tell them apart. NOTE: '*_grouped' types are roll-ups — never sum a grouped type together with the types it rolls up."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['account_id', 'currency', 'level', 'date_from', 'date_to', 'count', 'results'], 'properties': {'count': {'type': 'integer'}, 'level': {'enum': ['campaign', 'adset', 'ad'], 'type': 'string'}, 'date_to': {'type': 'string', 'description': 'Range end (YYYY-MM-DD).'}, 'results': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}, 'description': "Per-entity KPI rows. Each has id, name, status, spend, revenue, roas, ctr, plus the Meta 'Results' fields when response_format='full'."}, 'currency': {'type': 'string', 'description': 'Account currency (ISO 4217).'}, 'date_from': {'type': 'string', 'description': 'Range start (YYYY-MM-DD).'}, 'account_id': {'type': 'string'}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'last_synced': {'anyOf': [{'type': 'number'}, {'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "When this account's data was last refreshed — the synced copy's last ingestion, or the read time for a live answer. Unix timestamp (float) or ISO string depending on the sync source."}, 'data_available_to': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'data_available_from': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Earliest date for which we have synced data (None if never synced).'}}, 'description': "Aggregated KPIs for entities at a given hierarchy level.\n\n`results` rows include id / name / status / spend / revenue / roas / ctr\nplus result_count / result_value / cost_per_result / result_action_type /\nconversion_result_name when response_format='full'. Concise mode strips\nlow-info fields. When entities span mixed conversion KPIs, each row also\ncarries `kpi_breakdown` (sorted DESC by volume).", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['account_id', 'pixel_count', 'pixels'], 'properties': {'pixels': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}, 'description': 'Per-pixel summary: id, name, last_fired_time, code (Meta health code), events_by_type (PageView, Purchase, etc.) and events_by_source (browser, server/CAPI). Use to diagnose tracking gaps.'}, 'account_id': {'type': 'string'}, 'pixel_count': {'type': 'integer'}}, 'description': 'Per-pixel health snapshot for an ad account.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'entity_type', 'entity_id'], 'properties': {'end_date': {'anyOf': [{'type': 'string', 'pattern': '^\\d{4}-\\d{2}-\\d{2}$'}, {'type': 'null'}], 'default': None, 'description': 'End date (YYYY-MM-DD). Defaults to yesterday.'}, 'entity_id': {'type': 'string', 'minLength': 1, 'description': 'The specific campaign, ad set, or ad ID (from list_campaigns, list_adsets, list_ads, or get_performance). Must NOT be the account ID.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'start_date': {'anyOf': [{'type': 'string', 'pattern': '^\\d{4}-\\d{2}-\\d{2}$'}, {'type': 'null'}], 'default': None, 'description': 'Start date (YYYY-MM-DD). Defaults to 45 days ago.'}, 'entity_type': {'enum': ['campaign', 'adset', 'ad'], 'type': 'string', 'description': "Entity type: 'campaign', 'adset', or 'ad'. Account-level is NOT supported."}, 'response_format': {'enum': ['full', 'concise'], 'type': 'string', 'default': 'full', 'description': "'concise' keeps key daily metrics: date, spend, result_count, cost_per_result, conversion_result_name, result_action_type, roas, revenue, purchases, cpa, clicks, ctr. 'full' returns all fields."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['entity_type', 'entity_id', 'account_id', 'currency', 'start_date', 'end_date', 'days_fetched', 'days'], 'properties': {'days': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}, 'description': 'One entry per calendar day in the requested range, oldest first. Each contains date_start, spend, impressions, clicks, result_count, result_value, cost_per_result. Days with no activity are present with zero metrics.'}, 'currency': {'type': 'string'}, 'end_date': {'type': 'string'}, 'entity_id': {'type': 'string'}, 'account_id': {'type': 'string'}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'start_date': {'type': 'string', 'description': 'ISO date.'}, 'entity_type': {'enum': ['campaign', 'adset', 'ad'], 'type': 'string'}, 'days_fetched': {'type': 'integer', 'description': 'Number of days actually present in `days`.'}}, 'description': 'Daily metrics for a single entity across a date range.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id', 'files'], 'properties': {'files': {'type': 'array', 'items': {'type': 'object', 'required': ['filename', 'content_type', 'size'], 'properties': {'size': {'type': 'integer', 'minimum': 0, 'description': 'File size in bytes. Max 30 MB for images, 4 GB for videos — exceeding either cap rejects the file with a per-item error.'}, 'filename': {'type': 'string', 'minLength': 1, 'description': "Original file name shown to the user (e.g. 'hero.jpg')."}, 'content_type': {'type': 'string', 'minLength': 1, 'description': 'MIME type. Allowed: image/jpeg, image/png, video/mp4, video/quicktime.'}}, 'description': 'A file the upload widget intends to send to S3 (one per queued item).'}, 'minItems': 1, 'description': 'Files the widget intends to upload (one per queued asset).'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Meta ad account ID'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['task_id', 'files'], 'properties': {'files': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}, 'description': 'Per-file presigned upload metadata. Each entry has name, presigned PUT URL, content_type, and expiry timestamp.'}, 'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'task_id': {'type': 'string', 'description': 'Pass back to complete_upload once every file has uploaded.'}}, 'description': 'Result of init_upload — presigned S3 PUT URLs for client-side upload.', 'additionalProperties': True}
Input schema
{'type': 'object', 'properties': {}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['count', 'accounts'], 'properties': {'count': {'type': 'integer', 'description': 'Number of accounts returned.'}, 'accounts': {'type': 'array', 'items': {'type': 'object', 'required': ['ad_account_id', 'ad_account_name', 'account_type', 'is_active', 'data_synced', 'business_name', 'business_id'], 'properties': {'landed': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'The switch the data service routes on: true = every read comes from the synced copy; false = the first sync is still running. Null for non-Meta accounts or when the sync record is unreadable.'}, 'pixels': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'Pixel ID.'}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Pixel display name.'}, 'hosts': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'Domains the pixel fires on, sorted by hit count.'}, 'last_fired_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp of the last firing.'}}, 'description': 'Pixel summary attached to an ad account.', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': 'Pixel summary (Meta accounts only).'}, 'currency': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-4217 currency.'}, 'timezone': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'IANA timezone name.'}, 'is_active': {'type': 'boolean', 'description': 'Whether the account is connected and active.'}, 'sync_state': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Progress of the first sync (Meta accounts): landed, phase (initial | catchup | landed | failed), streams {name: pending | running | completed | failed}, streams_done / streams_total, insights_chunks {done, total}, started_at, landed_at, failed_at. Relay streams_done/streams_total when the user asks how far along the sync is.'}, 'business_id': {'type': 'string', 'description': 'Internal business UUID.'}, 'data_synced': {'type': 'boolean', 'description': "False until the account's first Meta sync has completed in full. While false (and live_reads_enabled is true) reads are served live from Meta and are complete for the last 90 days and current entities; only older history is still syncing."}, 'account_type': {'type': 'string', 'description': "Platform type, e.g. 'meta', 'tiktok'."}, 'ad_account_id': {'type': 'string', 'description': 'Numeric Meta ad account ID.'}, 'business_name': {'type': 'string', 'description': 'AdAdvisor-side business name.'}, 'ad_account_name': {'type': 'string', 'description': 'Human-readable account name.'}}, 'description': 'One ad account row from list_ad_accounts.', 'additionalProperties': True}}, 'live_reads_enabled': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'Environment switch. True: accounts whose first sync has not completed are read live from Meta (90-day window). False: such accounts read the partial synced copy, as before.'}}, 'description': 'Response from list_ad_accounts.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 200, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max results to return per page (default 50, max 200).'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Number of results to skip for pagination. Use next_offset from a prior response to fetch the next page.'}, 'search': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by name (case-insensitive substring match). Omit to return all.'}, 'status': {'anyOf': [{'enum': ['ACTIVE', 'PAUSED', 'DELETED', 'ARCHIVED', 'DISAPPROVED', 'PENDING_REVIEW', 'PREAPPROVED', 'PENDING_BILLING_INFO', 'CAMPAIGN_PAUSED', 'ADSET_PAUSED', 'IN_PROCESS', 'WITH_ISSUES'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by effective status. Includes the configured statuses (ACTIVE / PAUSED / ARCHIVED / DELETED) plus Meta-derived ones (DISAPPROVED, PENDING_REVIEW, WITH_ISSUES, IN_PROCESS, ADSET_PAUSED, CAMPAIGN_PAUSED). Omit for all.'}, 'adset_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': 'Filter to ads in this ad set. Omit to return all ads (optionally filtered by campaign_id).'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'campaign_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': 'Filter to ads in this campaign. Omit to return all ads in the account.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['total', 'count', 'offset', 'has_more', 'account_id', 'currency', 'ads'], 'properties': {'ads': {'type': 'array', 'items': {'type': 'object', 'required': ['id'], 'properties': {'id': {'type': 'string'}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'adset_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'creative': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': "Reference to the ad's creative — normally just {'id': '...'}, NOT the creative's contents. Pass creative.id to list_creatives(creative_ids=[...]) to get the copy, media and lead_gen_form_id."}, 'campaign_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'created_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'updated_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'tracking_specs': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None}}, 'description': 'One ad row from list_ads.', 'additionalProperties': True}}, 'count': {'type': 'integer', 'description': 'Number of rows in this response.'}, 'total': {'type': 'integer', 'description': 'Total matching rows across all pages.'}, 'offset': {'type': 'integer', 'description': 'Pagination offset of this page.'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Set when the result page is empty but rows exist — explains what the filters excluded. Relay it rather than concluding the entity is missing.'}, 'currency': {'type': 'string'}, 'has_more': {'type': 'boolean', 'description': 'True when there are more rows past this page.'}, 'account_id': {'type': 'string'}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'next_offset': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Pass to the next call's offset parameter (null when has_more is false)."}, 'total_unfiltered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "How many ads exist in scope before discretionary filtering. Zero means the scope genuinely has none; non-zero with an empty result means the filters excluded them all. A just-created (always PAUSED) ad never matches status='ACTIVE'."}}, 'description': 'Response from list_ads.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 200, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max results to return per page (default 50, max 200).'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Number of results to skip for pagination. Use next_offset from a prior response to fetch the next page.'}, 'search': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by name (case-insensitive substring match). Omit to return all.'}, 'status': {'anyOf': [{'enum': ['ACTIVE', 'PAUSED', 'DELETED', 'ARCHIVED', 'CAMPAIGN_PAUSED', 'IN_PROCESS', 'WITH_ISSUES'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by effective status. Includes the configured statuses (ACTIVE / PAUSED / ARCHIVED / DELETED) plus Meta-derived ones (CAMPAIGN_PAUSED, WITH_ISSUES, IN_PROCESS). Omit for all.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'campaign_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': 'Filter to ad sets in this campaign. Omit to return all ad sets in the account.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['total', 'count', 'offset', 'has_more', 'account_id', 'currency', 'adsets'], 'properties': {'count': {'type': 'integer', 'description': 'Number of rows in this response.'}, 'total': {'type': 'integer', 'description': 'Total matching rows across all pages.'}, 'adsets': {'type': 'array', 'items': {'type': 'object', 'required': ['id'], 'properties': {'id': {'type': 'string'}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'end_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'targeting': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Raw Meta targeting spec.'}, 'bid_amount': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'Bid amount in account currency.'}, 'start_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'campaign_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'bid_strategy': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'created_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'daily_budget': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'Daily budget in account currency.'}, 'updated_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'billing_event': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'daily_spend_cap': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'CBO only: daily spend ceiling for this ad set, in account currency. Null when unset OR pre-sync.'}, 'lifetime_budget': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'Lifetime budget in account currency.'}, 'promoted_object': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Conversion tracking config (pixel + event).'}, 'budget_remaining': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'Remaining budget in account currency.'}, 'destination_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Where the conversion happens. ON_AD = instant form on the ad (lead ads); WEBSITE = offsite landing page; MESSENGER / INSTAGRAM_DIRECT / WHATSAPP = messaging thread; PHONE_CALL = call ad. Null on ad sets last synced before this field was ingested.'}, 'optimization_goal': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'lifetime_spend_cap': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'CBO only: lifetime spend ceiling for this ad set, in account currency. Null when unset OR pre-sync.'}, 'daily_min_spend_target': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'CBO only: minimum daily spend Meta tries to allocate to this ad set, in account currency. Null when CBO floor is unset OR the row predates the field landing in the sync.'}, 'lifetime_min_spend_target': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'CBO only: minimum lifetime spend Meta tries to allocate to this ad set, in account currency. Null when unset OR pre-sync.'}}, 'description': 'One ad set row from list_adsets.', 'additionalProperties': True}}, 'offset': {'type': 'integer', 'description': 'Pagination offset of this page.'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Set when the result page is empty but rows exist — explains what the filters excluded. Relay it rather than concluding the entity is missing.'}, 'currency': {'type': 'string'}, 'has_more': {'type': 'boolean', 'description': 'True when there are more rows past this page.'}, 'account_id': {'type': 'string'}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'next_offset': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Pass to the next call's offset parameter (null when has_more is false)."}, 'total_unfiltered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "How many ad sets exist in scope before discretionary filtering. Zero means the scope genuinely has none; non-zero with an empty result means the filters excluded them all. A just-created (always PAUSED) ad set never matches status='ACTIVE'."}}, 'description': 'Response from list_adsets.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 200, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max results to return per page (default 50, max 200).'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Number of results to skip for pagination. Pair with limit to page through results — use the returned next_offset on each call.'}, 'search': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by name (case-insensitive substring match). Omit to return all.'}, 'status': {'anyOf': [{'enum': ['ACTIVE', 'PAUSED', 'DELETED', 'ARCHIVED', 'IN_PROCESS', 'WITH_ISSUES'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by effective status. Includes the configured statuses (ACTIVE / PAUSED / ARCHIVED / DELETED) plus Meta-derived ones (WITH_ISSUES, IN_PROCESS). Omit for all.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['total', 'count', 'offset', 'has_more', 'account_id', 'currency', 'campaigns'], 'properties': {'count': {'type': 'integer', 'description': 'Number of rows in this response.'}, 'total': {'type': 'integer', 'description': 'Total matching rows across all pages.'}, 'offset': {'type': 'integer', 'description': 'Pagination offset of this page.'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Set when the result page is empty but campaigns exist — explains what the filters excluded. Relay it rather than concluding a campaign is missing.'}, 'currency': {'type': 'string', 'description': 'Account currency (ISO-4217).'}, 'has_more': {'type': 'boolean', 'description': 'True when there are more rows past this page.'}, 'campaigns': {'type': 'array', 'items': {'type': 'object', 'required': ['id'], 'properties': {'id': {'type': 'string'}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Effective status (or fallback to configured status).'}, 'objective': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'spend_cap': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'Lifetime spend cap in account currency.'}, 'stop_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'start_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'buying_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'pacing_type': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None}, 'bid_strategy': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'created_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'daily_budget': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'Daily budget in account currency.'}, 'updated_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'lifetime_budget': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'Lifetime budget in account currency.'}, 'budget_remaining': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'Remaining budget in account currency.'}, 'special_ad_category': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'NONE, HOUSING, EMPLOYMENT, CREDIT, ISSUES_ELECTIONS_POLITICS, ONLINE_GAMBLING_AND_GAMING or FINANCIAL_PRODUCTS_SERVICES. Anything other than NONE/null means Meta restricts targeting on every ad set in this campaign — age, gender, ZIP and most detailed-targeting options are unavailable and will be rejected.'}, 'special_ad_category_country': {'anyOf': [{}, {'type': 'null'}], 'default': None, 'description': 'Countries the special ad category applies to.'}}, 'description': 'One campaign row from list_campaigns.', 'additionalProperties': True}}, 'account_id': {'type': 'string'}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'next_offset': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Pass to the next call's offset parameter (null when has_more is false)."}, 'total_unfiltered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "How many campaigns the account has before `search`/`status` filtering. A zero here means the account genuinely has no campaigns; a non-zero with an empty result means the filters excluded them all — e.g. a just-created (always PAUSED) campaign will never match status='ACTIVE'."}}, 'description': 'Response from list_campaigns.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 100, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max results to return per page (default 25, max 100).'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Number of results to skip for pagination. Use next_offset from a prior response to fetch the next page.'}, 'search': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by name (case-insensitive substring match). Omit to browse most recent.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'object_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by object_type: IMAGE, VIDEO, SHARE, etc. Omit for all types.'}, 'creative_ids': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Look up specific creatives by id — e.g. the creative.id returned by list_ads, so you can resolve what an ad is actually running. When set, the full (untruncated) body and the raw object_story_spec / call_to_action are returned as well.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['total', 'count', 'offset', 'has_more', 'account_id', 'creatives'], 'properties': {'count': {'type': 'integer', 'description': 'Number of rows in this response.'}, 'total': {'type': 'integer', 'description': 'Total matching rows across all pages.'}, 'offset': {'type': 'integer', 'description': 'Pagination offset of this page.'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Set when the result page is empty but rows exist — explains what the filters excluded. Relay it rather than concluding the entity is missing.'}, 'has_more': {'type': 'boolean', 'description': 'True when there are more rows past this page.'}, 'creatives': {'type': 'array', 'items': {'type': 'object', 'required': ['id'], 'properties': {'id': {'type': 'string'}, 'body': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Primary ad text. Truncated to 100 chars when browsing; full text when the creative was requested by id via creative_ids.'}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'title': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Headline.'}, 'status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'actor_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Page ID.'}, 'link_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'url_tags': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'URL parameters appended to the destination link.'}, 'video_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'image_hash': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'object_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'IMAGE, VIDEO, SHARE, etc.'}, 'call_to_action': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Raw top-level CTA — returned only when creative_ids was supplied.'}, 'ingestion_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'lead_gen_form_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "The Meta Lead Form this creative collects leads into, or null for non-lead ads. Pass it to get_lead_form to see the form's questions."}, 'instagram_user_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Instagram identity used by this creative, if any.'}, 'lead_gen_form_ids': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Every lead form referenced by this creative. Normally one; a carousel can point different cards at different forms.'}, 'object_story_spec': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Raw Meta creative spec — returned only when creative_ids was supplied.'}, 'call_to_action_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'effective_object_story_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'The underlying Page post ({page_id}_{post_id}), for post-backed creatives.'}}, 'description': 'One creative row from list_creatives.', 'additionalProperties': True}}, 'account_id': {'type': 'string'}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'next_offset': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Pass to the next call's offset parameter (null when has_more is false)."}, 'total_unfiltered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'How many creatives exist in scope before discretionary filtering. Zero means the scope genuinely has none; non-zero with an empty result means the filters excluded them all. Creatives sync every ~30 min — very recent ones may lag.'}}, 'description': 'Response from list_creatives.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 100, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max results to return per page (default 25, max 100).'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Number of results to skip for pagination. Use next_offset from a prior response to fetch the next page.'}, 'search': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by name (case-insensitive substring match). Omit to return all.'}, 'subtype': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by audience subtype: CUSTOM, LOOKALIKE, WEBSITE, ENGAGEMENT, etc. Omit for all types.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['total', 'count', 'offset', 'has_more', 'account_id', 'audiences'], 'properties': {'count': {'type': 'integer', 'description': 'Number of rows in this response.'}, 'total': {'type': 'integer', 'description': 'Total matching rows across all pages.'}, 'offset': {'type': 'integer', 'description': 'Pagination offset of this page.'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Set when the result page is empty but rows exist — explains what the filters excluded. Relay it rather than concluding the entity is missing.'}, 'has_more': {'type': 'boolean', 'description': 'True when there are more rows past this page.'}, 'audiences': {'type': 'array', 'items': {'type': 'object', 'required': ['id'], 'properties': {'id': {'type': 'string'}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'subtype': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'CUSTOM, LOOKALIKE, WEBSITE, ENGAGEMENT, IG_BUSINESS, etc.'}, 'description': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'time_updated': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Unix timestamp of last update.'}, 'retention_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}, 'delivery_status': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': '{code, description} — 200 means ready.'}, 'approximate_count_lower_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}, 'approximate_count_upper_bound': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}}, 'description': 'One custom audience row from list_custom_audiences.', 'additionalProperties': True}}, 'account_id': {'type': 'string'}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'next_offset': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Pass to the next call's offset parameter (null when has_more is false)."}, 'total_unfiltered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'How many custom audiences exist in scope before discretionary filtering. Zero means the scope genuinely has none; non-zero with an empty result means the filters excluded them all. Audiences sync every ~30 min — use the id from the create response directly.'}}, 'description': 'Response from list_custom_audiences.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 200, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max results to return per page (default 50, max 200).'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Number of results to skip for pagination. Use next_offset from a prior response to fetch the next page.'}, 'search': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by form name (case-insensitive substring match). Omit to return all.'}, 'status': {'anyOf': [{'enum': ['ACTIVE', 'ARCHIVED', 'DELETED', 'DRAFT'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Filter by form status. Omit for all statuses. Only ACTIVE forms can be attached to new ads — ARCHIVED forms will be rejected at ad creation.'}, 'page_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': "Facebook Page ID to query. Omit to use the account's default promote_page (the same page create_lead_form picks). Call list_pages if the account has more than one page and you need to pin a specific one."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['total', 'count', 'offset', 'has_more', 'account_id', 'page_id', 'lead_forms'], 'properties': {'count': {'type': 'integer', 'description': 'Number of rows in this response.'}, 'total': {'type': 'integer', 'description': 'Total matching rows across all pages.'}, 'offset': {'type': 'integer', 'description': 'Pagination offset of this page.'}, 'page_id': {'type': 'string', 'description': 'Facebook Page the forms were queried from.'}, 'has_more': {'type': 'boolean', 'description': 'True when there are more rows past this page.'}, 'page_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Display name of the queried Page.'}, 'account_id': {'type': 'string'}, 'lead_forms': {'type': 'array', 'items': {'type': 'object', 'required': ['id'], 'properties': {'id': {'type': 'string', 'description': 'Lead form ID (use as lead_gen_form_id in create_creative).'}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Form display name.'}, 'locale': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Form locale, e.g. 'en_US'."}, 'status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ACTIVE, ARCHIVED, DELETED, or DRAFT. Only ACTIVE forms can be used in new ads.'}, 'leads_count': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Total leads collected to date.'}, 'created_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'question_types': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Question types in display order, e.g. ['FULL_NAME','EMAIL','CUSTOM']. Call get_lead_form for the full labels, options and disclaimers."}, 'questions_count': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Number of questions in the form.'}, 'privacy_policy_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'expired_leads_count': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Leads that have aged past Meta's 90-day retrieval window."}, 'organic_leads_count': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Leads collected from non-ad (organic) traffic.'}, 'follow_up_action_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'is_optimized_for_quality': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None}, 'block_display_for_non_targeted_viewer': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': 'True when the form blocks organic / non-targeted submissions.'}}, 'description': 'One lead form row from list_lead_forms.', 'additionalProperties': True}}, 'next_offset': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Pass to the next call's offset parameter (null when has_more is false)."}, 'total_unfiltered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'How many forms the Page has before `search`/`status` filtering. A zero here means the Page genuinely has no forms; a non-zero with an empty result means the filters excluded them all.'}}, 'description': 'Response from list_lead_forms.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'tags': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Return assets carrying ANY of these tags.'}, 'limit': {'type': 'integer', 'default': 25, 'maximum': 100, 'minimum': 1, 'description': 'Max assets per page (default 25).'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Assets to skip; use next_offset to page.'}, 'search': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Case-insensitive substring match on the asset's display name. Use this when a user names a file you don't have — e.g. 'creative_e9267dd2_v4.png'."}, 'source': {'anyOf': [{'enum': ['ai_generated', 'user_upload', 'shopify_import', 'woocommerce_import', 'wix_import', 'magento_import', 'squarespace_import'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Restrict by origin. 'ai_generated' is what Iris (and any other generating agent) files here; 'user_upload' is what the customer uploaded; the *_import values come from store connectors."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Meta ad account ID'}, 'media_type': {'anyOf': [{'enum': ['image', 'video'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Restrict to images or videos. Omit for both.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['count', 'total', 'account_id', 'assets'], 'properties': {'count': {'type': 'integer'}, 'total': {'type': 'integer', 'description': 'Total matching assets across all pages.'}, 'assets': {'type': 'array', 'items': {'type': 'object', 'required': ['asset_id', 'display_name', 'media_type', 'content_type', 'source'], 'properties': {'tags': {'type': 'array', 'items': {'type': 'string'}}, 'width': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}, 'height': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}, 'source': {'type': 'string', 'description': 'Where the asset came from — e.g. an agent render, a user upload, an import connector.'}, 'asset_id': {'type': 'string', 'description': 'Pass to register_media_assets as an asset_id.'}, 'filename': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'created_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'media_type': {'enum': ['image', 'video'], 'type': 'string'}, 'content_type': {'type': 'string'}, 'display_name': {'type': 'string'}, 'registered_video_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'As registered_image_hash, for videos.'}, 'registered_image_hash': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Meta image_hash IF this asset is already registered to the queried ad account — pass it straight to create_creative and skip registration. Null means it has never been pushed to this account.'}}, 'description': 'One Creatives Hub asset as list_media_assets returns it.\n\n``asset_id`` is the Media-Library id — the handle every agent uses to move an asset\naround. It is NOT a Meta identifier: an asset only gains an ``image_hash``/``video_id``\nonce ``register_media_assets`` pushes it to a specific ad account, which is why\n``registered_image_hash`` is null for anything freshly generated.', 'additionalProperties': True}}, 'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Set when the result page is empty but rows exist — explains what the filters excluded. Relay it rather than concluding the entity is missing.'}, 'account_id': {'type': 'string'}, 'next_offset': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Pass as offset for the next page; null when exhausted.'}, 'total_unfiltered': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'How many media assets exist in scope before discretionary filtering. Zero means the scope genuinely has none; non-zero with an empty result means the filters excluded them all.'}}, 'description': "Result of list_media_assets — the business's Creatives Hub library.", 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['account_id', 'count', 'pages'], 'properties': {'count': {'type': 'integer'}, 'error': {'anyOf': [{'type': 'object', 'properties': {'code': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Meta error code.'}, 'reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'permission_denied | page_token_denied | api_error — why the read returned no data.'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Meta's error message."}, 'subcode': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Meta error subcode.'}, 'user_action': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'reconnect_meta | grant_page_access | null — what the human must do to unblock it. Tell the user this; do not retry the tool.'}}, 'description': 'Machine-readable reason a Meta read returned nothing.\n\nPresent on degraded read responses so an empty list is never mistaken for\n"this account genuinely has none". `reason` and `user_action` are the two\nfields the model should branch on; `code`/`subcode`/`message` are Meta\'s\nown values, forwarded verbatim for support triage.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': "Present when Meta refused one or more of the Page lookups. An empty `pages` list with this set means 'we were not allowed to look', NOT 'this account has no Pages'."}, 'pages': {'type': 'array', 'items': {'type': 'object', 'required': ['page_id', 'is_default'], 'properties': {'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Page display name.'}, 'tasks': {'type': 'array', 'items': {'type': 'string'}, 'description': "The connecting user's Page-role tasks (from /me/accounts), e.g. ADVERTISE, MANAGE, ANALYZE. Empty means the user holds no Page role on it — advisory only, not a hard block (a granular-scope or system-user grant can still allow ad/form creation)."}, 'page_id': {'type': 'string', 'description': 'Facebook Page ID.'}, 'is_default': {'type': 'boolean', 'description': 'True for the page used by create_lead_form when page_id is omitted.'}, 'can_advertise': {'type': 'boolean', 'default': False, 'description': "True if this Page is advertisable by this ad account — it is in the account's promote_pages OR the user holds the ADVERTISE task on it."}, 'instagram_business_account': {'anyOf': [{'type': 'object', 'required': ['ig_user_id'], 'properties': {'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Facebook Page this IG account resolves through (null when linked directly to the ad account).'}, 'username': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'IG handle.'}, 'ig_user_id': {'type': 'string', 'description': 'Instagram business account ID — pass as CreativeSpec.instagram_user_id when using placement asset customization or an Instagram identity.'}, 'profile_pic': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Profile picture URL.'}}, 'description': 'One Instagram business account reachable from an ad account or Page.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Instagram business account linked to this Page, when one is linked and readable. Its ig_user_id is the value to pass as CreativeSpec.instagram_user_id.'}}, 'description': 'One Page row from list_pages.', 'additionalProperties': True}}, 'warning': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Human-readable summary of a degraded read — relay it to the user.'}, 'account_id': {'type': 'string'}, 'instagram_accounts': {'type': 'array', 'items': {'type': 'object', 'required': ['ig_user_id'], 'properties': {'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Facebook Page this IG account resolves through (null when linked directly to the ad account).'}, 'username': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'IG handle.'}, 'ig_user_id': {'type': 'string', 'description': 'Instagram business account ID — pass as CreativeSpec.instagram_user_id when using placement asset customization or an Instagram identity.'}, 'profile_pic': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Profile picture URL.'}}, 'description': 'One Instagram business account reachable from an ad account or Page.', 'additionalProperties': True}, 'description': "Every Instagram business account reachable from this ad account (union of per-Page links and the account's connected_instagram_accounts)."}}, 'description': 'Response from list_pages.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'after': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Pagination cursor — pass next_after from a prior call.'}, 'limit': {'anyOf': [{'type': 'integer', 'maximum': 100, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max posts to return (default 24, max 100).'}, 'source': {'enum': ['facebook', 'instagram'], 'type': 'string', 'default': 'facebook', 'description': "'facebook' for the Page's published posts, 'instagram' for the connected IG business account's media."}, 'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Facebook Page ID (from list_pages). Omit to use the ad account's default Page."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string, from list_ad_accounts)'}, 'instagram_user_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Instagram business account ID (from list_pages -> instagram_accounts[].ig_user_id). Omit to resolve the account's connected IG account. Instagram source only."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['account_id', 'source', 'count', 'items'], 'properties': {'count': {'type': 'integer'}, 'error': {'anyOf': [{'type': 'object', 'properties': {'code': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Meta error code.'}, 'reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'permission_denied | page_token_denied | api_error — why the read returned no data.'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Meta's error message."}, 'subcode': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Meta error subcode.'}, 'user_action': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'reconnect_meta | grant_page_access | null — what the human must do to unblock it. Tell the user this; do not retry the tool.'}}, 'description': 'Machine-readable reason a Meta read returned nothing.\n\nPresent on degraded read responses so an empty list is never mistaken for\n"this account genuinely has none". `reason` and `user_action` are the two\nfields the model should branch on; `code`/`subcode`/`message` are Meta\'s\nown values, forwarded verbatim for support triage.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Machine-readable reason the read returned nothing.'}, 'items': {'type': 'array', 'items': {'type': 'object', 'required': ['source'], 'properties': {'shares': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Share count (Facebook only).'}, 'source': {'enum': ['facebook', 'instagram'], 'type': 'string', 'description': 'Which surface the post came from.'}, 'caption': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Post message / caption text.'}, 'comments': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Comment count — null when unreadable, not 0.'}, 'permalink': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Public post URL.'}, 'reactions': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Reaction/like count — null when unreadable, not 0.'}, 'media_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'IMAGE / VIDEO / CAROUSEL_ALBUM (Instagram only).'}, 'is_eligible': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'default': None, 'description': "Facebook only — Meta's is_eligible_for_promotion. False posts (cover/profile-photo stories) cannot be turned into ads."}, 'created_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 publish time.'}, 'thumbnail_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Displayable image URL — safe to render.'}, 'object_story_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Facebook posts only — '<page_id>_<post_id>'. Pass straight to create_creative(format='existing_post', object_story_id=...)."}, 'instagram_user_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Instagram posts only — the IG business account that owns the media.'}, 'source_instagram_media_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Instagram posts only — pair with instagram_user_id on an existing_post creative.'}}, 'description': 'One organic post that can be promoted as an ad creative.', 'additionalProperties': True}}, 'source': {'enum': ['facebook', 'instagram'], 'type': 'string'}, 'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Facebook Page the posts were read from.'}, 'warning': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Human-readable summary of a degraded read — relay it to the user.'}, 'has_more': {'type': 'boolean', 'default': False, 'description': 'True when more posts remain.'}, 'account_id': {'type': 'string'}, 'next_after': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Cursor for the next page (null when has_more=false).'}, 'instagram_user_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Instagram business account the media was read from.'}}, 'description': 'Response from list_page_posts / list_instagram_media.\n\nAn empty `items` with `error` set means the read was refused — it does NOT\nmean the Page or IG account has no posts.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 10, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max creatives to return (default 3, max 10).'}, 'video_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Video ID to search for. Provide this OR image_hash.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'image_hash': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Image hash to search for. Provide this OR video_id.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['count', 'total', 'asset_type', 'account_id', 'query_asset', 'creatives'], 'properties': {'count': {'type': 'integer'}, 'error': {'anyOf': [{'type': 'object', 'properties': {'code': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Meta error code.'}, 'reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'permission_denied | page_token_denied | api_error — why the read returned no data.'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Meta's error message."}, 'subcode': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Meta error subcode.'}, 'user_action': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'reconnect_meta | grant_page_access | null — what the human must do to unblock it. Tell the user this; do not retry the tool.'}}, 'description': 'Machine-readable reason a Meta read returned nothing.\n\nPresent on degraded read responses so an empty list is never mistaken for\n"this account genuinely has none". `reason` and `user_action` are the two\nfields the model should branch on; `code`/`subcode`/`message` are Meta\'s\nown values, forwarded verbatim for support triage.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Present when the Page access token needed for engagement could not be minted. Engagement counts in this response are unknown, not zero.'}, 'total': {'type': 'integer', 'description': 'Total matching creatives across all pages.'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Alternative to `recommendation` when no creatives have measured engagement.'}, 'creatives': {'type': 'array', 'items': {'type': 'object', 'required': ['creative_id', 'name', 'asset_type', 'thumbnail_url', 'engagement'], 'properties': {'name': {'type': 'string'}, 'message': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Body / primary text.'}, 'headline': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Title / headline text.'}, 'link_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'asset_type': {'enum': ['image', 'video'], 'type': 'string'}, 'engagement': {'type': 'object', 'description': 'Counts: {reactions, comments, shares} from the underlying organic post. Values are null (NOT 0) when engagement could not be read — check engagement_checked before treating them as zero social proof.', 'additionalProperties': True}, 'source_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Playable video source URL (videos only).'}, 'creative_id': {'type': 'string'}, 'thumbnail_url': {'type': 'string', 'description': 'High-res thumbnail URL — safe to render in UI.'}, 'engagement_checked': {'type': 'boolean', 'default': False, 'description': "True only when Meta actually returned engagement for this creative's post. False means unknown — never interpret the null counts as zero."}, 'call_to_action_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': 'Creative metadata + post engagement for preview_existing_creatives.', 'additionalProperties': True}}, 'account_id': {'type': 'string'}, 'asset_type': {'enum': ['image', 'video'], 'type': 'string'}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'query_asset': {'type': 'string', 'description': 'The image_hash or video_id that was queried.'}, 'recommendation': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Reuse hint — present when at least one creative has engagement. Names the top creative + its engagement counts, plus an example create_ad call.'}, 'engagement_available': {'type': 'boolean', 'default': False, 'description': 'True when engagement was readable for at least one creative. When false, social proof is UNKNOWN — do not advise creating a fresh creative on the assumption there is none.'}}, 'description': 'Result of preview_existing_creatives — creatives sharing the same asset.\n\nCreatives with *measured* engagement are sorted DESC and lead the list;\ncreatives whose engagement could not be read (engagement_checked=false) are\nkept last in DB order rather than being ranked as zeros. When at least one\ncreative has measured engagement, a `recommendation` string suggests reusing\nthe top creative; otherwise `message` explains whether engagement was\ngenuinely zero or simply unreadable.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id', 'asset_ids'], 'properties': {'asset_ids': {'type': 'array', 'items': {'type': 'string'}, 'maxItems': 25, 'minItems': 1, 'description': 'Creatives Hub asset ids, from list_media_assets. NOT Meta ids and NOT filenames — if you have a filename, look it up with list_media_assets(search=...) first.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Meta ad account ID'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['account_id', 'registered', 'failed', 'results'], 'properties': {'failed': {'type': 'integer'}, 'status': {'enum': ['success', 'partial', 'all_failed'], 'type': 'string', 'default': 'success'}, 'results': {'type': 'array', 'items': {'type': 'object', 'required': ['asset_id', 'status'], 'properties': {'error': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Present only when status is 'failed'."}, 'status': {'enum': ['registered', 'failed'], 'type': 'string'}, 'asset_id': {'type': 'string'}, 'video_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Pass to create_creative as video_id (videos).'}, 'image_hash': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Pass to create_creative as image_hash (images).'}, 'display_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': 'Per-asset outcome of register_media_assets.', 'additionalProperties': True}}, 'account_id': {'type': 'string'}, 'registered': {'type': 'integer'}}, 'description': "Result of register_media_assets — Library assets pushed to a Meta ad account.\n\nPartial success is normal and reported honestly: a bad asset never sinks the batch.\nRead ``failed`` before building creatives — an asset listed there has NO usable hash\nand must not be substituted with another asset's.", 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 100, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max images to return per page (default 20, max 100). When multiple queries are passed, results are deduplicated by hash before paging.'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Number of (deduped) results to skip for pagination. Use next_offset from a prior response to fetch the next page.'}, 'queries': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}, 'maxItems': 20}, {'type': 'null'}], 'default': None, 'description': "One or more search terms — case-insensitive match on image name. Pass multiple terms to search several keywords at once (e.g. ['Royal Blue', 'Indigo', 'Navy']) instead of calling the tool separately for each. Results are deduped by image hash and each image is tagged with which queries it matched. Omit to browse the most recent images."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['total', 'count', 'offset', 'has_more', 'account_id', 'asset_type', 'queries', 'unmatched_queries', 'images'], 'properties': {'count': {'type': 'integer', 'description': 'Number of rows in this response.'}, 'total': {'type': 'integer', 'description': 'Total matching rows across all pages.'}, 'images': {'type': 'array', 'items': {'type': 'object', 'required': ['hash'], 'properties': {'hash': {'type': 'string', 'description': 'Image hash — pass to create_creative as image_hash.'}, 'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'width': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}, 'height': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}, 'url_128': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': '128-pixel-wide thumbnail URL.'}, 'matched_queries': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Which input queries matched this image (empty in browse mode).'}}, 'description': 'One image row from search_ad_images.', 'additionalProperties': True}}, 'offset': {'type': 'integer', 'description': 'Pagination offset of this page.'}, 'queries': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Normalized input queries (deduped).'}, 'has_more': {'type': 'boolean', 'description': 'True when there are more rows past this page.'}, 'account_id': {'type': 'string'}, 'asset_type': {'type': 'string', 'description': "Always 'image'."}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'next_offset': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Pass to the next call's offset parameter (null when has_more is false)."}, 'unmatched_queries': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Queries that returned zero rows — agents must NOT substitute.'}}, 'description': 'Response from search_ad_images.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'limit': {'anyOf': [{'type': 'integer', 'maximum': 100, 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Max videos to return per page (default 20, max 100).'}, 'query': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Search term — case-insensitive match on video title. Omit to browse most recent videos.'}, 'offset': {'type': 'integer', 'default': 0, 'minimum': 0, 'description': 'Number of results to skip for pagination. Use next_offset from a prior response to fetch the next page.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'anyOf': [{'type': 'object', 'required': ['total', 'count', 'offset', 'has_more', 'account_id', 'asset_type', 'videos'], 'properties': {'count': {'type': 'integer', 'description': 'Number of rows in this response.'}, 'query': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'total': {'type': 'integer', 'description': 'Total matching rows across all pages.'}, 'offset': {'type': 'integer', 'description': 'Pagination offset of this page.'}, 'videos': {'type': 'array', 'items': {'type': 'object', 'required': ['video_id'], 'properties': {'title': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'duration': {'anyOf': [{'type': 'number'}, {'type': 'null'}], 'default': None, 'description': 'Duration in seconds.'}, 'video_id': {'type': 'string', 'description': 'Video ID — pass to create_creative as video_id.'}, 'source_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Playable source video URL.'}, 'thumbnail_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': 'One video row from search_ad_videos.', 'additionalProperties': True}}, 'has_more': {'type': 'boolean', 'description': 'True when there are more rows past this page.'}, 'account_id': {'type': 'string'}, 'asset_type': {'type': 'string', 'description': "Always 'video'."}, 'provenance': {'anyOf': [{'type': 'object', 'required': ['source'], 'properties': {'as_of': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO-8601 timestamp the data is current as of.'}, 'source': {'enum': ['mirror', 'live', 'live_only'], 'type': 'string', 'description': "'mirror' = the synced copy (refreshed every ~30 min); 'live' = read from Meta just now because the account's first sync has not completed; 'live_only' = data Meta alone holds."}, 'stale_reason': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Set when the synced copy can no longer advance: 'token_invalid' (reconnect Meta) or 'disconnected'. Null when healthy."}}, 'description': 'Where an account-data answer came from and as of when.\n\nEvery read served by the Meta Ads Data Service carries this. The row shape\nnever depends on the source — only this block does. A read the service\ncannot answer for an account whose first sync has not completed (history\nbeyond the live window, too many entities) is answered with\n``{"status": "not_yet_landed", "message", "sync_state", ...}`` instead of\nthe tool\'s normal payload; ``{"status": "live_read_unavailable",\n"retry_after"}`` means Meta could not be read right now — retry later.\nThose two payloads are ``NotYetLandedResponse`` and\n``LiveReadUnavailableResponse`` below, and a tool that can answer with\nthem declares it through ``Refusable[...]``.', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Source and freshness of this answer.'}, 'next_offset': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': "Pass to the next call's offset parameter (null when has_more is false)."}}, 'description': 'Response from search_ad_videos.', 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id'], 'properties': {'status': {'type': 'string', 'const': 'not_yet_landed'}, 'message': {'type': 'string', 'description': 'One sentence to relay: what is available until the sync completes and why this request needs more.'}, 'account_id': {'type': 'string'}, 'sync_state': {'anyOf': [{'type': 'object', 'required': ['landed'], 'properties': {'basis': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "What the verdict rests on: 'run' (a sync record), 'legacy' (the data_synced flag) or 'none'."}, 'phase': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'initial | catchup | landed | failed; null without a sync record.'}, 'landed': {'type': 'boolean', 'description': 'True once every stream of the first sync has completed.'}, 'streams': {'anyOf': [{'type': 'object', 'additionalProperties': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Per-stream status: pending | running | completed | failed.'}, 'task_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'failed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'landed_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'streams_done': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams completed so far.'}, 'streams_total': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Streams the first sync covers.'}, 'insights_chunks': {'anyOf': [{'type': 'object', 'additionalProperties': {'anyOf': [{'type': 'integer'}, {'type': 'null'}]}}, {'type': 'null'}], 'default': None, 'description': '{done, total} chunks of the insights backfill.'}, 'catchup_started_at': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}}, 'description': "Progress of an account's first sync — ``SyncState.as_dict()``\n(``app.services.meta_ads_data.landed``) field for field.", 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'How far the first sync is.'}, 'live_window_days': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None, 'description': 'Days of insights history a Live Read answers (the live window).'}}, 'description': "What a façade-backed tool answers instead of its normal payload when\nthe account's first sync has not completed and the question is outside\nwhat a Live Read may answer — ``NotYetLanded.to_mcp()`` field for field.\nA normal result, never ``isError``: relay ``message`` and, when asked how\nfar along the sync is, ``sync_state``.", 'additionalProperties': True}, {'type': 'object', 'required': ['status', 'message', 'account_id', 'retry_after'], 'properties': {'status': {'type': 'string', 'const': 'live_read_unavailable'}, 'message': {'type': 'string'}, 'account_id': {'type': 'string'}, 'retry_after': {'type': 'integer', 'description': 'Seconds to wait before retrying.'}}, 'description': 'Meta could not be read for the account right now (throttled, time\nbudget exceeded) — ``LiveReadUnavailable.to_mcp()`` field for field.\nA normal result, never ``isError``; retry after ``retry_after`` seconds.', 'additionalProperties': True}]}
Input schema
{'type': 'object', 'required': ['account_id', 'search_type', 'query'], 'properties': {'query': {'type': 'string', 'minLength': 1, 'description': "Search keyword (e.g. 'golf', 'New York', 'accounting'). For interests/geolocation/work_positions/work_employers/locale this searches by keyword. For behaviors/income/life_events/industries this filters the full category list by keyword."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'search_type': {'enum': ['interests', 'geolocation', 'behaviors', 'income', 'life_events', 'industries', 'work_positions', 'work_employers', 'locale'], 'type': 'string', 'description': 'Type of targeting to search for'}, 'location_types': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Only for geolocation searches. Filter by location type: 'country', 'city', 'region', 'zip', 'geo_market'. Omit to search all types."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['search_type', 'count', 'results'], 'properties': {'count': {'type': 'integer'}, 'query': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'results': {'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}, 'description': "Targeting options. Each has id (pass to create_adset/update_adset_targeting), name, type, audience_size_lower_bound/upper_bound, and a path showing the taxonomy hierarchy (e.g. ['Interests', 'Sports', 'Soccer'])."}, 'search_type': {'type': 'string', 'description': "Taxonomy class searched: 'interests', 'geo', 'behaviors', etc."}}, 'description': 'Results from a Meta targeting-taxonomy search.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'lead_form_id', 'status'], 'properties': {'status': {'enum': ['ACTIVE', 'ARCHIVED'], 'type': 'string', 'description': 'ARCHIVED retires the form: it stops accepting new submissions and cannot be attached to new ads. ACTIVE restores it. Already-collected leads are NOT affected.'}, 'page_id': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': "Facebook Page that owns the form. Omit to use the account's default promote_page."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'lead_form_id': {'type': 'string', 'minLength': 1, 'description': 'Lead form ID, from list_lead_forms or create_lead_form.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['status', 'lead_form_id', 'lead_form_status'], 'properties': {'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'status': {'type': 'string', 'description': "Literal 'success'."}, 'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None}, 'update_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'entity_updates audit row id for this change.'}, 'lead_form_id': {'type': 'string'}, 'previous_status': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "The form's status before the change."}, 'lead_form_status': {'type': 'string', 'description': "The form's status after the change (ACTIVE or ARCHIVED)."}}, 'description': 'Result of set_lead_form_status.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'adset_id'], 'properties': {'zips': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': "Zip key from search_targeting geolocation results, e.g. 'US:94304'"}}, 'description': "A zip code geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': "Zip code targets from search_targeting(search_type='geolocation')."}, 'cities': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'City key from search_targeting geolocation results'}, 'radius': {'anyOf': [{'type': 'integer', 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Radius around city center (10-50 mi / 17-80 km)'}, 'distance_unit': {'anyOf': [{'enum': ['mile', 'kilometer'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Unit for radius'}}, 'description': "A city geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': "City targets from search_targeting(search_type='geolocation')."}, 'income': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Income targets from search_targeting(search_type='income')."}, 'age_max': {'anyOf': [{'type': 'integer', 'maximum': 65, 'minimum': 18}, {'type': 'null'}], 'default': None, 'description': "Maximum age (18-65). Defaults to Meta's default (65). NOTE: Meta rejects age_max<65 when advantage_audience=True (the default). To cap age below 65, also pass advantage_audience=False."}, 'age_min': {'anyOf': [{'type': 'integer', 'maximum': 65, 'minimum': 18}, {'type': 'null'}], 'default': None, 'description': "Minimum age (18-65). Defaults to Meta's default (18)."}, 'genders': {'anyOf': [{'type': 'array', 'items': {'type': 'integer'}}, {'type': 'null'}], 'default': None, 'description': '[1]=male only, [2]=female only, [1,2]=both specified. Omit for all genders (Meta default).'}, 'regions': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'Region key from search_targeting geolocation results'}}, 'description': "A region geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': "Region targets from search_targeting(search_type='geolocation')."}, 'adset_id': {'type': 'string', 'minLength': 1, 'description': 'The ad set ID to update (from list_adsets or get_performance)'}, 'behaviors': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Behavior targets from search_targeting(search_type='behaviors')."}, 'countries': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "ISO country codes, e.g. ['US', 'CA']. At least one geo field (countries, cities, regions, or zips) is required."}, 'interests': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Interest targets from search_targeting(search_type='interests')."}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'industries': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Industry targets from search_targeting(search_type='industries')."}, 'life_events': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Life event targets from search_targeting(search_type='life_events')."}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is changing targeting (e.g. 'narrow to high-intent lookalike after soak'). Logged for telemetry."}, 'work_employers': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Work employer targets from search_targeting(search_type='work_employers')."}, 'work_positions': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': "Work position targets from search_targeting(search_type='work_positions')."}, 'custom_audiences': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Custom audiences to target (from list_custom_audiences). Includes lookalikes, website audiences, customer lists.'}, 'device_platforms': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Device types: mobile, desktop. Omit for both — but note that omitting it also CLEARS an existing restriction, so a call ad set (which requires ['mobile']) must re-supply it."}, 'advantage_audience': {'type': 'boolean', 'default': True, 'description': "Enable Advantage+ audience expansion (Meta's ML broadens targeting beyond your spec). DEFAULTS TO TRUE — recommended for most cases. Set to False when you need a hard demographic cap (age_max<65, narrow gender) or any constraint Meta must respect exactly. Meta rejects advantage_audience=True combined with age_max<65. When True, any custom audiences / lookalikes / detailed targeting are submitted as Advantage+ *signals* (the 'relaxed setup'); they cannot be hard controls while Advantage+ is on (Meta rejects that as subcode 1359202) — set False to use them as controls."}, 'excluded_behaviors': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Behaviors to EXCLUDE from targeting.'}, 'excluded_interests': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Interests to EXCLUDE from targeting.'}, 'facebook_positions': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Facebook surfaces, e.g. ['feed','story','reels']. Re-supply to keep."}, 'instagram_positions': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Instagram surfaces, e.g. ['stream','story','reels']. Re-supply to keep."}, 'publisher_platforms': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Platforms the ad set may deliver on: facebook, instagram, audience_network, messenger, threads. IMPORTANT: this tool replaces the entire targeting spec — omit this and any existing placement restriction is cleared, reverting the ad set to Advantage+ placements. Read list_adsets first and re-supply what you want to keep.'}, 'excluded_geo_locations': {'anyOf': [{'type': 'object', 'properties': {'zips': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': "Zip key from search_targeting geolocation results, e.g. 'US:94304'"}}, 'description': "A zip code geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': 'Zip codes to exclude'}, 'cities': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'City key from search_targeting geolocation results'}, 'radius': {'anyOf': [{'type': 'integer', 'minimum': 1}, {'type': 'null'}], 'default': None, 'description': 'Radius around city center (10-50 mi / 17-80 km)'}, 'distance_unit': {'anyOf': [{'enum': ['mile', 'kilometer'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Unit for radius'}}, 'description': "A city geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': 'Cities to exclude'}, 'regions': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['key'], 'properties': {'key': {'type': 'string', 'description': 'Region key from search_targeting geolocation results'}}, 'description': "A region geo-target from search_targeting(search_type='geolocation')."}}, {'type': 'null'}], 'default': None, 'description': 'Regions to exclude'}, 'countries': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "ISO country codes to exclude, e.g. ['MX']"}}, 'description': 'Geo locations to exclude from targeting.'}, {'type': 'null'}], 'default': None, 'description': 'Geo locations to EXCLUDE from targeting.'}, 'excluded_custom_audiences': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['id', 'name'], 'properties': {'id': {'type': 'string', 'description': 'Targeting option ID from search_targeting results'}, 'name': {'type': 'string', 'description': 'Display name from search_targeting results'}}, 'description': 'A targeting option with Meta ID and display name (from search_targeting).'}}, {'type': 'null'}], 'default': None, 'description': 'Custom audiences to EXCLUDE from targeting (from list_custom_audiences).'}, 'audience_network_positions': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': 'Audience Network surfaces. Re-supply to keep.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['next_steps', 'account_id', 'adset_id', 'targeting_spec_applied'], 'properties': {'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'adset_id': {'type': 'string'}, 'update_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Audit-record ID — the entity_updates row created for this change.'}, 'account_id': {'type': 'string'}, 'next_steps': {'type': 'string', 'description': 'Recommended follow-up tool call.'}, 'entity_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Human-readable name of the entity.'}, 'state_after': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot after the change (shape varies by entity type).'}, 'state_before': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot before the change (shape varies by entity type).'}, 'estimated_reach': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Audience-size estimate for the new targeting (Meta returns lower/upper bounds).'}, 'targeting_spec_applied': {'type': 'object', 'description': 'Final targeting_spec sent to Meta (post-validation, post-auto-fix).', 'additionalProperties': True}}, 'description': 'Result of replacing the targeting_spec on an ad set.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'entity_type', 'entity_id', 'fields'], 'properties': {'fields': {'type': 'object', 'properties': {'name': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'default': None, 'description': 'Display name. Applies to: campaign, adset, ad.'}, 'status': {'anyOf': [{'enum': ['PAUSED', 'ACTIVE', 'ARCHIVED', 'DELETED'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Lifecycle status. Applies to: campaign, adset, ad. PAUSED, ACTIVE, ARCHIVED, DELETED. For pause/resume prefer change_entity_status.'}, 'adlabels': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'additionalProperties': True}}, {'type': 'null'}], 'default': None, 'description': "Meta ad labels for cross-entity tagging/analytics, e.g. [{'name': 'BlackFriday2026'}]. Applies to: campaign, ad."}, 'end_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO datetime for ad set end (required with lifetime_budget). Applies to: adset.'}, 'dsa_payor': {'anyOf': [{'type': 'string', 'maxLength': 512}, {'type': 'null'}], 'default': None, 'description': 'DSA payor (entity paying for the ads). Required for ad sets targeting the EU or associated territories per the EU Digital Services Act. Max 512 chars. Applies to: adset.'}, 'spend_cap': {'anyOf': [{'type': 'number', 'minimum': 0}, {'type': 'null'}], 'default': None, 'description': "Lifetime spend ceiling in account currency (e.g. 5000.0 = $5,000 max). Pass 0 to REMOVE an existing spend cap (Meta's documented remove-cap value). Applies to: campaign."}, 'stop_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'ISO datetime for campaign end. Applies to: campaign.'}, 'bid_amount': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'Bid amount in account currency (cost-per-result for COST_CAP, max bid for LOWEST_COST_WITH_BID_CAP). Applies to: adset.'}, 'start_time': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "ISO datetime, e.g. '2026-01-15T00:00:00-0500'. Applies to: campaign, adset."}, 'bid_strategy': {'anyOf': [{'enum': ['LOWEST_COST_WITHOUT_CAP', 'LOWEST_COST_WITH_BID_CAP', 'COST_CAP', 'LOWEST_COST_WITH_MIN_ROAS'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Bid strategy. Applies to: campaign, adset.'}, 'daily_budget': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'Daily budget in account currency (e.g. 50.0 = $50/day). Applies to: campaign, adset. Mutually exclusive with lifetime_budget. Prefer change_entity_budget for safer edits.'}, 'billing_event': {'anyOf': [{'enum': ['IMPRESSIONS', 'LINK_CLICKS', 'THRUPLAY'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Billing event (IMPRESSIONS, LINK_CLICKS, THRUPLAY). Applies to: adset.'}, 'daily_spend_cap': {'anyOf': [{'type': 'number', 'minimum': 0}, {'type': 'null'}], 'default': None, 'description': 'CBO only: daily spend ceiling in account currency. Requires the parent campaign to own a daily_budget. Meta enforces a $1 minimum (subcode 1885056) — passing 0 is rejected; to remove an existing cap, clear it in Ads Manager. Applies to: adset.'}, 'dsa_beneficiary': {'anyOf': [{'type': 'string', 'maxLength': 512}, {'type': 'null'}], 'default': None, 'description': 'DSA beneficiary (entity benefiting from the ads). Required for ad sets targeting the EU or associated territories per the EU Digital Services Act. Max 512 chars. Applies to: adset.'}, 'lifetime_budget': {'anyOf': [{'type': 'number', 'exclusiveMinimum': 0}, {'type': 'null'}], 'default': None, 'description': 'Lifetime budget in account currency. Applies to: campaign, adset. Mutually exclusive with daily_budget. Prefer change_entity_budget.'}, 'promoted_object': {'anyOf': [{'type': 'object', 'properties': {'page_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Facebook page ID (for engagement/message objectives)'}, 'pixel_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Meta pixel ID for conversion tracking'}, 'application_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'App ID (for app promotion objectives)'}, 'object_store_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'App Store / Google Play URL (required with application_id for app promotion)'}, 'custom_event_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "Conversion event, e.g. 'PURCHASE', 'LEAD', 'ADD_TO_CART'"}}, 'description': 'Conversion tracking config for an ad set.\n\nAuto-resolved for OUTCOME_SALES campaigns if omitted (picks account pixel + PURCHASE).', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': "What the ad set optimizes toward. For lead gen: {page_id} for instant forms (destination_type=ON_AD) and for calls; {pixel_id, custom_event_type='LEAD'} for website leads. Send the WHOLE object — Meta replaces it rather than merging, so include every key you want to keep. Change it together with destination_type when switching a lead ad set between destinations; changing one without the other leaves a combination Meta rejects. Applies to: adset."}, 'destination_type': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Destination: WEBSITE, APP, MESSENGER, ON_AD, etc. Applies to: adset.'}, 'optimization_goal': {'anyOf': [{'enum': ['OFFSITE_CONVERSIONS', 'LINK_CLICKS', 'IMPRESSIONS', 'REACH', 'LEAD_GENERATION', 'QUALITY_LEAD', 'QUALITY_CALL', 'LANDING_PAGE_VIEWS', 'VALUE', 'CONVERSATIONS'], 'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Optimization goal. Meta rejects this on an ad set whose campaign has already published — duplicate the ad set and change it on the copy. Applies to: adset.'}, 'lifetime_spend_cap': {'anyOf': [{'type': 'number', 'minimum': 0}, {'type': 'null'}], 'default': None, 'description': 'CBO only: lifetime spend ceiling in account currency. Requires the parent campaign to own a lifetime_budget. Meta enforces a minimum (subcode 1885056) — passing 0 is rejected; to remove an existing cap, clear it in Ads Manager. Applies to: adset.'}, 'special_ad_categories': {'anyOf': [{'type': 'array', 'items': {'enum': ['NONE', 'HOUSING', 'EMPLOYMENT', 'FINANCIAL_PRODUCTS_SERVICES', 'ISSUES_ELECTIONS_POLITICS'], 'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "Special ad categories. Allowed: NONE, EMPLOYMENT, HOUSING, FINANCIAL_PRODUCTS_SERVICES (replaces CREDIT from Jan 14 2025), ISSUES_ELECTIONS_POLITICS. Use ['NONE'] for standard ads. Pair with special_ad_category_country whenever a non-NONE value is supplied. Applies to: campaign."}, 'daily_min_spend_target': {'anyOf': [{'type': 'number', 'minimum': 0}, {'type': 'null'}], 'default': None, 'description': 'CBO only: minimum daily spend target in account currency. Requires the parent campaign to own a daily_budget. Pass 0 to clear (e.g. when duplicate_adset deep_copy carried over a floor that pushes the campaign over budget — subcode 1885648). Applies to: adset.'}, 'lifetime_min_spend_target': {'anyOf': [{'type': 'number', 'minimum': 0}, {'type': 'null'}], 'default': None, 'description': 'CBO only: minimum lifetime spend target in account currency. Requires the parent campaign to own a lifetime_budget. Pass 0 to clear. Applies to: adset.'}, 'special_ad_category_country': {'anyOf': [{'type': 'array', 'items': {'type': 'string'}}, {'type': 'null'}], 'default': None, 'description': "ISO country codes scoping special_ad_categories (e.g. ['CA', 'GB']). Required by Meta whenever special_ad_categories contains a non-NONE value. Applies to: campaign."}}, 'description': "Object whose set fields are applied to the entity. Each field in EntityFieldsPatch lists which entity_type(s) it applies to; passing a field that doesn't apply to the chosen entity_type returns a validation error.\nCurrency fields (daily_budget, lifetime_budget, spend_cap, bid_amount) are in account currency — converted server-side.", 'additionalProperties': False}, 'entity_id': {'type': 'string', 'minLength': 1, 'description': 'ID of the entity to patch (from list_campaigns, list_adsets, or list_ads — never invent IDs).'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID. The account where entity_id lives — Meta returns code 100 (parameter error) if entity_id belongs to a different account. Look up via list_campaigns, list_adsets, or list_ads when uncertain.'}, 'entity_type': {'enum': ['campaign', 'adset', 'ad'], 'type': 'string', 'description': 'The type of entity to patch.'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is making this edit (e.g. 'pausing low-CTR ad sets to reallocate budget'). Logged for telemetry — does not affect behavior."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['entity_type', 'entity_id', 'account_id', 'currency', 'fields_applied'], 'properties': {'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'currency': {'type': 'string'}, 'entity_id': {'type': 'string'}, 'update_id': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Audit-record ID — the entity_updates row created for this change.'}, 'account_id': {'type': 'string'}, 'next_steps': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Recommended follow-up tool call(s) for the LLM. Required on every successful mutation per CLAUDE.md; left Optional here because some tools (e.g. update_entity) currently omit it.'}, 'entity_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Human-readable name of the entity.'}, 'entity_type': {'enum': ['campaign', 'adset', 'ad'], 'type': 'string'}, 'state_after': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot after the change (shape varies by entity type).'}, 'state_before': {'anyOf': [{'type': 'object', 'additionalProperties': True}, {'type': 'null'}], 'default': None, 'description': 'Entity state snapshot before the change (shape varies by entity type).'}, 'fields_applied': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Sorted names of the fields actually sent to Meta.'}}, 'description': 'Result of patching arbitrary fields on a Meta entity.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id', 'file_name'], 'properties': {'file_name': {'type': 'string', 'minLength': 1, 'description': 'File name for the image (e.g. hero.jpg)'}, 'image_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Public URL to download the image from. Used if image_data is not provided.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Meta ad account ID'}, 'image_data': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Image as data URL (data:image/...;base64,...). Provide this or image_url.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['image_hash', 'name', 'account_id'], 'properties': {'name': {'type': 'string'}, 'width': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}, 'height': {'anyOf': [{'type': 'integer'}, {'type': 'null'}], 'default': None}, 'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'account_id': {'type': 'string'}, 'image_hash': {'type': 'string', 'description': 'Pass to create_creative as image_hash.'}}, 'description': 'Result of upload_ad_image — registers a single image with Meta.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'title': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Display title for the video.'}, 'video_url': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Public URL to the video file. Provide this or video_data.'}, 'account_id': {'type': 'string', 'minLength': 1, 'description': 'Meta ad account ID'}, 'video_data': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': 'Video as data URL (data:video/...;base64,...). Provide this or video_url.'}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['video_id', 'title', 'account_id'], 'properties': {'title': {'type': 'string'}, 'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'video_id': {'type': 'string', 'description': 'Pass to create_creative as video_id.'}, 'account_id': {'type': 'string'}}, 'description': 'Result of upload_ad_video — registers a single video with Meta.', 'additionalProperties': True}
Input schema
{'type': 'object', 'required': ['account_id'], 'properties': {'account_id': {'type': 'string', 'minLength': 1, 'description': 'Ad account ID (numeric string)'}, 'agent_intent': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'default': None, 'description': "OPTIONAL free-text describing why the agent is opening the uploader (e.g. 'upload 3 new hook-driven statics to replace recycled images'). Logged for telemetry."}}, 'additionalProperties': False}
Output schema
{'type': 'object', 'required': ['account_id', 'message'], 'properties': {'status': {'type': 'string', 'const': 'success', 'default': 'success'}, 'message': {'type': 'string', 'description': 'Confirms the widget is open and waiting for input.'}, 'account_id': {'type': 'string'}}, 'description': 'Result of opening the user-facing upload widget.', 'additionalProperties': True}
Recent tool changes
Similar MCP servers
google-ads
Reports on and manages Google Ads campaigns, with newly created items starting paused.
google-ads
Manages Google Ads campaigns and keywords and provides advertising performance metrics.
Analytics Legends — SAP Analytics Intelligence
Provides SAP analytics intelligence through searchable firm, market, opportunity, news, concept, study, training, and knowledge-g…
goji
Searches and explains GOJI material about SEO, AEO, websites, AI builds, branding, services, and related marketing topics, and ca…
beauty
Finds and recommends sponsored beauty products, brands, and skincare routines based on shopper preferences and skin profiles.
Inside Ads
Estimates Telegram advertising reach and cost, creates campaign drafts, and reports campaign draft status.
GroundTruth - Environmental Records
Retrieves environmental, infrastructure, hazard, and address due-diligence records for locations in the United States.
CounterScript - Drug Price Benchmarks
Provides US drug acquisition benchmarks, estimated cash-price ranges, generic equivalents, and applicable Medicare negotiated pri…