{ "actions": [ { "id": "action:adjust/list_accounts", "namespace": "adjust", "action": "list_accounts", "description": "List the Adjust accounts this brand can query.", "mode": "read", "platforms": [ "adjust" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the Adjust accounts granted to the current brand (account_id + name). START HERE for any Adjust task: every other adjust action requires one of these account_ids. Do NOT ask the user for an account id — this action is the source of truth. Adjust supports multi-account, so one connected token can expose more than one account; each is pinned per request by its real account id.", "input_params": [], "output_shape": "data.rows[].{account_id, name}" }, { "id": "action:adjust/list_tools", "namespace": "adjust", "action": "list_tools", "description": "List the Adjust attribution/analytics tools available for an account.", "mode": "read", "platforms": [ "adjust" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the live Adjust MCP tool catalog for one granted account, each annotated with `requires_review`. The default response is a slim catalog without input schemas. Pass `tool` to fetch one exact tool's full input schema before calling `adjust/query`. Tools with `requires_review=true` (anything not on our read-only allowlist) must go through `adjust/request_change`, not `adjust/query`. Data is live from Adjust (realtime, no cached pipeline); Adjust MCP is Early Access, so treat results as best-effort.", "input_params": [ { "name": "project_id", "type": "string", "required": false, "description": "Adjust account id (from adjust/list_accounts).", "example": "adjust_acct_demo" }, { "name": "tool", "type": "string", "required": false, "description": "Exact Adjust tool name; when provided, returns that tool's full input schema.", "example": "get_report" } ], "output_shape": "data.project_id, data.tools[].{name, description, requires_review[, input_schema when tool is provided]}" }, { "id": "action:adjust/query", "namespace": "adjust", "action": "query", "description": "Run a read-only Adjust tool (aggregated performance, cohorts, events, SKAN, app metadata).", "mode": "read", "platforms": [ "adjust" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Forwards a read-only Adjust tool call for one granted account and returns live data (realtime — there is no cached sync pipeline). The hosted MCP exposes aggregated attribution/performance reporting (installs, cost, revenue, ROAS, retention by network/campaign/date), cohort and event aggregates, SKAN (iOS) data, and app metadata. Get the account_id from `adjust/list_accounts` (never ask the user for it). Only allowlisted read tools run immediately; anything else returns `requires_review` — call `adjust/request_change`. The response echoes which account was queried.", "input_params": [ { "name": "project_id", "type": "string", "required": true, "description": "Adjust account id (from adjust/list_accounts).", "example": "adjust_acct_demo" }, { "name": "tool", "type": "string", "required": true, "description": "Exact Adjust tool name from adjust/list_tools.", "example": "get_report" }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments object per the tool's input schema.", "example": { "app_token": "abc123", "from": "2026-06-01", "to": "2026-06-30", "metrics": [ "installs", "revenue" ] } } ], "output_shape": "data.{project_id, tool, is_error, content[], structured, truncated}" }, { "id": "action:adjust/request_change", "namespace": "adjust", "action": "request_change", "description": "Request a non-allowlisted Adjust tool (or a future write) via human approval.", "mode": "risk", "platforms": [ "adjust" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "Submits an Adjust tool call that is not on our read-only allowlist for human approval (HITL). Returns a pending_review_id immediately; the call is forwarded to Adjust only after a human approves. Use this for anything `adjust/query` rejected as requires_review. Note: account/org-wide listings (e.g. listing all accounts or users) are hard-hidden and never available, even here.", "input_params": [ { "name": "project_id", "type": "string", "required": true, "description": "Adjust account id (from adjust/list_accounts).", "example": "adjust_acct_demo" }, { "name": "tool", "type": "string", "required": true, "description": "Exact Adjust tool name.", "example": "get_report" }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments object per the tool's input schema.", "example": { "app_token": "abc123" } } ], "output_shape": "202 -> {ok, pending_review_id}; on approval data.{project_id, tool, is_error, content[], structured}" }, { "id": "action:ads/add_account_negative_keywords", "namespace": "ads", "action": "add_account_negative_keywords", "description": "Create or append to a Google account-level negative-keyword shared set (atomic); review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/add_campaign_negative_keywords", "action:ads/remove_account_negative_keywords", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nCreate a new SharedSet of type NEGATIVE_KEYWORDS plus N SharedCriterion rows in one atomic mutate, OR append keywords to an existing shared set.\n\nUse when:\n- The user wants a reusable account-wide negative-keyword list (omit shared_set_id).\n- The user wants to add keywords to an existing shared set (pass shared_set_id).\n- A list of brand-safety terms must be excluded across many campaigns.\n\nDo not use when:\n- The user wants to add negatives to one campaign only. Use action:ads/add_campaign_negative_keywords.\n- The platform is Meta.\n\nWorkflow:\n1. (If appending) Find the shared_set_id via GAQL: gaql_search(resource_name='shared_set', dimensions=['shared_set.id','shared_set.name'], filters={'shared_set.type': 'NEGATIVE_KEYWORDS'}).\n2. Build the keyword list with text + match_type.\n3. (Optional) Choose a list_name for a new shared set (ignored when shared_set_id is set).\n4. Submit via action:_batch/submit for HITL review.\n5. (If new set) Attach the new set to campaigns via the Google Ads UI (no L1.5 attach handler).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- When creating: raw.results contains the sharedSetResult followed by N sharedCriterionResult entries.\n- When appending: raw.results contains N sharedCriterionResult entries only.\n\nSide effects:\n- Atomic mutate that creates SharedCriterion rows (and optionally a SharedSet).\n- Criteria are reversible via action:ads/remove_account_negative_keywords (list first to collect resource_names); deleting the shared set itself is not exposed (Google Ads UI only).\n\nAudit guidance (batch 4 P2):\n- google: Guidance: The agent is not told that a newly created shared set is inert until manually attached to campaigns — the long_description mentions UI attach as a workflow step, but does not warn that NO campaigns are excluded until then. An agent may report success and believe the negatives are live. Also note: keywords item shape is documented as {text, match_type} but the docs do not state that plain strings are also accepted (defaulting to BROAD) — minor; agents will likely send objects. Capability gap: CampaignSharedSet attachment: creating a new SharedSet does not attach it to any campaign. Per the official guide, a negative-keyword shared set has no effect until linked to campaigns via CampaignSharedSet/CampaignSharedSetService. The capability docs disclose this (\"Attach the new set to campaigns via the Google Ads UI; no L1.5 attach handler\"), so the created set is inert until a human attaches it. This is the one capability dimension that is unreachable through the agent.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "keywords", "type": "list", "required": true, "description": "List (or JSON string) of {text, match_type} entries. match_type one of EXACT, PHRASE, BROAD." }, { "name": "shared_set_id", "type": "string", "required": false, "description": "ID of an existing shared set to append to. Find via gaql_search on shared_set resource (filter shared_set.type = NEGATIVE_KEYWORDS). Omit to create a new shared set.", "example": "123456" }, { "name": "list_name", "type": "string", "required": false, "description": "Optional display name for a new shared set. Default \"Account negative keywords\". Ignored when shared_set_id is provided." } ], "output_shape": "{\"success\": true, \"message\": \"Appended 5 keyword(s) to shared set 123456\", \"results\": [{\"sharedCriterionResult\": {...}}]} / new set: {\"success\": true, \"message\": \"Created account negative keyword set with 5 keyword(s)\", \"results\": [{\"sharedSetResult\": {...}}, {\"sharedCriterionResult\": {...}}]}" }, { "id": "action:ads/add_ad_group_negative_keywords", "namespace": "ads", "action": "add_ad_group_negative_keywords", "description": "Add negative keywords to a Google ad group (batch); review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_campaign_negative_keywords", "action:ads/add_account_negative_keywords", "action:ads/remove_ad_group_negative_keywords", "action:ads/list_ad_group_negative_keywords", "action:ads/query_single_dimension", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-add ad-group-level negative keywords to a Google ad group in one mutate. Negatives prevent the ad group from serving on the given queries.\n\nUse when:\n- The user wants to block specific search terms within a single ad group.\n- A search_terms report shows wasted spend on terms irrelevant to this ad group but potentially valid for other ad groups in the same campaign.\n\nDo not use when:\n- The user wants campaign-wide negatives. Use action:ads/add_campaign_negative_keywords.\n- The user wants account-wide negatives. Use action:ads/add_account_negative_keywords.\n- The user wants positive keywords. Use action:ads/add_keywords.\n- The platform is Meta. Meta does not expose keyword negatives.\n\nWorkflow:\n1. Inspect search terms via action:ads/query_single_dimension.\n2. Build the keyword list with text + match_type.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/gaql_search.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message reports the count added.\n\nSide effects:\n- Per-keyword ad_group_criterion_operation.create with negative=True, all in one mutate.\n- match_type validated against {EXACT, PHRASE, BROAD}.\n- Reversible via action:ads/remove_ad_group_negative_keywords (list first to collect resource_names).\n\nAudit guidance (batch 4 P2):\n- google: Guidance: keywords accepts three shapes (list of dicts, JSON-array string, or list of bare strings). Capability docs mention 'list (or JSON string) of {text, match_type}' but do not state that bare-string entries are accepted and silently default match_type=BROAD, nor that entries with empty/missing text are silently dropped (no error). An agent passing malformed entries could submit fewer keywords than intended without warning. Also note: match_type default behavior (omitted -> BROAD) is not stated in the capability docs; an agent expecting EXACT to be the default, or expecting an error on omission, would be surprised. Capability gap: No remove/L1.5 delete handler for ad-group negatives — once added, the agent cannot undo via this capability set (capability docs explicitly say 'no L1.5 remove handler exposed; reversible via the Google Ads UI'). This is a one-directional capability gap, not a bug.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Ad group id from action:ads/query_single_dimension(dimension='adgroup')." }, { "name": "keywords", "type": "list", "required": true, "description": "List (or JSON string) of {text, match_type} entries. match_type one of EXACT, PHRASE, BROAD.", "example": [ { "text": "free", "match_type": "BROAD" } ] } ], "output_shape": "{\"success\": true, \"message\": \"Added 5 ad group negative keyword(s)\", \"results\": [...]}" }, { "id": "action:ads/add_audience_targeting", "namespace": "ads", "action": "add_audience_targeting", "description": "Add an audience criterion to an ad group (ENABLED); supports USER_LIST, USER_INTEREST, CUSTOM_INTENT, CUSTOM_AUDIENCE, COMBINED_AUDIENCE; review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/add_campaign_audience_targeting", "action:ads/remove_audience_targeting", "action:ads/create_user_list", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nAttach an audience criterion on a Google ad group. Supports multiple criterion types: USER_LIST (default), USER_INTEREST, CUSTOM_INTENT, CUSTOM_AUDIENCE, COMBINED_AUDIENCE.\n\nUse when:\n- The user wants to add an audience targeting layer to an existing ad group.\n- A copy workflow needs to replicate ad_group_criterion audience settings.\n\nDo not use when:\n- The target is a campaign. Use action:ads/add_campaign_audience_targeting.\n- The platform is Meta. Meta uses targeting inside the ad set spec.\n\nWorkflow:\n1. Identify the audience resource_id via gaql_search on ad_group_criterion or campaign_criterion.\n2. Call this action with criterion_type and resource_id.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via gaql_search on ad_group_criterion.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n\nSide effects:\n- Creates an ad_group_criterion with status=ENABLED.\n- Sets bid_modifier if provided.\n- Reversible via action:ads/remove_audience_targeting (by criterion resource_name).\n\nAudit guidance (batch 4 P2):\n- google: Guidance: criterion_type valid values are listed, but the docs do not tell the agent how to obtain the resource_id for non-USER_LIST types (USER_INTEREST category ids, CUSTOM_INTENT/customInterests ids, CUSTOM_AUDIENCE ids, COMBINED_AUDIENCE ids). The Workflow only mentions gaql_search on ad_group_criterion/campaign_criterion (a copy flow), not how to discover a fresh user-interest/custom-audience id. The agent must guess the lookup path for those types. Also note: No mention of campaign-type restrictions (USER_INTEREST not on search-only, display-only USER_LIST not on search/shopping, CUSTOM_INTENT positive-only). The agent can pick a criterion_type that the parent campaign rejects and only learn at mutate time. Capability gap: negative / exclusion audience targeting: the official AdGroupCriterion.negative field is not exposed in handler/docs/whitelist, so the agent cannot exclude an audience (audience exclusion) at the ad-group level — a whole targeting dimension is unreachable via this action.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Ad group id from action:ads/gaql_search.", "source": "upstream:action:ads/create_ad_group,action:ads/gaql_search" }, { "name": "criterion_type", "type": "string", "required": false, "description": "Audience criterion type: USER_LIST (default), USER_INTEREST, CUSTOM_INTENT, CUSTOM_AUDIENCE, COMBINED_AUDIENCE.", "example": "USER_LIST", "enum": [ "COMBINED_AUDIENCE", "CUSTOM_AUDIENCE", "CUSTOM_INTENT", "USER_INTEREST", "USER_LIST" ], "source": "constant" }, { "name": "resource_id", "type": "string", "required": true, "description": "Audience resource id. For USER_LIST: user list id; for USER_INTEREST: user interest category id; etc. Legacy alias: user_list_id." }, { "name": "bid_modifier", "type": "number", "required": false, "description": "Optional bid multiplier for this audience (e.g. 1.2 for +20%)." } ], "output_shape": "{\"success\": true, \"message\": \"Audience targeting (USER_LIST) added successfully\", \"results\": [{\"adGroupCriterionResult\": {...}}]}" }, { "id": "action:ads/add_callouts", "namespace": "ads", "action": "add_callouts", "description": "Add Google callout assets to a campaign (batch, atomic); review-gated.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/add_sitelinks", "action:ads/add_structured_snippets", "action:ads/remove_campaign_assets", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-create Callout assets AND CampaignAsset link rows for a Google campaign in one atomic mutate. Callouts are short marketing phrases shown below the ad.\n\nUse when:\n- The user wants to add callout extensions like 'Free shipping' or '24/7 support'.\n\nDo not use when:\n- The user wants clickable extensions. Use action:ads/add_sitelinks.\n- The user wants structured headers / values. Use action:ads/add_structured_snippets.\n- The platform is Meta.\n\nWorkflow:\n1. Build the callouts list, each with non-empty callout_text or text.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/gaql_search.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message reports the count of callouts added.\n\nSide effects:\n- Same model as add_sitelinks: assets + campaign_asset links in one atomic mutate.\n- Raises ValueError if any entry has neither callout_text nor text.\n- Reversible: detach the campaign links via action:ads/remove_campaign_assets (field_type=CALLOUT); the assets stay in the account for re-attachment.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: The 25-char (12 double-width) limit is stated in the callouts param description but there is no local pre-validation; the agent only learns of an over-length failure from a raw Google API error at mutate time. Docs could state the failure mode explicitly. Also note: callouts[].name is documented as accepted but its semantics are unclear to the agent — it is the internal Asset.name (a label), NOT a user-visible serving field. An agent might think it changes ad rendering. Capability gap: start_date / end_date — callout date scheduling (official optional CalloutAsset fields) cannot be set; callouts always serve continuously.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "callouts", "type": "list", "required": true, "description": "List (or JSON string) of {callout_text|text, name?} entries. Each entry must have non-empty callout text (max 25 chars per callout).", "example": [ { "callout_text": "Free shipping" }, { "callout_text": "24/7 support" } ] } ], "output_shape": "{\"success\": true, \"message\": \"Added 2 callout asset(s)\", \"results\": [...]}" }, { "id": "action:ads/add_campaign_ad_units", "namespace": "ads", "action": "add_campaign_ad_units", "description": "Deprecated legacy compatibility: Append ChatGPT Ads ad units to a campaign.", "mode": "risk", "platforms": [ "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad_unit", "action:ads/get_campaign" ], "long_description": "Deprecated compatibility path. New deployments use inline Campaign -> Ad Group -> Ad.\n\nPrimary use:\nAppend ChatGPT Ads ad units to a campaign while keeping existing campaign ad units.\n\nUse when:\n- The user approved additional ad units for an existing campaign.\n- Existing campaign ad units should remain attached.\n\nDo not use when:\n- The desired final set should exactly match a new list. Use replace_campaign_ad_units.\n- The ad_unit_ids are unknown. Create or inspect ad units first.\n\nWorkflow:\n1. Resolve campaign_id and ad_unit_ids.\n2. Submit the append operation for HITL review.\n3. Read the campaign back to confirm the final binding.\n\nSide effects:\n- Mutates the campaign's attached ad units by adding ids.\n- Does not activate the campaign.\n\nOutput semantics:\n- raw is the ChatGPT Ads platform response for the ad-unit binding mutation.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "" }, { "name": "campaign_id", "type": "string", "required": true, "description": "", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "ad_unit_ids", "type": "list", "required": true, "description": "", "source": "upstream:action:ads/create_ad_unit" } ], "output_shape": "{\"success\": true, \"raw\": {...}}" }, { "id": "action:ads/add_campaign_audience_targeting", "namespace": "ads", "action": "add_campaign_audience_targeting", "description": "Add an audience criterion to a campaign; supports USER_LIST, USER_INTEREST, CUSTOM_AUDIENCE, COMBINED_AUDIENCE; review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_user_list", "action:ads/add_audience_targeting", "action:ads/remove_campaign_audience_targeting", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nAttach an audience criterion on a Google campaign. Supports multiple criterion types: USER_LIST (default), USER_INTEREST, CUSTOM_AUDIENCE, COMBINED_AUDIENCE.\n\nUse when:\n- The user wants to add campaign-level audience targeting.\n- A copy workflow needs to replicate campaign_criterion audience settings.\n\nDo not use when:\n- The target is an ad group. Use action:ads/add_audience_targeting.\n- The platform is Meta. Meta uses targeting inside the ad set spec.\n\nWorkflow:\n1. Query source campaign's audience criteria via gaql_search on campaign_criterion.\n2. For each criterion, call this action with the matching criterion_type and resource_id.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via gaql_search on campaign_criterion.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n\nSide effects:\n- Creates a campaign_criterion of the specified type.\n- Sets bid_modifier if provided.\n- Additive — does not remove existing audience criteria.\n- Reversible via action:ads/remove_campaign_audience_targeting (by criterion resource_name).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from list_campaigns or gaql_search.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "criterion_type", "type": "string", "required": false, "description": "Google only: audience criterion type: USER_LIST (default), USER_INTEREST, CUSTOM_AUDIENCE, COMBINED_AUDIENCE. CUSTOM_INTENT is ad-group-only. NOTE: positive campaign-level USER_LIST works only on SEARCH campaigns; for Display/PMax use negative=true. resource_id must match the chosen criterion_type (user list id for USER_LIST, user_interest_category id for USER_INTEREST via gaql_search on user_interest, custom_audience id for CUSTOM_AUDIENCE, combined_audience id for COMBINED_AUDIENCE). Source: constant enum; resource_id source depends on type (create_user_list output for USER_LIST, gaql_search for others).", "example": "USER_LIST", "enum": [ "COMBINED_AUDIENCE", "CUSTOM_AUDIENCE", "USER_INTEREST", "USER_LIST" ], "source": "constant" }, { "name": "resource_id", "type": "string", "required": true, "description": "Audience resource id. For USER_LIST: user list id; for USER_INTEREST: user interest category id; etc. Legacy alias: user_list_id." }, { "name": "negative", "type": "boolean", "required": false, "description": "Google only: set true to create an EXCLUDED (negative) audience criterion instead of a positive one. Required for campaign-level USER_LIST on Display/PMax campaigns (positive campaign-level user_list is Search-only) and for any audience exclusion. Cannot be combined with bid_modifier (the API rejects bid modifiers on negative criteria). Default false (positive ENABLED). Source: agent decision / user intent (exclude vs include); constant boolean.", "example": true }, { "name": "bid_modifier", "type": "number", "required": false, "description": "Google only: optional bid multiplier for this audience (e.g. 1.2 for +20%). Only valid on POSITIVE criteria -- must be omitted when negative=true (the API rejects bid modifiers on negative criteria). Source: agent decision / user-provided multiplier.", "example": 1.2 } ], "output_shape": "{\"success\": true, \"message\": \"Campaign audience targeting (USER_LIST) added successfully\", \"results\": [{\"campaignCriterionResult\": {...}}]}" }, { "id": "action:ads/add_campaign_negative_keywords", "namespace": "ads", "action": "add_campaign_negative_keywords", "description": "Add negative keywords to a Google campaign (batch); review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_account_negative_keywords", "action:ads/remove_campaign_negative_keywords", "action:ads/list_campaign_negative_keywords", "action:ads/query_single_dimension", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-add campaign-level negative keywords to a Google campaign in one mutate. Negatives prevent the campaign from serving on the given queries.\n\nUse when:\n- The user wants to block specific search terms across the whole campaign.\n- A search_terms report shows wasted spend on terms that should be excluded.\n\nDo not use when:\n- The user wants account-wide negatives (a new shared set). Use action:ads/add_account_negative_keywords.\n- The user wants positive keywords. Use action:ads/add_keywords.\n- The platform is Meta. Meta does not expose keyword negatives.\n\nWorkflow:\n1. Inspect search terms via action:ads/query_single_dimension.\n2. Build the keyword list with text + match_type.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/gaql_search.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message reports the count added.\n\nSide effects:\n- Per-keyword campaign_criterion_operation.create with negative=True, all in one mutate.\n- match_type validated against {EXACT, PHRASE, BROAD}.\n- Reversible via action:ads/remove_campaign_negative_keywords (list first to collect resource_names).\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Capability docs phrase each keyword entry as '{text, match_type}' implying match_type is mandatory per entry, but the implementation defaults a missing match_type to BROAD (utils.py:68). The agent is not told that match_type is optional / defaults to BROAD, nor that a bare string is accepted. Also note: Capability docs do not state the official 80-char / 10-word text limit, so the agent may submit over-long negatives that fail the whole atomic batch with no local hint. Capability gap: No client-side enforcement of the official 80-character / 10-word keyword.text limit, so KEYWORD_HAS_TOO_MANY_WORDS / over-length errors only surface from the Google API mid-batch (the mutate is atomic, so one bad keyword fails the whole batch).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "keywords", "type": "list", "required": true, "description": "List (or JSON string) of {text, match_type} entries. match_type one of EXACT, PHRASE, BROAD.", "example": [ { "text": "free", "match_type": "BROAD" } ] } ], "output_shape": "{\"success\": true, \"message\": \"Added 5 campaign negative keyword(s)\", \"results\": [...]}" }, { "id": "action:ads/add_campaign_webpage_exclusion", "namespace": "ads", "action": "add_campaign_webpage_exclusion", "description": "Exclude webpages (URL paths, page titles, content) from a Google campaign via negative WEBPAGE criteria; review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/update_campaign", "action:ads/add_campaign_negative_keywords", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nCreate negative WEBPAGE campaign criteria so Google will not send traffic to the given pages of the advertiser's own site. This is the prevention side of final URL expansion: with expansion enabled, Google may pick any page on the domain as the landing page, including one that is factually wrong for the campaign (documented failure: a livestock-vaccine campaign expanded onto pet-vaccine pages on the same domain — the ad copy looks perfectly correct and only the link is wrong).\n\nUse when:\n- A Performance Max / Search campaign has final URL expansion ON and only part of the site is appropriate for it.\n- The user wants specific URL paths, page titles, or content off-limits as landing pages.\n\nDo not use when:\n- The goal is to switch expansion off entirely. Use action:ads/update_campaign with asset_automation={'final_url_expansion': 'OPTED_OUT'} (a cleaner, campaign-wide control) — exclusions only narrow which pages expansion may use.\n- The goal is to block search queries rather than landing pages. Use action:ads/add_campaign_negative_keywords.\n- The platform is Meta or TikTok. Neither has WEBPAGE criteria.\n\nWorkflow:\n1. Identify the URL paths to exclude (from the site structure or the campaign's expansion behaviour).\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/gaql_search on campaign_criterion where type = 'WEBPAGE'.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message reports the count created; raw.exclusions echoes the resolved criterion_name + conditions per criterion.\n\nSide effects:\n- One campaign_criterion_operation.create per exclusion, negative=True, all in one mutate.\n- Conditions WITHIN one exclusion are AND-ed by Google. To express alternatives, pass several entries in 'exclusions' (one criterion each).\n- An empty condition list is rejected: to Google it means the entire site, which as a negative criterion would exclude every page.\n- Reversible only via the Google Ads UI or a raw criterion remove; this capability set has no WEBPAGE remove handler.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890", "source": "upstream:action:ads/list_ad_accounts" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "example": "123456", "source": "upstream:action:ads/list_campaigns" }, { "name": "conditions", "type": "list", "required": false, "description": "Single-criterion shorthand: list (or JSON string) of conditions, AND-ed together. Each entry is either a bare string (shorthand for operand=URL, operator=CONTAINS) or an object {operand, operator, argument}. operand one of URL, CATEGORY, PAGE_TITLE, PAGE_CONTENT, CUSTOM_LABEL; operator one of EQUALS, CONTAINS (omit operator for non-string operands). Supply either this or 'exclusions'.", "example": [ "/pet-vaccines" ], "one_of_group": "webpage_exclusion_shape", "source": "user" }, { "name": "exclusions", "type": "list", "required": false, "description": "Multi-criterion form: list (or JSON string) of {criterion_name, conditions} objects, one negative WEBPAGE criterion each. Use this when the exclusions are alternatives rather than a single AND-ed condition set. Supply either this or 'conditions'.", "example": [ { "criterion_name": "Pet pages", "conditions": [ "/pet-vaccines" ] } ], "one_of_group": "webpage_exclusion_shape", "source": "user" }, { "name": "criterion_name", "type": "string", "required": false, "description": "Optional label for the criterion built from the 'conditions' shorthand. Google requires a name on create; one is derived from the conditions when omitted.", "example": "Pet vaccine pages", "source": "user" } ], "output_shape": "{\"success\": true, \"message\": \"Added 1 campaign webpage exclusion(s)\", \"exclusions\": [{\"criterion_name\": \"Pet pages\", \"conditions\": [{\"operand\": \"URL\", \"operator\": \"CONTAINS\", \"argument\": \"/pet-vaccines\"}]}], \"results\": [{\"campaignCriterionResult\": {\"resourceName\": \"...\"}}]}" }, { "id": "action:ads/add_conversion_group_rule", "namespace": "ads", "action": "add_conversion_group_rule", "description": "Review-gated add of a conversion_group rule.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_actions", "action:ads/list_conversion_groups" ], "long_description": "Adds a rule to an Admin View conversion_group after human approval. The server validates conversion_action_id/account_id against the current brand's granted ad accounts at approval execution time.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "group_id", "type": "integer", "required": true, "description": "Conversion group id." }, { "name": "expected_version", "type": "string", "required": true, "description": "Opaque version from list_conversion_groups.groups[].version." }, { "name": "account_id", "type": "integer", "required": false, "description": "Internal ad account id." }, { "name": "conversion_action_id", "type": "string", "required": false, "description": "Platform conversion action id from list_conversion_actions." }, { "name": "conversion_action_name_pattern", "type": "string", "required": false, "description": "Optional SQL LIKE pattern for synced conversion action names." }, { "name": "value_multiplier", "type": "number", "required": false, "description": "Rule value multiplier." } ], "output_shape": "{\"rule\": {\"id\": 1}, \"conversion_group\": {\"version\": \"opaque\"}}" }, { "id": "action:ads/add_keywords", "namespace": "ads", "action": "add_keywords", "description": "Add keywords to a Google ad group (batch, ENABLED); review-gated, reversible per keyword.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/update_keyword", "action:ads/remove_keyword", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-add positive keywords to a Google ad group in one mutate. Created with status=ENABLED because criteria are not lifecycle resources.\n\nUse when:\n- The user wants to add new search keywords with explicit match types.\n- A workflow needs to expand an ad group's targeting after checking existing keywords via GAQL.\n\nDo not use when:\n- The user wants negative keywords. Use action:ads/add_campaign_negative_keywords or action:ads/add_account_negative_keywords.\n- The platform is Meta. Meta does not use keyword targeting; use ad set targeting.\n\nWorkflow:\n1. Call action:ads/gaql_search to choose ad_group_id.\n2. Call action:ads/gaql_search to avoid duplicates.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/gaql_search.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message reports the count added; raw.results contains one entry per keyword.\n- Returns success=false with 'No valid keywords provided.' if the list parses empty.\n\nSide effects:\n- Batch of ad_group_criterion_operation.create entries in one mutate.\n- Status=ENABLED; the keyword starts matching once the parent ad group is enabled.\n- Reversible per keyword via action:ads/remove_keyword (terminal REMOVED).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Ad group id from action:ads/gaql_search.", "example": "987654", "source": "upstream:action:ads/create_ad_group,action:ads/gaql_search" }, { "name": "keywords", "type": "list", "required": true, "description": "List of {text, match_type} objects (or a JSON string). match_type one of EXACT, PHRASE, BROAD.", "example": [ { "text": "best running shoes", "match_type": "PHRASE" } ] }, { "name": "cpc_bid_micros", "type": "integer", "required": false, "description": "Google only: optional per-criterion CPC bid override applied to every keyword in this batch, in micros (1 USD = 1_000_000). Now actually applied on create. Source: user-provided / agent-chosen bid amount converted to micros.", "example": 500000 }, { "name": "final_urls", "type": "list", "required": false, "description": "Google only: optional keyword-level landing-page override (list of full https URLs) applied to every keyword in this batch; overrides the ad-level final URL for these criteria. Source: user-provided landing page URL(s).", "example": [ "https://example.com/running-shoes" ] }, { "name": "final_mobile_urls", "type": "list", "required": false, "description": "Google only: optional keyword-level mobile landing-page override (list of full https URLs) applied to every keyword in this batch. Source: user-provided mobile landing page URL(s).", "example": [ "https://m.example.com/running-shoes" ] } ], "output_shape": "{\"success\": true, \"message\": \"Added 5 keyword(s)\", \"results\": [{\"adGroupCriterionResult\": {\"resourceName\": \"customers/X/adGroupCriteria/AG~CID\"}}]}" }, { "id": "action:ads/add_sitelinks", "namespace": "ads", "action": "add_sitelinks", "description": "Add Google sitelink assets to a campaign (batch, atomic); review-gated.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/add_callouts", "action:ads/add_structured_snippets", "action:ads/remove_campaign_assets", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-create Sitelink assets AND CampaignAsset link rows for a Google campaign in one atomic mutate.\n\nUse when:\n- The user wants to add sitelink extensions (extra links shown below the ad).\n\nDo not use when:\n- The user wants callouts (short marketing phrases). Use action:ads/add_callouts.\n- The user wants structured snippet headers. Use action:ads/add_structured_snippets.\n- The platform is Meta. Meta does not use sitelink extensions.\n\nWorkflow:\n1. Build the sitelink list with link_text and final_urls per entry.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/gaql_search.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message reports the count of sitelinks added.\n- raw.results contains assetResult entries (the new assets) followed by campaignAssetResult entries (the campaign links).\n\nSide effects:\n- Creates N Asset rows (SITELINK type) AND N CampaignAsset link rows in one atomic mutate. Uses temp resource names for in-batch linking.\n- Reversible: detach the campaign links via action:ads/remove_campaign_assets (field_type=SITELINK); the assets stay in the account for re-attachment.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "sitelinks", "type": "list", "required": true, "description": "List (or JSON string) of {link_text|text, final_urls|urls, name?} entries. link_text is the visible link label.", "example": [ { "link_text": "About us", "final_urls": [ "https://example.com/about" ] } ] } ], "output_shape": "{\"success\": true, \"message\": \"Added 3 sitelink asset(s)\", \"results\": [{\"assetResult\": {...}}, {\"campaignAssetResult\": {...}}]}" }, { "id": "action:ads/add_structured_snippets", "namespace": "ads", "action": "add_structured_snippets", "description": "Add a Google structured snippet asset to a campaign; review-gated, reversible.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/add_sitelinks", "action:ads/add_callouts", "action:ads/remove_campaign_assets", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nCreate one structured snippet asset (header + values list) and link it to a Google campaign.\n\nUse when:\n- The user wants to highlight a list of services, brands, models, etc. under a structured header.\n\nDo not use when:\n- The user wants sitelink URLs. Use action:ads/add_sitelinks.\n- The user wants callouts. Use action:ads/add_callouts.\n- The platform is Meta.\n\nWorkflow:\n1. Choose a header from Google's predefined list (e.g. Services, Types, Brands).\n2. Build the values list.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/gaql_search.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.results contains exactly one assetResult and one campaignAssetResult.\n\nSide effects:\n- Creates one structured-snippet Asset and one CampaignAsset link in one mutate.\n- Reversible: detach the campaign link via action:ads/remove_campaign_assets (field_type=STRUCTURED_SNIPPET); the asset stays in the account for re-attachment.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "header", "type": "string", "required": true, "description": "Google only: header from Google's predefined, locale-specific structured-snippet header set. Canonical English headers include: Amenities, Brands, Courses, Degree programs, Destinations, Featured hotels, Insurance coverage, Models, Neighborhoods, Service catalog, Shows, Styles, Types. NOTE 'Services'/'Programs' are NOT canonical (use 'Service catalog' / 'Degree programs'). Header is language-dependent: on non-English accounts use that locale's header strings or Google rejects it. Source: constant enum chosen by the agent from Google's predefined header list (https://developers.google.com/google-ads/api/reference/data/structured-snippet-headers).", "example": "Brands" }, { "name": "values", "type": "list", "required": true, "description": "Google only: list (or JSON string) of 3 to 10 value strings matching the header, each 1-25 characters. Fewer than 3, more than 10, or any value over 25 chars is rejected (the asset+link mutate is atomic and fails wholesale on a single bad value). Source: user-provided / agent-composed snippet items consistent with the chosen header.", "example": [ "Nike", "Adidas", "Puma" ] } ], "output_shape": "{\"success\": true, \"message\": \"Added 1 structured_snippet asset(s)\", \"results\": [{\"assetResult\": {...}}, {\"campaignAssetResult\": {...}}]}" }, { "id": "action:ads/apply_ad_group_label", "namespace": "ads", "action": "apply_ad_group_label", "description": "Attach a Google label to an ad group; review-gated, reversible.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_label", "action:ads/remove_ad_group_label", "action:ads/apply_campaign_label", "action:_batch/submit" ], "long_description": "Primary use:\nCreate an ad_group_label join row so a label appears on a Google ad group.\n\nUse when:\n- The user wants to tag an ad group with an existing label_id.\n\nDo not use when:\n- The label does not exist yet. Use action:ads/create_label first.\n- The user wants to label a campaign. Use action:ads/apply_campaign_label.\n- The platform is Meta. Use action:ads/apply_label.\n\nWorkflow:\n1. Look up label_id via action:ads/create_label or gaql_search.\n2. Choose ad_group_id via action:ads/gaql_search.\n3. Submit via action:_batch/submit for HITL review.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n\nSide effects:\n- Creates an ad_group_label join row via mutate.create.\n- Reversible via action:ads/remove_ad_group_label.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: customer_id is required by the handler but never surfaced to the agent as a parameter; the capability doc only mentions account_id. This is correct by design (account_id->customer_id mapping is internal) but an agent reading only the handler would not know account_id is the controllable source. Low impact. Also note: The doc workflow says 'Choose ad_group_id via action:ads/gaql_search' but does not give an example GAQL (e.g. SELECT ad_group.id, ad_group.name FROM ad_group). Minor — agent must construct the query. Capability gap: No remove/detach handler for ad_group_label (official AdGroupLabelOperation supports remove). Docs explicitly note 'no L1.5 remove handler exposed; reversible via Google Ads UI' — detaching a label via the agent is impossible. This is a deliberate scope limit, not a break of the apply happy-path.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Ad group id from action:ads/gaql_search.", "source": "upstream:action:ads/create_ad_group,action:ads/gaql_search" }, { "name": "label_id", "type": "string", "required": true, "description": "Label id from action:ads/create_label." } ], "output_shape": "{\"success\": true, \"message\": \"Ad group label applied successfully\", \"results\": [...]}" }, { "id": "action:ads/apply_campaign_label", "namespace": "ads", "action": "apply_campaign_label", "description": "Attach a Google label to a campaign; review-gated, reversible.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_label", "action:ads/remove_campaign_label", "action:ads/apply_ad_group_label", "action:_batch/submit" ], "long_description": "Primary use:\nCreate a campaign_label join row so a label appears on a Google campaign.\n\nUse when:\n- The user wants to tag a campaign with an existing label_id.\n\nDo not use when:\n- The label does not exist yet. Use action:ads/create_label first.\n- The user wants to label an ad group. Use action:ads/apply_ad_group_label.\n- The platform is Meta. Use action:ads/apply_label (different semantics — by name).\n\nWorkflow:\n1. Call action:ads/create_label or look up label_id via gaql_search.\n2. Call action:ads/list_campaigns to choose campaign_id.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via gaql_search on campaign_label.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n\nSide effects:\n- Creates a campaign_label join row via mutate.create.\n- Reversible via action:ads/remove_campaign_label.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: The capability doc tells the agent to obtain label_id via 'action:ads/create_label or gaql_search', but does not give the exact GAQL (e.g. SELECT label.id, label.name FROM label) nor warn that create_label is review-gated (HITL via _batch/submit), so in a single turn the label_id may not yet exist when apply_campaign_label is attempted — a sequencing ambiguity the agent could mishandle. Also note: No mention of the non-idempotent behavior: re-applying an existing (campaign,label) pair errors. The agent is not told to verify absence first, so a retry after a partial success could surface a confusing duplicate error. Capability gap: No L1.5 remove handler is exposed (capability docs explicitly note 'no L1.5 remove handler exposed'; removal only via Google Ads UI). Removing a campaign label is a normal CampaignLabelService remove operation, so the unlabel dimension is unreachable through the agent. P1-ish but documented as intentional.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "label_id", "type": "string", "required": true, "description": "Label id from action:ads/create_label." } ], "output_shape": "{\"success\": true, \"message\": \"Campaign label applied successfully\", \"results\": [...]}" }, { "id": "action:ads/apply_conversion_group_template", "namespace": "ads", "action": "apply_conversion_group_template", "description": "Review-gated onboarding action that creates missing conversion groups and applies selected rules/goals/manual campaigns from templates.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/suggest_conversion_group_setup", "action:ads/list_conversion_groups", "action:ads/list_conversion_actions" ], "long_description": "Primary use:\nApply user-approved conversion group setup recommendations from suggest_conversion_group_setup. This is the default write path for onboarding: it can create missing Admin View groups and add explicitly selected rules, goal links, or manual campaign include/exclude entries in one human approval.\n\nSafety semantics:\n- Existing groups are not overwritten by default; precise updates still use update_conversion_group with expected_version.\n- Every rule target is validated against the current brand's granted ad accounts.\n- The action is review-gated and initially returns pending_review_id.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "templates", "type": "list", "required": true, "description": "Template objects. Each requires slug/template_slug and may include name, description, derived_metrics, metadata, rules[], goal_ids[], manual_campaigns[]." } ], "output_shape": "{\"created_groups\": [{\"id\": 1, \"slug\": \"qualified-new-user\"}], \"existing_groups\": [], \"rules_added\": [{\"id\": 10}], \"existing_rules\": [], \"goals_bound\": [], \"manual_campaigns_set\": []}" }, { "id": "action:ads/apply_label", "namespace": "ads", "action": "apply_label", "description": "Apply a Meta ad label (by NAME) to a campaign/adset/ad/creative; review-gated, reversible.", "mode": "write", "platforms": [ "meta" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad_label", "action:ads/list_ad_labels", "action:ads/remove_label", "action:_batch/submit" ], "long_description": "Primary use:\nApply a Meta ad label to an object (campaign, ad set, ad, or creative). Unlike Google, Meta resolves the label by NAME (case-sensitive), not by id.\n\nUse when:\n- The user wants to tag an existing object with a previously-created label.\n\nDo not use when:\n- The label does not exist. Use action:ads/create_ad_label first.\n- The platform is Google. Use action:ads/apply_campaign_label or apply_ad_group_label.\n\nWorkflow:\n1. Look up the exact label name via action:ads/list_ad_labels.\n2. Identify object_id (campaign / adset / ad / creative id).\n3. Submit via action:_batch/submit for HITL review.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body: {success: true}.\n\nSide effects:\n- POSTs {object_id}/adlabels with body adlabels=[{name: label_name}] (JSON-stringified).\n- If label_name does not exist as a label, Meta returns an error.\n- Reversible via action:ads/remove_label.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: create_ad_label long_description says 'raw.id is the new ad label id (note: Meta apply_label uses NAME, not id)'. The agent gets an id back from create but must supply name to apply — the docs flag this but it is an easy footgun if the agent stores only the id. Also note: Docs do not state that label resolution is account-scoped: if the same display name exists in multiple labels (Meta does allow duplicate-named labels) the by-name resolution behavior is ambiguous and the agent cannot disambiguate (only id could). Capability gap: execution_options=['validate_only'] — a native dry-run that would let the HITL review/_batch flow validate the label application without mutating; absent from handler, allowlist, and capability docs.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "object_id", "type": "string", "required": true, "description": "Target object id (campaign / ad set / ad / creative id) to label." }, { "name": "label_name", "type": "string", "required": true, "description": "Label name (case-sensitive) from action:ads/list_ad_labels. Meta resolves by name, not id." } ], "output_shape": "{\"success\": true}" }, { "id": "action:ads/archive_ad", "namespace": "ads", "action": "archive_ad", "description": "Archive one authored ChatGPT Ads Ad; review-gated risk action.", "mode": "risk", "platforms": [ "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/get_ad_group", "action:_batch/submit" ], "long_description": "Primary use:\nArchive one manual authored Ad inside a confirmed Ad Group.\n\nUse when:\n- The user explicitly approved archiving the exact authored Ad.\n\nDo not use when:\n- The Ad is provider-generated; generated Ads are read-only.\n- The complete parent read model is unavailable.\n\nWorkflow:\n1. Read the parent Ad Group and verify the Ad id, manual source, and lifecycle.\n2. Submit archive_ad through HITL review.\n3. Read the parent group again before any retry.\n\nOutput semantics:\n- resource_id/ad_id identify the Ad and ad_group_id identifies its parent.\n- Unknown outcomes require reconciliation before retry.\n\nSide effects:\n- Archives one authored Ad; it does not activate the Campaign or group.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant", "platform": [ "chatgpt_ads" ] }, { "name": "account_id", "type": "string", "required": true, "description": "Advertiser account id.", "example": "account_123", "platform": [ "chatgpt_ads" ] }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Canonical parent Ad Group id.", "example": "ag_123", "platform": [ "chatgpt_ads" ] }, { "name": "ad_id", "type": "string", "required": true, "description": "Authored Ad id.", "example": "ad_123", "platform": [ "chatgpt_ads" ] } ], "output_shape": "{\"success\": true, \"resource_id\": \"ad_123\", \"ad_id\": \"ad_123\", \"ad_group_id\": \"ag_123\"}" }, { "id": "action:ads/archive_ad_group", "namespace": "ads", "action": "archive_ad_group", "description": "Archive one ChatGPT Ads Ad Group and its Ads; review-gated risk action.", "mode": "risk", "platforms": [ "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/get_ad_group", "action:ads/list_ad_groups", "action:_batch/submit" ], "long_description": "Primary use:\nArchive one confirmed Ad Group and its Ads.\n\nUse when:\n- The user explicitly approved archiving the exact Ad Group.\n\nDo not use when:\n- The target or current state is unknown; read it first.\n- The user only wants to pause delivery; use update_ad_group.\n\nWorkflow:\n1. Read the target with get_ad_group.\n2. Present the group-and-Ads impact in the HITL summary.\n3. Submit archive_ad_group and reconcile the returned id.\n\nOutput semantics:\n- resource_id and ad_group_id identify the archived group.\n- Unknown outcomes require readback before retry.\n\nSide effects:\n- Archives the Ad Group and its Ads; this is not an activation operation.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant", "platform": [ "chatgpt_ads" ] }, { "name": "account_id", "type": "string", "required": true, "description": "Advertiser account id.", "example": "account_123", "platform": [ "chatgpt_ads" ] }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Canonical Ad Group id; adset_id is not accepted.", "example": "ag_123", "platform": [ "chatgpt_ads" ] } ], "output_shape": "{\"success\": true, \"resource_id\": \"ag_123\", \"ad_group_id\": \"ag_123\"}" }, { "id": "action:ads/archive_ad_unit", "namespace": "ads", "action": "archive_ad_unit", "description": "Deprecated legacy compatibility: Toggle archive state for one ChatGPT Ads ad unit; review-gated risk action.", "mode": "risk", "platforms": [ "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/get_campaign" ], "long_description": "Deprecated compatibility path. New deployments use inline Campaign -> Ad Group -> Ad.\n\nPrimary use:\nToggle archive state for one ChatGPT Ads ad unit after explicit user confirmation.\n\nUse when:\n- The current ad-unit state has been verified and the user wants to archive it.\n- The user understands the ChatGPT Ads endpoint is a toggle, not a one-way delete.\n\nDo not use when:\n- The current state is unknown. Read dashboard/campaign detail first.\n- The intent is campaign removal. Use action:ads/remove_campaign, whose ChatGPT Ads handler guards the campaign archive toggle with pre-read and post-read proof.\n\nWorkflow:\n1. Verify the target ad_unit_id and current state.\n2. Present the toggle risk in the HITL summary.\n3. Submit archive_ad_unit only after explicit approval.\n\nSide effects:\n- Calls the ChatGPT Ads ad-unit archive toggle endpoint.\n- Repeating the same action may unarchive the ad unit.\n\nOutput semantics:\n- resource_id is the ad_unit_id when available or the requested id fallback.\n- raw preserves the ChatGPT Ads platform response.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "" }, { "name": "ad_unit_id", "type": "string", "required": true, "description": "", "source": "upstream:action:ads/create_ad_unit,action:ads/get_campaign" } ], "output_shape": "{\"success\": true, \"resource_id\": \"AD_UNIT_ID\", \"raw\": {...}}" }, { "id": "action:ads/archive_lead_form", "namespace": "ads", "action": "archive_lead_form", "description": "Archive a Meta lead form (status=ARCHIVED); review-gated. Meta has no lead-form delete; collected leads remain readable.", "mode": "write", "platforms": [ "meta" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_lead_form", "action:ads/list_lead_forms", "action:ads/get_leads", "action:ads/get_account_pages", "action:_batch/submit" ], "long_description": "Primary use:\nRetire a lead form that should no longer be attached to new ads. Meta's Graph API does not support deleting lead forms — archiving is the only retirement path; ARCHIVED hides the form from ad-creation flows.\n\nUse when:\n- The user wants to clean up outdated or test lead forms on a Page.\n- A form was created with wrong questions and must not be attached to new ads (create a corrected form via action:ads/create_lead_form first, then archive the old one).\n\nDo not use when:\n- The user wants the collected leads deleted. Archiving does NOT delete leads; they remain readable via action:ads/get_leads.\n- The user wants to stop ads that use the form. Archiving does not pause delivery; pause or remove the ads instead (action:ads/update_ad / action:ads/remove_ad).\n- The platform is Google.\n\nWorkflow:\n1. Get page_id via action:ads/get_account_pages and confirm form_id via action:ads/list_lead_forms.\n2. Check no running ad still relies on the form (action:ads/list_ads).\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/list_lead_forms (status=ARCHIVED).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body: {success: true}.\n\nSide effects:\n- POSTs status=ARCHIVED to {form_id} using the Page access token resolved for page_id (same page-token path as create_lead_form; requires pages_show_list / leadgen page access). When no page token can be resolved, the action fails with a permission error — the Meta credential itself stays valid; reconnect Meta with page access.\n- ARCHIVED hides the form from ad-creation flows; it cannot be attached to new ads.\n- Collected leads are NOT deleted and remain readable via action:ads/get_leads.\n- Not reversible via this surface: Meta itself allows setting status back to ACTIVE (Meta UI / a Graph status write), but no unarchive action is exposed here — treat archive as terminal within this surface.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "page_id", "type": "string", "required": true, "description": "Facebook Page id the form belongs to, from action:ads/get_account_pages. Used to resolve the Page access token required for lead-form writes." }, { "name": "form_id", "type": "string", "required": true, "description": "Lead form id to archive, from action:ads/list_lead_forms or action:ads/create_lead_form (raw.id)." } ], "output_shape": "{\"success\": true}" }, { "id": "action:ads/audit_ad_automation_inventory", "namespace": "ads", "action": "audit_ad_automation_inventory", "description": "Read-only paginated TikTok, Google, Meta, or ChatGPT Ads audit of every automation baseline currently tracked for one brand-scoped ad account.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/list_ad_accounts", "action:ads/enable_ad_automation_audits" ], "long_description": "Primary use:\nAudit every stored automation baseline in a brand-scoped ad account against fresh Google, Meta, TikTok, or ChatGPT Ads provider state.\n\nUse when:\n- A scheduled or manual audit needs full current-state coverage.\n- New baselines must be discovered without editing an existing Automation.\n\nDo not use when:\n- Establishing or changing an approved baseline; use preview_automation_baseline followed by establish_automation_baseline.\n- Mutating provider settings. This action is strictly read-only.\n\nWorkflow:\n1. Resolve the brand-scoped account and page its current snapshot inventory.\n2. Fresh-read every entity through its platform adapter and compare it with the authoritative baseline.\n3. Continue with next_after_id when returned. Scheduled brand-wide runs handle this pagination internally.\n4. If audit_schedule_required is true, submit enable_ad_automation_audits for HITL approval.\n\nOutput semantics:\n- account_disconnected means the account is no longer connected; entity_deleted means the provider returned not found. Neither state deletes the stored baseline.\n- results contains per-entity OK, DRIFTED, or NO_BASELINE findings; next_after_id is the stable continuation cursor.\n- audit_schedule_required reports whether either canonical recurring schedule is missing.\n\nSide effects:\n- None. The action performs provider reads and local baseline reads only.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "google, meta, tiktok, or chatgpt_ads.", "example": "google", "enum": [ "google", "meta", "tiktok", "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Brand-scoped ad account id from list_ad_accounts.", "example": "1234567890", "source": "upstream:action:ads/list_ad_accounts" }, { "name": "after_id", "type": "integer", "required": false, "description": "Stable snapshot cursor returned as next_after_id.", "example": 100, "source": "upstream:action:ads/audit_ad_automation_inventory" }, { "name": "limit", "type": "integer", "required": false, "description": "Snapshot page size from 1 through 500; defaults to 100.", "example": 100, "source": "constant" } ], "output_shape": "{\"success\":true,\"account_status\":\"connected|account_disconnected\",\"results\":[{\"entity_id\":\"...\",\"findings\":[...]}],\"next_after_id\":123|null,\"read_only\":true,\"audit_schedule_required\":true|false,\"audit_schedule_recovery_action\":{\"action\":\"enable_ad_automation_audits\"}|null}\nTikTok output: uses the same account-scoped inventory envelope." }, { "id": "action:ads/audit_creative_automation", "namespace": "ads", "action": "audit_creative_automation", "description": "Read-only drift audit: compare a Meta creative's or Google campaign's live AI-automation settings against the approved intent baseline.", "mode": "read", "platforms": [ "google", "meta" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/update_campaign", "action:ads/create_ad_creative", "action:ads/get_campaign" ], "long_description": "Primary use:\nDetect platform AI-automation settings that no longer match what was approved — toggles that flipped back on, or settings reset by duplicating a campaign/placement.\n\nUse when:\n- The user asks whether AI enhancements / asset automation are still configured the way they were approved.\n- A campaign or placement was duplicated (duplication resets automation settings on the copy).\n- A periodic re-check is due. Settings can change after a correct write, so a single check at write time does not stay true.\n\nDo not use when:\n- The user wants to CHANGE a setting. This action never writes; use action:ads/update_campaign (google asset_automation) or action:ads/create_ad_creative (meta creative_features) instead.\n- The user wants Meta's own recommendations. Use action:ads/get_opportunity_score.\n\nWorkflow:\n1. platform=meta: pass creative_id (from action:ads/list_ad_creatives or action:ads/get_ad_creatives). platform=google: pass campaign_id (from action:ads/list_campaigns).\n2. The service loads the account-scoped recorded intent baseline; callers must not supply baseline state or authority.\n3. Read findings[]: act on status=DRIFTED, and treat status=NO_BASELINE as 'establish a baseline first', NOT as a violation.\n\nOutput semantics:\n- freshness=realtime: the read goes straight to the platform, so findings reflect the current value, not a synced snapshot.\n- STRICTLY READ-ONLY. No platform write is issued on any path, so it is safe to re-run on live spending accounts as often as needed.\n- findings[].status is one of OK | DRIFTED | NO_BASELINE. NO_BASELINE is reported SEPARATELY from DRIFTED on purpose: without a recorded baseline nothing can be said about whether the value changed, and reporting such assets as drifted would false-alarm on all pre-existing inventory.\n- findings[] covers EVERY feature/automation type the platform returned, not only the three Meta features Soku opts out of by default. Do not conclude from a short list that 'everything is disabled' — most features keep their platform default (usually ON) unless explicitly opted out.\n- An expected feature the platform did not return appears with actual=null; a disappeared setting matters as much as a flipped one.\n- baseline_source=soku_write is authoritative (Soku performed the write, so the baseline IS the approved configuration). baseline_source=human_baseline is NOT: an operator confirmed the CURRENT state was acceptable at baseline_captured_at, on inventory Soku did not write. That asset may already have drifted before the baseline was taken and the original intent is unknowable, so those findings carry baseline_disclaimer and baseline_authoritative=false. Never report a human_baseline finding to the user as if it were the originally approved state.\n- meta: standard_enhancements / standard_enhancements_catalog appear in GET responses even though writing them fails since Graph v22 (read/write asymmetry). Seeing them in this audit is normal, NOT an anomaly; they are listed under read_write_asymmetric_features.\n- meta: music is not part of creative_features_spec at all. music_opt_out_observed reads asset_feed_spec.audios instead (true = empty audios = opted out, null = no asset_feed_spec, so unknown).\n- google: unrecognized_types lists automation types the platform returned that this build does not model; they are reported rather than hidden.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta.", "example": "meta", "enum": [ "google", "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Ad account id (Meta) / customer id (Google) from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890", "source": "upstream:action:ads/list_ad_accounts" }, { "name": "creative_id", "type": "string", "required": false, "description": "Meta only, required when platform=meta: AdCreative id to audit. Source: output of action:ads/list_ad_creatives or action:ads/get_ad_creatives.", "example": "120210000000000000", "source": "upstream:action:ads/list_ad_creatives,action:ads/get_ad_creatives", "platform": [ "meta" ] }, { "name": "campaign_id", "type": "string", "required": false, "description": "Google only, required when platform=google: campaign id whose asset_automation_settings are audited. Source: output of action:ads/list_campaigns.", "example": "12345678901", "source": "upstream:action:ads/list_campaigns", "platform": [ "google" ] } ], "output_shape": "{\"success\": true, \"platform\": \"meta\", \"entity_type\": \"creative\", \"entity_id\": \"...\", \"baseline_source\": \"soku_write|human_baseline|null\", \"baseline_captured_at\": \"...|null\", \"baseline_authoritative\": true, \"read_only\": true, \"findings\": [{\"feature\": \"image_touchups\", \"expected\": {\"enroll_status\": \"OPT_OUT\"}, \"actual\": {\"enroll_status\": \"OPT_IN\"}, \"status\": \"DRIFTED\", \"baseline_source\": \"soku_write\", \"baseline_captured_at\": \"...\", \"baseline_authoritative\": true}], \"summary\": {\"ok\": 1, \"drifted\": 1, \"no_baseline\": 3, \"observed_features\": 5}} // status=NO_BASELINE findings carry `note` instead of baseline_* fields; human_baseline findings additionally carry `baseline_disclaimer` (the baseline is an operator-confirmed CURRENT state, not the originally approved one). meta adds creative_name / unrecognized_features / read_write_asymmetric_features / default_opt_out_features / music_opt_out_observed; google adds campaign_level_enum_names / unrecognized_types. No platform write is issued on any path." }, { "id": "action:ads/bind_conversion_group_goal", "namespace": "ads", "action": "bind_conversion_group_goal", "description": "Review-gated link of a platform conversion goal to a conversion group.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_goals", "action:ads/unbind_conversion_group_goal", "action:ads/list_conversion_groups" ], "long_description": "Links a platform conversion goal to an Admin View conversion group after human approval. The group then inherits the goal's campaigns (auto source) in list_conversion_groups.groups[].campaigns. Requires expected_version.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "group_id", "type": "integer", "required": true, "description": "Conversion group id." }, { "name": "expected_version", "type": "string", "required": true, "description": "Opaque version from list_conversion_groups.groups[].version." }, { "name": "goal_id", "type": "integer", "required": true, "description": "Internal goal id from list_conversion_goals." } ], "output_shape": "{\"bound\": true, \"conversion_group\": {\"id\": 1, \"slug\": \"...\", \"version\": \"opaque\"}}" }, { "id": "action:ads/bulk_create_ad_creatives", "namespace": "ads", "action": "bulk_create_ad_creatives", "description": "Bulk-create Meta ad creatives in one review-gated request; executes items sequentially.", "mode": "write", "platforms": [ "meta" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad_creative" ], "long_description": "Primary use:\nCreate multiple Meta ad creatives as one human-approved CLI/agent operation. This is a single-layer bulk action: it does not create a campaign tree or resolve cross-layer dependencies by itself.\n\nUse when:\n- The user has already prepared several Meta ad creatives with the same account_id.\n- The operation is one hierarchy layer at a time, with all parent ids already known.\n- The user wants one approval record and ordered per-item results.\n\nDo not use when:\n- The user needs a full campaign -> ad set -> ad tree in one request. Create each layer separately and feed returned ids into the next layer.\n- The user only needs one object. Use action:ads/create_ad_creative instead.\n- The platform is Google or the account_id is unknown.\n\nWorkflow:\n1. Prepare a JSON items array. Every item must include unique client_ref.\n2. Put fields accepted by action:ads/create_ad_creative either at the top level as shared defaults or on individual items as overrides.\n3. Submit this action for review. Approval runs asynchronously for CLI bulk flows; poll the review for final item results.\n\nSide effects:\n- After approval, creates Meta ad creatives in the target ad account.\n- Execution is sequential and non-transactional; already-created platform objects remain even if a later item fails.\n- Review rejection creates no platform-side objects.\n\nOutput semantics:\n- items[] preserves input order and includes index, client_ref, success, stage, created id field when available, and raw/error details.\n- The operation is not transactional: earlier successes are not rolled back if a later item fails.\n- No resume/idempotency in v1; use client_ref to map outputs and build a new retry file for failed items.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id.", "example": "act_123456789", "platform": [ "meta" ] }, { "name": "items", "type": "list", "required": true, "description": "Non-empty array of item objects. Each item requires unique client_ref and may include fields accepted by action:ads/create_ad_creative.", "example": [ { "client_ref": "row-1", "name": "Example" } ], "platform": [ "meta" ] } ], "output_shape": "{\"success\": true|false, \"total\": 2, \"succeeded\": 1, \"failed\": 1, \"items\": [{\"index\": 0, \"client_ref\": \"row-1\", \"success\": true, \"stage\": \"...\", \"campaign_id|adset_id|creative_id|ad_id\": \"...\"}]}" }, { "id": "action:ads/bulk_create_ads", "namespace": "ads", "action": "bulk_create_ads", "description": "Bulk-create Meta ads in one review-gated request; executes items sequentially. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nEach merged item requires client_ref, adset_id, and creatives. Each creative follows create_ad's identity/material contract: CUSTOMIZED_USER uses uploaded video_id/image_ids; uploaded video_id may include exactly one cover image_id; AUTH_CODE uses tiktok_item_id; TT_USER/BC_AUTH_TT can use video_id, tiktok_item_id, or CAROUSEL_ADS image_ids, and BC_AUTH_TT also requires identity_authorized_bc_id. Shared top-level defaults may supply adset_id/creatives unless an item overrides them. Items cannot override the top-level advertiser account. Missing per-item fields fail locally without a TikTok request; valid items continue and preserve input order.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nCreate multiple Meta ads as one human-approved CLI/agent operation. This is a single-layer bulk action: it does not create a campaign tree or resolve cross-layer dependencies by itself.\n\nUse when:\n- The user has already prepared several Meta ads with the same account_id.\n- The operation is one hierarchy layer at a time, with all parent ids already known.\n- The user wants one approval record and ordered per-item results.\n\nDo not use when:\n- The user needs a full campaign -> ad set -> ad tree in one request. Create each layer separately and feed returned ids into the next layer.\n- The user only needs one object. Use action:ads/create_ad instead.\n- The platform is Google or the account_id is unknown.\n\nWorkflow:\n1. Prepare a JSON items array. Every item must include unique client_ref.\n2. Put fields accepted by action:ads/create_ad either at the top level as shared defaults or on individual items as overrides.\n3. Submit this action for review. Approval runs asynchronously for CLI bulk flows; poll the review for final item results.\n\nSide effects:\n- After approval, creates Meta ads in the target ad account.\n- Execution is sequential and non-transactional; already-created platform objects remain even if a later item fails.\n- Review rejection creates no platform-side objects.\n\nOutput semantics:\n- items[] preserves input order and includes index, client_ref, success, stage, created id field when available, and raw/error details.\n- The operation is not transactional: earlier successes are not rolled back if a later item fails.\n- No resume/idempotency in v1; use client_ref to map outputs and build a new retry file for failed items.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id.", "example": "act_123456789", "platform": [ "meta", "tiktok" ] }, { "name": "items", "type": "array", "required": true, "description": "Non-empty array of item objects. Each item requires a unique client_ref and may include create_ad fields. For TikTok, each item must resolve adset_id and creatives after shared defaults merge.", "example": [ { "client_ref": "row-1", "adset_id": "17840000000000000", "creatives": [ { "identity_type": "CUSTOMIZED_USER", "identity_id": "identity-1", "ad_format": "SINGLE_VIDEO", "video_id": "video-1" } ] } ], "platform": [ "meta", "tiktok" ] }, { "name": "ad_group_id", "type": "string", "required": false, "description": "TikTok ad group id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "adgroup_id", "type": "string", "required": false, "description": "TikTok adgroup id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "adset_id", "type": "string", "required": false, "description": "TikTok adset id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "concurrency", "type": "integer", "required": false, "description": "TikTok only: parallel item limit; defaults to 4 and is clamped to 1–8.", "example": 4, "platform": [ "tiktok" ] }, { "name": "creatives", "type": "array", "required": false, "description": "TikTok creatives.", "example": [], "platform": [ "tiktok" ] }, { "name": "status", "type": "string", "required": false, "description": "TikTok only: optional shared create state. Omit or PAUSED to create disabled; ENABLED requires explicit HITL approval. Items may override with the same two-value contract.", "example": "PAUSED", "enum": [ "PAUSED", "ENABLED" ], "source": "constant", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true|false, \"total\": 2, \"succeeded\": 1, \"failed\": 1, \"items\": [{\"index\": 0, \"client_ref\": \"row-1\", \"success\": true, \"stage\": \"...\", \"campaign_id|adset_id|creative_id|ad_id\": \"...\"}]}\nTikTok output: raw.items[] carries each TikTok result under raw; read raw.items[].raw.ad_ids, raw.items[].raw.ad_id, or raw.items[].raw.id for created ad ids." }, { "id": "action:ads/bulk_create_adsets", "namespace": "ads", "action": "bulk_create_adsets", "description": "Bulk-create Meta ad sets in one review-gated request; executes items sequentially. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_adset" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nEach merged item requires client_ref, campaign_id, name, optimization_goal, budget_mode, budget, schedule_type, and schedule_start_time. promotion_type is conditional on the parent campaign objective. Each item also requires location_ids or zipcode_ids. Shared top-level defaults may supply these fields unless an item overrides them. Items cannot override the top-level advertiser account. Missing per-item fields fail locally without a TikTok request; valid items continue and preserve input order.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nCreate multiple Meta ad sets as one human-approved CLI/agent operation. This is a single-layer bulk action: it does not create a campaign tree or resolve cross-layer dependencies by itself.\n\nUse when:\n- The user has already prepared several Meta ad sets with the same account_id.\n- The operation is one hierarchy layer at a time, with all parent ids already known.\n- The user wants one approval record and ordered per-item results.\n\nDo not use when:\n- The user needs a full campaign -> ad set -> ad tree in one request. Create each layer separately and feed returned ids into the next layer.\n- The user only needs one object. Use action:ads/create_adset instead.\n- The platform is Google or the account_id is unknown.\n\nWorkflow:\n1. Prepare a JSON items array. Every item must include unique client_ref.\n2. Put fields accepted by action:ads/create_adset either at the top level as shared defaults or on individual items as overrides.\n3. Submit this action for review. Approval runs asynchronously for CLI bulk flows; poll the review for final item results.\n\nSide effects:\n- After approval, creates Meta ad sets in the target ad account.\n- Execution is sequential and non-transactional; already-created platform objects remain even if a later item fails.\n- Review rejection creates no platform-side objects.\n\nOutput semantics:\n- items[] preserves input order and includes index, client_ref, success, stage, created id field when available, and raw/error details.\n- The operation is not transactional: earlier successes are not rolled back if a later item fails.\n- No resume/idempotency in v1; use client_ref to map outputs and build a new retry file for failed items.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id.", "example": "act_123456789", "platform": [ "meta", "tiktok" ] }, { "name": "items", "type": "array", "required": true, "description": "Non-empty array of item objects. Each item requires unique client_ref and may include fields accepted by action:ads/create_adset. Required when platform=tiktok.", "example": [ { "client_ref": "row-1", "name": "Example" } ], "platform": [ "meta", "tiktok" ] }, { "name": "adgroup_name", "type": "string", "required": false, "description": "TikTok adgroup name.", "example": "value", "platform": [ "tiktok" ] }, { "name": "age_groups", "type": "list", "required": false, "description": "TikTok age groups.", "example": [], "platform": [ "tiktok" ] }, { "name": "app_id", "type": "string", "required": false, "description": "TikTok app id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "audience_ids", "type": "list", "required": false, "description": "TikTok audience ids.", "example": [], "platform": [ "tiktok" ] }, { "name": "bid_amount", "type": "number", "required": false, "description": "TikTok bid amount.", "example": 1.0, "platform": [ "tiktok" ] }, { "name": "bid_type", "type": "string", "required": false, "description": "TikTok bid type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "billing_event", "type": "string", "required": false, "description": "TikTok billing event.", "example": "value", "platform": [ "tiktok" ] }, { "name": "budget", "type": "number", "required": false, "description": "TikTok budget.", "example": 1.0, "platform": [ "tiktok" ] }, { "name": "budget_mode", "type": "string", "required": false, "description": "TikTok budget mode.", "example": "value", "platform": [ "tiktok" ] }, { "name": "campaign_id", "type": "string", "required": false, "description": "TikTok campaign id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "concurrency", "type": "integer", "required": false, "description": "TikTok only: parallel item limit; defaults to 4 and is clamped to 1–8.", "example": 4, "platform": [ "tiktok" ] }, { "name": "excluded_audience_ids", "type": "list", "required": false, "description": "TikTok excluded audience ids.", "example": [], "platform": [ "tiktok" ] }, { "name": "frequency", "type": "integer", "required": false, "description": "TikTok frequency.", "example": 1, "platform": [ "tiktok" ] }, { "name": "frequency_schedule", "type": "integer", "required": false, "description": "TikTok frequency schedule.", "example": 1, "platform": [ "tiktok" ] }, { "name": "gender", "type": "string", "required": false, "description": "TikTok gender.", "example": "value", "platform": [ "tiktok" ] }, { "name": "interest_category_ids", "type": "list", "required": false, "description": "TikTok interest category ids.", "example": [], "platform": [ "tiktok" ] }, { "name": "interest_keyword_ids", "type": "list", "required": false, "description": "TikTok interest keyword ids.", "example": [], "platform": [ "tiktok" ] }, { "name": "languages", "type": "list", "required": false, "description": "TikTok languages.", "example": [], "platform": [ "tiktok" ] }, { "name": "location_ids", "type": "list", "required": false, "description": "TikTok location ids.", "example": [], "platform": [ "tiktok" ] }, { "name": "name", "type": "string", "required": false, "description": "TikTok name.", "example": "value", "platform": [ "tiktok" ] }, { "name": "operating_systems", "type": "list", "required": false, "description": "TikTok operating systems.", "example": [], "platform": [ "tiktok" ] }, { "name": "optimization_goal", "type": "string", "required": false, "description": "TikTok optimization goal.", "example": "value", "platform": [ "tiktok" ] }, { "name": "pacing", "type": "string", "required": false, "description": "TikTok pacing.", "example": "value", "platform": [ "tiktok" ] }, { "name": "placement_type", "type": "string", "required": false, "description": "TikTok placement type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "placements", "type": "list", "required": false, "description": "TikTok placements.", "example": [], "platform": [ "tiktok" ] }, { "name": "promotion_type", "type": "string", "required": false, "description": "TikTok promotion type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "schedule_end_time", "type": "string", "required": false, "description": "TikTok schedule end time.", "example": "value", "platform": [ "tiktok" ] }, { "name": "schedule_start_time", "type": "string", "required": false, "description": "TikTok schedule start time.", "example": "value", "platform": [ "tiktok" ] }, { "name": "schedule_type", "type": "string", "required": false, "description": "TikTok schedule type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "status", "type": "string", "required": false, "description": "TikTok only: optional shared create state. Omit or PAUSED to create disabled; ENABLED requires explicit HITL approval. Items may override with the same two-value contract.", "example": "PAUSED", "enum": [ "PAUSED", "ENABLED" ], "source": "constant", "platform": [ "tiktok" ] }, { "name": "zipcode_ids", "type": "list", "required": false, "description": "TikTok zipcode ids.", "example": [], "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true|false, \"total\": 2, \"succeeded\": 1, \"failed\": 1, \"items\": [{\"index\": 0, \"client_ref\": \"row-1\", \"success\": true, \"stage\": \"...\", \"campaign_id|adset_id|creative_id|ad_id\": \"...\"}]}\nTikTok output: raw.items[] carries each TikTok result under raw; read raw.items[].raw.adgroup_id or raw.items[].raw.id for created ad-group ids." }, { "id": "action:ads/bulk_create_campaigns", "namespace": "ads", "action": "bulk_create_campaigns", "description": "Bulk-create Meta campaigns in one review-gated request; executes items sequentially. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_campaign" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nEach merged item requires client_ref, name, and objective_type. Shared top-level defaults may supply name/objective_type unless an item overrides them. Items cannot override the top-level advertiser account. Missing per-item fields fail locally without a TikTok request; valid items continue and preserve input order.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nCreate multiple Meta campaigns as one human-approved CLI/agent operation. This is a single-layer bulk action: it does not create a campaign tree or resolve cross-layer dependencies by itself.\n\nUse when:\n- The user has already prepared several Meta campaigns with the same account_id.\n- The operation is one hierarchy layer at a time, with all parent ids already known.\n- The user wants one approval record and ordered per-item results.\n\nDo not use when:\n- The user needs a full campaign -> ad set -> ad tree in one request. Create each layer separately and feed returned ids into the next layer.\n- The user only needs one object. Use action:ads/create_campaign instead.\n- The platform is Google or the account_id is unknown.\n\nWorkflow:\n1. Prepare a JSON items array. Every item must include unique client_ref.\n2. Put fields accepted by action:ads/create_campaign either at the top level as shared defaults or on individual items as overrides.\n3. Submit this action for review. Approval runs asynchronously for CLI bulk flows; poll the review for final item results.\n\nSide effects:\n- After approval, creates Meta campaigns in the target ad account.\n- Execution is sequential and non-transactional; already-created platform objects remain even if a later item fails.\n- Review rejection creates no platform-side objects.\n\nOutput semantics:\n- items[] preserves input order and includes index, client_ref, success, stage, created id field when available, and raw/error details.\n- The operation is not transactional: earlier successes are not rolled back if a later item fails.\n- No resume/idempotency in v1; use client_ref to map outputs and build a new retry file for failed items.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id.", "example": "act_123456789", "platform": [ "meta", "tiktok" ] }, { "name": "items", "type": "array", "required": true, "description": "Non-empty array of item objects. Each item requires unique client_ref and may include fields accepted by action:ads/create_campaign. Required when platform=tiktok.", "example": [ { "client_ref": "row-1", "name": "Example" } ], "platform": [ "meta", "tiktok" ] }, { "name": "budget", "type": "number", "required": false, "description": "TikTok budget.", "example": 1.0, "platform": [ "tiktok" ] }, { "name": "budget_mode", "type": "string", "required": false, "description": "TikTok budget mode.", "example": "value", "platform": [ "tiktok" ] }, { "name": "budget_optimize_on", "type": "boolean", "required": false, "description": "TikTok budget optimize on.", "example": true, "platform": [ "tiktok" ] }, { "name": "concurrency", "type": "integer", "required": false, "description": "TikTok only: parallel item limit; defaults to 4 and is clamped to 1–8.", "example": 4, "platform": [ "tiktok" ] }, { "name": "name", "type": "string", "required": false, "description": "TikTok name.", "example": "value", "platform": [ "tiktok" ] }, { "name": "objective_type", "type": "string", "required": false, "description": "TikTok objective type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "special_industries", "type": "list", "required": false, "description": "TikTok special industries.", "example": [], "platform": [ "tiktok" ] }, { "name": "status", "type": "string", "required": false, "description": "TikTok only: optional shared create state. Omit or PAUSED to create disabled; ENABLED requires explicit HITL approval. Items may override with the same two-value contract.", "example": "PAUSED", "enum": [ "PAUSED", "ENABLED" ], "source": "constant", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true|false, \"total\": 2, \"succeeded\": 1, \"failed\": 1, \"items\": [{\"index\": 0, \"client_ref\": \"row-1\", \"success\": true, \"stage\": \"...\", \"campaign_id|adset_id|creative_id|ad_id\": \"...\"}]}\nTikTok output: raw.items[] carries each TikTok result under raw; read raw.items[].raw.campaign_id or raw.items[].raw.id for created campaign ids." }, { "id": "action:ads/clear_conversion_group_manual_campaign", "namespace": "ads", "action": "clear_conversion_group_manual_campaign", "description": "Review-gated clear of a manual campaign include/exclude on a conversion group.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/set_conversion_group_manual_campaign", "action:ads/list_conversion_groups" ], "long_description": "Removes a manual include or exclude for a campaign on an Admin View conversion group after human approval. Idempotent. Requires expected_version and the INTERNAL campaign_id.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "group_id", "type": "integer", "required": true, "description": "Conversion group id." }, { "name": "expected_version", "type": "string", "required": true, "description": "Opaque version from list_conversion_groups.groups[].version." }, { "name": "campaign_id", "type": "integer", "required": true, "description": "Internal campaign id." } ], "output_shape": "{\"cleared\": true, \"conversion_group\": {\"id\": 1, \"slug\": \"...\", \"version\": \"opaque\"}}" }, { "id": "action:ads/create_ad", "namespace": "ads", "action": "create_ad", "description": "Create an ad on Google (RSA) or Meta in PAUSED state; review-gated, reversible via remove_ad. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ads", "action:ads/update_ad", "action:ads/remove_ad", "action:ads/create_ad_creative", "action:_batch/submit" ], "long_description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nEvery inline creative is created with operation_status=DISABLE by default. Only an explicitly approved top-level status=ENABLED applies ENABLE to every creative; a per-creative operation_status value cannot bypass the reviewed status. Choose one compatible creative route: SINGLE_VIDEO uses video_id or tiktok_item_id; an uploaded video_id may include exactly one image_id as a cover. SINGLE_IMAGE/CAROUSEL_ADS use image_ids. AUTH_CODE requires tiktok_item_id. TT_USER and BC_AUTH_TT accept a discovered post, an uploaded video pushed through the linked identity, or CAROUSEL_ADS image_ids; BC_AUTH_TT additionally requires identity_authorized_bc_id. Call get_account_pages once to list identities, then again with identity_id and identity_type to discover Spark tiktok_item_id values. References are verified before write.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nCreate a new ad. On Google this is a Responsive Search Ad (RSA) requiring at least 3 headlines and 2 descriptions. On Meta it uses an existing creative_id. Meta lead-gen creatives are semantically preflighted before the create call. Always force-paused at create.\n\nUse when:\n- The user wants to add an ad to an ad group (Google) or ad set (Meta).\n- A workflow needs to register a new RSA or creative-based ad.\n\nDo not use when:\n- The Meta creative does not exist yet. Use action:ads/create_ad_creative first.\n- The Meta creative is an inline lead-gen spec with lead_gen_form_id. Use create_ad_creative first and pass creative_id so the handler can validate the existing creative, adset, campaign, page, and form before creating the ad.\n- The user wants to update an existing ad's URLs or creative. Use action:ads/update_ad.\n- The Google RSA's headlines or descriptions need to change later. RSA creative fields are immutable; use remove_ad then create_ad.\n\nWorkflow:\n1. (Google) Choose ad_group_id via action:ads/gaql_search; build headlines (>=3) and descriptions (>=2). (Meta) Choose adset_id via list_adsets and creative_id via list_ads or create_ad_creative.\n2. (Meta lead-gen) Confirm the creative uses the same page as the lead form; the handler then checks destination_type=ON_AD, optimization_goal=LEAD_GENERATION, campaign objective OUTCOME_LEADS/LEAD_GENERATION, and ACTIVE form ownership.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/list_ads.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Google raw.resource_id is the new ad's numeric id; ad_group_ad_resource_name is the composite path needed for later remove_ad.\n- Meta raw.id is the new ad id.\n- Google returns {success: false, error: '...'} (no platform call) on RSA validation failure (fewer than 3 headlines or 2 descriptions).\n\n- Meta lead-gen semantic failures return success=false with META_LEADGEN_* error_code before the create-ad write.\n\nSide effects:\n- Creates an AdGroupAd (Google) or Ad (Meta).\n- Status is force-paused on both platforms regardless of caller input.\n- No immediate spend; activation requires update_ad with status=ENABLED/ACTIVE.\n- Reversible via action:ads/remove_ad (Google REMOVED terminal; Meta archives).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin.", "example": "google", "enum": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "adset_id", "type": "string", "required": false, "description": "Provider-native parent id for Google, Meta, and TikTok. Do not send this field for ChatGPT Ads; send ad_group_id instead.", "example": "23...", "source": "upstream:action:ads/create_ad_group,action:ads/gaql_search,action:ads/create_adset,action:ads/list_adsets", "platform": [ "google", "meta", "tiktok" ] }, { "name": "name", "type": "string", "required": true, "description": "Meta only (required): the ad name. Required by the registry and by Meta's Ad node create contract. Source: user-provided ad name. ChatGPT Ads: Required non-empty authored Ad name used for provider persistence and reconciliation.", "example": "Research_workflow_01", "platform": [ "meta", "chatgpt_ads" ] }, { "name": "final_urls", "type": "list", "required": false, "description": "Google only (required): landing-page URLs for the RSA. Schema required flag now matches the registry's required_extras_by_platform=google. Source: user-provided landing-page URL(s).", "example": [ "https://example.com/landing" ], "platform": [ "google" ] }, { "name": "headlines", "type": "list", "required": false, "description": "Google only (required): 3-15 RSA headlines, each <=30 chars. Each item may be a plain string OR an object {text, pinned_field} where pinned_field is HEADLINE_1|HEADLINE_2|HEADLINE_3 to lock the asset into a fixed slot. Source: agent-generated ad copy; pinned_field is a constant enum chosen for brand/legal lines.", "example": [ "Free Shipping", { "text": "Acme Official", "pinned_field": "HEADLINE_1" }, "Shop Now" ], "platform": [ "google" ] }, { "name": "descriptions", "type": "list", "required": false, "description": "Google only (required): 2-4 RSA descriptions, each <=90 chars. Each item may be a plain string OR {text, pinned_field} with pinned_field DESCRIPTION_1|DESCRIPTION_2. Source: agent-generated ad copy; pinned_field is a constant enum.", "example": [ "Quality gear delivered fast.", { "text": "30-day returns guaranteed.", "pinned_field": "DESCRIPTION_1" } ], "platform": [ "google" ] }, { "name": "path1", "type": "string", "required": false, "description": "Google only: first display-URL path segment shown after the domain (max 15 chars). Pure cosmetic display URL, independent of final_urls. Source: user-provided / agent-composed short keyword string.", "example": "shoes", "platform": [ "google", "meta" ] }, { "name": "path2", "type": "string", "required": false, "description": "Google only: second display-URL path segment (max 15 chars). Requires path1 to be set; rejected otherwise. Source: user-provided / agent-composed short keyword string.", "example": "running", "platform": [ "google", "meta" ] }, { "name": "tracking_url_template", "type": "string", "required": false, "description": "Google only: ad-level click-tracking URL template (ValueTrack/{lpurl} macros). Attaches tracking without changing final_urls. Source: user-provided tracking template, often from an ad-tech/analytics vendor.", "example": "{lpurl}?utm_source=google", "platform": [ "google", "meta" ] }, { "name": "final_url_suffix", "type": "string", "required": false, "description": "Google only: query-string suffix appended to the final URL after redirects (ValueTrack params). Source: user-provided URL suffix string.", "example": "utm_campaign=spring&utm_medium=cpc", "platform": [ "google", "meta" ] }, { "name": "creative_id", "type": "string", "required": false, "description": "Meta only: ID of a pre-created AdCreative to attach to this ad. Required UNLESS you pass an inline 'creative' spec instead. One of creative_id or creative must be provided. Source: output of create_ad_creative (the returned creative id).", "example": "23847900000000123", "one_of_group": "creative_ref", "source": "upstream:action:ads/create_ad_creative", "platform": [ "google", "meta" ] }, { "name": "creative", "type": "object", "required": false, "description": "Meta only: inline AdCreative spec used INSTEAD of creative_id, e.g. {\"object_story_spec\": {\"page_id\": \"...\", \"link_data\": {...}}}. Provide either this or creative_id, not both. If both are sent the inline spec is ignored. Inline lead-gen creatives containing lead_gen_form_id are rejected; create the creative first and pass creative_id. Source: constructed by the agent from page_id + an uploaded image_hash (output of upload_image) / video_id.", "example": { "object_story_spec": { "page_id": "1234567890", "link_data": { "message": "Shop now", "link": "https://example.com", "image_hash": "abc123" } } }, "one_of_group": "creative_ref", "source": "user", "platform": [ "google", "meta" ] }, { "name": "tracking_specs", "type": "list", "required": false, "description": "Meta only: list of conversion-tracking spec objects, e.g. [{\"action.type\": [\"offsite_conversion\"], \"fb_pixel\": [\"\"]}]. Source: pixel id from the account's tracking setup.", "example": [ { "action.type": [ "offsite_conversion" ], "fb_pixel": [ "1234567890" ] } ], "platform": [ "google", "meta" ] }, { "name": "bid_amount", "type": "integer", "required": false, "description": "Meta only: ad-level bid override in the account currency's minor units (cents). Usually managed at the ad-set level; only set when an ad-specific bid cap is required. Source: user-provided bid cap (minor units).", "example": 150, "platform": [ "google", "meta" ] }, { "name": "status", "type": "string", "required": false, "description": "Provider-specific create lifecycle value with no global enum: Google and Meta force paused; TikTok maps omitted/PAUSED to operation_status=DISABLE and an explicitly approved status=ENABLED to operation_status=ENABLE; ChatGPT Ads accepts only omitted or lowercase paused.", "source": "constant" }, { "name": "content", "type": "object", "required": false, "description": "LinkedIn only: creative content union, e.g. a post reference {\"reference\": \"urn:li:ugcPost:123\"}.", "platform": [ "linkedin" ] }, { "name": "inline_content", "type": "object", "required": false, "description": "LinkedIn only: full inline post payload (createInline action) used instead of content when sponsoring a new post in one call.", "platform": [ "linkedin" ] }, { "name": "creatives", "type": "array", "required": true, "description": "TikTok only: required — up to 20 creative objects for one ad group. Every creative needs identity_type, identity_id, and ad_format. SINGLE_VIDEO uses video_id or tiktok_item_id; an uploaded video_id may also include image_ids as its cover. CUSTOMIZED_USER supports uploaded images. TT_USER and BC_AUTH_TT additionally support CAROUSEL_ADS; CAROUSEL_ADS requires 2 to 35 image_ids. AUTH_CODE uses tiktok_item_id. BC_AUTH_TT also requires identity_authorized_bc_id. Add objective-specific copy, CTA, and destination fields required by the target ad group. TikTok has no standalone creative object.", "example": [ { "identity_type": "CUSTOMIZED_USER", "identity_id": "identity-1", "ad_format": "SINGLE_VIDEO", "video_id": "video-1", "image_ids": [ "cover-image-1" ] }, { "identity_type": "BC_AUTH_TT", "identity_id": "identity-2", "identity_authorized_bc_id": "bc-1", "ad_format": "SINGLE_VIDEO", "tiktok_item_id": "7663153197380406542" } ], "platform": [ "tiktok" ] }, { "name": "headline", "type": "string", "required": false, "description": "Authored Ad headline.", "platform": [ "chatgpt_ads" ] }, { "name": "copy", "type": "string", "required": false, "description": "Authored Ad body copy.", "platform": [ "chatgpt_ads" ] }, { "name": "cta", "type": "string", "required": false, "description": "Explicitly approved call to action.", "platform": [ "chatgpt_ads" ] }, { "name": "description", "type": "string", "required": false, "description": "Optional authored Ad description.", "platform": [ "chatgpt_ads" ] }, { "name": "landing_page", "type": "string", "required": false, "description": "Final HTTP(S) destination URL.", "platform": [ "chatgpt_ads" ] }, { "name": "image_url", "type": "string", "required": false, "description": "Optional durable public image URL.", "platform": [ "chatgpt_ads" ] }, { "name": "ad_group_id", "type": "string", "required": false, "description": "ChatGPT Ads canonical Ad Group id. Required for ChatGPT Ads, which does not accept adset_id.", "example": "ag_123", "source": "upstream:action:ads/create_ad_group,action:ads/list_ad_groups,action:ads/gaql_search", "platform": [ "chatgpt_ads" ] }, { "name": "adgroup_id", "type": "string", "required": false, "description": "TikTok adgroup id.", "example": "value", "source": "upstream:action:ads/create_adset,action:ads/list_adsets", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true, \"message\": \"Responsive Search Ad created successfully\", \"resource_id\": \"123\", \"results\": [{\"adGroupAdResult\": {\"resourceName\": \"customers/X/adGroupAds/AG~AD\"}}]} // Meta: {\"id\": \"23...\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_ad_creative", "namespace": "ads", "action": "create_ad_creative", "description": "Create a Meta ad creative (image / video / carousel); review-gated, reversible via UI.", "mode": "write", "platforms": [ "meta" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/upload_image", "action:ads/upload_video", "action:ads/get_account_pages", "action:ads/create_ad", "action:ads/remove_ad_creative", "action:_batch/submit" ], "long_description": "Primary use:\nBuild a Meta ad creative wired to a Page, with one of image_hash / image_url / video_id / child_attachments (carousel). The creative is then attached to ads.\n\nUse when:\n- The user wants to create a new ad creative for an existing media asset.\n- A workflow needs a creative_id for action:ads/create_ad.\n\nDo not use when:\n- The user wants only an inline preview without persisting a creative. Use action:ads/generate_ad_preview.\n- The image / video hasn't been uploaded. Use action:ads/upload_image or action:ads/upload_video first.\n- The platform is Google. Use action:ads/create_ad directly.\n\nWorkflow:\n1. Upload media via action:ads/upload_image or upload_video to get image_hash / video_id.\n2. Get a page_id via action:ads/get_account_pages.\n3. Submit via action:_batch/submit for HITL review.\n4. Wire the new creative_id into action:ads/create_ad.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.id is the new creative id.\n- Returns success=false with 'create_ad_creative requires image_hash, image_url, video_id, or child_attachments' if no media identifier was provided.\n\nSide effects:\n- Builds object_story_spec from the supplied media:\n - video_id -> video_data (title=headline, image_url=thumbnail_url, image_hash=thumbnail_image_hash)\n - child_attachments -> link_data with child_attachments JSON list (carousel)\n - else -> link_data with image_hash OR picture=image_url\n- For lead-gen image creatives, link_data.link and call_to_action.value.link must remain the external landing-page URL; do not use a Facebook Page short link or create_ad may fail with 'lead ad creative did not use external URL'.\n- Public URLs (image_url, thumbnail_url) are validated for https, reachability, no IP literals.\n- Removable via action:ads/remove_ad_creative (irreversible hard delete; fails while ads still reference the creative).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Creative display name." }, { "name": "page_id", "type": "string", "required": true, "description": "Facebook Page id from action:ads/get_account_pages." }, { "name": "image_hash", "type": "string", "required": false, "description": "Image hash from action:ads/upload_image.image_hash.", "one_of_group": "media", "source": "upstream:action:ads/upload_image" }, { "name": "image_url", "type": "string", "required": false, "description": "Public https image URL (alternative to image_hash).", "one_of_group": "media", "source": "user" }, { "name": "video_id", "type": "string", "required": false, "description": "Video id from action:ads/upload_video.id.", "one_of_group": "media", "source": "upstream:action:ads/upload_video" }, { "name": "thumbnail_url", "type": "string", "required": false, "description": "Public https thumbnail URL (used with video creatives).", "source": "user" }, { "name": "thumbnail_image_hash", "type": "string", "required": false, "description": "Meta image hash used as the thumbnail for video creatives. Prefer this when action:ads/upload_image already produced an image_hash for the video thumbnail.", "source": "upstream:action:ads/upload_image" }, { "name": "child_attachments", "type": "list", "required": false, "description": "Carousel child attachments list (alternative to single image/video).", "one_of_group": "media", "source": "user" }, { "name": "message", "type": "string", "required": false, "description": "Meta only: primary text of the ad. Required for carousel (child_attachments) creatives per AdCreativeLinkData. Source: user-provided ad copy.", "example": "Shop our summer sale now", "source": "user" }, { "name": "headline", "type": "string", "required": false, "description": "Headline shown above the link description.", "source": "user" }, { "name": "description", "type": "string", "required": false, "description": "Meta only: description below the headline. For image/carousel it maps to link_data.description; for video creatives it maps to AdCreativeVideoData.link_description (the field name differs). Source: user-provided ad copy.", "example": "Free shipping on orders over $50", "source": "user" }, { "name": "link", "type": "string", "required": false, "description": "Meta only: destination URL. Effectively required for single-image and carousel creatives (Graph rejects with error 100 'link is required' otherwise). For image lead-form creatives this MUST be an external landing-page URL; the handler keeps it on link_data.link and call_to_action.value.link so create_ad(creative_id) remains valid for ON_AD lead ads. For video creatives the link only takes effect through the call_to_action and is ignored unless a CTA is built. Source: user-provided landing-page URL.", "example": "https://example.com/landing", "source": "user" }, { "name": "caption", "type": "string", "required": false, "description": "Meta only: caption (display URL) shown in the link attachment for image/carousel link_data creatives. Source: user-provided display URL string.", "example": "example.com", "source": "user" }, { "name": "url_tags", "type": "string", "required": false, "description": "Meta only: query-string appended to the creative's link URLs for click tracking (e.g. 'utm_source=meta&utm_id=...'). Source: user-provided UTM / tracking template.", "example": "utm_source=meta&utm_medium=paid", "source": "user" }, { "name": "lead_gen_form_id", "type": "string", "required": false, "description": "Meta only: Lead Ad instant-form id; placed in call_to_action.value.lead_gen_form_id so the creative opens the form. Do not place this id directly on link_data or video_data; Meta rejects those story-data fields. When supplied without call_to_action_type the handler defaults the CTA type to SIGN_UP. For image creatives also provide link as the external landing-page URL; do not use a Facebook Page short link. Source: raw.id output of action:ads/create_lead_form.", "example": "1203948576019283", "source": "upstream:action:ads/create_lead_form" }, { "name": "object_story_id", "type": "string", "required": false, "description": "Meta only: id of an existing published Page post (format _) to boost. Mutually exclusive with building object_story_spec; when provided, image_hash/video_id/child_attachments/link/message are ignored. Source: user-provided existing Page post id, or a post id surfaced by a Page-content read.", "example": "1234567890_9876543210", "one_of_group": "media", "source": "user" }, { "name": "call_to_action_type", "type": "string", "required": false, "description": "Meta only: CTA button. The documented set (LEARN_MORE, SHOP_NOW, SIGN_UP, DOWNLOAD, GET_QUOTE, CONTACT_US, SUBSCRIBE, APPLY_NOW, BOOK_TRAVEL) is a subset of Meta's enum; the chosen value must be compatible with the campaign objective or Meta rejects the creative. For video creatives the destination URL only takes effect when a CTA is present. Source: constant enum chosen by the agent.", "example": "SHOP_NOW", "enum": [ "LEARN_MORE", "SHOP_NOW", "SIGN_UP", "DOWNLOAD", "GET_QUOTE", "CONTACT_US", "SUBSCRIBE", "APPLY_NOW", "BOOK_TRAVEL" ], "source": "constant" }, { "name": "instagram_user_id", "type": "string", "required": false, "description": "Optional Instagram user id for cross-posting. Must be the ad-account-connected IG identity from action:ads/get_connected_instagram_accounts — a public IG @handle or the Facebook Page id is rejected by the ads API." }, { "name": "instagram_actor_id", "type": "string", "required": false, "description": "Optional Instagram actor id (legacy alias)." }, { "name": "asset_feed_spec", "type": "object", "required": false, "description": "Meta only: asset feed spec, serving TWO distinct modes. (A) Dynamic creative (Advantage+): multiple assets per type, Meta auto-combines optimized variations — this mode REQUIRES a dynamic-creative-enabled ad set (see update_adset.is_dynamic_creative). (B) Placement asset customization: one asset per placement (e.g. 4:5 image for feed, 9:16 for story) mapped via asset_customization_rules — this mode works on a NORMAL ad set, NO dynamic-creative flag needed (live-verified). Either way the handler builds a minimal object_story_spec (page identity only) alongside it. Shape: {images:[{hash, adlabels:[{name}]}] and/or videos:[{video_id,thumbnail_hash}], bodies:[{text}], titles:[{text}], descriptions:[{text}], link_urls:[{website_url}], call_to_action_types:[...], ad_formats:[...], asset_customization_rules:[{customization_spec:{publisher_platforms, facebook_positions and/or instagram_positions}, image_label:{name}, priority}]}. For mode B: give each image an adlabels name, then map it with a rule whose image_label.name matches; Meta auto-fills age_min/age_max into customization_spec on readback. REQUIRED sub-keys: at least one of images/videos, AND ad_formats (e.g. ['SINGLE_IMAGE'], ['CAROUSEL'], ['SINGLE_VIDEO']) — the handler rejects the call otherwise; placement customization uses ['SINGLE_IMAGE']. Mutually exclusive with the single-asset fields (image_hash / image_url / video_id / child_attachments / object_story_id): put those assets inside the asset_feed_spec arrays. Top-level copy fields (message / headline / link / description / caption) are ignored on this path — put copy in bodies / titles / descriptions / link_urls instead. Top-level call_to_action_type and lead_gen_form_id are REJECTED (not silently dropped) — put call_to_action_types (and the lead form) inside the asset_feed_spec. Instagram placements additionally need platform_extras.instagram_user_id from get_connected_instagram_accounts. Source: Meta Advantage+ creative / asset_feed_spec reference + live verification (2026-07, facebook feed+story placement customization end-to-end).", "example": { "images": [ { "hash": "hashA", "adlabels": [ { "name": "feed-45" } ] }, { "hash": "hashB", "adlabels": [ { "name": "story-916" } ] } ], "bodies": [ { "text": "Shop the new collection" } ], "titles": [ { "text": "Summer Sale" } ], "link_urls": [ { "website_url": "https://example.com" } ], "call_to_action_types": [ "LEARN_MORE" ], "ad_formats": [ "SINGLE_IMAGE" ], "asset_customization_rules": [ { "customization_spec": { "publisher_platforms": [ "facebook" ], "facebook_positions": [ "feed" ] }, "image_label": { "name": "feed-45" }, "priority": 1 }, { "customization_spec": { "publisher_platforms": [ "facebook" ], "facebook_positions": [ "story" ] }, "image_label": { "name": "story-916" }, "priority": 2 } ] }, "source": "user" }, { "name": "creative_features", "type": "object", "required": false, "description": "Meta only: opt in/out of Meta's AI creative enhancements, mapping feature name -> OPT_IN | OPT_OUT (or {'enroll_status': 'OPT_OUT'}). 69 features are accepted. THREE are OPTED OUT BY DEFAULT even when this parameter is omitted, because Meta enables them silently and they rewrite approved creative: add_text_overlay, image_touchups, text_optimizations. Pass OPT_IN explicitly to re-enable one. The other 66 are controllable but inherit Meta's own default, so omitting them is NOT the same as disabling them. Feature names carry three confidence tiers: 42 appear in both Meta's reference page and its SDK, 4 only in the docs (generate_cta, text_extraction_for_headline, text_extraction_for_tap_target, translate_voiceover), and 23 only in the SDK (e.g. image_enhancement, video_auto_crop, text_generation) — for those last two tiers per-account availability is unverified, so a platform-side rejection is expected rather than a bug. REJECTED here: music_generation (Meta implements music via asset_feed_spec.audios — use disable_music) and standard_enhancements / standard_enhancements_catalog (Marketing API v22+ fails writes carrying them, though a GET still returns them). Features Meta marks AI-generated (image_animation, image_background_gen, image_templates, image_uncrop, video_uncrop) additionally require creating the ad with status=PAUSED and previewing before activation. Ineligible OPT_IN features are silently removed by Meta rather than erroring, so read the creative back to confirm what actually applied. Source: user/agent decision.", "example": { "image_enhancement": "OPT_OUT", "video_auto_crop": "OPT_OUT" }, "platform": [ "meta" ] }, { "name": "disable_music", "type": "boolean", "required": false, "description": "Meta only: when true, suppresses Meta's automatically added background music by writing an empty asset_feed_spec.audios. Music is the one enhancement creative_features cannot control — Meta implements it through asset_feed_spec, so passing music_generation to creative_features is rejected and routed here. Only valid on the asset_feed_spec path: a single-asset creative (image_hash / image_url / video_id / child_attachments / object_story_id) cannot carry audios at all, and the call is rejected with that reason rather than silently having no effect. When no asset_feed_spec is supplied, an audios-only spec is created alongside the page-identity object_story_spec, matching Meta's own documented music sample. Optional. Source: user/agent decision boolean.", "example": true, "platform": [ "meta" ] } ], "output_shape": "{\"id\": \"creative_id\"}" }, { "id": "action:ads/create_ad_group", "namespace": "ads", "action": "create_ad_group", "description": "Create a paused Google or ChatGPT Ads Ad Group; review-gated. ChatGPT Ads supports manual authored Ads or a generative group.", "mode": "write", "platforms": [ "google", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/update_ad_group", "action:ads/remove_ad_group", "action:_batch/submit" ], "long_description": "Primary use:\nCreate a paused Ad Group under an existing Google or ChatGPT Ads Campaign.\n\nUse when:\n- The user approved a new Google search Ad Group or a ChatGPT Ads manual/generative group.\n- A ChatGPT Ads hierarchy needs an independently created group after Campaign creation.\n\nDo not use when:\n- The platform is Meta or TikTok; use action:ads/create_adset.\n- The ChatGPT Ads group lacks required name, landing_page, or brand_name.\n- The caller wants an active create; both providers create paused.\n\nWorkflow:\n1. Read the parent Campaign and prepare the exact group fields.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify the returned id and persisted group detail. ChatGPT Ads additionally verifies the complete authored-Ad window and caller-controlled field signature.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- resource_id is the new Ad Group id; ChatGPT Ads also returns ad_group_id and exact persisted fields.\n\nSide effects:\n- Creates one paused Ad Group. A ChatGPT Ads manual group may atomically create its paused authored Ads; a generative group must not contain authored Ads.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, chatgpt_ads.", "example": "google", "enum": [ "google", "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Parent campaign id from action:ads/list_campaigns.", "example": "123456", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "name", "type": "string", "required": true, "description": "Ad group display name.", "example": "Search - Branded" }, { "name": "cpc_bid_micros", "type": "integer", "required": false, "description": "Default CPC bid in micros (1 USD = 1_000_000).", "example": 1000000, "platform": [ "google" ] }, { "name": "type", "type": "string", "required": false, "description": "Google only: AdGroup type enum, immutable after create. Defaults to SEARCH_STANDARD when omitted. Must match the parent campaign's channel or the platform rejects it. Source: constant enum value chosen by the agent based on the parent campaign type from action:ads/list_campaigns.", "example": "DISPLAY_STANDARD", "platform": [ "google" ] }, { "name": "target_cpa_micros", "type": "integer", "required": false, "description": "Google only: ad-group-level target CPA override in micros (1 USD = 1_000_000). Only effective when the parent campaign uses TargetCpa or MaximizeConversions with a target CPA. Source: user-provided target CPA budget converted to micros.", "example": 5000000, "platform": [ "google" ] }, { "name": "target_roas", "type": "number", "required": false, "description": "Google only: ad-group-level target ROAS override as a ratio (e.g. 4.0 = 400%). Only effective when the parent campaign uses TargetRoas or MaximizeConversionValue with a target ROAS. Source: user-provided target return-on-ad-spend ratio.", "example": 4.0, "platform": [ "google" ] }, { "name": "tracking_url_template", "type": "string", "required": false, "description": "Google only: ad-group-level tracking URL template for click measurement. Source: user-provided tracking URL template (often from a third-party click tracker).", "example": "https://tracker.example.com/?url={lpurl}", "platform": [ "google" ] }, { "name": "final_url_suffix", "type": "string", "required": false, "description": "Google only: ad-group-level suffix appended to the final URL when parallel tracking is enabled. Source: user-provided URL suffix string.", "example": "src=google&campaign=spring", "platform": [ "google" ] }, { "name": "group_type", "type": "string", "required": false, "description": "manual (default) or generative.", "example": "manual", "enum": [ "manual", "generative" ], "source": "constant", "platform": [ "chatgpt_ads" ] }, { "name": "context", "type": "string", "required": false, "description": "Conversation context used by the Ad Group.", "platform": [ "chatgpt_ads" ] }, { "name": "negative_context", "type": "string", "required": false, "description": "Explicitly approved contexts where the Ad Group must not appear.", "platform": [ "chatgpt_ads" ] }, { "name": "ad_prompt", "type": "string", "required": false, "description": "Explicitly approved generation prompt; required by the deployment workflow for generative groups.", "platform": [ "chatgpt_ads" ] }, { "name": "image_url", "type": "string", "required": false, "description": "Optional durable public Ad Group image URL.", "platform": [ "chatgpt_ads" ] }, { "name": "daily_budget", "type": "number", "required": false, "description": "Optional Ad Group daily budget.", "platform": [ "chatgpt_ads" ] }, { "name": "auto_optimize_budget", "type": "boolean", "required": false, "description": "Whether the platform automatically optimizes the Ad Group budget. Defaults to true.", "platform": [ "chatgpt_ads" ] }, { "name": "min_daily_budget", "type": "number", "required": false, "description": "Optional minimum daily-spend bound.", "platform": [ "chatgpt_ads" ] }, { "name": "max_daily_budget", "type": "number", "required": false, "description": "Optional maximum daily-spend bound.", "platform": [ "chatgpt_ads" ] }, { "name": "max_cpc", "type": "number", "required": false, "description": "Optional maximum CPC bound.", "platform": [ "chatgpt_ads" ] }, { "name": "sort_order", "type": "integer", "required": false, "description": "Optional stable Ad Group display order.", "platform": [ "chatgpt_ads" ] }, { "name": "allowed_countries", "type": "list", "required": false, "description": "Explicitly approved country targeting.", "platform": [ "chatgpt_ads" ] }, { "name": "allowed_devices", "type": "list", "required": false, "description": "Explicitly approved device targeting.", "platform": [ "chatgpt_ads" ] }, { "name": "include_audience_ids", "type": "list", "required": false, "description": "Audience ids explicitly included.", "platform": [ "chatgpt_ads" ] }, { "name": "exclude_audience_ids", "type": "list", "required": false, "description": "Audience ids explicitly excluded.", "platform": [ "chatgpt_ads" ] }, { "name": "example_query", "type": "string", "required": false, "description": "Example query that should match the group.", "platform": [ "chatgpt_ads" ] }, { "name": "utm_params", "type": "list", "required": false, "description": "Ordered UTM objects with key and value fields.", "example": [ { "key": "utm_source", "value": "chatgpt" } ], "platform": [ "chatgpt_ads" ] }, { "name": "preview", "type": "object", "required": false, "description": "Structured advertiser-dashboard preview metadata.", "platform": [ "chatgpt_ads" ] }, { "name": "landing_page", "type": "string", "required": true, "description": "Required final HTTP(S) destination URL for the Ad Group.", "example": "https://example.com", "platform": [ "chatgpt_ads" ] }, { "name": "status", "type": "string", "required": false, "description": "Provider-specific paused create value: Google uses uppercase PAUSED; ChatGPT Ads accepts only lowercase paused. Omit when possible because the handler forces the provider-safe paused state.", "source": "constant" }, { "name": "brand_name", "type": "string", "required": true, "description": "Required brand display name shown by the advertiser dashboard.", "example": "Acme", "platform": [ "chatgpt_ads" ] }, { "name": "ads", "type": "list", "required": false, "description": "Authored Ads for a manual group. Each object requires name, headline, copy, cta, and landing_page; create forces every Ad to paused. Omit for generative groups.", "example": [ { "name": "Research_workflow_01", "headline": "Approved headline", "copy": "Approved body copy.", "cta": "Learn more", "landing_page": "https://example.com/?utm_source=nexad_c" } ], "platform": [ "chatgpt_ads" ] } ], "output_shape": "{\"success\": true, \"message\": \"Ad group created successfully\", \"resource_id\": \"123\", \"results\": [{\"adGroupResult\": {\"resourceName\": \"customers/X/adGroups/Y\"}}]}" }, { "id": "action:ads/create_ad_label", "namespace": "ads", "action": "create_ad_label", "description": "Create a Meta ad label on an account; review-gated, reversible via UI.", "mode": "write", "platforms": [ "meta" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_labels", "action:ads/apply_label", "action:_batch/submit" ], "long_description": "Primary use:\nCreate a Meta ad label for tagging campaigns, ad sets, ads, or creatives.\n\nUse when:\n- The user wants a new Meta-side tag (e.g. by quarter, theme).\n\nDo not use when:\n- The platform is Google. Use action:ads/create_label.\n- The user wants to apply an existing label. Use action:ads/apply_label.\n\nWorkflow:\n1. Choose name.\n2. Submit via action:_batch/submit for HITL review.\n3. Apply via action:ads/apply_label.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.id is the new ad label id (note: Meta apply_label uses NAME, not id).\n\nSide effects:\n- POSTs {account_id}/adlabels.\n- Reversible only via the Meta UI.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: account_id is marked required=True in the capability docs but its example/description is shared with Google ('For Google, digits only without dashes') even though create_ad_label is Meta-only; minor confusion, harmless because the action is gated to meta. Also note: Capability docs do not state that account_id may be omitted (executor auto-resolves from the active/owned account). Not a functional gap, but the agent is told it is required, which is the safe instruction. Capability gap: None material. The ad-account adlabels create edge has no additional commonly-needed official parameters (no description/color/reference fields on the modern AdLabel create). The action is contract-complete for its scope.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Ad label display name (case-sensitive; used as the key when applying)." } ], "output_shape": "{\"id\": \"ad_label_id\"}" }, { "id": "action:ads/create_ad_rule", "namespace": "ads", "action": "create_ad_rule", "description": "Create a Meta or TikTok automated ad rule; review-gated.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_rules", "action:ads/update_ad_rule", "action:ads/remove_ad_rule", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nCreate a scheduled automated rule that evaluates conditions against scoped objects and runs an execution (PAUSE, CHANGE_BUDGET, NOTIFY).\n\nUse when:\n- The user wants to automate pausing ads above a spend threshold.\n- A workflow needs scheduled budget adjustments based on metrics.\n\nDo not use when:\n- The platform is Google. Google has automated rules but they are not exposed here.\n- The user wants one-off mutations. Use the matching action:ads/update_* directly.\n\nWorkflow:\n1. Build evaluation_spec (e.g. {evaluation_type, filters: [{field, operator, value}]}).\n2. Build execution_spec (e.g. {execution_type: PAUSE, execution_options: [...]}).\n3. Optionally build schedule_spec.\n4. Submit via action:_batch/submit for HITL review.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.id is the new rule_id.\n\nSide effects:\n- Meta POSTs {account_id}/adrules_library; TikTok POSTs /optimizer/rule/create/. The rule begins running on its schedule.\n- The rule can pause ads or change budgets automatically once scheduled — review carefully.\n- Reversible only via the Meta UI.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Ad rule display name.", "platform": [ "meta" ] }, { "name": "evaluation_spec", "type": "object", "required": true, "description": "Meta only: evaluation spec. Shape {evaluation_type: SCHEDULE|TRIGGER, filters: [{field, operator, value}]}. MUST include a scoping filter on field 'entity_type' (e.g. {field:'entity_type', operator:'EQUAL', value:'AD'|'ADSET'|'CAMPAIGN'}) plus metric filters; metric thresholds should set a window via a 'time_preset' filter (e.g. LAST_7_DAYS). For TRIGGER use a trigger{type,field,value,operator} block. Source: agent-constructed from user intent (entity scope + thresholds).", "example": { "evaluation_type": "SCHEDULE", "filters": [ { "field": "entity_type", "operator": "EQUAL", "value": "AD" }, { "field": "spend", "operator": "GREATER_THAN", "value": 1000000 }, { "field": "time_preset", "operator": "EQUAL", "value": "LAST_7_DAYS" } ] }, "platform": [ "meta" ] }, { "name": "execution_spec", "type": "object", "required": true, "description": "Meta only: execution spec. Shape {execution_type: PAUSE|UNPAUSE|CHANGE_BUDGET|NOTIFICATION|PING_ENDPOINT, execution_options: [...]}. NOTE the enum value is NOTIFICATION (not NOTIFY). execution_options carries the action params: for NOTIFICATION a user_ids list; for CHANGE_BUDGET a change_spec {amount, unit, limit}; optionally an action_frequency. Source: agent-constructed from user intent (which action + its options).", "example": { "execution_type": "PAUSE" }, "platform": [ "meta" ] }, { "name": "schedule_spec", "type": "object", "required": false, "description": "Meta only: schedule spec; REQUIRED when evaluation_spec.evaluation_type=SCHEDULE, otherwise optional. Shape {schedule_type: DAILY|HOURLY|SEMI_HOURLY|CUSTOM, schedule: [...]} where CUSTOM needs explicit time windows. Source: agent-constructed from user cadence.", "example": { "schedule_type": "DAILY" }, "platform": [ "meta" ] }, { "name": "status", "type": "string", "required": false, "description": "Meta only: ad rule lifecycle status. Valid values are ENABLED, DISABLED, DELETED, HAS_ISSUES (the ad-rule enum, NOT the generic ACTIVE/PAUSED). Omit to default to ENABLED. Use DISABLED for an off/paused rule. Source: constant enum chosen by the agent.", "example": "ENABLED", "platform": [ "meta" ] }, { "name": "rules", "type": "array", "required": true, "description": "TikTok only: Non-empty optimizer rule objects. Every apply_objects item requires dimension and pre_condition_type.", "example": [ { "name": "Pause expensive ads", "actions": [ { "action_type": "DEFAULT", "subject_type": "TURN_OFF" } ], "apply_objects": [ { "dimension": "AD", "dimension_ids": [ "1870855452608946" ], "pre_condition_type": "SELECTED" } ], "conditions": [ { "subject_type": "COST", "match_type": "GT", "values": [ "100" ] } ], "notification": { "notification_type": "NOT_NOTIFICATION" }, "rule_exec_info": { "exec_time_type": "PER_HALF_HOUR" } } ], "source": "user", "platform": [ "tiktok" ] }, { "name": "lang", "type": "string", "required": false, "description": "TikTok lang.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"id\": \"rule_id\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_ad_unit", "namespace": "ads", "action": "create_ad_unit", "description": "Deprecated legacy compatibility: Create a ChatGPT Ads ad unit draft; review-gated.", "mode": "write", "platforms": [ "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_campaign" ], "long_description": "Deprecated compatibility path. New deployments use inline Campaign -> Ad Group -> Ad.\n\nPrimary use:\nCreate one ChatGPT Ads ad unit before creating a campaign. ChatGPT Ads campaigns require at least one ad_unit_id.\n\nUse when:\n- The user has approved ChatGPT Ads copy and landing page inputs.\n- A deployment flow needs an ad_unit_id for action:ads/create_campaign.\n\nDo not use when:\n- The platform is Google or Meta. Use their creative/ad actions instead.\n- The user only needs a media plan draft. Use the ChatGPT Ads media-plan workflow.\n\nWorkflow:\n1. Map media-plan Context Hints to context_hints and Body to static_ad_text; these values must be distinct. Prepare landing_page and static_cta.\n2. Submit create_ad_unit for HITL review.\n3. Use the returned resource_id/id in create_campaign.ad_unit_ids.\n\nSide effects:\n- Creates a draft ad unit in the advertiser dashboard.\n- Projects the ordered 2-4 context_hints into the provider description field. A legacy description input remains backend-compatible but should not be used by new workflows.\n- Rejects upstream server-managed fields like ad_text_mode, source, status, review_status, review_reason, metadata, and generate_static_text before calling the dashboard API.\n- Does not create or activate a campaign and does not start spend by itself.\n\nOutput semantics:\n- resource_id is the ad unit id when the dashboard response includes id.\n- raw preserves the ChatGPT Ads platform response for later campaign binding.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "ChatGPT Ads advertiser account id from ads.list_ad_accounts.", "example": "" }, { "name": "name", "type": "string", "required": true, "description": "Required non-empty ad unit name." }, { "name": "headline", "type": "string", "required": false, "description": "Optional ad unit headline." }, { "name": "context_hints", "type": "list", "required": true, "description": "Two to four distinct single-line audience scenarios from the media plan Context Hints column. These become ad-unit context, not ad copy.", "example": [ "Comparing client account performance", "Preparing a weekly agency report" ] }, { "name": "landing_page", "type": "string", "required": true, "description": "Required ad unit landing page URL.", "example": "https://example.com/?utm_source=chatgpt_ads" }, { "name": "static_ad_text", "type": "string", "required": true, "description": "Required static ad body text.", "example": "Meet the shoes made from nature." }, { "name": "static_cta", "type": "string", "required": true, "description": "Required static call to action.", "example": "Shop now" }, { "name": "brand_name", "type": "string", "required": false, "description": "Advertised brand name.", "example": "Allbirds" }, { "name": "favicon_url", "type": "string", "required": false, "description": "Optional favicon URL." }, { "name": "image_url", "type": "string", "required": false, "description": "Optional image URL." }, { "name": "utm_params", "type": "list", "required": false, "description": "Optional UTM parameter list." } ], "output_shape": "{\"success\": true, \"resource_id\": \"AD_UNIT_ID\", \"raw\": {\"id\": \"AD_UNIT_ID\"}}" }, { "id": "action:ads/create_adset", "namespace": "ads", "action": "create_adset", "description": "Create a review-gated Meta ad set or TikTok ad group. Meta is force-paused; TikTok defaults to operation_status=DISABLE.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_adsets", "action:ads/update_adset", "action:ads/remove_adset", "action:ads/estimate_audience_size", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nTikTok destination contract: for a TRAFFIC campaign that sends users to a website, pass promotion_type=WEBSITE. Omit promotion_type only when the parent campaign objective is REACH, VIDEO_VIEWS, or ENGAGEMENT. TikTok bidding: bid_amount is a positive account-currency number, not Meta cents. Providing it selects BID_TYPE_CUSTOM and maps to bid_price for CPC/CPM/CPV or conversion_bid_price for OCPM. Omitting it selects BID_TYPE_NO_BID. A manual bid must be lower than the ad group and parent campaign budgets. Creation defaults to operation_status=DISABLE unless an explicitly approved status=ENABLED is supplied.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nCreate a new Meta ad set inside a campaign, with billing, optimization, targeting, and budget. Force-paused at create so ads can be attached before activating.\n\nUse when:\n- The user wants to add a new ad set to a Meta campaign.\n- A workflow needs an adset_id to host ads.\n\nDo not use when:\n- The platform is Google. Use action:ads/create_ad_group.\n- The campaign already has a matching ad set. Reuse via action:ads/list_adsets.\n\nWorkflow:\n1. Call action:ads/list_campaigns to choose a campaign_id.\n2. Build the targeting spec (geo_locations, age_min/max, interests, ...).\n3. Call action:ads/estimate_audience_size to validate reach if needed.\n4. Submit via action:_batch/submit for HITL review.\n5. After approval, call action:ads/create_ad with the new adset_id.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API response: {id: '...'}; the envelope's resource_id matches.\n- budget_daily_micros is converted to daily_budget in cents (int(micros/10_000)) by the dispatcher.\n\nMeta lead-gen / ON_AD notes:\n- For Campaign Budget Optimization campaigns, set is_cbo_adset=true and omit ad-set budget fields; budget stays on the campaign.\n- destination_type=ON_AD + optimization_goal=LEAD_GENERATION uses an instant form flow. The handler preserves manual targeting by defaulting targeting.targeting_automation.advantage_audience to 0 when omitted.\n- For OTHER objectives the handler does NOT inject that flag, and Meta now rejects detailed-targeting ad sets without it (error 100/1870227, live-verified): always set targeting.targeting_automation.advantage_audience explicitly (0 = manual targeting, 1 = Advantage+ audience).\n- If targeting.instagram_positions contains explore_home, Meta also requires explore; the handler auto-adds explore when it is missing.\n\nSide effects:\n- Creates an AdSet under the campaign.\n- Force-paused at create (status=PAUSED), overwriting any caller value.\n- No immediate spend; activation requires a later action:ads/update_adset with status=ACTIVE.\n- Reversible via action:ads/remove_adset (archives; can un-archive to PAUSED).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Parent Meta campaign id from action:ads/list_campaigns. Required when platform=tiktok.", "example": "23...", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "name", "type": "string", "required": true, "description": "Ad set display name. Required when platform=tiktok." }, { "name": "billing_event", "type": "string", "required": false, "description": "Meta: required; use a Meta billing event such as IMPRESSIONS or LINK_CLICKS. TikTok: optional; when omitted, the handler derives the required CPC/CPM/OCPM/CPV value from optimization_goal." }, { "name": "optimization_goal", "type": "string", "required": true, "description": "Provider-native optimization goal. Meta examples include REACH, IMPRESSIONS, LINK_CLICKS, OFFSITE_CONVERSIONS, and LEAD_GENERATION. TikTok examples include CLICK, PAGE_VISIT, CONVERT, INSTALL, REACH, ENGAGED_VIEW, and LEAD_GENERATION. Required when platform=tiktok.", "example": "REACH" }, { "name": "targeting", "type": "object", "required": true, "description": "Meta targeting spec object. Typical fields: geo_locations, age_min, age_max, interests, behaviors, flexible_spec, publisher_platforms, facebook_positions. For Instagram placements, explore_home requires explore; the handler auto-adds explore when missing. For ON_AD lead generation ad sets, targeting_automation.advantage_audience defaults to 0 if omitted.", "platform": [ "meta" ] }, { "name": "budget_daily_micros", "type": "integer", "required": false, "description": "Meta only: daily budget in micros (1 USD = 1_000_000); converted to daily_budget cents. CONDITIONAL: required for non-CBO ad sets (or use lifetime_budget); MUST be omitted for CBO campaigns (set is_cbo_adset=true instead). Source: user-provided budget amount.", "example": 50000000, "one_of_group": "adset_budget", "platform": [ "meta" ] }, { "name": "lifetime_budget", "type": "integer", "required": false, "description": "Lifetime budget in cents (Meta native unit). Alternative to budget_daily_micros.", "one_of_group": "adset_budget", "platform": [ "meta" ] }, { "name": "bid_strategy", "type": "string", "required": false, "description": "Meta only: ad-set bid strategy. One of LOWEST_COST_WITHOUT_CAP, LOWEST_COST_WITH_BID_CAP, COST_CAP, LOWEST_COST_WITH_MIN_ROAS. Cap strategies (LOWEST_COST_WITH_BID_CAP/COST_CAP) require bid_amount. Source: constant enum chosen by the agent based on the user's bidding intent.", "example": "COST_CAP", "enum": [ "COST_CAP", "LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "LOWEST_COST_WITH_MIN_ROAS" ], "source": "constant", "platform": [ "meta" ] }, { "name": "is_cbo_adset", "type": "boolean", "required": false, "description": "Meta only: handler control flag (not a Graph field). Set true when the parent campaign uses Campaign Budget Optimization (Advantage Campaign Budget); the ad-set budget is then omitted and budget stays on the campaign. Source: derived from the parent campaign's budget setting (list_campaigns shows daily_budget/lifetime_budget on the campaign => CBO). For lead-gen ON_AD ad sets under a CBO campaign, keep this true and do not try to move budget to the ad set.", "example": true, "platform": [ "meta" ] }, { "name": "dsa_beneficiary", "type": "string", "required": false, "description": "Meta only: EU DSA beneficiary -- the brand/person/org that benefits from the ad. Mandatory for ad sets targeting EU-regulated countries, else the ad cannot be published. Source: user-provided beneficiary name (the advertising brand).", "example": "Acme Inc", "platform": [ "meta" ] }, { "name": "dsa_payor", "type": "string", "required": false, "description": "Meta only: EU DSA payer -- who pays for the ad. Required alongside dsa_beneficiary for EU-regulated targeting. Source: user-provided payer name.", "example": "Acme Inc", "platform": [ "meta" ] }, { "name": "destination_type", "type": "string", "required": false, "description": "Meta only: conversion destination. Common values WEBSITE, MESSENGER, WHATSAPP, INSTAGRAM_DIRECT, APP, ON_AD. Needed for messaging/lead/app destinations. Source: constant enum chosen from the campaign objective / user intent.", "example": "WEBSITE", "platform": [ "meta" ] }, { "name": "is_dynamic_creative", "type": "boolean", "required": false, "description": "Meta only: enable Dynamic Creative on the ad set (required for Advantage+/dynamic-creative ad sets). Source: user intent (dynamic creative on/off).", "example": true, "platform": [ "meta" ] }, { "name": "pacing_type", "type": "list", "required": false, "description": "Meta only: pacing control, e.g. [\"standard\"] (default) or [\"no_pacing\"]. List of strings. Source: constant enum chosen by the agent.", "example": [ "standard" ], "platform": [ "meta" ] }, { "name": "attribution_spec", "type": "list", "required": false, "description": "Meta only: conversion attribution windows, list of {event_type, window_days} objects (e.g. [{\"event_type\":\"CLICK_THROUGH\",\"window_days\":7}]). Source: constant config chosen by the agent for the conversion window.", "example": [ { "event_type": "CLICK_THROUGH", "window_days": 7 } ], "platform": [ "meta" ] }, { "name": "bid_amount", "type": "number", "required": false, "description": "Manual bid amount. Meta uses integer cents. TikTok uses a positive float in the advertiser account currency, lower than the ad group and campaign budgets; CPC/CPM/CPV map to bid_price and OCPM maps to conversion_bid_price.", "example": 100 }, { "name": "promoted_object", "type": "object", "required": false, "description": "Promoted object spec, e.g. {pixel_id, custom_event_type} for conversion optimization, or {page_id} for engagement campaigns.", "platform": [ "meta" ] }, { "name": "start_time", "type": "string", "required": false, "description": "ISO-8601 ad set start time.", "platform": [ "meta" ] }, { "name": "end_time", "type": "string", "required": false, "description": "ISO-8601 ad set end time.", "platform": [ "meta" ] }, { "name": "adgroup_name", "type": "string", "required": false, "description": "TikTok only: ad group name (falls back to `name`).", "example": "AG 1", "platform": [ "tiktok" ] }, { "name": "promotion_type", "type": "string", "required": false, "description": "TikTok only: conditional promotion destination type, for example WEBSITE. TikTok requires it for some campaign objectives and permits omission for REACH, VIDEO_VIEWS, and ENGAGEMENT.", "example": "WEBSITE", "platform": [ "tiktok" ] }, { "name": "app_id", "type": "string", "required": false, "description": "TikTok only: App ID from /app/list/; required for APP_ANDROID and APP_IOS promotion types.", "example": "1234567890123456789", "platform": [ "tiktok" ] }, { "name": "pacing", "type": "string", "required": false, "description": "TikTok only: delivery pacing mode.", "example": "PACING_MODE_SMOOTH", "platform": [ "tiktok" ] }, { "name": "schedule_type", "type": "string", "required": true, "description": "TikTok only: SCHEDULE_FROM_NOW or SCHEDULE_START_END; lifetime budgets require START_END.", "example": "SCHEDULE_FROM_NOW", "platform": [ "tiktok" ] }, { "name": "schedule_start_time", "type": "string", "required": true, "description": "TikTok only: start time (YYYY-MM-DD HH:MM:SS).", "example": "2026-07-10 00:00:00", "platform": [ "tiktok" ] }, { "name": "schedule_end_time", "type": "string", "required": false, "description": "TikTok only: end time; required for SCHEDULE_START_END and BUDGET_MODE_TOTAL.", "example": "2026-07-20 00:00:00", "platform": [ "tiktok" ] }, { "name": "budget_mode", "type": "string", "required": true, "description": "TikTok only: budget mode; BUDGET_MODE_TOTAL requires SCHEDULE_START_END and schedule_end_time.", "example": "BUDGET_MODE_DAY", "platform": [ "tiktok" ] }, { "name": "budget", "type": "number", "required": true, "description": "TikTok only: budget as a float in the account currency (NOT micros).", "example": 20.0, "platform": [ "tiktok" ] }, { "name": "bid_type", "type": "string", "required": false, "description": "TikTok only: bidding strategy. Use BID_TYPE_CUSTOM with bid_amount, or BID_TYPE_NO_BID without bid_amount. Omit to infer CUSTOM when bid_amount is present and NO_BID otherwise.", "example": "BID_TYPE_CUSTOM", "enum": [ "BID_TYPE_CUSTOM", "BID_TYPE_NO_BID" ], "platform": [ "tiktok" ] }, { "name": "frequency", "type": "integer", "required": false, "description": "TikTok only: Required for REACH: maximum impressions per person within frequency_schedule days. Must be an integer from 1 to 1000 and supplied together with frequency_schedule.", "example": 1, "platform": [ "tiktok" ] }, { "name": "frequency_schedule", "type": "integer", "required": false, "description": "TikTok only: Required for REACH: number of days in the frequency-cap window. Must be an integer from 1 to 30 and supplied together with frequency.", "example": 1, "platform": [ "tiktok" ] }, { "name": "status", "type": "string", "required": false, "description": "TikTok only: optional reviewed create state; omit/PAUSED creates disabled, ENABLED creates enabled.", "example": "PAUSED", "enum": [ "PAUSED", "ENABLED" ], "platform": [ "tiktok" ] }, { "name": "location_ids", "type": "list", "required": false, "description": "TikTok only: Target location ids; provide this or zipcode_ids when geography is required.", "example": [ "6252001" ], "one_of_group": "tiktok_adgroup_geography", "platform": [ "tiktok" ] }, { "name": "zipcode_ids", "type": "list", "required": false, "description": "TikTok only: Target ZIP/postal code ids; provide this or location_ids when geography is required. ZIP targeting requires PLACEMENT_TYPE_NORMAL and PLACEMENT_TIKTOK.", "example": [ "123456789" ], "one_of_group": "tiktok_adgroup_geography", "platform": [ "tiktok" ] }, { "name": "age_groups", "type": "list", "required": false, "description": "TikTok only: Target age group enums.", "example": [ "AGE_25_34" ], "platform": [ "tiktok" ] }, { "name": "gender", "type": "string", "required": false, "description": "TikTok only: Target gender.", "example": "GENDER_UNLIMITED", "platform": [ "tiktok" ] }, { "name": "languages", "type": "list", "required": false, "description": "TikTok only: Target language codes.", "example": [ "en" ], "platform": [ "tiktok" ] }, { "name": "interest_category_ids", "type": "list", "required": false, "description": "TikTok only: Interest category ids.", "example": [ "100" ], "platform": [ "tiktok" ] }, { "name": "interest_keyword_ids", "type": "list", "required": false, "description": "TikTok only: Interest keyword ids.", "example": [ "200" ], "platform": [ "tiktok" ] }, { "name": "audience_ids", "type": "list", "required": false, "description": "TikTok only: Included custom-audience ids.", "example": [ "300" ], "platform": [ "tiktok" ] }, { "name": "excluded_audience_ids", "type": "list", "required": false, "description": "TikTok only: Excluded custom-audience ids.", "example": [ "400" ], "platform": [ "tiktok" ] }, { "name": "operating_systems", "type": "list", "required": false, "description": "TikTok only: Target operating systems.", "example": [ "ANDROID" ], "platform": [ "tiktok" ] }, { "name": "placement_type", "type": "string", "required": false, "description": "TikTok only: Placement mode; ad-group creation defaults to PLACEMENT_TYPE_AUTOMATIC.", "example": "PLACEMENT_TYPE_AUTOMATIC", "enum": [ "PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL" ], "platform": [ "tiktok" ] }, { "name": "placements", "type": "list", "required": false, "description": "TikTok only: Placements; required when placement_type is PLACEMENT_TYPE_NORMAL.", "example": [ "PLACEMENT_TIKTOK" ], "platform": [ "tiktok" ] } ], "output_shape": "{\"id\": \"23...\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_bidding_strategy", "namespace": "ads", "action": "create_bidding_strategy", "description": "Create a reusable Google portfolio Target CPA or Target ROAS strategy.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/update_campaign" ], "long_description": "Primary use:\nCreate a reusable Google portfolio Target CPA or Target ROAS strategy.\n\nUse when:\n- You need a reusable Google Target CPA or Target ROAS portfolio strategy.\n\nDo not use when:\n- You need a campaign-specific standard bidding strategy; use action:ads/update_campaign.\n\nWorkflow:\n1. Read the current strategy or campaign state when changing an existing resource.\n2. For write operations, submit through action:_batch/submit for HITL review.\n3. Verify the resulting state with action:ads/get_bidding_strategy.\n\nOutput semantics:\n- Returns the created, read, updated, listed, or removed Google portfolio strategy result.\n\nSide effects:\n- Creates, updates, or removes a Google portfolio strategy. Removing a strategy is irreversible and requires it to be unused.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Portfolio bidding strategy name; optional when updating.", "example": "Target CPA portfolio" }, { "name": "bidding_strategy_type", "type": "string", "required": true, "description": "Portfolio strategy type.", "example": "TARGET_CPA", "enum": [ "TARGET_CPA", "TARGET_ROAS" ] }, { "name": "target_cpa_micros", "type": "integer", "required": false, "description": "Required when bidding_strategy_type is TARGET_CPA.", "example": 5000000 }, { "name": "target_roas", "type": "number", "required": false, "description": "Required when bidding_strategy_type is TARGET_ROAS.", "example": 3.5 } ], "output_shape": "{\"success\":true,\"raw\":{}}" }, { "id": "action:ads/create_campaign", "namespace": "ads", "action": "create_campaign", "description": "Create a review-gated campaign on Google, Meta, TikTok, or ChatGPT Ads. Creation defaults to PAUSED; TikTok preserves an explicitly approved ENABLED status.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:_batch/submit", "action:ads/list_campaigns", "action:ads/get_campaign", "action:ads/list_ad_groups", "action:ads/get_ad_group", "action:ads/update_campaign" ], "long_description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nCreate a new advertising campaign. For ChatGPT Ads, the default source is an inline Campaign -> Ad Group -> Ad hierarchy; Campaign, groups, and authored Ads are created paused, and the Campaign is explicitly inactive. Every ChatGPT Ads Campaign create uses the server-owned OpenAI-only allocation; callers must not send network_mode, network_type, or network_config. The connected advertiser account must already be enabled for OpenAI-only campaigns by the advertising platform. TikTok defaults to operation_status=DISABLE; explicitly approved status=ENABLED is sent as ENABLE.\n\nUse when:\n- The user approved a new campaign, budget, objective, targeting, and creative hierarchy.\n- For ChatGPT Ads, you have a non-empty ad_groups list built from approved media-plan rows.\n\nDo not use when:\n- The user wants immediate activation. Activation is a separate approved update_campaign.\n- The campaign already exists. Use update_campaign and the Ad Group/Ad actions.\n- For ChatGPT Ads, do not use deprecated ad_unit_ids unless legacy compatibility was explicit.\n\nWorkflow:\n1. Resolve account_id and review current campaigns.\n2. For ChatGPT Ads, submit exactly one source: default ad_groups or deprecated ad_unit_ids.\n3. Submit the create through HITL review.\n4. Require the returned materialization proof and perform the documented readback.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Inline ChatGPT Ads success includes campaign_id, ad_group_ids, ad_ids, creation_success=true, materialization_verified=true, campaign_status=paused, is_active=false, campaign_field_verification, and separate POST/GET lifecycle observations. materialization_verified proves the hierarchy and readable fields; never claim create-only Campaign fields persisted when they appear under campaign_field_verification.unverifiable_fields.\n- Review/deployment observations never imply activation or billing readiness.\n- Legacy compatibility success instead includes binding_verified=true and exact ad_unit_ids.\n\nSide effects:\n- Creates provider resources only after approval.\n- ChatGPT Ads inline create writes the complete paused hierarchy and then performs readback.\n- ChatGPT Ads sends the server-owned network_mode=openai. Provider network_mode read metadata is unfinished, so create verification does not treat it as provider proof.\n- A partial or unknown result may represent completed provider writes; reconcile known ids before any retry.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin.", "example": "chatgpt_ads", "enum": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Campaign display name; must be unique within the account. Required when platform=tiktok.", "example": "Spring Promo 2026" }, { "name": "campaign_type", "type": "string", "required": false, "description": "Google only (required): campaign type. One of SEARCH, DISPLAY, SHOPPING, VIDEO. Use create_pmax_campaign for PERFORMANCE_MAX; unknown values default to SEARCH with a warning. Backend (registry/executor) rejects the call with HTTP 400 if omitted for Google. Source: constant enum chosen by the agent based on user intent.", "example": "SEARCH", "enum": [ "DISPLAY", "SEARCH", "SHOPPING", "VIDEO" ], "source": "constant", "platform": [ "google" ] }, { "name": "budget_daily_micros", "type": "integer", "required": false, "description": "Google only (required) / Meta: daily budget in micros (1 USD = 1_000_000). Backend rejects with HTTP 400 if omitted for Google. For Meta this is the ONLY campaign-level daily budget input (becomes daily_budget in cents) and becomes mandatory when bid_strategy is supplied at the campaign level; use lifetime_budget instead for a fixed-duration budget. Source: user-provided budget amount converted to micros.", "example": 50000000, "platform": [ "google", "meta", "chatgpt_ads" ] }, { "name": "target_cpa_micros", "type": "integer", "required": false, "description": "Google only: target cost-per-action in micros (1 USD = 1_000_000). Required companion when bidding_strategy=TARGET_CPA (the handler errors without it). Optional target when bidding_strategy=MAXIMIZE_CONVERSIONS — Google's standard tCPA form since standalone TargetCpa strategies were deprecated. Any other strategy rejects it with a clear error. Source: user-provided target CPA converted to micros.", "example": 20000000, "platform": [ "google", "meta" ] }, { "name": "target_roas", "type": "number", "required": false, "description": "Google only: target return on ad spend as a ratio (e.g. 4.0 = 400%). Required companion when bidding_strategy=TARGET_ROAS (the handler errors without it). Optional target when bidding_strategy=MAXIMIZE_CONVERSION_VALUE — Google's standard tROAS form since standalone TargetRoas strategies were deprecated. Any other strategy rejects it with a clear error. Source: user-provided target ROAS ratio.", "example": 4.0, "platform": [ "google", "meta" ] }, { "name": "start_date", "type": "string", "required": false, "description": "Google only: campaign start date in YYYY-MM-DD. Defaults to start-today when omitted. Source: user-provided date.", "example": "2026-07-01", "platform": [ "google", "meta" ] }, { "name": "end_date", "type": "string", "required": false, "description": "Google only: campaign end date in YYYY-MM-DD. Defaults to no-end when omitted. Source: user-provided date.", "example": "2026-07-31", "platform": [ "google", "meta" ] }, { "name": "network_settings", "type": "object", "required": false, "description": "Google only: object controlling serving networks. Keys (booleans): target_google_search, target_search_network, target_content_network, target_partner_search_network. Omitted keys keep platform defaults. Source: user/agent-provided serving preferences (constant booleans).", "example": "{\"target_google_search\": true, \"target_search_network\": false, \"target_content_network\": false}", "platform": [ "google", "meta" ] }, { "name": "bidding_strategy", "type": "string", "required": false, "description": "Google only: one of MANUAL_CPC, MAXIMIZE_CLICKS, MAXIMIZE_CONVERSIONS, MAXIMIZE_CONVERSION_VALUE, TARGET_CPA, TARGET_ROAS, TARGET_SPEND. Default: TARGET_SPEND.", "example": "MAXIMIZE_CLICKS", "enum": [ "MANUAL_CPC", "MAXIMIZE_CLICKS", "MAXIMIZE_CONVERSIONS", "MAXIMIZE_CONVERSION_VALUE", "TARGET_CPA", "TARGET_ROAS", "TARGET_SPEND" ], "source": "constant", "platform": [ "google", "meta" ] }, { "name": "max_cpc_bid_micros", "type": "integer", "required": false, "description": "Google only: max CPC bid in micros; applies only to MAXIMIZE_CLICKS and TARGET_SPEND. Ignored for other strategies (warning emitted).", "platform": [ "google", "meta" ] }, { "name": "objective", "type": "string", "required": false, "description": "Meta only (required): campaign objective. One of OUTCOME_TRAFFIC, OUTCOME_AWARENESS, OUTCOME_LEADS, OUTCOME_SALES, OUTCOME_ENGAGEMENT, OUTCOME_APP_PROMOTION. Required for Meta even though the shared spec previously marked it optional. Source: constant enum chosen by the agent from the user's campaign goal.", "example": "OUTCOME_TRAFFIC", "enum": [ "OUTCOME_APP_PROMOTION", "OUTCOME_AWARENESS", "OUTCOME_ENGAGEMENT", "OUTCOME_LEADS", "OUTCOME_SALES", "OUTCOME_TRAFFIC" ], "source": "constant", "platform": [ "meta" ] }, { "name": "special_ad_categories", "type": "list", "required": false, "description": "Meta only: special category list. Defaults to []. Values include EMPLOYMENT, HOUSING, CREDIT, ISSUES_ELECTIONS_POLITICS, ONLINE_GAMBLING_AND_GAMING.", "platform": [ "google", "meta" ] }, { "name": "buying_type", "type": "string", "required": false, "description": "Meta only: AUCTION (default) or RESERVED.", "example": "AUCTION", "enum": [ "AUCTION", "RESERVED" ], "source": "constant", "platform": [ "google", "meta" ] }, { "name": "bid_strategy", "type": "string", "required": false, "description": "Meta only: LOWEST_COST_WITHOUT_CAP, LOWEST_COST_WITH_BID_CAP, COST_CAP, LOWEST_COST_WITH_MIN_ROAS. For Meta campaigns with campaign-level budget (CBO / Advantage Campaign Budget), the handler defaults to LOWEST_COST_WITHOUT_CAP when omitted so the downstream ad set does not inherit a cap strategy that requires bid_amount. Only choose a cap strategy when the user also provides the required bid/cost cap.", "example": "LOWEST_COST_WITHOUT_CAP", "enum": [ "COST_CAP", "LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "LOWEST_COST_WITH_MIN_ROAS" ], "source": "constant", "platform": [ "google", "meta", "chatgpt_ads" ] }, { "name": "spend_cap", "type": "integer", "required": false, "description": "Meta only: lifetime account-level spend cap for the campaign in account-currency cents (NOT dollars, NOT micros). Source: user-provided cap converted to cents.", "example": 100000, "platform": [ "google", "meta" ] }, { "name": "special_ad_category_country", "type": "list", "required": false, "description": "Meta only: list of ISO country codes; REQUIRED whenever special_ad_categories contains a regulated category (HOUSING, EMPLOYMENT, CREDIT, ISSUES_ELECTIONS_POLITICS, FINANCIAL_PRODUCTS_SERVICES). Omit for non-regulated campaigns. Source: user-provided target country/countries of the regulated campaign.", "example": [ "US" ], "platform": [ "google", "meta" ] }, { "name": "lifetime_budget", "type": "integer", "required": false, "description": "Meta only: campaign-level lifetime budget in account-currency cents (CBO/Advantage Campaign Budget). Mutually exclusive with the daily budget derived from budget_daily_micros. Use for fixed-duration/scheduled campaigns (pair with stop_time). Source: user-provided total spend cap converted to cents (1 USD = 100).", "example": 50000, "platform": [ "google", "meta" ] }, { "name": "is_adset_budget_sharing_enabled", "type": "boolean", "required": false, "description": "Meta only: when no campaign-level budget is set, Meta requires this flag to be explicit (each ad set keeps its own budget). The handler defaults it to false for budget-less campaigns; pass true to let ad sets share budget. Source: user-provided campaign-budget-optimization choice.", "example": false, "source": "user", "platform": [ "google", "meta" ] }, { "name": "status", "type": "string", "required": false, "description": "TikTok only: omit to create with operation_status=DISABLE, or pass ENABLED after explicit HITL approval to send operation_status=ENABLE. Google and Meta force a paused/non-active create. ChatGPT Ads accepts only omitted or lowercase paused and rejects every other caller value before the provider request.", "example": "PAUSED", "source": "constant" }, { "name": "start_time", "type": "string", "required": false, "description": "Meta only: ISO-8601 scheduled start time.", "platform": [ "google", "meta", "chatgpt_ads" ] }, { "name": "stop_time", "type": "string", "required": false, "description": "Meta only: ISO-8601 scheduled stop time.", "platform": [ "google", "meta" ] }, { "name": "campaignGroup", "type": "string", "required": false, "description": "LinkedIn only: sponsoredCampaignGroup URN to attach the campaign to; omitted -> a per-account 'Soku Default' group is found or created.", "example": "urn:li:sponsoredCampaignGroup:635137195", "platform": [ "linkedin" ] }, { "name": "costType", "type": "string", "required": false, "description": "LinkedIn only: bid cost type.", "example": "CPC", "enum": [ "CPM", "CPC", "CPV" ], "platform": [ "linkedin" ] }, { "name": "creativeSelection", "type": "string", "required": false, "description": "LinkedIn only: creative rotation strategy.", "enum": [ "ROUND_ROBIN", "OPTIMIZED" ], "platform": [ "linkedin" ] }, { "name": "dailyBudget", "type": "object", "required": false, "description": "LinkedIn only: daily budget as a decimal-string MoneyAmount in the account currency, e.g. {\"amount\": \"25\", \"currencyCode\": \"USD\"}. Amounts are strings, never floats.", "platform": [ "linkedin" ] }, { "name": "totalBudget", "type": "object", "required": false, "description": "LinkedIn only: lifetime budget MoneyAmount (decimal-string amount).", "platform": [ "linkedin" ] }, { "name": "unitCost", "type": "object", "required": false, "description": "LinkedIn only: bid amount MoneyAmount (decimal-string amount).", "platform": [ "linkedin" ] }, { "name": "locale", "type": "object", "required": false, "description": "LinkedIn only: campaign locale, e.g. {\"country\": \"US\", \"language\": \"en\"}.", "platform": [ "linkedin" ] }, { "name": "objectiveType", "type": "string", "required": false, "description": "LinkedIn only: campaign objective.", "enum": [ "BRAND_AWARENESS", "ENGAGEMENT", "JOB_APPLICANTS", "LEAD_GENERATION", "WEBSITE_CONVERSIONS", "WEBSITE_VISITS", "VIDEO_VIEWS" ], "platform": [ "linkedin" ] }, { "name": "offsiteDeliveryEnabled", "type": "boolean", "required": false, "description": "LinkedIn only: serve on the LinkedIn Audience Network beyond the feed.", "platform": [ "linkedin" ] }, { "name": "runSchedule", "type": "object", "required": false, "description": "LinkedIn only: epoch-ms schedule, e.g. {\"start\": 1755000000000}; start is required by LinkedIn even when end is open.", "platform": [ "linkedin" ] }, { "name": "targetingCriteria", "type": "object", "required": false, "description": "LinkedIn only: AND/OR targeting expression over adTargetingFacet URNs (include/exclude clauses).", "platform": [ "linkedin" ] }, { "name": "type", "type": "string", "required": false, "description": "LinkedIn only: campaign format type.", "enum": [ "TEXT_AD", "SPONSORED_UPDATES", "SPONSORED_INMAILS", "DYNAMIC" ], "platform": [ "linkedin" ] }, { "name": "audienceExpansionEnabled", "type": "boolean", "required": false, "description": "LinkedIn only: enable audience expansion for the targeting criteria.", "platform": [ "linkedin" ] }, { "name": "optimizationTargetType", "type": "string", "required": false, "description": "LinkedIn only: spend optimization target; default NONE.", "platform": [ "linkedin" ] }, { "name": "pacingStrategy", "type": "string", "required": false, "description": "LinkedIn only: budget pacing.", "enum": [ "LIFETIME", "ACCELERATED" ], "platform": [ "linkedin" ] }, { "name": "politicalIntent", "type": "string", "required": false, "description": "LinkedIn only: political-advertising declaration; defaults to NOT_POLITICAL. EU-targeted campaigns must surface the non-political confirmation to the human during HITL review and pass their answer here.", "enum": [ "POLITICAL", "NOT_POLITICAL", "NOT_DECLARED" ], "platform": [ "linkedin" ] }, { "name": "objective_type", "type": "string", "required": true, "description": "TikTok only: required — campaign objective (e.g. TRAFFIC, REACH, VIDEO_VIEWS, PRODUCT_SALES).", "example": "TRAFFIC", "platform": [ "tiktok" ] }, { "name": "budget_mode", "type": "string", "required": false, "description": "TikTok only: budget mode (BUDGET_MODE_DAY / BUDGET_MODE_TOTAL / BUDGET_MODE_INFINITE).", "example": "BUDGET_MODE_DAY", "platform": [ "tiktok" ] }, { "name": "budget", "type": "number", "required": false, "description": "TikTok only: budget as a float in the account currency (NOT micros).", "example": 50.0, "platform": [ "tiktok" ] }, { "name": "budget_optimize_on", "type": "boolean", "required": false, "description": "TikTok only: enable campaign budget optimization (CBO).", "example": true, "platform": [ "tiktok" ] }, { "name": "special_industries", "type": "list", "required": false, "description": "TikTok only: regulated-industry declarations where required.", "example": [ "HOUSING" ], "platform": [ "tiktok" ] }, { "name": "request_id", "type": "string", "required": false, "description": "TikTok only: optional unique request id used for provider-side idempotency.", "example": "202607230001", "platform": [ "tiktok" ] }, { "name": "landing_page", "type": "string", "required": false, "description": "ChatGPT Ads only (required): final landing page URL for the campaign. Use the UTM-tagged URL prepared by the ChatGPT Ads media plan.", "example": "https://example.com/?utm_source=chatgpt_ads", "platform": [ "chatgpt_ads" ] }, { "name": "daily_budget", "type": "number", "required": false, "description": "ChatGPT Ads only (required): dashboard daily budget in dollars. Prefer budget_daily_micros for cross-platform payloads; either field satisfies the ChatGPT Ads required budget.", "example": 300, "platform": [ "google", "meta", "chatgpt_ads" ] }, { "name": "is_active", "type": "boolean", "required": false, "description": "ChatGPT Ads only: accepted by the upstream create API, but Soku forces new campaigns to non-active by sending status=paused and is_active=false.", "example": false, "platform": [ "chatgpt_ads" ] }, { "name": "campaign_objective", "type": "string", "required": false, "description": "ChatGPT Ads only (required): campaign objective. Current ChatGPT Ads dashboard values are clicks, reach, conversions, or leads.", "example": "clicks", "enum": [ "clicks", "reach", "conversions", "leads" ], "source": "constant", "platform": [ "chatgpt_ads" ] }, { "name": "ad_unit_ids", "type": "list", "required": false, "description": "Deprecated legacy compatibility source. Use only when the caller explicitly requests the old Ad Unit binding path; mutually exclusive with ad_groups.", "example": [ "legacy_ad_unit_123" ], "source": "upstream:action:ads/create_ad_unit", "platform": [ "chatgpt_ads" ] }, { "name": "billing_model", "type": "string", "required": false, "description": "ChatGPT Ads create-only billing model: cpm, cpc, or cpa. The platform accepts this field but may omit it from both create response and Campaign readback; inspect campaign_field_verification before claiming persistence.", "enum": [ "cpm", "cpc", "cpa" ], "platform": [ "chatgpt_ads" ] }, { "name": "end_time", "type": "string", "required": false, "description": "ChatGPT Ads campaign end time.", "platform": [ "chatgpt_ads" ] }, { "name": "click_goal", "type": "integer", "required": false, "description": "ChatGPT Ads create-only click goal. Check campaign_field_verification; The platform may accept it without exposing readback evidence.", "platform": [ "chatgpt_ads" ] }, { "name": "max_cpc", "type": "number", "required": false, "description": "ChatGPT Ads create-only Campaign max CPC. Prefer Ad Group max_cpc when operator-visible persisted verification is required.", "platform": [ "chatgpt_ads" ] }, { "name": "impression_goal", "type": "integer", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "max_cpm", "type": "number", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "conversion_type", "type": "string", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "max_cpa", "type": "number", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "demographics", "type": "object", "required": false, "description": "ChatGPT Ads create-only demographic configuration. Check campaign_field_verification; the platform may omit it from readback.", "platform": [ "chatgpt_ads" ] }, { "name": "utm_params", "type": "list", "required": false, "description": "ChatGPT Ads ordered UTM objects with key and value fields.", "example": [ { "key": "utm_source", "value": "chatgpt" } ], "platform": [ "chatgpt_ads" ] }, { "name": "allowed_countries", "type": "list", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "allowed_devices", "type": "list", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "lead_form_config", "type": "object", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "ad_groups", "type": "list", "required": false, "description": "Default ChatGPT Ads hierarchy source: a non-empty inline list of paused manual or generative Ad Groups. Mutually exclusive with deprecated ad_unit_ids.", "example": [ { "name": "Research workflow", "group_type": "manual", "context": "Comparing tools for a research workflow", "landing_page": "https://example.com", "brand_name": "Example", "ads": [ { "name": "Research_workflow_01", "headline": "Approved headline", "copy": "Approved body copy.", "cta": "Learn more", "landing_page": "https://example.com/?utm_source=nexad_c" } ] } ], "platform": [ "chatgpt_ads" ] }, { "name": "dimensions", "type": "object", "required": false, "description": "TikTok dimensions.", "example": {}, "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true, \"message\": \"Campaign created successfully\", \"resource_id\": \"123\", \"resource_name\": \"customers/X/campaigns/123\", \"results\": [{\"campaignBudgetResult\": {\"resourceName\": \"...\"}}, {\"campaignResult\": {\"resourceName\": \"...\"}}]} // Meta: {\"id\": \"23...\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_change_history_task", "namespace": "ads", "action": "create_change_history_task", "description": "Create a TikTok asynchronous change-log export task.", "mode": "write", "platforms": [ "tiktok" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nCreate a TikTok asynchronous change-log export task.\n\nUse when:\nYou need an exportable audit log of TikTok account changes over a time window.\n\nDo not use when:\nYou want live campaign metrics or current entity state — use the reporting / read actions.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nEnqueues an asynchronous export job on TikTok's side; poll get_change_history_task and fetch with download_change_history. No advertiser entities are modified.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "start_time", "type": "string", "required": true, "description": "TikTok start time.", "example": "value", "platform": [ "tiktok" ] }, { "name": "end_time", "type": "string", "required": true, "description": "TikTok end time.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_conversion_action", "namespace": "ads", "action": "create_conversion_action", "description": "Create an ENABLED Google Ads conversion action for attribution; review-gated.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_actions", "action:ads/update_conversion_action", "action:ads/remove_conversion_action", "action:_batch/submit", "action:ads/list_conversion_groups" ], "long_description": "Primary use:\nRegister a new Google Ads conversion action for attribution. Created with status=ENABLED because conversion actions are not lifecycle resources.\n\nUse when:\n- The user wants to track a new website / call / lead conversion type.\n- A workflow needs a conversion_action to feed into bidding strategies.\n\nDo not use when:\n- The platform is Meta. Use action:ads/create_pixel and action:ads/send_conversion_event.\n- The action already exists and only its name / status / category / value settings change. Use action:ads/update_conversion_action.\n- The user wants to delete a conversion action. Use action:ads/remove_conversion_action (permanent).\n\nWorkflow:\n1. Choose type and category from the Google enums below.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/list_conversion_actions.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.resource_id is the new conversion_action's numeric id.\n\nSide effects:\n- Creates a ConversionAction with status=ENABLED.\n- Reversal requires action:ads/remove_conversion_action, which is permanent — the removed action can never be re-enabled.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Conversion action display name; must be unique within the account." }, { "name": "type", "type": "string", "required": true, "description": "Google only: one of AD_CALL, CLICK_TO_CALL, UPLOAD_CLICKS, WEBPAGE, WEBSITE_CALL. NOTE: WEBSITE_CALL and AD_CALL additionally require default_value (always_use_default_value is forced true). Source: constant enum.", "example": "WEBPAGE", "enum": [ "AD_CALL", "CLICK_TO_CALL", "UPLOAD_CLICKS", "WEBPAGE", "WEBSITE_CALL" ], "source": "constant" }, { "name": "category", "type": "string", "required": true, "description": "Google only: conversion category. One of DEFAULT, PAGE_VIEW, PURCHASE, SIGNUP, DOWNLOAD, ADD_TO_CART, BEGIN_CHECKOUT, SUBSCRIBE_PAID, PHONE_CALL_LEAD, IMPORTED_LEAD, SUBMIT_LEAD_FORM, BOOK_APPOINTMENT, REQUEST_QUOTE, GET_DIRECTIONS, OUTBOUND_CLICK, CONTACT. DOWNLOAD is only valid with type=GOOGLE_PLAY_DOWNLOAD (not currently a supported type) so do not pair DOWNLOAD with the supported types; prefer DEFAULT/PURCHASE/SIGNUP/PHONE_CALL_LEAD etc. Source: constant enum.", "example": "PURCHASE", "enum": [ "ADD_TO_CART", "BEGIN_CHECKOUT", "BOOK_APPOINTMENT", "CONTACT", "DEFAULT", "DOWNLOAD", "GET_DIRECTIONS", "IMPORTED_LEAD", "OUTBOUND_CLICK", "PAGE_VIEW", "PHONE_CALL_LEAD", "PURCHASE", "REQUEST_QUOTE", "SIGNUP", "SUBMIT_LEAD_FORM", "SUBSCRIBE_PAID" ], "source": "constant" }, { "name": "default_value", "type": "number", "required": false, "description": "Google only: default monetary value recorded per conversion when always_use_default_value is true. REQUIRED for type=WEBSITE_CALL and type=AD_CALL. Nested into value_settings.default_value. Source: user-provided / derived from the business value of the conversion (e.g. average call value).", "example": 25.0 }, { "name": "default_currency_code", "type": "string", "required": false, "description": "Google only: ISO 4217 currency code for default_value (value_settings.default_currency_code). If omitted, the account currency is used. Source: user-provided / account default currency.", "example": "USD" }, { "name": "always_use_default_value", "type": "boolean", "required": false, "description": "Google only: when true, every conversion is recorded with default_value instead of a transaction-specific value (value_settings.always_use_default_value). MUST be true for type=WEBSITE_CALL and type=AD_CALL (handler defaults it to true for those types; passing false errors). Source: user-provided; forced true for call conversion types.", "example": true }, { "name": "counting_type", "type": "string", "required": false, "description": "Google only: how conversions are counted. ONE_PER_CLICK (typical for leads) or MANY_PER_CLICK (typical for ecommerce/purchases). Defaults to Google's per-type default if omitted. Source: constant enum, chosen by conversion intent (lead vs purchase).", "example": "ONE_PER_CLICK", "enum": [ "MANY_PER_CLICK", "ONE_PER_CLICK" ], "source": "constant" }, { "name": "primary_for_goal", "type": "boolean", "required": false, "description": "Google only: whether this conversion action feeds bidding/goals. Google defaults this to true when omitted. Source: user-provided.", "example": true }, { "name": "view_through_lookback_window_days", "type": "integer", "required": false, "description": "Google only: number of days a view-through conversion can be attributed back to an impression (1-30 typical). Source: user-provided.", "example": 1 } ], "output_shape": "{\"success\": true, \"message\": \"Conversion action created successfully\", \"resource_id\": \"123\", \"results\": [{\"conversionActionResult\": {\"resourceName\": \"customers/X/conversionActions/Y\"}}]}" }, { "id": "action:ads/create_conversion_group", "namespace": "ads", "action": "create_conversion_group", "description": "Review-gated create of an Admin View conversion_group.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_groups" ], "long_description": "Creates a brand-scoped Admin View conversion_group after human approval. Do not include scope, brand_id, owner_admin_user_id, color, sort_order, or level; those are not agent-facing fields.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "name", "type": "string", "required": true, "description": "Conversion group display name." }, { "name": "slug", "type": "string", "required": true, "description": "Stable conversion group slug." }, { "name": "metadata", "type": "list", "required": false, "description": "Customer-defined [{key, value, description}] metadata fields." } ], "output_shape": "{\"conversion_group\": {\"id\": 1, \"slug\": \"...\", \"version\": \"opaque\"}}" }, { "id": "action:ads/create_custom_audience", "namespace": "ads", "action": "create_custom_audience", "description": "Create a Meta custom audience; review-gated, reversible via Meta UI. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_custom_audiences", "action:ads/create_lookalike_audience", "action:ads/update_custom_audience", "action:ads/remove_custom_audience", "action:ads/create_user_list", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nCreate a Meta custom audience of a chosen subtype (CUSTOM, WEBSITE, APP, etc.) for later retargeting.\n\nUse when:\n- The user wants to build a custom audience from pixel events, an uploaded list, app activity, or engagement signals.\n\nDo not use when:\n- The user wants a Lookalike audience. Use action:ads/create_lookalike_audience.\n- The platform is Google. Use action:ads/create_user_list.\n\nWorkflow:\n1. Choose subtype and optional rule / pixel_id / customer_file_source.\n2. Submit via action:_batch/submit for HITL review.\n3. List or use the new audience_id via action:ads/list_custom_audiences or in action:ads/create_lookalike_audience as source_audience_id.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.id is the new audience id.\n\nSide effects:\n- POSTs to {account_id}/customaudiences. Audience may populate asynchronously.\n- Metadata (name/description) is editable via action:ads/update_custom_audience; removable via action:ads/remove_custom_audience (irreversible hard delete).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Audience display name. Required when platform=tiktok." }, { "name": "subtype", "type": "string", "required": true, "description": "One of CUSTOM, WEBSITE, APP, OFFLINE_CONVERSION, CLAIM, PARTNER, MANAGED, VIDEO, LOOKALIKE, ENGAGEMENT, BAG_OF_ACCOUNTS, STUDY_RULE_AUDIENCE, FOX. Default CUSTOM.", "example": "WEBSITE", "platform": [ "meta" ] }, { "name": "description", "type": "string", "required": false, "description": "Audience description.", "platform": [ "meta" ] }, { "name": "customer_file_source", "type": "string", "required": false, "description": "Meta only: source of the uploaded data for CUSTOM (customer-file) audiences ONLY: USER_PROVIDED_ONLY (default), PARTNER_PROVIDED_ONLY, BOTH_USER_AND_PARTNER_PROVIDED. Ignored/omitted by the handler for rule-based subtypes (WEBSITE/ENGAGEMENT/etc.). Source: constant enum; choose based on how you obtained the user list.", "example": "USER_PROVIDED_ONLY", "enum": [ "BOTH_USER_AND_PARTNER_PROVIDED", "PARTNER_PROVIDED_ONLY", "USER_PROVIDED_ONLY" ], "source": "constant", "platform": [ "meta" ] }, { "name": "pixel_id", "type": "string", "required": false, "description": "Pixel id for WEBSITE-subtype audiences.", "platform": [ "meta" ] }, { "name": "rule", "type": "object", "required": false, "description": "Meta only: flexible-rule JSON for rule-based subtypes (WEBSITE/ENGAGEMENT/VIDEO/APP/OFFLINE_CONVERSION). Shape: {inclusions:{operator,rules:[{event_sources:[{id,type}],retention_seconds,filter:{operator,filters:[{field,operator,value}]}}]}, exclusions:{...}}. Required for these subtypes unless WEBSITE supplies pixel_id; the handler now rejects rule-based audiences with no rule/pixel_id. Source: agent-constructed per Meta flexible-rule schema using pixel/event-source ids from ads.list_pixels.", "example": { "inclusions": { "operator": "or", "rules": [ { "event_sources": [ { "id": "123456789", "type": "pixel" } ], "retention_seconds": 2592000, "filter": { "operator": "and", "filters": [ { "field": "url", "operator": "i_contains", "value": "/checkout" } ] } } ] } }, "platform": [ "meta" ] }, { "name": "retention_days", "type": "integer", "required": false, "description": "Meta only: days a user stays in the audience. Non-purchase website/rule audiences cap at 180; purchase-event website/app audiences may go up to 730 (raised by Meta 2026-05). Exceeding the applicable cap is rejected by Meta. Source: user/agent decision within the cap above.", "example": 180, "platform": [ "meta" ] }, { "name": "is_value_based", "type": "boolean", "required": false, "description": "Meta only: whether this audience carries a per-user value, so it can later seed a value-based lookalike. Only meaningful for customer-file (CUSTOM) audiences whose uploaded list includes a value column. Source: user/agent decision; set true only when you intend to upload value-weighted user data and later build a value-based lookalike from this audience id.", "example": true, "platform": [ "meta" ] }, { "name": "prefill", "type": "boolean", "required": false, "description": "Meta only: for rule-based (e.g. WEBSITE) audiences, backfill matching historical pixel/event data captured before the audience was created instead of only collecting from now on. Source: user/agent decision; true to include pre-existing pixel history, false (default) to start fresh.", "example": true, "platform": [ "meta" ] }, { "name": "opt_out_link", "type": "string", "required": false, "description": "Meta only: URL where users can opt out of this customer-file audience (compliance field for CUSTOM audiences). Source: user-provided opt-out/privacy URL.", "example": "https://example.com/opt-out", "platform": [ "meta" ] }, { "name": "allowed_domains", "type": "list", "required": false, "description": "Meta only: list of domain strings allowed to use/scope this audience. Source: user-provided list of domains the audience is scoped to.", "example": [ "example.com" ], "platform": [ "meta" ] }, { "name": "event_sources", "type": "list", "required": false, "description": "Meta only: list of {id,type} event-source objects (e.g. pixels) used to build a multi-source rule-based audience. type is one of 'pixel','app','offline_event'. Use instead of a single pixel_id when more than one source is needed. Source: ids from ads.list_pixels (type='pixel') or app/offline-event-set ids; agent assembles the list.", "example": [ { "id": "123456789", "type": "pixel" } ], "platform": [ "meta" ] }, { "name": "event_source_group", "type": "string", "required": false, "description": "Meta only: id of an existing event source group (ESG) backing a rule-based audience, as an alternative to listing event_sources individually. Source: id of an existing event source group from Events Manager.", "example": "987654321", "platform": [ "meta" ] }, { "name": "inclusions", "type": "object", "required": false, "description": "Meta only: simplified inclusion rule object (operator + filters describing who to add) for rule-based audiences, used together with exclusions instead of a single hand-rolled rule. Source: agent-constructed inclusion spec per Meta flexible-rule schema.", "example": { "operator": "or", "rules": [ { "event_sources": [ { "id": "123456789", "type": "pixel" } ], "retention_seconds": 2592000, "filter": { "operator": "and", "filters": [ { "field": "event", "operator": "eq", "value": "Purchase" } ] } } ] }, "platform": [ "meta" ] }, { "name": "exclusions", "type": "object", "required": false, "description": "Meta only: simplified exclusion rule object (who to remove) for rule-based audiences, paired with inclusions. Source: agent-constructed exclusion spec per Meta flexible-rule schema.", "example": { "operator": "or", "rules": [ { "event_sources": [ { "id": "123456789", "type": "pixel" } ], "retention_seconds": 604800, "filter": { "operator": "and", "filters": [ { "field": "event", "operator": "eq", "value": "CompleteRegistration" } ] } } ] }, "platform": [ "meta" ] }, { "name": "origin_audience_id", "type": "string", "required": false, "description": "Meta only: id of an existing custom audience to derive this new audience from (seed). For lookalikes use create_lookalike_audience instead; here it is for deriving a custom audience from another. Source: output id of a prior create_custom_audience or ads.list_custom_audiences rows[].id.", "example": "23845000000000000", "platform": [ "meta" ] }, { "name": "dataset_id", "type": "string", "required": false, "description": "Meta only: id of an offline/CAPI dataset to bind a rule-based audience to. Source: id of an offline event set / CAPI dataset from Events Manager.", "example": "112233445566", "platform": [ "meta" ] }, { "name": "audience_enhancement", "type": "boolean", "required": false, "description": "TikTok audience enhancement.", "example": true, "platform": [ "tiktok" ] }, { "name": "audience_sub_type", "type": "string", "required": false, "description": "TikTok audience sub type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "calculate_type", "type": "string", "required": true, "description": "TikTok only: Hash scheme matching the uploaded identifier column.", "example": "EMAIL_SHA256", "enum": [ "EMAIL_SHA256", "PHONE_SHA256", "IDFA_MD5", "IDFA_SHA256", "GAID_MD5", "GAID_SHA256" ], "source": "constant", "platform": [ "tiktok" ] }, { "name": "file_paths", "type": "list", "required": true, "description": "TikTok file paths.", "example": [], "platform": [ "tiktok" ] }, { "name": "retention_in_days", "type": "integer", "required": false, "description": "TikTok retention in days.", "example": 1, "platform": [ "tiktok" ] } ], "output_shape": "{\"id\": \"audience_id\", \"name\": \"...\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_gmv_max_campaign", "namespace": "ads", "action": "create_gmv_max_campaign", "description": "Create a TikTok Shop GMV Max campaign.", "mode": "risk", "platforms": [ "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nCreate a TikTok Shop GMV Max campaign.\n\nUse when:\nLaunching a TikTok Shop GMV Max campaign for a store that passed the availability check.\n\nDo not use when:\nBefore confirming eligibility with get_gmv_max_availability, or for non-Shop campaigns.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nCreates a live GMV Max campaign bound to a Shop / store; spend can begin per schedule. Requires shop_exclusivity_acknowledged.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "store_id", "type": "string", "required": true, "description": "TikTok store id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "store_authorized_bc_id", "type": "string", "required": true, "description": "TikTok store authorized bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "campaign_name", "type": "string", "required": true, "description": "TikTok campaign name.", "example": "value", "platform": [ "tiktok" ] }, { "name": "deep_bid_type", "type": "string", "required": true, "description": "TikTok deep bid type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "optimization_goal", "type": "string", "required": true, "description": "TikTok optimization goal.", "example": "value", "platform": [ "tiktok" ] }, { "name": "request_id", "type": "string", "required": true, "description": "TikTok request id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "schedule_start_time", "type": "string", "required": true, "description": "TikTok schedule start time.", "example": "value", "platform": [ "tiktok" ] }, { "name": "schedule_type", "type": "string", "required": true, "description": "TikTok schedule type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "shopping_ads_type", "type": "string", "required": true, "description": "TikTok shopping ads type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "shop_exclusivity_acknowledged", "type": "boolean", "required": true, "description": "Explicit acknowledgement required before this side effect.", "example": true, "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_label", "namespace": "ads", "action": "create_label", "description": "Create a Google Ads label for organization tagging; review-gated, reversible.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/apply_campaign_label", "action:ads/apply_ad_group_label", "action:_batch/submit" ], "long_description": "Primary use:\nCreate a reusable label that can be attached to Google campaigns or ad groups for organization and filtering.\n\nUse when:\n- The user wants a new tag like 'Q2 Promo' or 'Brand Awareness' to organize entities.\n\nDo not use when:\n- The platform is Meta. Use action:ads/create_ad_label.\n- The user wants to apply an existing label. Use action:ads/apply_campaign_label or action:ads/apply_ad_group_label.\n\nWorkflow:\n1. Choose a unique label name and optional background color (hex).\n2. Submit via action:_batch/submit for HITL review.\n3. Apply via apply_campaign_label / apply_ad_group_label.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.resource_id is the new label's numeric id.\n\nSide effects:\n- Creates a Label row on the account.\n- Reversible via Google Ads UI; no L1.5 remove handler exposed.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Label display name; must be unique within the account." }, { "name": "background_color", "type": "string", "required": false, "description": "Google only: optional HEX background color for the label, applied to the nested TextLabel. Must match '^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$' (the leading '#' is required). May not be visible on manager accounts. Source: user-provided HEX color constant chosen by the agent/user.", "example": "#FF5733" }, { "name": "description", "type": "string", "required": false, "description": "Google only: optional short descriptive text for the label (nested TextLabel.description, max 200 chars). This is the colored-label text that pairs with background_color; the top-level 'name' is the label's unique identifier name. Source: user-provided text chosen by the agent/user.", "example": "Q2 promotional campaigns" } ], "output_shape": "{\"success\": true, \"message\": \"Label created successfully\", \"resource_id\": \"123\", \"results\": [{\"labelResult\": {\"resourceName\": \"customers/X/labels/Y\"}}]}" }, { "id": "action:ads/create_lead_form", "namespace": "ads", "action": "create_lead_form", "description": "Create a Meta lead form attached to a Page; review-gated, reversible via UI.", "mode": "write", "platforms": [ "meta" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_lead_forms", "action:ads/archive_lead_form", "action:ads/get_leads", "action:ads/get_account_pages", "action:_batch/submit" ], "long_description": "Primary use:\nCreate a Lead Ads form on a Facebook Page with questions, privacy policy URL, optional context card and thank-you page.\n\nUse when:\n- The user wants to launch a Lead Ads campaign and needs a leadgen_form_id.\n\nDo not use when:\n- The form already exists. Use action:ads/list_lead_forms first.\n- The platform is Google.\n\nWorkflow:\n1. Choose page_id via action:ads/get_account_pages.\n2. Define questions, privacy_policy {url, link_text} (url must be public https), follow_up_action_url, and optional thank_you_page.website_url.\n3. Submit via action:_batch/submit for HITL review.\n4. Reference the new form_id in action:ads/create_ad with creative wired to leadgen_form_id.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.id is the new lead_form_id.\n\nSide effects:\n- POSTs {page_id}/leadgen_forms. The form is attached to the Page, not the ad account.\n- privacy_policy.url, follow_up_action_url, and (if provided) thank_you_page.website_url are validated as public https URLs.\n- Meta has no lead-form delete; retire an unwanted form via action:ads/archive_lead_form.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Lead form display name." }, { "name": "page_id", "type": "string", "required": true, "description": "Facebook Page id from action:ads/get_account_pages." }, { "name": "questions", "type": "list", "required": true, "description": "List of question objects. Each entry typically includes {type: EMAIL|FULL_NAME|PHONE|...|CUSTOM, key?, label?}." }, { "name": "privacy_policy", "type": "object", "required": true, "description": "Privacy policy object {url, link_text}. url must be a public https URL; link_text defaults to 'Privacy Policy'. This is the create-time field — the flat privacy_policy_url is a read-only field and is silently ignored by Meta on create (a bare url string is still accepted for back-compat).", "example": "{\"url\": \"https://example.com/privacy\", \"link_text\": \"Privacy Policy\"}" }, { "name": "context_card", "type": "object", "required": false, "description": "Optional context card object shown before the form." }, { "name": "thank_you_page", "type": "object", "required": false, "description": "Optional thank-you page object. website_url (if set) must be public https. Legacy button_url is accepted by Soku for compatibility but is converted before calling Meta." }, { "name": "locale", "type": "string", "required": false, "description": "Form locale, e.g. en_US." }, { "name": "is_optimized_for_quality", "type": "boolean", "required": false, "description": "Toggle Meta's higher-intent optimization." }, { "name": "follow_up_action_url", "type": "string", "required": true, "description": "Required public https follow-up URL embedded into the form completion flow. Meta currently rejects create calls without this field." } ], "output_shape": "{\"id\": \"lead_form_id\"}" }, { "id": "action:ads/create_lookalike_audience", "namespace": "ads", "action": "create_lookalike_audience", "description": "Create a Meta lookalike audience from a source audience; review-gated, populates asynchronously. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_custom_audience", "action:ads/list_custom_audiences", "action:ads/remove_custom_audience", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nBuild a similarity-based Lookalike audience from an existing source audience in a chosen country and ratio (e.g. 0.01 = top 1%).\n\nUse when:\n- The user wants to scale acquisition by targeting users similar to an existing high-value audience.\n\nDo not use when:\n- The source audience does not exist yet. Use action:ads/create_custom_audience first.\n- The user wants a non-lookalike custom audience. Use action:ads/create_custom_audience.\n- The platform is Google.\n\nWorkflow:\n1. Identify source_audience_id via action:ads/list_custom_audiences.\n2. Pick country (ISO-2) and ratio (float).\n3. Submit via action:_batch/submit for HITL review.\n4. Allow Meta time to populate; check via list_custom_audiences.delivery_status.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.id is the new audience id.\n\nSide effects:\n- POSTs to {account_id}/customaudiences (same endpoint as create_custom_audience).\n- Forces subtype=LOOKALIKE and builds lookalike_spec={type:similarity, origin, country, ratio} if not supplied.\n- Audience populates asynchronously after creation; no immediate spend.\n- Removable via action:ads/remove_custom_audience (irreversible hard delete).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Lookalike audience display name. Required when platform=tiktok." }, { "name": "source_audience_id", "type": "string", "required": true, "description": "Source custom audience id from action:ads/list_custom_audiences. Required when platform=tiktok." }, { "name": "country", "type": "string", "required": true, "description": "ISO-2 country code.", "example": "US", "platform": [ "meta" ] }, { "name": "ratio", "type": "number", "required": true, "description": "Lookalike ratio (e.g. 0.01 = top 1%, 0.05 = top 5%).", "example": 0.01, "platform": [ "meta" ] }, { "name": "subtype", "type": "string", "required": false, "description": "Forced to LOOKALIKE by the handler.", "platform": [ "meta" ] }, { "name": "lookalike_spec", "type": "object", "required": true, "description": "TikTok only: Lookalike settings with audience_size and non-empty location_ids. source_audience_id is injected from the top-level field.", "example": { "audience_size": "NARROW", "location_ids": [ "6252001" ] }, "source": "user", "platform": [ "tiktok" ] }, { "name": "context_info", "type": "object", "required": false, "description": "TikTok context info.", "example": {}, "platform": [ "tiktok" ] } ], "output_shape": "{\"id\": \"lookalike_audience_id\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_pixel", "namespace": "ads", "action": "create_pixel", "description": "Create a Meta or TikTok Pixel (ads pixel) on an ad account; review-gated, reversible via UI.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_pixels", "action:ads/update_pixel", "action:ads/send_conversion_event", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nCreate a Meta or TikTok Pixel on the resolved ad account.\n\nUse when:\nStanding up conversion tracking for an advertiser that has no suitable existing pixel.\n\nDo not use when:\nA pixel already exists — reuse it via list_pixels; never reuse a pixel id across providers.\n\nWorkflow:\n1. Resolve the advertiser account (meta or tiktok).\n2. Submit through the review queue.\n3. Read the new pixel identity from the provider response.\n\nOutput semantics:\nFor TikTok, resource_id/raw.pixel_id is the numeric mutation ID used by update_pixel, while raw.pixel_code is the Events API event_source_id. Meta and TikTok responses are not coerced into fake cross-provider parity.\n\nSide effects:\nCreates a new tracking pixel on the ad account; it begins accepting events once installed. Reversible via the provider UI.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Pixel display name. Required when platform=tiktok." } ], "output_shape": "{\"id\": \"pixel_id\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_pmax_campaign", "namespace": "ads", "action": "create_pmax_campaign", "description": "Create a complete Google Performance Max campaign (budget + campaign + asset group + assets + optional audience signals) in one atomic mutate, PAUSED; review-gated, reversible via remove_campaign. Google only.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_campaign", "action:ads/list_campaigns", "action:ads/remove_campaign", "action:ads/upload_image", "action:_batch/submit" ], "long_description": "Primary use:\nCreate a ready-to-review Performance Max campaign in a single atomic mutate. Unlike create_campaign (which only builds a campaign shell), this wires budget -> campaign -> asset group -> asset group assets -> optional audience signals together so the campaign is structurally complete and force-paused.\n\nUse when:\n- The user asks to launch a Google Performance Max campaign and already has uploaded assets (text + image asset ids).\n\nDo not use when:\n- Platform is Meta. PMax is Google only.\n- The campaign is a standard Search/Display/Shopping/Video campaign. Use action:ads/create_campaign.\n- This is a retail PMax tied to a Merchant Center feed (listing groups / shopping_settings). Not supported in phase 1.\n- The user wants to modify or copy an existing PMax campaign. Not supported in phase 1.\n\nPhase-1 constraints:\n- Assets must already be uploaded; pass them by asset_id. The field_type MUST match the asset's real type (text asset for HEADLINE/DESCRIPTION, image asset for MARKETING_IMAGE/SQUARE_MARKETING_IMAGE/LOGO), otherwise the whole atomic mutate fails and nothing is created.\n- Brand assets (BUSINESS_NAME, LOGO) are attached at asset-group level (brand_guidelines_enabled=false path).\n- Exactly one asset group per call.\n- The account must have conversion tracking configured; PMax optimizes toward conversion goals.\n\nMinimum assets (by field_type): HEADLINE>=3, LONG_HEADLINE>=1, DESCRIPTION>=2, MARKETING_IMAGE>=1, SQUARE_MARKETING_IMAGE>=1, BUSINESS_NAME>=1, LOGO>=1. The handler validates these locally before submitting and raises if unmet.\n\nBidding:\n- bidding_strategy MUST be MAXIMIZE_CONVERSIONS or MAXIMIZE_CONVERSION_VALUE. Optionally pass target_cpa_micros (with MAXIMIZE_CONVERSIONS) or target_roas (with MAXIMIZE_CONVERSION_VALUE).\n\nWorkflow:\n1. Ensure the text and image assets already exist in the Google Ads account and collect their numeric asset ids. (action:ads/upload_image is Meta-only and cannot create Google assets; phase 1 does not upload Google assets — they must be created out-of-band.)\n2. Submit via action:_batch/submit for HITL review.\n3. After approval, confirm with action:ads/list_campaigns.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.resource_id is the new campaign's numeric id; raw.asset_group_id is the new asset group's numeric id. raw.results holds every mutate operation result (campaignBudgetResult, campaignResult, assetGroupResult, assetGroupAssetResult, assetGroupSignalResult) from the single atomic mutate.\n\nSide effects:\n- Creates a CampaignBudget, a PERFORMANCE_MAX Campaign, one AssetGroup, its AssetGroupAssets, and any AssetGroupSignals, all PAUSED, in one atomic mutate (all-or-nothing).\n- No immediate spend; both the campaign AND the asset group are created PAUSED. Phase 1 exposes no asset-group enable action, so activating spend currently requires enabling the campaign (update_campaign -> ENABLED) AND enabling the asset group in the Google Ads UI. Do not promise spend starts from update_campaign alone.\n- Reversible via action:ads/remove_campaign.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Campaign display name; must be unique within the account.", "example": "PMax Spring 2026" }, { "name": "budget_amount_micros", "type": "integer", "required": true, "description": "Daily budget in micros (1 USD = 1_000_000).", "example": 50000000 }, { "name": "bidding_strategy", "type": "string", "required": true, "description": "MAXIMIZE_CONVERSIONS or MAXIMIZE_CONVERSION_VALUE only. Other strategies are rejected for PMax.", "example": "MAXIMIZE_CONVERSIONS" }, { "name": "target_cpa_micros", "type": "integer", "required": false, "description": "Optional target CPA in micros; only with MAXIMIZE_CONVERSIONS." }, { "name": "target_roas", "type": "number", "required": false, "description": "Optional target ROAS (e.g. 3.5 = 350%); only with MAXIMIZE_CONVERSION_VALUE." }, { "name": "asset_group", "type": "object", "required": true, "description": "The single asset group: {name, final_urls (>=1), final_mobile_urls?, path1?, path2?, assets: [{asset_id, field_type}] meeting minimum counts, audience_signals?: [{audience_id} | {search_theme}]}. field_type is one of HEADLINE, LONG_HEADLINE, DESCRIPTION, MARKETING_IMAGE, SQUARE_MARKETING_IMAGE, BUSINESS_NAME, LOGO, YOUTUBE_VIDEO, etc.", "example": { "name": "AG 1", "final_urls": [ "https://example.com" ], "assets": [ { "asset_id": "111", "field_type": "HEADLINE" }, { "asset_id": "222", "field_type": "MARKETING_IMAGE" } ], "audience_signals": [ { "search_theme": "running shoes" } ] } }, { "name": "final_url_suffix", "type": "string", "required": false, "description": "Google only: campaign-level final URL suffix (query params appended to the landing page URL, e.g. for analytics). Set on the PERFORMANCE_MAX campaign. Optional. Source: user-provided tracking string (ValueTrack params allowed, e.g. {lpurl}).", "example": "utm_source=google&utm_medium=cpc" }, { "name": "tracking_url_template", "type": "string", "required": false, "description": "Google only: campaign-level tracking URL template (third-party click measurement). Set on the PERFORMANCE_MAX campaign. Optional. Source: user-provided / tracking-vendor-supplied template containing {lpurl}.", "example": "{lpurl}?cid=12345" }, { "name": "url_expansion_opt_out", "type": "boolean", "required": false, "description": "Google only: when true, opts the PMax campaign OUT of final URL expansion. Final URL expansion is ON by default for PMax; this maps to the v24 asset_automation_settings FINAL_URL_EXPANSION_TEXT_ASSET_AUTOMATION = OPTED_OUT (the removed Campaign.url_expansion_opt_out field). Optional. Source: user/agent decision boolean.", "example": true }, { "name": "asset_automation", "type": "object", "required": false, "description": "Google only: opt in/out of Google's automatically generated ad assets at create time. Object mapping automation type -> OPTED_IN | OPTED_OUT, e.g. {'text_asset_automation': 'OPTED_OUT'}. Campaign-level types: text_asset_automation, final_url_expansion, generate_enhanced_youtube_videos, generate_image_enhancement, generate_image_extraction — all five are ON by default for Performance Max, so omitting this parameter leaves Google free to generate assets. Ad-level Demand Gen types are rejected here. Supersedes url_expansion_opt_out, which remains accepted for backward compatibility and covers only final_url_expansion. Trap: with final_url_expansion left OPTED_IN, Google still generates text for expanded URLs and that generation cannot be opted out, so OPTED_OUT on text_asset_automation alone does not stop all text generation. Source: user/agent decision.", "example": { "text_asset_automation": "OPTED_OUT" }, "platform": [ "google" ] }, { "name": "contains_eu_political_advertising", "type": "string", "required": false, "description": "Google only: EU political advertising declaration for the campaign. Defaults to DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING. Set to CONTAINS_EU_POLITICAL_ADVERTISING (or 2 / true) only when the campaign actually contains EU political ads. Optional; required to be accurate by Google policy. Source: constant enum (DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING | CONTAINS_EU_POLITICAL_ADVERTISING) chosen by user/advertiser truthfulness.", "example": "DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING" } ], "output_shape": "{\"success\": true, \"message\": \"PMax campaign created successfully\", \"resource_id\": \"123\", \"asset_group_id\": \"456\", \"results\": [{\"campaignBudgetResult\": {...}}, {\"campaignResult\": {...}}, {\"assetGroupResult\": {...}}, {\"assetGroupAssetResult\": {...}}]}" }, { "id": "action:ads/create_product_catalog", "namespace": "ads", "action": "create_product_catalog", "description": "Create a Meta product catalog under the account business; review-gated platform write. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:ads/list_product_catalogs", "action:ads/update_product_catalog", "action:ads/remove_product_catalog", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nCreate a new Meta product catalog for commerce/product ads workflows.\n\nUse when:\n- The user needs a new catalog container before creating feeds or product sets.\n- The catalog will belong to the business that owns the resolved Meta ad account.\n\nDo not use when:\n- The user only needs to inspect catalog state. Use the matching list/get action first.\n- The user needs cached ads performance metrics. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Call action:ads/list_ad_accounts if account_id is unknown.\n2. Provide a catalog name and optional vertical/catalog settings.\n3. Submit through action:_batch/submit for HITL approval.\n4. After approval, call action:ads/list_product_catalogs to confirm the new catalog.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Meta Graph API mutation response.\n- The action is review-gated through the ads HITL path.\n\nSide effects:\n- Creates a product catalog in Meta under the account business.\n- Requires catalog_management permission on the Meta credential.\n- Does not upload products or create feeds by itself.\n\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: vertical enum values are not listed in the capability doc (only 'e.g. commerce'); the agent must guess the legal set {commerce,hotels,flights,destinations,home_listings,vehicles} and the commerce default. Also note: destination_catalog_settings is described only as 'Optional Meta destination catalog settings payload' with no inner-field schema or example, and no mention that it is only valid when vertical=destinations — the agent cannot build a correct object without external knowledge. Capability gap: flight_catalog_settings — needed to create a flights-vertical catalog; absent from capability docs, whitelist, and handler, so the flights vertical cannot be properly configured at creation.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "Catalog display name. Required when platform=tiktok." }, { "name": "vertical", "type": "string", "required": false, "description": "Optional Meta catalog vertical, e.g. commerce.", "platform": [ "meta" ] }, { "name": "destination_catalog_settings", "type": "object", "required": false, "description": "Optional Meta destination catalog settings payload.", "platform": [ "meta" ] }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "catalog_conf", "type": "object", "required": true, "description": "TikTok catalog conf.", "example": {}, "platform": [ "tiktok" ] }, { "name": "catalog_type", "type": "string", "required": true, "description": "TikTok catalog type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "creative_asset_type", "type": "string", "required": false, "description": "TikTok creative asset type.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"id\": \"catalog_id\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_product_feed", "namespace": "ads", "action": "create_product_feed", "description": "Create a Meta product feed in an owned catalog; review-gated platform write. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:ads/list_product_catalogs", "action:ads/get_product_feed_details", "action:ads/update_product_feed", "action:ads/remove_product_feed", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nAttach a product feed source to an existing Meta product catalog.\n\nUse when:\n- The user has an owned catalog_id and wants Meta to ingest products from a feed.\n- A catalog exists but needs a scheduled or uploaded product feed.\n\nDo not use when:\n- The user only needs to inspect catalog state. Use the matching list/get action first.\n- The user needs cached ads performance metrics. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Call action:ads/list_product_catalogs or get_product_catalog_details to confirm catalog_id.\n2. Provide name plus feed_url or Meta feed scheduling fields.\n3. Submit through action:_batch/submit for HITL approval.\n4. After approval, call action:ads/get_product_feed_details or get_catalog_diagnostics.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Meta Graph API mutation response.\n- The action is review-gated through the ads HITL path.\n\nSide effects:\n- Creates a feed under the owned product catalog.\n- May cause Meta to ingest product data from the provided feed source.\n- Requires catalog_management permission on the Meta credential.\n", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id. Required when platform=tiktok." }, { "name": "name", "type": "string", "required": true, "description": "Feed display name.", "platform": [ "meta" ] }, { "name": "feed_url", "type": "string", "required": false, "description": "Meta only: product feed file URL (http/https/ftp/sftp). The handler nests this into schedule.url and defaults schedule.interval to DAILY, so passing feed_url alone now schedules a real ingestion. To control the cadence, pass schedule instead. Source: user-provided public URL of the feed file (TSV/CSV/XML), not a website/Page URL.", "example": "https://example.com/catalog/feed.tsv", "platform": [ "meta" ] }, { "name": "schedule", "type": "object", "required": false, "description": "Meta only: scheduled-fetch config. Subfields: url (string, the feed file URL), interval (enum HOURLY|DAILY|WEEKLY|MONTHLY), hour (0-23 PT), minute (0-59), day_of_week (SUNDAY..SATURDAY for WEEKLY). If both feed_url and schedule are given, schedule.url wins. Source: user-provided URL + chosen cadence; interval from the official enum constant.", "example": { "interval": "DAILY", "url": "https://example.com/feed.tsv", "hour": 22 }, "platform": [ "meta" ] }, { "name": "update_schedule", "type": "object", "required": false, "description": "Meta only: separate schedule for incremental/update fetches; same shape as schedule (url, interval enum HOURLY|DAILY|WEEKLY|MONTHLY, hour, minute, day_of_week). Source: user-provided URL + cadence.", "example": { "interval": "HOURLY", "url": "https://example.com/updates.tsv" }, "platform": [ "meta" ] }, { "name": "default_currency", "type": "string", "required": false, "description": "Meta only: ISO-4217 catalog-wide fallback currency applied to feed items that omit a currency. Source: user-provided currency code constant.", "example": "USD", "platform": [ "meta" ] }, { "name": "override_type", "type": "string", "required": false, "description": "Meta only: marks this feed as a localized supplementary override feed. Enum: COUNTRY, LANGUAGE, LANGUAGE_AND_COUNTRY, LOCAL, VERSION (and others). Requires override_value. Source: constant enum chosen by the agent for localized/supplementary feeds.", "example": "COUNTRY", "platform": [ "meta" ] }, { "name": "override_value", "type": "string", "required": false, "description": "Meta only: the value for override_type (e.g. a country code when override_type=COUNTRY, a locale when override_type=LANGUAGE). Source: user-provided value paired with override_type.", "example": "US", "platform": [ "meta" ] }, { "name": "country", "type": "string", "required": false, "description": "Meta only: ISO country code scoping the feed for localized feeds. Source: user-provided country code constant.", "example": "US", "platform": [ "meta" ] }, { "name": "file_name", "type": "string", "required": false, "description": "Meta only: file name label for the feed (already accepted by the backend; documenting it). Source: user-provided file name.", "example": "products.tsv", "platform": [ "meta" ] }, { "name": "delimiter", "type": "string", "required": false, "description": "Meta only: column delimiter for CSV/TSV feeds. Enum: AUTODETECT, BAR, COMMA, SEMICOLON, TAB, TILDE. Source: constant enum.", "example": "TAB", "platform": [ "meta" ] }, { "name": "encoding", "type": "string", "required": false, "description": "Meta only: file character encoding. Enum: AUTODETECT, LATIN1, UTF8, UTF16BE, UTF16LE, UTF32BE, UTF32LE. Source: constant enum.", "example": "UTF8", "platform": [ "meta" ] }, { "name": "quoted_fields_mode", "type": "string", "required": false, "description": "Meta only: how quoted CSV fields are handled. Enum: AUTODETECT, OFF, ON. Source: constant enum.", "example": "AUTODETECT", "platform": [ "meta" ] }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "feed_name", "type": "string", "required": true, "description": "TikTok feed name.", "example": "value", "platform": [ "tiktok" ] }, { "name": "schedule_param", "type": "object", "required": false, "description": "TikTok only: Feed schedule source; source.uri is the provider feed URL.", "example": { "source": { "uri": "https://example.com/feed.csv" } }, "source": "user", "platform": [ "tiktok" ] }, { "name": "update_mode", "type": "string", "required": true, "description": "TikTok update mode.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"id\": \"product_feed_id\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_rule_custom_audience", "namespace": "ads", "action": "create_rule_custom_audience", "description": "Create a TikTok rule-based custom audience.", "mode": "write", "platforms": [ "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nCreate a TikTok rule-based custom audience.\n\nUse when:\nBuilding a TikTok audience from engagement / web / app rules rather than an uploaded customer file.\n\nDo not use when:\nYou already hold a customer file to upload — use upload_custom_audience_file.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nCreates a new TikTok rule-based custom audience that immediately becomes available for ad-group targeting.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "name", "type": "string", "required": true, "description": "TikTok name.", "example": "value", "platform": [ "tiktok" ] }, { "name": "audience_type", "type": "string", "required": true, "description": "TikTok audience type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "rule_spec", "type": "object", "required": true, "description": "TikTok only: Rule spec with inclusion_rule_set.operator=OR and a non-empty inclusion_rule_set.rules array.", "example": { "inclusion_rule_set": { "operator": "OR", "rules": [ { "event_source_ids": [ "7665621501147643922" ], "retention_days": 30 } ] } }, "source": "user", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_smart_plus_campaign", "namespace": "ads", "action": "create_smart_plus_campaign", "description": "Create a TikTok Smart+ campaign.", "mode": "write", "platforms": [ "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nCreate a TikTok Smart+ campaign.\n\nUse when:\nLaunching a TikTok Smart+ (automated) campaign where TikTok manages targeting and creative optimization.\n\nDo not use when:\nYou want a manually managed campaign — use create_campaign.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nCreates a live TikTok Smart+ campaign; spend can begin once ad groups / ads are added and the campaign is enabled. Requires explicit smart_plus_acknowledged.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "campaign_name", "type": "string", "required": true, "description": "TikTok campaign name.", "example": "value", "platform": [ "tiktok" ] }, { "name": "objective_type", "type": "string", "required": true, "description": "TikTok objective type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "request_id", "type": "string", "required": true, "description": "TikTok only: Digit-only idempotency request identifier.", "example": "202607230001", "source": "user", "platform": [ "tiktok" ] }, { "name": "smart_plus_acknowledged", "type": "boolean", "required": true, "description": "Explicit acknowledgement required before this side effect.", "example": true, "platform": [ "tiktok" ] }, { "name": "app_id", "type": "string", "required": false, "description": "TikTok app id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "app_promotion_type", "type": "string", "required": false, "description": "TikTok app promotion type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "bid_align_type", "type": "string", "required": false, "description": "TikTok bid align type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "budget", "type": "number", "required": false, "description": "TikTok only: Positive account-currency budget; provide with budget_mode.", "example": 100.0, "source": "user", "platform": [ "tiktok" ] }, { "name": "budget_mode", "type": "string", "required": false, "description": "TikTok only: Budget mode; provide with budget.", "example": "BUDGET_MODE_DAY", "source": "constant", "platform": [ "tiktok" ] }, { "name": "budget_optimize_on", "type": "boolean", "required": false, "description": "TikTok budget optimize on.", "example": true, "platform": [ "tiktok" ] }, { "name": "campaign_app_profile_page_state", "type": "string", "required": false, "description": "TikTok campaign app profile page state.", "example": "value", "platform": [ "tiktok" ] }, { "name": "campaign_type", "type": "string", "required": false, "description": "TikTok campaign type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "catalog_enabled", "type": "boolean", "required": false, "description": "TikTok catalog enabled.", "example": true, "platform": [ "tiktok" ] }, { "name": "catalog_type", "type": "string", "required": false, "description": "TikTok catalog type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "disable_skan_campaign", "type": "boolean", "required": false, "description": "TikTok disable skan campaign.", "example": true, "platform": [ "tiktok" ] }, { "name": "is_advanced_dedicated_campaign", "type": "boolean", "required": false, "description": "TikTok is advanced dedicated campaign.", "example": true, "platform": [ "tiktok" ] }, { "name": "is_promotional_campaign", "type": "boolean", "required": false, "description": "TikTok is promotional campaign.", "example": true, "platform": [ "tiktok" ] }, { "name": "open_api_partner", "type": "string", "required": false, "description": "TikTok open api partner.", "example": "value", "platform": [ "tiktok" ] }, { "name": "operation_status", "type": "string", "required": false, "description": "TikTok only: Initial native lifecycle status. Defaults to DISABLE for safe review.", "example": "DISABLE", "enum": [ "DISABLE", "ENABLE" ], "source": "constant", "platform": [ "tiktok" ] }, { "name": "po_number", "type": "string", "required": false, "description": "TikTok po number.", "example": "value", "platform": [ "tiktok" ] }, { "name": "postback_window_mode", "type": "string", "required": false, "description": "TikTok postback window mode.", "example": "value", "platform": [ "tiktok" ] }, { "name": "sales_destination", "type": "string", "required": false, "description": "TikTok sales destination.", "example": "value", "platform": [ "tiktok" ] }, { "name": "special_industries", "type": "list", "required": false, "description": "TikTok special industries.", "example": [], "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/create_text_asset", "namespace": "ads", "action": "create_text_asset", "description": "Create a Google Ads text asset (headline / description / long headline); returns the asset_id.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/upload_image_asset", "action:ads/create_pmax_campaign" ], "long_description": "Primary use:\nCreate an inline Google TextAsset via the Google Ads API generic mutate. Returns asset_id to reference in create_pmax_campaign's asset_group.\n\nUse when:\n- A Google PMax / asset-based campaign needs a text asset by id.\n\nDo not use when:\n- The platform is Meta (Meta copy is set inline on the creative).\n\nWorkflow:\n1. Provide the text string.\n2. Submit this write through action:_batch/submit for HITL review.\n3. Feed asset_id into the asset_group.\n\nOutput semantics:\n- resource_id is the numeric asset id; raw.resource_name is the full path.\n\nSide effects:\n- Creates an account-level TextAsset; no ad spend.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id (digits only, no dashes).", "example": "1234567890" }, { "name": "text", "type": "string", "required": true, "description": "Asset text (e.g. a headline or description).", "example": "Book a free consultation" }, { "name": "name", "type": "string", "required": false, "description": "Optional asset name.", "example": "headline-1" } ], "output_shape": "{\"success\": true, \"resource_id\": \"77\", \"resource_name\": \"customers/123/assets/77\"}" }, { "id": "action:ads/create_user_list", "namespace": "ads", "action": "create_user_list", "description": "Create a Google CRM-based Customer Match user list destination; review-gated.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/add_audience_targeting", "action:ads/update_user_list", "action:ads/create_custom_audience", "action:_batch/submit" ], "long_description": "Primary use:\nCreate a Customer Match user list with crm_based_user_list.upload_key_type = CONTACT_INFO. Use this as the destination for hashed customer-info uploads.\n\nUse when:\n- The user wants a new Customer Match audience for hashed email / phone uploads.\n- A workflow needs a user_list_id to target via action:ads/add_audience_targeting.\n\nDo not use when:\n- The user wants a Meta custom audience. Use action:ads/create_custom_audience.\n- The user wants to delete a user list. User lists cannot be deleted via API.\n- The list already exists and only its name / description / membership window changes. Use action:ads/update_user_list.\n\nWorkflow:\n1. Choose name, optional description, optional membership_lifespan_days.\n2. Submit via action:_batch/submit for HITL review.\n3. Upload contacts via Google's Customer Match upload (outside this L1.5 surface).\n4. Reference the list via action:ads/add_audience_targeting.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.resource_id is the new user_list's numeric id.\n\nSide effects:\n- Creates a CRM-based UserList. No deletion via API.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": true, "description": "User list display name; must be unique within the account." }, { "name": "description", "type": "string", "required": false, "description": "Google only: optional description. Default \"Created by Soku ads-ops\". Source: user-provided or default constant.", "example": "Newsletter subscribers" }, { "name": "membership_lifespan_days", "type": "integer", "required": false, "description": "Days a contact stays in the list. Valid range: 0-540 inclusive, OR exactly 10000 for no expiration (CRM-based lists). Values 541-9999 are invalid. Default 540. Source: agent/user choice within the allowed range; constant integer.", "example": 540 }, { "name": "upload_key_type", "type": "string", "required": false, "description": "Google only: Customer Match key type for the list. One of CONTACT_INFO (default; hashed email/phone/address), CRM_ID (your own user ids), MOBILE_ADVERTISING_ID (device ids IDFA/AAID). Each list holds exactly one type. MOBILE_ADVERTISING_ID additionally requires app_id. Source: agent decision based on what identifiers will be uploaded; constant enum.", "example": "CONTACT_INFO", "enum": [ "CONTACT_INFO", "CRM_ID", "MOBILE_ADVERTISING_ID" ], "source": "constant" }, { "name": "app_id", "type": "string", "required": false, "description": "Google only: REQUIRED when upload_key_type=MOBILE_ADVERTISING_ID. The mobile app id that uniquely identifies the app the device ids were collected from. Ignored for other key types. Source: user-provided app store / package id of the advertiser's mobile app.", "example": "com.example.myapp" } ], "output_shape": "{\"success\": true, \"message\": \"User list created successfully\", \"resource_id\": \"123\", \"results\": [{\"userListResult\": {\"resourceName\": \"customers/X/userLists/Y\"}}]}" }, { "id": "action:ads/create_youtube_video_asset", "namespace": "ads", "action": "create_youtube_video_asset", "description": "Create a Google Ads YouTube video asset from an existing YouTube video id; returns the asset_id.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/upload_image_asset", "action:ads/create_pmax_campaign" ], "long_description": "Primary use:\nCreate a Google YouTubeVideoAsset referencing a public YouTube video. Google does NOT host video bytes — the video must already be on YouTube; pass its 11-char youtube_video_id (not a URL). Returns asset_id.\n\nUse when:\n- A Google PMax / Video campaign needs a video asset already on YouTube.\n\nDo not use when:\n- You have raw video bytes / a non-YouTube URL — upload to YouTube first.\n- The platform is Meta. Use action:ads/upload_video (returns video_id).\n\nWorkflow:\n1. Get the 11-char youtube_video_id of a public YouTube video.\n2. Submit this write through action:_batch/submit for HITL review.\n3. Feed asset_id into the asset_group / creative.\n\nOutput semantics:\n- resource_id is the numeric asset id; raw.resource_name is the full path.\n\nSide effects:\n- Creates an account-level YouTubeVideoAsset; no ad spend.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id (digits only, no dashes).", "example": "1234567890" }, { "name": "youtube_video_id", "type": "string", "required": true, "description": "11-char id of a public YouTube video (not a URL).", "example": "dQw4w9WgXcQ" }, { "name": "name", "type": "string", "required": false, "description": "Optional asset name.", "example": "brand-video" } ], "output_shape": "{\"success\": true, \"resource_id\": \"55\", \"resource_name\": \"customers/123/assets/55\"}" }, { "id": "action:ads/delete_conversion_group", "namespace": "ads", "action": "delete_conversion_group", "description": "Review-gated delete of an Admin View conversion_group.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_groups" ], "long_description": "Soft-deletes a brand-scoped Admin View conversion_group after human approval. Requires expected_version from list_conversion_groups; stale versions are rejected.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "group_id", "type": "integer", "required": true, "description": "Conversion group id." }, { "name": "expected_version", "type": "string", "required": true, "description": "Opaque version from list_conversion_groups.groups[].version." } ], "output_shape": "{\"deleted\": true, \"group_id\": 1}" }, { "id": "action:ads/deploy_image_ads_batch", "namespace": "ads", "action": "deploy_image_ads_batch", "description": "Create many Meta image creatives and paused ads from existing image_hash values; review-gated bulk write.", "mode": "write", "platforms": [ "meta" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/upload_images", "action:ads/create_ad_creative", "action:ads/list_ads", "action:_batch/submit" ], "long_description": "Primary use:\nDeploy a batch of Meta image ads after images have already been uploaded. The handler creates AdCreatives in Graph Batch, then creates PAUSED Ads in Graph Batch using the returned creative ids.\n\nUse when:\n- The user has many uploaded Meta image_hash values and wants to create matching creatives + ads in one approved operation.\n- The adset/page/link and optional lead_gen_form_id are shared across the batch, with item-level copy/image variations.\n\nDo not use when:\n- Images are not uploaded yet. Use upload_images first.\n- The user needs videos, carousel creatives, or Google ads. Use the platform-specific primitive actions instead.\n- The request previously returned META_BATCH_AMBIGUOUS. Query Meta by client_ref/name before retrying; do not automatically resubmit.\n\nWorkflow:\n1. Use upload_images to get image_hash values, or reuse known hashes for the same Meta ad account.\n2. Build items[] with stable client_ref, image_hash, and name. client_ref is appended to creative/ad names so ambiguous recovery can query by name.\n3. For lead-gen, provide lead_gen_form_id and page_id. The handler preflights destination_type=ON_AD, optimization_goal=LEAD_GENERATION, campaign objective OUTCOME_LEADS/LEAD_GENERATION, and ACTIVE form ownership.\n4. Submit via action:_batch/submit for HITL review.\n5. Verify via list_ads filtered by adset_id or by the deterministic names.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.items[] preserves input order and includes client_ref, creative_id, ad_id, stage, success, ambiguous, error, and error_code.\n- raw.success=false with META_BATCH_AMBIGUOUS means a non-idempotent Graph Batch write may have reached Meta but no reliable response was received. Do not retry without manual/query confirmation.\n- Partial item failures leave successful creative/ad ids in raw.items[].\n\nSide effects:\n- Creates Meta AdCreatives under the supplied account.\n- Creates Meta Ads under the supplied adset with status=PAUSED.\n- No immediate spend; activation requires update_ad with status=ACTIVE.\n- Non-idempotent creative/ad create batches are not automatically whole-batch retried on request-level unknown outcome.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "adset_id", "type": "string", "required": true, "description": "Meta ad set id that will receive the new paused ads.", "example": "23847900000000123" }, { "name": "page_id", "type": "string", "required": true, "description": "Facebook Page id for object_story_spec.page_id and lead-form ownership preflight.", "example": "1234567890" }, { "name": "link", "type": "string", "required": true, "description": "Shared landing URL for image link creatives.", "example": "https://example.com/landing" }, { "name": "items", "type": "list", "required": true, "description": "Batch items. Each item requires stable client_ref and image_hash, plus name unless top-level name is provided. Items may override message/headline/description/link/caption/call_to_action_type/url_tags.", "example": [ { "client_ref": "asset-001", "image_hash": "abc123", "name": "Lead ad asset 001", "message": "Book a demo today.", "headline": "Demo available" } ], "source": "upstream:action:ads/upload_images" }, { "name": "name", "type": "string", "required": false, "description": "Optional shared base name used when an item omits name. The handler appends [client_ref] to creative and ad names.", "example": "Lead campaign batch" }, { "name": "message", "type": "string", "required": false, "description": "Optional shared primary text; item.message overrides it.", "example": "Book a demo today." }, { "name": "headline", "type": "string", "required": false, "description": "Optional shared headline; item.headline overrides it.", "example": "Grow pipeline faster" }, { "name": "description", "type": "string", "required": false, "description": "Optional shared link description; item.description overrides it.", "example": "AI-powered campaign workflow." }, { "name": "caption", "type": "string", "required": false, "description": "Optional shared display caption; item.caption overrides it.", "example": "example.com" }, { "name": "call_to_action_type", "type": "string", "required": false, "description": "Optional shared Meta CTA type. Defaults to SIGN_UP for lead-gen when lead_gen_form_id is supplied.", "example": "SIGN_UP", "source": "constant" }, { "name": "url_tags", "type": "string", "required": false, "description": "Optional shared Meta url_tags appended to creative links; item.url_tags overrides it.", "example": "utm_source=meta&utm_medium=paid_social" }, { "name": "lead_gen_form_id", "type": "string", "required": false, "description": "Optional Meta Lead Form id. When present the handler validates the adset/campaign/page/form semantics before creating anything.", "example": "123456789012345", "source": "upstream:action:ads/create_lead_form" }, { "name": "instagram_user_id", "type": "string", "required": false, "description": "Optional Instagram user id for the creative object_story_spec. Must be the ad-account-connected IG identity from action:ads/get_connected_instagram_accounts — a public IG @handle or the Facebook Page id is rejected by the ads API.", "example": "17841400000000000" }, { "name": "instagram_actor_id", "type": "string", "required": false, "description": "Optional legacy alias for instagram_user_id. Use only when the account setup exposes instagram_actor_id.", "example": "17841400000000000" } ], "output_shape": "{\"success\": true, \"account_id\": \"act_...\", \"total\": 50, \"succeeded\": 50, \"failed\": 0, \"ambiguous\": 0, \"items\": [{\"index\": 0, \"client_ref\": \"asset-001\", \"creative_id\": \"23...\", \"ad_id\": \"23...\", \"stage\": \"ad\", \"success\": true}]}" }, { "id": "action:ads/download_change_history", "namespace": "ads", "action": "download_change_history", "description": "Download a completed TikTok change-log export.", "mode": "read", "platforms": [ "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nDownload a completed TikTok change-log export.\n\nUse when:\nA change-history export task has completed and you need its file / rows.\n\nDo not use when:\nThe task is still running — poll get_change_history_task first.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nFetches the completed export payload; no advertiser-side mutation. Gated with the export flow.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "task_id", "type": "string", "required": true, "description": "TikTok task id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/enable_ad_automation_audits", "namespace": "ads", "action": "enable_ad_automation_audits", "description": "HITL provision or recover the current brand's 48-hour and weekly full-current-state Google, Meta, TikTok, and ChatGPT Ads automation audits.", "mode": "risk", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:ads/audit_ad_automation_inventory" ], "long_description": "Primary use:\nProvision or recover the canonical 48-hour and weekly full-current-state ads automation audits across the current brand's Google, Meta, TikTok, and ChatGPT Ads accounts.\n\nUse when:\n- audit_ad_automation_inventory reports audit_schedule_required.\n- A current authorized user needs to take ownership after the former owner lost organization membership.\n\nDo not use when:\n- Running a one-time audit; call audit_ad_automation_inventory instead.\n- The user has not approved recurring task creation. This action requires HITL review.\n\nWorkflow:\n1. Submit the action through action:_batch/submit.\n2. Review the server-bound brand, owner, and both cadence definitions.\n3. Approve to atomically create, reuse, or recover the two Automations.\n4. Each run rediscovers current accounts and Meta Pages, then internally pages audit_ad_automation_inventory for every account.\n\nOutput semantics:\n- automations contains exactly the canonical 48-hour interval and weekly cron tasks, including stable client_request_id and lifecycle status.\n- Active semantic matches are returned idempotently. Paused/error tasks are reactivated and same-owner soft-deleted tasks are restored; semantic conflicts fail closed.\n\nSide effects:\n- Creates or reactivates two user-owned Automation rows for the brand.\n- Does not mutate ad provider settings or start ad spend.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Connected ads platform that anchors capability routing. Provisioning always creates both brand-wide schedules and audits every currently connected Google, Meta, TikTok, and ChatGPT Ads account.", "example": "google", "enum": [ "google", "meta", "tiktok", "chatgpt_ads" ], "source": "constant" } ], "output_shape": "{\"success\":true,\"created_via\":\"ads_automation_audit\",\"brand_id\":\"...\",\"owner_user_id\":\"...\",\"automations\":[{\"automation_id\":\"...\",\"client_request_id\":\"...\",\"status\":\"active\",\"schedule_type\":\"interval|cron\",\"schedule_config\":{...}}]}\nTikTok output: uses the same brand-scoped provisioning envelope." }, { "id": "action:ads/establish_automation_baseline", "namespace": "ads", "action": "establish_automation_baseline", "description": "HITL Google, Meta, TikTok, or ChatGPT Ads confirmation of a fresh provider state as a human baseline.", "mode": "risk", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:ads/preview_automation_baseline" ], "long_description": "Primary use:\nConfirm a freshly previewed Google, Meta, TikTok, or ChatGPT Ads provider state as a human baseline for a pre-existing entity.\n\nUse when:\n- preview_automation_baseline returned the digest the user is approving.\n\nDo not use when:\n- The preview is stale or belongs to another identity; preview again.\n- Replacing an authoritative Soku-write baseline. Soku-write authority always wins the atomic conflict.\n\nWorkflow:\n1. Submit the identity and preview_digest through action:_batch/submit.\n2. Review the observed automation state and approve it.\n3. Execution revalidates ownership and fresh-reads provider state.\n4. If the identity-bound digest still matches, atomically store the human baseline; otherwise the action rejects and requires a new preview.\n\nOutput semantics:\n- baseline_status is established for a new write or idempotent for the same already-confirmed observation.\n- baseline_source is human_baseline, which is explicitly less authoritative than an original Soku write.\n\nSide effects:\n- Inserts or updates the local human automation baseline after HITL approval.\n- Does not mutate provider automation settings.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "google, meta, tiktok, or chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "google", "meta", "tiktok", "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Brand-scoped ad account id from ads.list_ad_accounts.", "example": "adv_123", "source": "upstream:action:ads/list_ad_accounts" }, { "name": "entity_type", "type": "string", "required": true, "description": "Audited entity type: meta=creative; google/tiktok=campaign; chatgpt_ads=campaign or ad_group.", "example": "campaign", "enum": [ "campaign", "creative", "ad_group" ], "source": "constant" }, { "name": "entity_id", "type": "string", "required": true, "description": "Same provider entity id used for the preview.", "example": "campaign_123" }, { "name": "preview_digest", "type": "string", "required": true, "description": "Identity-bound digest returned by preview_automation_baseline.", "example": "6f1a...", "source": "upstream:action:ads/preview_automation_baseline" } ], "output_shape": "{\"success\":true,\"baseline_source\":\"human_baseline\",\"baseline_status\":\"established|idempotent\",\"observation\":{...},\"observed_at\":\"...\"}\nTikTok output: the same human-baseline result envelope." }, { "id": "action:ads/estimate_audience_size", "namespace": "ads", "action": "estimate_audience_size", "description": "Estimate audience size for a Meta targeting spec or flat TikTok ad-group targeting.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/search_interests", "action:ads/search_geo_locations", "action:ads/create_adset" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nEstimate reach and delivery for Meta or TikTok targeting. Use this before action:ads/create_adset to validate the audience size.\n\nUse when:\n- The user wants to know the reachable audience for a targeting spec.\n- A workflow needs DAU/MAU to compare two targeting variants.\n\nDo not use when:\n- The user wants delivered impressions or clicks. Use action:ads/get_insights.\n- The platform is Google. Google does not expose an equivalent endpoint here.\n\nWorkflow:\n1. Build a targeting spec (use action:ads/search_interests and action:ads/search_geo_locations for canonical keys).\n2. Meta: pass the nested targeting object and optional optimization_goal (default REACH). TikTok: pass objective_type plus flat ad-group targeting fields. The official automatic-placement example also passes placement_type, optimization_goal, location_ids, and auto_targeting_enabled. For PLACEMENT_TYPE_NORMAL, placements is required.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.data[0].estimate_ready indicates whether Meta produced the estimate.\n- users_lower_bound / users_upper_bound is the reach range.\n- daily_outcomes_curve plots Meta spend vs reach trade-off points.\n- TikTok returns the estimated account count in raw.user_count.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "targeting", "type": "object", "required": true, "description": "Meta only: targeting spec object. MUST contain geo_locations (the handler rejects specs without it); also accepts age_min/age_max, genders, interests, behaviors, custom_audiences, etc. Estimate is account-level (no ad set context). Source: build from search_geo_locations (geo_locations keys) and search_interests (interests[].id). geo_locations is effectively mandatory at the account delivery_estimate edge.", "example": { "geo_locations": { "countries": [ "US" ] }, "age_min": 18, "age_max": 65 }, "platform": [ "meta" ] }, { "name": "optimization_goal", "type": "string", "required": false, "description": "Meta only: delivery optimization goal; default REACH. Non-REACH conversion/app/lead/DPA goals (OFFSITE_CONVERSIONS, VALUE, APP_INSTALLS, LEAD_GENERATION, PAGE_LIKES, CONVERSATIONS, PRODUCT_CATALOG_SALES) also require promoted_object. estimate_ready=false in the response is a valid low-confidence result, not an error. Source: constant enum from Meta OptimizationGoal (see create_adset for the full legal set). TikTok only: optimization goal used for the estimate; the official REACH example uses CLICK.", "example": "OFFSITE_CONVERSIONS" }, { "name": "promoted_object", "type": "object", "required": false, "description": "Meta only: object identifying the promoted entity for conversion/app/lead/DPA optimization goals. Required when optimization_goal needs a dependent object (OFFSITE_CONVERSIONS/VALUE -> {pixel_id, custom_event_type}; APP_INSTALLS -> {application_id, object_store_url}; LEAD_GENERATION/PAGE_LIKES/CONVERSATIONS -> {page_id}; Dynamic Product Ads -> {product_catalog_id, optional product_set_id}). Omit for REACH/IMPRESSIONS/LINK_CLICKS etc. Source: pixel_id from the account's tracking pixel; application_id/object_store_url from the promoted app; page_id from the connected Facebook Page; product_catalog_id from the product catalog. Same values used in create_adset promoted_object.", "example": { "pixel_id": "1234567890", "custom_event_type": "PURCHASE" }, "platform": [ "meta" ] }, { "name": "objective_type", "type": "string", "required": true, "description": "TikTok only: required — campaign objective used for the estimate, for example REACH or TRAFFIC.", "example": "REACH", "platform": [ "tiktok" ] }, { "name": "auto_targeting_enabled", "type": "boolean", "required": false, "description": "TikTok only: whether TikTok automatic targeting is enabled for the estimated ad group.", "example": true, "platform": [ "tiktok" ] }, { "name": "location_ids", "type": "list", "required": false, "description": "TikTok only: Target location ids; provide this or zipcode_ids when geography is required.", "example": [ "6252001" ], "platform": [ "tiktok" ] }, { "name": "zipcode_ids", "type": "list", "required": false, "description": "TikTok only: Target ZIP/postal code ids; provide this or location_ids when geography is required. ZIP targeting requires PLACEMENT_TYPE_NORMAL and PLACEMENT_TIKTOK.", "example": [ "123456789" ], "platform": [ "tiktok" ] }, { "name": "age_groups", "type": "list", "required": false, "description": "TikTok only: Target age group enums.", "example": [ "AGE_25_34" ], "platform": [ "tiktok" ] }, { "name": "gender", "type": "string", "required": false, "description": "TikTok only: Target gender.", "example": "GENDER_UNLIMITED", "platform": [ "tiktok" ] }, { "name": "languages", "type": "list", "required": false, "description": "TikTok only: Target language codes.", "example": [ "en" ], "platform": [ "tiktok" ] }, { "name": "interest_category_ids", "type": "list", "required": false, "description": "TikTok only: Interest category ids.", "example": [ "100" ], "platform": [ "tiktok" ] }, { "name": "interest_keyword_ids", "type": "list", "required": false, "description": "TikTok only: Interest keyword ids.", "example": [ "200" ], "platform": [ "tiktok" ] }, { "name": "audience_ids", "type": "list", "required": false, "description": "TikTok only: Included custom-audience ids.", "example": [ "300" ], "platform": [ "tiktok" ] }, { "name": "excluded_audience_ids", "type": "list", "required": false, "description": "TikTok only: Excluded custom-audience ids.", "example": [ "400" ], "platform": [ "tiktok" ] }, { "name": "operating_systems", "type": "list", "required": false, "description": "TikTok only: Target operating systems.", "example": [ "ANDROID" ], "platform": [ "tiktok" ] }, { "name": "placement_type", "type": "string", "required": false, "description": "TikTok only: Placement mode; ad-group creation defaults to PLACEMENT_TYPE_AUTOMATIC.", "example": "PLACEMENT_TYPE_AUTOMATIC", "enum": [ "PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL" ], "platform": [ "tiktok" ] }, { "name": "placements", "type": "list", "required": false, "description": "TikTok only: Placements; required when placement_type is PLACEMENT_TYPE_NORMAL.", "example": [ "PLACEMENT_TIKTOK" ], "platform": [ "tiktok" ] }, { "name": "adgroup_id", "type": "string", "required": false, "description": "TikTok adgroup id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "Meta output: {\"raw\": {\"data\": [{\"estimate_ready\": true, \"users_lower_bound\": N, \"users_upper_bound\": N, \"estimate_dau\": N, \"estimate_mau\": N, \"bid_estimate\": {...}, \"daily_outcomes_curve\": [{\"spend\": 100.0, \"reach\": 1234.0}]}]}}; TikTok output: {\"raw\": {\"user_count\": 123456}}" }, { "id": "action:ads/gaql_search", "namespace": "ads", "action": "gaql_search", "description": "Realtime Google Ads GAQL exploration for native fields, segments, resource attributes, or combinations outside cached query coverage.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/resolve_terms", "action:ads/query_single_dimension", "action:ads/query_multi_dimension", "action:ads/get_resource_metadata" ], "long_description": "FALLBACK PATH for Google-native ads exploration.\n\nPrimary use:\nRealtime GAQL search for fields, segments, or dimension combinations that are not exposed by cached query_single_dimension or query_multi_dimension.\n\nUse when:\n- list_dimensions does not expose the field or segment the user needs.\n- query_multi_dimension returns unsupported_dimension_combination and a GAQL fallback is acceptable.\n- You need Google-native resource fields, raw.segments.*, raw.metrics.*, or deep attribute paths such as raw.ad_group_ad.ad.responsive_search_ad.headlines.\n\nFirst-class Google Ads readback dimensions:\n- Use dimensions=['campaign','bidding_strategy'] for campaign bidding strategy reports.\n- Use dimensions=['keyword_status'] with a raw.ad_group_criterion.criterion_id filter to read current keyword status after pause/enable actions; this pure entity read does not require date_range.\n\nDo not use when:\n- Cached business-layer analytics can answer the question. Use query_single_dimension or query_multi_dimension.\n- The user needs CPA, CPP, ROAS, Reg Rate, Pur Rate, or cg.{slug}.* fan-out. Use query_single_dimension.\n- The user needs platform-side change history. Use get_change_history.\n- The platform is Meta. This action is Google-only.\n\nWorkflow:\n1. Call list_dimensions first to confirm cached analytics cannot satisfy the query.\n2. Call get_resource_metadata if you do not know selectable/filterable/sortable Google fields for the target resource.\n3. Call this action with account_id, dimensions[], metrics[], optional resource_name, filters, date_range, order_by, limit, and mode. Set resource_name when the select list mixes the root resource with selectable-with related resources, such as FROM asset_group_asset selecting asset.* fields.\n\nOutput semantics:\n- Returns translated Google Ads query text in query.\n- columns contains normalized field arrays; rows may also be present for convenience.\n- Native Google Ads declares the v24 RPC before I/O. Responses are parsed as complete official protobuf responses, so lowerCamel fieldMask paths and row fields become canonical snake_case together.\n- Every returned field-mask path is validated against the official GoogleAdsRow descriptor. A response-shape violation returns gaql_response_contract_error (HTTP 424) with provider, api_version, rpc, field_path, and request_id; it is not an OAuth error.\n- account.data_freshness is realtime call time, not Parquet sync time.\n- No mapping-aware derived metrics or conversion-group fan-out are computed.", "input_params": [ { "name": "platform", "type": "string", "required": false, "description": "Optional platform. Omit or use google; Meta is not supported." }, { "name": "account_id", "type": "string", "required": true, "description": "Google account id from list_ad_accounts.rows[].account_id. Use digits only with no dashes." }, { "name": "dimensions", "type": "list", "required": true, "description": "Dimension fields. Use L1.5 slugs such as date, campaign, bidding_strategy, or keyword_status, or raw..[....] fields discovered via get_resource_metadata. keyword_status is a current entity-status readback dimension and does not require date_range when used without metrics or segments." }, { "name": "metrics", "type": "list", "required": true, "description": "Metric fields. Use L1.5 slugs such as clicks or cost, or raw.metrics.X fields discovered via get_resource_metadata." }, { "name": "resource_name", "type": "string", "required": false, "description": "Optional explicit GAQL FROM resource, e.g. ad_group_ad or asset_group_asset. Use when dimensions include selectable-with fields from related resources." }, { "name": "date_range", "type": "list", "required": false, "description": "Two-item [date_start, date_end] array in YYYY-MM-DD format. Required for metric or segment queries; optional for pure metadata-style entity reads. Top-level date_start/date_end are also accepted for compatibility." }, { "name": "filters", "type": "object", "required": false, "description": "Filter expressions over fields marked filterable by get_resource_metadata." }, { "name": "order_by", "type": "list", "required": false, "description": "[{field, direction}] where direction is ASC or DESC; field must be sortable." }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap. Omit to use the service default; hard budget still applies." }, { "name": "mode", "type": "string", "required": false, "description": "rows or summary." } ], "output_shape": "{\"account\": {\"platform\": \"google\", \"account_id\": \"123\", \"data_freshness\": \"ISO\"}, \"columns\": {: [...]}, \"rows\": [...], \"row_count\": N, \"query\": \"\"}" }, { "id": "action:ads/generate_ad_preview", "namespace": "ads", "action": "generate_ad_preview", "description": "Read a Meta ad preview HTML iframe for a creative + ad_format; read-only. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad_creative", "action:ads/get_ad_creatives" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nPreview an existing TikTok ad by supplying ad_id from list_ads or get_ad. preview_type=AD and may be omitted when ad_id is supplied because the handler defaults it to AD. This action does not currently expose TikTok's unsaved-material preview routes; do not send adgroup_id, identity fields, or an inline creative.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nRender a preview iframe for a Meta creative in a chosen placement format. Read-only — does not create or modify any creative.\n\nUse when:\n- The user wants to see what an ad will look like on a placement before publishing.\n- QA flow needs to confirm creative renders correctly across placements.\n\nDo not use when:\n- The user wants to actually create a creative. Use action:ads/create_ad_creative.\n- The platform is Google. Google's preview is not exposed here.\n\nWorkflow:\n1. Have a creative_id ready (from create_ad_creative or list_ads.creative.id).\n2. Pick ad_format (see enum below).\n3. Call this action.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.data[0].body is the iframe HTML markup; render it as-is for the preview.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: ad_format enum in capability docs is a curated 12-value subset of Meta's much larger AdPreview.AdFormat enum; an agent asked to preview a Reels or Audience Network placement will either guess an unlisted value (and the normalize whitelist will pass it through to Meta, which may accept it) or wrongly conclude it is unsupported. The docs do not state the list is non-exhaustive. Also note: Docs say creative_id comes 'from action:ads/create_ad_creative' but the long_description also points to list_ads.creative.id; the input_params description omits the list_ads/get_ad_creatives sources, narrowing the agent's discovery path. Capability gap: Inline/unsaved creative preview (creative={'object_story_spec': {}}) — cannot preview a creative before persisting it; agent is forced to call create_ad_creative first, defeating the documented 'see before publishing' use case.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "creative_id", "type": "string", "required": true, "description": "Creative id from action:ads/create_ad_creative.", "platform": [ "meta" ] }, { "name": "ad_format", "type": "string", "required": true, "description": "One of DESKTOP_FEED_STANDARD, MOBILE_FEED_STANDARD, MOBILE_FEED_BASIC, RIGHT_COLUMN_STANDARD, MOBILE_BANNER, MOBILE_FULLWIDTH, MOBILE_INTERSTITIAL, INSTAGRAM_STANDARD, INSTAGRAM_STORY, FACEBOOK_STORY_MOBILE, MESSENGER_MOBILE_INBOX_MEDIA, MARKETPLACE_MOBILE.", "example": "MOBILE_FEED_STANDARD", "enum": [ "DESKTOP_FEED_STANDARD", "FACEBOOK_STORY_MOBILE", "INSTANT_ARTICLE_STANDARD", "INSTAGRAM_STANDARD", "INSTAGRAM_STORY", "MARKETPLACE_MOBILE", "MESSENGER_MOBILE_INBOX_MEDIA", "MOBILE_BANNER", "MOBILE_FEED_BASIC", "MOBILE_FEED_STANDARD", "MOBILE_FULLWIDTH", "MOBILE_INTERSTITIAL", "RIGHT_COLUMN_STANDARD" ], "source": "constant", "platform": [ "meta" ] }, { "name": "ad_id", "type": "string", "required": true, "description": "TikTok only: existing ad id to preview. When supplied, preview_type defaults to AD.", "example": "17840000000000000", "source": "upstream:action:ads/list_ads,action:ads/get_ad", "platform": [ "tiktok" ] }, { "name": "preview_type", "type": "string", "required": false, "description": "TikTok only: AD for an existing ad. Optional when ad_id is supplied and defaults to AD.", "example": "AD", "enum": [ "AD" ], "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"body\": \"\"}]}\nTikTok output: raw preserves TikTok's provider preview_link and also exposes its normalized HTTP(S) value as raw.preview_url for the generated existing-ad preview URL." }, { "id": "action:ads/generate_ad_units", "namespace": "ads", "action": "generate_ad_units", "description": "Deprecated legacy compatibility: Generate ChatGPT Ads draft ad units from a landing page URL.", "mode": "write", "platforms": [ "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/get_campaign", "action:ads/replace_campaign_ad_units" ], "long_description": "Deprecated compatibility path. New deployments use inline Campaign -> Ad Group -> Ad.\n\nPrimary use:\nAsk the ChatGPT Ads platform to generate 1-5 draft ad units from a site URL and attach them to an existing ChatGPT Ads campaign.\n\nUse when:\n- A campaign already exists and the user wants platform-generated draft variants.\n- The landing page is public and appropriate for creative generation.\n\nDo not use when:\n- The campaign does not exist yet. Create ad units and a campaign first.\n- The user needs deterministic approved copy. Use create_ad_unit instead.\n\nWorkflow:\n1. Resolve campaign_id via create_campaign, get_campaign, or list_campaigns.\n2. Submit site_url and count for HITL review. count must be between 1 and 5.\n3. Read the campaign back and review generated drafts before activation.\n\nSide effects:\n- Creates draft ad units on the ChatGPT Ads platform and attaches them to the campaign.\n- Does not activate the campaign.\n\nOutput semantics:\n- raw is the ChatGPT Ads platform response and may include generated ad-unit records or ids.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "" }, { "name": "campaign_id", "type": "string", "required": true, "description": "", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "site_url", "type": "string", "required": true, "description": "" }, { "name": "count", "type": "integer", "required": false, "description": "Number of ad units to generate, 1-5. Default 2.", "example": 2 } ], "output_shape": "{\"success\": true, \"raw\": {\"ad_units\": [...]}}" }, { "id": "action:ads/get_account_info", "namespace": "ads", "action": "get_account_info", "description": "Read Meta, TikTok, or ChatGPT Ads account health and provider-native billing facts.", "mode": "read", "platforms": [ "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/get_account_pages", "action:ads/get_insights", "action:ads/list_ad_accounts" ], "long_description": "Supported platforms: meta, tiktok, chatgpt_ads, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nSnapshot of provider-native account health. For ChatGPT Ads, combines account and billing/pixel reads and returns conservative activation and tracking preflights.\n\nUse when:\n- The user asks for the account's currency, total spend, or balance.\n- A Meta workflow needs to confirm account_status before issuing any write.\n- A ChatGPT Ads workflow needs billing and pixel readiness facts before a separately approved activation.\n- A Meta workflow needs to confirm an account-level spend cap (ad sets have NO spend_cap field; campaign-level caps come from action:ads/get_campaign).\n\nDo not use when:\n- The user wants per-campaign or per-ad metrics. Use action:ads/get_insights.\n- The platform is Google. Use action:ads/list_customers or get_tree_structure.\n\nWorkflow:\n1. Call this action.\n2. For Meta, interpret account_status. For TikTok, read the advertiser row in raw.data.list. For ChatGPT Ads, inspect raw.activation_preflight and raw.tracking_preflight separately, but never treat either as activation authorization.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Meta raw is the Graph API body: id, name, account_status (1=ACTIVE, 2=DISABLED, ...), currency, timezone_name, amount_spent (cents string, total lifetime), balance (cents string), spend_cap, disable_reason, min_daily_budget.\n- Meta spend_cap: account-level maximum total spend (minor-units string, same unit as amount_spent); delivery stops account-wide when reached. '0' means NO spend cap is set — never infer a cap (or its absence) from a missing field.\n- Meta disable_reason: int enum — 0=NONE, 1=ADS_INTEGRITY_POLICY, 2=ADS_IP_REVIEW, 3=RISK_PAYMENT, 4=GRAY_ACCOUNT_SHUT_DOWN, 5=ADS_AFC_REVIEW, 6=BUSINESS_INTEGRITY_RAR, 7=PERMANENT_CLOSE, 8=UNUSED_RESELLER_ACCOUNT, 9=UNUSED_ACCOUNT, 10=UMBRELLA_AD_ACCOUNT, 11=BUSINESS_MANAGER_INTEGRITY_POLICY, 12=MISREPRESENTED_AD_ACCOUNT, 13=AOAB_DESHARE_LEGAL_ENTITY, 14=CTX_THREAD_REVIEW, 15=COMPROMISED_AD_ACCOUNT. Meaningful when account_status=2 (DISABLED); 0 on healthy accounts.\n- Meta min_daily_budget: minimum daily budget for this account (int, minor units of the account currency). Source: Meta Marketing API AdAccount reference.\n- TikTok raw is the advertiser-info response. Read raw.data.list advertiser rows and do not apply Meta account_status, balance, or spend_cap semantics.\n- ChatGPT Ads raw.account, raw.billing, and raw.pixels preserve provider facts. raw.activation_preflight.status is ready only when both reads succeed and billing_enabled is exactly true; false means billing_required, while missing, malformed, or unavailable facts mean unknown. Credits and Soku funding do not imply readiness. raw.tracking_preflight is ready only when the ChatGPT Ads platform returns an active pixel with is_functional=true; null is pending, no functional pixel is pixel_required, and unavailable/malformed facts are unknown. The campaign activation PATCH remains authoritative.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: Capability docs mark account_id required=True, but the executor auto-fills account_id from the bound/resolved account when it is omitted (executor.py:51-52). The agent is told it must supply account_id even though a single-account context works without it. Minor over-statement of requiredness. Also note: long_description maps account_status integers partially (1=ACTIVE, 2=DISABLED, ) and tells the agent to 'surface an error if account_status != 1' but does not enumerate the other meaningful states (3=UNSETTLED, 7=PENDING_RISK_REVIEW, 8=PENDING_SETTLEMENT, 9=IN_GRACE_PERIOD, 100=PENDING_CLOSURE, 101=CLOSED, 201=ANY_ACTIVE, 202=ANY_CLOSED). An UNSETTLED or IN_GRACE_PERIOD account may still accept reads/writes, so a blanket 'block all writes unless status==1' rule the doc implies is overly strict and could cause the agent to wrongly refuse valid operations. Capability gap: No agent-facing way to request additional AdAccount fields beyond the 10 hard-coded ones (e.g. funding_source_details, business, capabilities, is_prepay_account, tos_accepted, age, account_id). spend_cap, disable_reason and min_daily_budget are now in the hard-coded list, closing the former account-health gap. This is a coverage limitation, not a break.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok, chatgpt_ads, linkedin.", "example": "chatgpt_ads", "enum": [ "meta", "tiktok", "chatgpt_ads", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta, TikTok, or ChatGPT Ads account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" } ], "output_shape": "Provider-specific normalized envelope. Meta raw contains id, name, account_status, currency, timezone_name, amount_spent, balance, spend_cap, disable_reason, and min_daily_budget. TikTok raw contains the provider advertiser-info response, including data.list advertiser rows. ChatGPT Ads raw contains account, billing, pixels, sources, activation_preflight {status, authoritative, activation_authority}, and tracking_preflight {status, authoritative, reason}.\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_account_pages", "namespace": "ads", "action": "get_account_pages", "description": "Read Meta Pages promotable from this ad account; returns data[] with page id, name, category, tasks. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad_creative", "action:ads/create_lead_form", "action:ads/get_account_info" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nFor TikTok, omit identity_id/identity_type to list advertiser identities. To discover constructible Spark material, call again with one returned identity_id + identity_type; the action then calls identity/video/get and returns posts whose item_id is the tiktok_item_id accepted by create_ad. For BC_AUTH_TT also pass identity_authorized_bc_id. Follow has_more/cursor until exhausted.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nList Facebook Pages the current ad account can promote. Use the returned page_id when building creatives or lead forms.\n\nUse when:\n- You need a page_id before action:ads/create_ad_creative or create_lead_form.\n- The user asks which pages are connected to the ad account.\n\nDo not use when:\n- The user wants all pages the user manages. This endpoint returns only promotable pages (subset).\n- The platform is Google.\n\nWorkflow:\n1. Call this action.\n2. Pick a page_id for creative or lead form workflows.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.data[].tasks lists capabilities (ADVERTISE, ANALYZE, MANAGE, ...).\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: The documented output_shape includes \"paging\": {\"cursors\": {\"after\": \"\"}} but no input param accepts a cursor, so an agent that sees paging.cursors.after has no documented way to request the next page — misleading guidance about pagination availability. Also note: The capability doc does not enumerate the full tasks enum (it lists \"ADVERTISE, ANALYZE, MANAGE, \" with an ellipsis). Agents deciding whether a page is usable for ads should be told the relevant gate is the presence of \"ADVERTISE\" in tasks; the complete enum is ANALYZE, ADVERTISE, MODERATE, CREATE_CONTENT, MANAGE, MESSAGING. Capability gap: Pagination (limit/after/before) is not wired: _EXTRAS_ALLOWLIST[(\"meta\",\"get_account_pages\")] is an empty frozenset (normalize.py:157), and the handler hard-codes no limit. For ad accounts with many promotable pages the agent only ever sees Meta's default first page and cannot iterate, even though the documented output_shape promises paging.cursors.after. Compare list_custom_audiences / list_saved_audiences / list_adsets which DO allowlist {limit, after}.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "identity_id", "type": "string", "required": false, "description": "TikTok only: identity id; provide with identity_type to list posts.", "example": "identity-1", "platform": [ "tiktok" ] }, { "name": "identity_type", "type": "string", "required": false, "description": "TikTok only: identity type; provide with identity_id to list posts.", "example": "BC_AUTH_TT", "enum": [ "AUTH_CODE", "TT_USER", "BC_AUTH_TT" ], "platform": [ "tiktok" ] }, { "name": "identity_authorized_bc_id", "type": "string", "required": false, "description": "TikTok only: required when listing posts for BC_AUTH_TT.", "example": "bc-1", "platform": [ "tiktok" ] }, { "name": "item_type", "type": "string", "required": false, "description": "TikTok only: post type returned by identity/video/get.", "example": "VIDEO", "enum": [ "VIDEO", "CAROUSEL" ], "platform": [ "tiktok" ] }, { "name": "keyword", "type": "string", "required": false, "description": "TikTok only: optional AUTH_CODE post search keyword.", "example": "summer", "platform": [ "tiktok" ] }, { "name": "cursor", "type": "string", "required": false, "description": "TikTok only: pagination cursor from the previous response.", "example": "20", "platform": [ "tiktok" ] }, { "name": "count", "type": "integer", "required": false, "description": "TikTok only: posts per page, from 1 through 20.", "example": 20, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"page_id\", \"name\": \"...\", \"category\": \"...\", \"tasks\": [\"ADVERTISE\", \"ANALYZE\", \"MANAGE\"]}], \"paging\": {\"cursors\": {\"after\": \"...\"}}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_ad", "namespace": "ads", "action": "get_ad", "description": "Read one Meta ad by id; returns realtime platform status, effective status, creative, tracking specs, and timestamps. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_ads", "action:ads/create_ad", "action:ads/bulk_create_ads", "action:ads/update_ad" ], "long_description": "Supported platforms: meta, tiktok, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nRealtime single-ad verification after create_ad or bulk_create_ads. Use this when the workflow already has an ad_id and needs current delivery state or creative linkage without paging through list_ads.\n\nUse when:\n- A write just returned a Meta ad id and the agent needs to verify status.\n- The user asks for one ad's current creative, adset, campaign, or tracking specs.\n\nDo not use when:\n- The platform is Google. Use action:ads/gaql_search or list_ads.\n- The user needs a page of ads. Use action:ads/list_ads.\n\nWorkflow:\n1. Obtain account_id from list_ad_accounts and ad_id from create_ad, bulk_create_ads, or list_ads.\n2. Call this action directly; no HITL review is required.\n3. Use raw.status / raw.effective_status to confirm PAUSED / ACTIVE / IN_PROCESS / DISAPPROVED state.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body with id, name, adset_id, campaign_id, status, effective_status, creative{id,...}, tracking_specs, conversion_specs, created_time, and updated_time.\n\nSide effects:\n- None. Read-only Graph API call.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok, linkedin.", "example": "meta", "enum": [ "meta", "tiktok", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "ad_id", "type": "string", "required": true, "description": "Meta ad id from create_ad, bulk_create_ads, or list_ads. Required when platform=tiktok.", "example": "120251913609470079" } ], "output_shape": "{\"id\": \"120...\", \"name\": \"...\", \"adset_id\": \"120...\", \"campaign_id\": \"120...\", \"status\": \"PAUSED\", \"effective_status\": \"PAUSED|IN_PROCESS|DISAPPROVED\", \"creative\": {\"id\": \"...\", \"name\": \"...\"}, \"created_time\": \"ISO\", \"updated_time\": \"ISO\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_ad_creative", "namespace": "ads", "action": "get_ad_creative", "description": "Read one Meta ad creative by id; returns creative content and object_story_spec.", "mode": "read", "platforms": [ "meta" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_creatives", "action:ads/get_ad_creatives", "action:ads/create_ad_creative", "action:ads/remove_ad_creative", "action:ads/generate_ad_preview" ], "long_description": "Primary use:\nRealtime single-creative inspection by creative_id, including image / video / title / body / object_story_spec. Reads ANY creative in the account by id — it is not scoped to one ad, so it also works for creatives not yet attached to any ad (e.g. fresh output of action:ads/create_ad_creative).\n\nUse when:\n- A write just returned a creative_id (create_ad_creative, bulk_create_ad_creatives) and the agent needs to verify the persisted content.\n- The user asks about one specific creative and the creative_id is already known from list_ad_creatives or get_ad_creatives.\n\nDo not use when:\n- The creative_id is unknown. Use action:ads/list_ad_creatives (account-wide) or action:ads/get_ad_creatives (per ad) first.\n- You want the creatives wired to a specific ad. Use action:ads/get_ad_creatives.\n- The user wants creative-level metrics. Use action:ads/get_insights with level=ad.\n- The platform is Google. Use action:ads/list_ads (headlines/descriptions on RSAs).\n\nWorkflow:\n1. Obtain creative_id from create_ad_creative, list_ad_creatives, or get_ad_creatives.\n2. Call this action directly; no HITL review is required.\n3. Read raw.object_story_spec / raw.asset_feed_spec to confirm the creative content before wiring it into action:ads/create_ad or update_ad.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is a single Graph AdCreative object (not a paged data[] list); object_story_spec and asset_feed_spec are nested creative-content objects.\n- The AdCreative node exposes status (ACTIVE|IN_PROCESS|WITH_ISSUES|DELETED), not effective_status.\n\nSide effects:\n- None. Read-only Graph API call.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "creative_id", "type": "string", "required": true, "description": "Meta ad creative id from action:ads/create_ad_creative (raw.id), action:ads/list_ad_creatives, or action:ads/get_ad_creatives.", "example": "120251905634300081" } ], "output_shape": "{\"id\": \"120...\", \"name\": \"...\", \"title\": \"...\", \"body\": \"...\", \"image_url\": \"...\", \"thumbnail_url\": \"...\", \"object_story_spec\": {...}, \"asset_feed_spec\": {...}, \"status\": \"ACTIVE|IN_PROCESS|WITH_ISSUES|DELETED\"}" }, { "id": "action:ads/get_ad_creatives", "namespace": "ads", "action": "get_ad_creatives", "description": "Read Meta ad creatives for a given ad; returns data[] with creative content and object_story_spec.", "mode": "read", "platforms": [ "meta" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_ads", "action:ads/get_ad_creative", "action:ads/list_ad_creatives", "action:ads/create_ad_creative", "action:ads/generate_ad_preview" ], "long_description": "Primary use:\nInspect the creatives wired to a Meta ad, including image / video / title / body / object_story_spec.\n\nUse when:\n- The user asks what creative is running on a Meta ad.\n- A workflow needs to debug missing fields before action:ads/update_ad.\n\nDo not use when:\n- The user wants creative-level metrics. Use action:ads/get_insights with level=ad.\n- The platform is Google. Use action:ads/list_ads (headlines/descriptions on RSAs).\n\nWorkflow:\n1. Call action:ads/list_ads to get ad_id.\n2. Call this action.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph paged response; object_story_spec and asset_feed_spec are nested creative-content objects.\n- For lead ads, the paired form id is at object_story_spec.link_data|video_data.call_to_action.value.lead_gen_form_id — read it here to confirm which lead form an ad actually uses (cross-check against action:ads/list_lead_forms).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_id", "type": "string", "required": true, "description": "Meta ad id from action:ads/list_ads.", "example": "23..." } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"name\": \"...\", \"title\": \"...\", \"body\": \"...\", \"image_url\": \"...\", \"thumbnail_url\": \"...\", \"object_story_spec\": {...}, \"asset_feed_spec\": {...}, \"status\": \"...\", \"effective_status\": \"...\"}], \"paging\": {...}}" }, { "id": "action:ads/get_ad_group", "namespace": "ads", "action": "get_ad_group", "description": "Read one ChatGPT Ads Ad Group and its embedded Ads window; read-only.", "mode": "read", "platforms": [ "chatgpt_ads" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_groups" ], "long_description": "Primary use:\nRead one Ad Group by canonical id, including embedded Ads and ads_total.\n\nUse when:\n- You need lifecycle, source, or complete-window evidence before an Ad write.\n- You need to verify a create/update result.\n\nDo not use when:\n- The Ad Group id is unknown. Use list_ad_groups first.\n\nWorkflow:\n1. Resolve the canonical ad_group_id.\n2. Call directly; no HITL review is required.\n3. Treat a missing, malformed, or incomplete Ads window as unsafe for mutation.\n\nOutput semantics:\n- Returns one platform-native Ad Group object with ads and ads_total when available.\n- Readback is observational and does not imply review, activation, or billing readiness.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant", "platform": [ "chatgpt_ads" ] }, { "name": "account_id", "type": "string", "required": true, "description": "Advertiser account id.", "example": "account_123", "platform": [ "chatgpt_ads" ] }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Canonical Ad Group id; adset_id is not accepted.", "example": "ag_123", "platform": [ "chatgpt_ads" ] } ], "output_shape": "{\"id\": \"ag_123\", \"campaign_id\": \"campaign_123\", \"status\": \"paused\", \"group_type\": \"manual\", \"ads\": [...], \"ads_total\": 1}" }, { "id": "action:ads/get_adset", "namespace": "ads", "action": "get_adset", "description": "Read one Meta ad set by id; returns realtime platform status, targeting, budget, bidding, and delivery-detail fields. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_adsets", "action:ads/create_adset", "action:ads/bulk_create_adsets", "action:ads/update_adset" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nRealtime single-ad-set verification after create_adset, bulk_create_adsets, or update_adset. Use this when the workflow already has an adset_id and needs the current platform state or the extended detail fields (attribution_spec, promoted_object, pacing_type, budget_remaining) that list_adsets does not return.\n\nUse when:\n- A write just returned a Meta adset_id and the agent needs to verify status.\n- The user asks for one ad set's current targeting, budget, bid strategy, promoted object, or learning stage without paging through list_adsets.\n\nDo not use when:\n- The adset_id is unknown. Use action:ads/list_adsets first.\n- The platform is Google. Google uses ad groups; use action:ads/gaql_search.\n- The user needs cached analytics. Use action:ads/query_single_dimension with dimension=adgroup.\n\nWorkflow:\n1. Obtain account_id from list_ad_accounts and adset_id from create_adset, bulk_create_adsets, or list_adsets.\n2. Call this action directly; no HITL review is required.\n3. Read raw.status, raw.budget_remaining, and raw.learning_stage_info to confirm delivery state before the next write.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API ad set object with id, name, campaign_id, status, daily_budget, lifetime_budget, targeting, bid_amount, bid_strategy, optimization_goal, billing_event, start_time, end_time, created_time, updated_time, attribution_spec, destination_type, promoted_object, pacing_type, budget_remaining, and learning_stage_info.\n- daily_budget, lifetime_budget, and budget_remaining are strings in cents; targeting is a nested object.\n- Spend caps: an ad set has NO spend_cap field — spend_cap is a campaign/account concept. Its absence here does NOT mean 'no spend cap'; it was never a field on this resource. To confirm a spend cap, read action:ads/get_campaign (returns spend_cap) or action:ads/get_account_info (returns account-level spend_cap; '0' means no cap). More generally, a field missing from raw means 'not present on this resource', never a confirmed value; and this action ignores any custom fields param (you cannot widen the returned field set here).\n\nSide effects:\n- None. Read-only Graph API call.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "adset_id", "type": "string", "required": true, "description": "Meta ad set id from create_adset, bulk_create_adsets, or list_adsets. Required when platform=tiktok.", "example": "120251905634300080" } ], "output_shape": "{\"id\": \"120...\", \"name\": \"...\", \"campaign_id\": \"120...\", \"status\": \"ACTIVE|PAUSED|ARCHIVED\", \"daily_budget\": \"5000\", \"lifetime_budget\": \"...\", \"targeting\": {...}, \"bid_amount\": 100, \"bid_strategy\": \"...\", \"optimization_goal\": \"...\", \"billing_event\": \"...\", \"start_time\": \"ISO\", \"end_time\": \"ISO\", \"attribution_spec\": [...], \"destination_type\": \"...\", \"promoted_object\": {...}, \"pacing_type\": [...], \"budget_remaining\": \"...\", \"learning_stage_info\": {...}, \"created_time\": \"ISO\", \"updated_time\": \"ISO\"}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_bidding_strategy", "namespace": "ads", "action": "get_bidding_strategy", "description": "Read one reusable Google portfolio bidding strategy.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/update_campaign" ], "long_description": "Primary use:\nRead one reusable Google portfolio bidding strategy.\n\nUse when:\n- You need a reusable Google Target CPA or Target ROAS portfolio strategy.\n\nDo not use when:\n- You need a campaign-specific standard bidding strategy; use action:ads/update_campaign.\n\nWorkflow:\n1. Read the current strategy or campaign state when changing an existing resource.\n2. For write operations, submit through action:_batch/submit for HITL review.\n3. Verify the resulting state with action:ads/get_bidding_strategy.\n\nOutput semantics:\n- Returns the created, read, updated, listed, or removed Google portfolio strategy result.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id without dashes.", "example": "1234567890" }, { "name": "bidding_strategy_id", "type": "string", "required": true, "description": "Google portfolio bidding strategy id.", "example": "123" } ], "output_shape": "{\"success\":true,\"raw\":{}}" }, { "id": "action:ads/get_campaign", "namespace": "ads", "action": "get_campaign", "description": "Read one Google, Meta, TikTok, or ChatGPT Ads campaign by id; returns realtime platform state.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_campaigns", "action:ads/create_campaign", "action:ads/update_campaign" ], "long_description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nRealtime read-back for a known Google, Meta, or ChatGPT Ads campaign id, or a TikTok campaign id, especially after a create/update approval returns a campaign_id.\n\nUse when:\n- You already know a campaign_id and need the current platform state.\n- You need to confirm status after a write.\n- You need one campaign's budget, bidding targets, targeting, landing page, objective, or timestamps without paging through list_campaigns.\n\nDo not use when:\n- The campaign id is unknown. Use action:ads/list_campaigns first.\n- You need cached analytics or KPI data. Use action:ads/query_single_dimension.\nWorkflow:\n1. Obtain account_id from list_ad_accounts and campaign_id from a create result, review result, or list_campaigns.\n2. Call this action with platform, account_id, and campaign_id.\n3. Read raw.campaign for Google, or the platform-native delivery fields for Meta and ChatGPT Ads.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Google raw.campaign includes status, budget, flight dates, bidding strategy and targets, network settings, language ids, and geo target ids.\n- Meta, TikTok, and ChatGPT Ads raw values are their platform-native campaign objects. For ChatGPT Ads, determine campaign review only from the campaign object's own review_status and deployment_status. A nested ad unit review_status=not_submitted does not mean the campaign itself is unsubmitted, and there is no separate submit-review action in this capability.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin.", "example": "google", "enum": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "983991084374811" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from create_campaign, bulk_create_campaigns, or list_campaigns. Required when platform=tiktok.", "example": "120251905634300079" } ], "output_shape": "{\"success\": true, \"raw\": {\"id\": \"120...\", \"name\": \"...\", \"objective\": \"OUTCOME_TRAFFIC\", \"status\": \"PAUSED\", \"effective_status\": \"PAUSED|IN_PROCESS|WITH_ISSUES|...\", \"daily_budget\": \"5000\", \"lifetime_budget\": \"...\", \"created_time\": \"ISO\", \"updated_time\": \"ISO\"}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_catalog_diagnostics", "namespace": "ads", "action": "get_catalog_diagnostics", "description": "Read Meta product catalog diagnostics for feed and item quality issues.", "mode": "read", "platforms": [ "meta" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/get_product_feed_details", "action:ads/list_products" ], "long_description": "Primary use:\nInspect live Meta catalog diagnostics without using synced ads metrics.\n\nUse when:\n- The user asks why catalog products or feeds are rejected, missing, or unhealthy.\n- A product ads workflow needs item/feed issue evidence before making changes.\n\nDo not use when:\n- The user needs cached business-layer analytics or KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs to mutate catalog resources. Use a create_* catalog action through review-gated batch submission.\n- The platform is Google; this action is Meta-only.\n\nWorkflow:\n1. Get catalog_id from action:ads/list_product_catalogs.\n2. Call this action with catalog_id.\n3. Use diagnostics to decide whether to fix feeds, products, or account permissions.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph fields without L1.5 normalization.\n- For catalog object ids, the backend verifies ownership against the resolved Meta ad account business before returning data.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id." }, { "name": "affected_entity", "type": "string", "required": false, "description": "Meta only: scope diagnostics to one entity type. Common enum values: PRODUCT_ITEM, CHECKOUT, HOME_LISTINGS, VEHICLES. Omit to return all diagnostics. Source: constant enum chosen by the agent.", "example": "PRODUCT_ITEM" }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from a previous response's paging.cursors.after to fetch the next page of diagnostics. Source: output of a prior get_catalog_diagnostics call (paging.cursors.after).", "example": "QVFIU...cursor" }, { "name": "limit", "type": "integer", "required": false, "description": "Meta only: page size; default 100. Combine with after to page. Source: agent-chosen integer.", "example": 100 } ], "output_shape": "{\"data\": [{\"type\": \"...\", \"severity\": \"...\", \"description\": \"...\"}], \"paging\": {...}}" }, { "id": "action:ads/get_change_history", "namespace": "ads", "action": "get_change_history", "description": "Live Google/Meta platform-side change history: Google change_event or Meta ad account activities; distinct from Nex audit.", "mode": "read", "platforms": [ "google", "meta" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/list_operation_records", "action:ads/list_campaigns" ], "long_description": "Primary use:\nRealtime platform-side change history for Google Ads or Meta Ads. Google returns change_event rows with userEmail and changedFields; Meta returns ad account activities with actor_name, translated_event_type, object_type, and extra_data. Distinct from Nex-recorded ads history.\n\nUse when:\n- The user asks who changed what in Google Ads UI/API or Meta Ads Manager/API.\n- You need Google clientType (e.g. GOOGLE_ADS_AUTOMATED_RULE vs GOOGLE_ADS_API) or Meta actor_name / translated_event_type to attribute changes.\n\nDo not use when:\n- The user wants Nex-side agent action records. Use action:ads/list_operation_records.\n- The user wants KPI metrics or reporting breakdowns. Use action:ads/query_single_dimension or action:ads/query_multi_dimension.\n\nWorkflow:\n1. Pick platform, account_id, start_date, and end_date.\n2. For Google, optionally narrow by resource_type (CAMPAIGN, AD_GROUP, ...).\n3. For Meta, optionally narrow returned rows by event_type or object_type.\n4. Call this action.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Google raw.changes[] contains change_event fields; changeDateTime is ISO-8601, resourceChangeOperation is CREATE / UPDATE / REMOVE, and limit is 1000.\n- Meta raw.changes[] contains ad account activity rows; event_time, event_type, translated_event_type, object_type, actor_name, event_message, and extra_data come directly from Graph API activities.\n- This action returns platform-side history, not Nex-side HITL/audit records.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: event_type post-filter: docs give a single example (ad_account_update) and never state that the value must exactly equal one of the 79 official lowercase AdActivity.EventType values (e.g. update_campaign_name, update_ad_run_status). An agent guessing 'STATUS_CHANGE' or 'budget_update' gets zero rows with no error. Also note: object_type post-filter: docs example 'campaign' but there is NO official enum for object_type (it is a read-only returned field). The exact casing/vocabulary the Graph API returns is undocumented, so the lowercase-exact-match filter may silently drop every row. Capability gap: category (official server-side filter: account|ad|ad_keywords|ad_set|audience|bid|budget|campaign|date|status|targeting) — absent from capability docs, normalize allowlist, and handler. This is the canonical efficient way to narrow activities; its absence forces over-fetch + brittle client-side event_type/object_type filtering.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta.", "example": "google", "enum": [ "google", "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes. For Meta, act_ prefix is optional.", "example": "1234567890" }, { "name": "start_date", "type": "string", "required": true, "description": "Google only: inclusive start date YYYY-MM-DD. Google change_event only retains the past 30 days; start_date older than today-30d is rejected with a clear error. Meta has no such hard window. Source: user-provided / computed date within the last 30 days for Google.", "example": "2026-06-09" }, { "name": "end_date", "type": "string", "required": true, "description": "Inclusive end date YYYY-MM-DD.", "example": "2026-05-18" }, { "name": "resource_type", "type": "string", "required": false, "description": "Google only: optional change_resource_type filter. Full supported set: AD, AD_GROUP, AD_GROUP_AD, AD_GROUP_ASSET, AD_GROUP_BID_MODIFIER, AD_GROUP_CRITERION, AD_GROUP_FEED, ASSET, ASSET_SET, ASSET_SET_ASSET, CAMPAIGN, CAMPAIGN_ASSET, CAMPAIGN_ASSET_SET, CAMPAIGN_BUDGET, CAMPAIGN_CRITERION, CAMPAIGN_FEED, CUSTOMER_ASSET, FEED, FEED_ITEM (asset/feed values matter for Performance Max accounts). Source: constant enum (ChangeEventResourceType).", "example": "ASSET", "enum": [ "AD", "AD_GROUP", "AD_GROUP_AD", "AD_GROUP_ASSET", "AD_GROUP_BID_MODIFIER", "AD_GROUP_CRITERION", "AD_GROUP_FEED", "ASSET", "ASSET_SET", "ASSET_SET_ASSET", "CAMPAIGN", "CAMPAIGN_ASSET", "CAMPAIGN_ASSET_SET", "CAMPAIGN_BUDGET", "CAMPAIGN_CRITERION", "CAMPAIGN_FEED", "CUSTOMER_ASSET", "FEED", "FEED_ITEM" ], "source": "constant" }, { "name": "event_type", "type": "string", "required": false, "description": "Meta-only optional post-filter on activity event_type.", "example": "ad_account_update" }, { "name": "object_type", "type": "string", "required": false, "description": "Meta-only optional post-filter on activity object_type.", "example": "campaign" }, { "name": "limit", "type": "integer", "required": false, "description": "Meta: page size, default 100, max 1000. Google only: max change_event rows to return, 1..10000 (default 1000) -- caps the result set at the official 10,000 ceiling instead of a silent 1000-row cap. Source: user-provided integer.", "example": 5000 }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from paging.cursors.after of the previous page; pass it back to fetch the next page. Google returns all rows in one call and has no cursor.", "source": "upstream:action:ads/get_change_history" } ], "output_shape": "{\"success\": true, \"platform\": \"google|meta\", \"raw\": {\"changes\": [{\"changeEvent\": {\"changeDateTime\": \"ISO\", \"userEmail\": \"...\", \"changedFields\": \"field1,field2\"}}, {\"event_time\": \"...\", \"translated_event_type\": \"...\", \"actor_name\": \"...\", \"extra_data\": \"...\"}]}}" }, { "id": "action:ads/get_change_history_task", "namespace": "ads", "action": "get_change_history_task", "description": "Check a TikTok change-log export task.", "mode": "read", "platforms": [ "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nCheck a TikTok change-log export task.\n\nUse when:\nPolling the status of a change-history export task created earlier.\n\nDo not use when:\nBefore create_change_history_task has returned a task_id.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nRead-only status poll of an async export task; no advertiser-side mutation. Gated with the export flow rather than exposed as an immediate read.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "task_id", "type": "string", "required": true, "description": "TikTok task id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_connected_instagram_accounts", "namespace": "ads", "action": "get_connected_instagram_accounts", "description": "Read Instagram accounts connected to this Meta ad account; returns data[] with the IG id and username to use as instagram_user_id in creatives.", "mode": "read", "platforms": [ "meta" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad_creative", "action:ads/deploy_image_ads_batch", "action:ads/get_account_pages" ], "long_description": "Primary use:\nResolve the ad-account-connected Instagram identity BEFORE building any creative that runs on Instagram placements. The ads API rejects a public IG handle or Page id as instagram_user_id — it requires the IG identity connected to the ad account, which this action returns.\n\nUse when:\n- You are about to call action:ads/create_ad_creative or deploy_image_ads_batch with an Instagram placement and need a valid instagram_user_id.\n- A creative or ad create failed because the supplied Instagram id was rejected (e.g. a public @handle or the Page id).\n\nDo not use when:\n- The platform is Google.\n- You only need Facebook Pages. Use action:ads/get_account_pages.\n\nWorkflow:\n1. Call this action with platform=meta, account_id.\n2. Pick the returned data[].id and pass it as instagram_user_id to create_ad_creative / deploy_image_ads_batch.\n3. If data is empty, no IG account is connected to this ad account — connect it in Business Manager first (a public handle will not work).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.data[].id is the ad-account-connected IG identity; use it directly as instagram_user_id (creatives currently map it to that field). If a creative still rejects it, fall back to the Page's page_backed_instagram_accounts.\n\nSide effects:\n- None. Read-only Graph API call.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Meta only: page size for the connected IG accounts list." }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from a prior response's paging.cursors.after; pass it back to fetch the next page.", "example": "QVFIU..." } ], "output_shape": "{\"data\": [{\"id\": \"17841400000000000\", \"username\": \"arthaus.works\"}], \"paging\": {\"cursors\": {\"after\": \"...\"}}}" }, { "id": "action:ads/get_conversion_tracking_snippets", "namespace": "ads", "action": "get_conversion_tracking_snippets", "description": "Read installable Google website or call-conversion tag snippets.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_conversion_action" ], "long_description": "Primary use:\nRetrieve the installable snippets for an existing Google website or click-to-call conversion.\n\nUse when:\n- A developer needs the global site tag and event snippet after conversion creation.\n\nDo not use when:\n- The conversion action has not been created; use action:ads/create_conversion_action first.\n- The conversion type does not provide website/call tag snippets.\n\nWorkflow:\n1. Pass the numeric conversion_action_id.\n2. Install the returned snippets according to the type and page format.\n\nOutput semantics:\n- Returns structured global_site_tag and event_snippet values; it does not modify the conversion action.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id without dashes.", "example": "1234567890" }, { "name": "conversion_action_id", "type": "string", "required": true, "description": "Google conversion action id.", "example": "123" } ], "output_shape": "{\"success\":true,\"raw\":{\"tag_snippets\":[{\"global_site_tag\":\"...\",\"event_snippet\":\"...\"}]}}" }, { "id": "action:ads/get_data_readiness", "namespace": "ads", "action": "get_data_readiness", "description": "Read per-account window×scope data-readiness so you can plan a segmented audit: audit ready windows now, flag not-ready windows as still syncing (never as zero).", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/list_ad_accounts", "action:ads/query_single_dimension", "action:ads/gaql_search" ], "long_description": "Primary use:\nBefore a deep / multi-window audit of a freshly-onboarded account, check which time windows are already synced. Onboarding metrics land in tiers, so recent windows are ready while older ones are still backfilling — an empty result for a not-ready window means 'still syncing', NOT zero spend.\n\nWindows: last_30d, 31_90d, ytd, history. Scopes per window: core (spend/impr/clicks/conv), dimensions (breakdowns), search_terms. Status per window×scope: ready | syncing | failed | stalled | pending. bootstrap_state is the account-level lifecycle (not_started | in_progress | completed | stalled); eta_hint is a coarse 'minutes' / 'hours' / null. live_fallback names a live query path (google -> gaql_search) available while syncing.\n\nSegmented-audit protocol:\n1. Call get_data_readiness first for the account(s) under audit.\n2. Audit ready window×scope with query_single_dimension / query_multi_dimension as normal.\n3. For a not-ready window: on google use gaql_search (live) if live_fallback=google; on meta/tiktok/chatgpt_ads note it is still syncing and revisit after eta_hint. NEVER report a not-ready window as zero spend.\n4. State the data boundary honestly in your answer, e.g. 'based on the last 30 days (ready); 31–90 days and full history are still syncing, ~{eta_hint}'.\n5. A 409 data_syncing error or a 200 response carrying a top-level 'readiness' object with partial=true means the same thing — apply this protocol.\n\nNotes:\n- readiness reflects the LAST PERSISTED bootstrap progress; it is not a live re-read of the ad platform.\n- A granted account on a platform with no ads metrics manifest (e.g. GA4) returns readiness=null with reason=no_metrics_manifest. That is NOT a missing or ungranted account — it simply has no window matrix.\n\nDo not use when:\n- bootstrap_state is already completed for the account (all windows are ready; just query normally).", "input_params": [ { "name": "account_id", "type": "string", "required": false, "description": "Optional. Narrow to one granted account_id (from list_ad_accounts). Omit to return readiness for every brand-granted ads account.", "example": "1234567890" } ], "output_shape": "{\"accounts\": [{\"account_id\": \"1234567890\", \"platform\": \"google\", \"bootstrap_state\": \"in_progress\", \"windows\": {\"last_30d\": {\"core\": \"ready\", \"dimensions\": \"syncing\"}, \"31_90d\": {\"core\": \"syncing\"}}, \"eta_hint\": \"minutes\", \"pct\": 40, \"live_fallback\": \"google\"}, {\"account_id\": \"G-XYZ\", \"platform\": \"ga4\", \"readiness\": null, \"reason\": \"no_metrics_manifest\"}]}" }, { "id": "action:ads/get_dataset_quality", "namespace": "ads", "action": "get_dataset_quality", "description": "Read Meta Pixel/Dataset signal quality fields from Graph API.", "mode": "read", "platforms": [ "meta" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/list_pixels", "action:ads/send_conversion_event" ], "long_description": "Primary use:\nInspect live Pixel/Dataset quality signals such as event stats and match rate.\n\nUse when:\n- The user asks whether Meta Pixel/CAPI signal quality looks healthy.\n- A conversion tracking workflow needs dataset diagnostics before changing ads.\n\nDo not use when:\n- The user needs stable cross-platform KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs Google recommendations or Google change history. Use action:ads/get_recommendations or action:ads/get_change_history.\n- The diagnostic candidate has no public Graph API endpoint; such actions are not registered in this PR.\n\nWorkflow:\n1. Call action:ads/list_ad_accounts if account_id is unknown.\n2. Call this action with optional pixel_id or dataset_id for one dataset; omit it to list account pixels.\n3. Use raw.event_stats and raw.match_rate_approx to diagnose signal quality.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph diagnostic fields.\n- These diagnostics are live platform reads and are not written to synced metrics storage.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "pixel_id", "type": "string", "required": false, "description": "Optional Meta Pixel id to inspect." }, { "name": "dataset_id", "type": "string", "required": false, "description": "Optional Meta Dataset id; treated as the Pixel/Dataset Graph object id." }, { "name": "limit", "type": "integer", "required": false, "description": "Account pixel page size; default 100." }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor (paging.cursors.after from a prior response) for the account dataset list path. Only used when pixel_id/dataset_id are omitted. Lets the agent page large accounts beyond the first 'limit' datasets. Source: output of a prior get_dataset_quality list call (paging.cursors.after).", "example": "QVFIU...cursor..." } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"name\": \"...\", \"event_stats\": {...}, \"match_rate_approx\": \"...\"}], \"paging\": {\"cursors\": {\"after\": \"...\"}}} // Omitting both pixel_id and dataset_id triggers the list-all path (pass 'after' back to page). Supplying an unowned/nonexistent pixel_id returns {success:false, error:\"Dataset not found\", error_code:\"not_found\"}; match_rate_approx is approximate and may be null for low-volume or recently-created datasets." }, { "id": "action:ads/get_gmv_max_availability", "namespace": "ads", "action": "get_gmv_max_availability", "description": "Check TikTok Shop GMV Max availability and prerequisites.", "mode": "read", "platforms": [ "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nCheck TikTok Shop GMV Max availability and prerequisites.\n\nUse when:\nChecking whether a TikTok Shop account / store is eligible for GMV Max before creating a campaign.\n\nDo not use when:\nEligibility is already confirmed and you are ready to create — use create_gmv_max_campaign.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Call this read directly (no review needed).\n3. Use the returned ids / metrics in the next step.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_gmv_max_campaign", "namespace": "ads", "action": "get_gmv_max_campaign", "description": "Read a TikTok Shop GMV Max campaign.", "mode": "read", "platforms": [ "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nRead a TikTok Shop GMV Max campaign.\n\nUse when:\nReading back a known GMV Max campaign's configuration and state.\n\nDo not use when:\nYou want performance metrics — use get_gmv_max_report.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Call this read directly (no review needed).\n3. Use the returned ids / metrics in the next step.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "campaign_id", "type": "string", "required": true, "description": "TikTok campaign id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_gmv_max_report", "namespace": "ads", "action": "get_gmv_max_report", "description": "Read TikTok Shop GMV Max reporting.", "mode": "read", "platforms": [ "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nRead TikTok Shop GMV Max reporting.\n\nUse when:\nPulling GMV Max performance by store / dimension / metric over a date range.\n\nDo not use when:\nYou want campaign configuration — use get_gmv_max_campaign.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Call this read directly (no review needed).\n3. Use the returned ids / metrics in the next step.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "store_ids", "type": "list", "required": true, "description": "TikTok store ids.", "example": "value", "platform": [ "tiktok" ] }, { "name": "dimensions", "type": "list", "required": true, "description": "TikTok dimensions.", "example": "value", "platform": [ "tiktok" ] }, { "name": "metrics", "type": "list", "required": true, "description": "TikTok metrics.", "example": "value", "platform": [ "tiktok" ] }, { "name": "start_date", "type": "string", "required": true, "description": "TikTok start date.", "example": "value", "platform": [ "tiktok" ] }, { "name": "end_date", "type": "string", "required": true, "description": "TikTok end date.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_google_ads_report", "namespace": "ads", "action": "get_google_ads_report", "description": "Run a predefined, fixed-column Google Ads performance report.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/gaql_search", "action:ads/get_resource_metadata" ], "long_description": "Use this action for account, campaign, ad, keyword, search-term, or bidding-strategy reports with stable default columns. It is read-only and uses the live Google Ads API. Omit dates for the latest continuous 30-day range.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Must be google.", "example": "google" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id without dashes.", "example": "1234567890" }, { "name": "report_type", "type": "string", "required": true, "description": "Predefined Google Ads report type.", "example": "campaign", "enum": [ "account", "campaign", "ad", "keyword", "search_term", "bidding_strategy" ] }, { "name": "start_date", "type": "string", "required": false, "description": "Inclusive YYYY-MM-DD; supply with end_date." }, { "name": "end_date", "type": "string", "required": false, "description": "Inclusive YYYY-MM-DD; supply with start_date." }, { "name": "limit", "type": "integer", "required": false, "description": "Row limit; defaults to 100." } ], "output_shape": "{\"report_type\":\"campaign\",\"effective_date_range\":{\"start_date\":\"2026-01-01\",\"end_date\":\"2026-01-30\"},\"columns\":{}}" }, { "id": "action:ads/get_insights", "namespace": "ads", "action": "get_insights", "description": "Live Meta Insights for an account/campaign/adset/ad; returns data[] with actions and metrics by breakdown. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/query_single_dimension", "action:ads/query_multi_dimension", "action:ads/gaql_search" ], "long_description": "Supported platforms: meta, tiktok, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nRealtime Meta Marketing Insights API. Returns metrics and action breakdowns by level (account, campaign, adset, ad) and optional breakdown dimensions. Distinct from cached business-layer ads analytics.\n\nUse when:\n- The user wants up-to-the-minute Meta performance numbers.\n- A workflow needs Meta-native breakdowns (age, gender, country, placement, ...).\n\nDo not use when:\n- The user is fine with cached, cross-platform business metrics. Use action:ads/query_single_dimension or action:ads/query_multi_dimension.\n- The user needs CPA / ROAS via Nex conversion-mapping groups. Use action:ads/query_single_dimension with cg.* columns.\n- The platform is Google. Use action:ads/gaql_search or action:ads/gaql_search.\n\nWorkflow:\n1. Identify object_id (ad account, campaign, ad set, or ad id).\n2. Pick date_preset or time_range, optional breakdown, optional fields.\n3. Call this action.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph paged response.\n- raw.data[].actions is a list of {action_type, value}, e.g. {\"action_type\": \"link_click\", \"value\": \"123\"} or {\"action_type\": \"offsite_conversion.fb_pixel_purchase\", \"value\": \"45\"}.\n- raw.data[].action_values mirrors actions with monetary value.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok, linkedin.", "example": "meta", "enum": [ "meta", "tiktok", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "object_id", "type": "string", "required": true, "description": "Insights target: ad account id (act_...), campaign_id, adset_id, or ad_id.", "platform": [ "meta" ] }, { "name": "date_preset", "type": "string", "required": false, "description": "Meta only: relative window. One of today, yesterday, this_week_mon_today, this_week_sun_today, last_week_mon_sun, last_week_sun_sat, last_3d, this_month, last_month, this_quarter, last_quarter, this_year, last_year, last_7d, last_14d, last_28d, last_30d, last_90d, maximum. Mutually exclusive with time_range/time_ranges (if an explicit range is supplied, date_preset is ignored). Source: constant enum chosen by the agent.", "example": "last_30d", "platform": [ "meta" ] }, { "name": "time_range", "type": "object", "required": false, "description": "Explicit {\"since\": \"YYYY-MM-DD\", \"until\": \"YYYY-MM-DD\"} object.", "platform": [ "meta" ] }, { "name": "level", "type": "string", "required": false, "description": "One of account, campaign, adset, ad.", "example": "campaign", "platform": [ "meta" ] }, { "name": "breakdown", "type": "string", "required": false, "description": "Meta only: DEPRECATED legacy singular form -- Graph never accepted a singular `breakdown` param. Prefer `breakdowns` (list). If supplied alone it is now auto-promoted into `breakdowns` so it still takes effect. Source: constant enum chosen by the agent (prefer breakdowns instead).", "example": "publisher_platform", "platform": [ "meta" ] }, { "name": "breakdowns", "type": "list", "required": false, "description": "List of breakdown dimensions. Values include age, gender, country, region, dma, impression_device, publisher_platform, platform_position, placement, device_platform.", "platform": [ "meta" ] }, { "name": "fields", "type": "string", "required": false, "description": "Comma-separated field list. Default 'campaign_id,campaign_name,adset_id,adset_name,ad_id,ad_name,impressions,clicks,spend,actions,action_values'.", "platform": [ "meta", "linkedin" ] }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "time_increment", "type": "string", "required": false, "description": "Meta only: time-series bucketing. 'all_days' (single total, default), 'monthly', or an integer number of days as a string ('1' daily, '7' weekly). Without it you only get one aggregated total for the window. Hourly is not supported. Source: constant enum chosen by the agent based on the user's requested granularity (daily/weekly/monthly).", "example": "7", "platform": [ "meta" ] }, { "name": "action_attribution_windows", "type": "list", "required": false, "description": "Meta only: list of attribution windows controlling how conversions in actions[]/action_values are counted. Valid values (post Jan 2026): '1d_click', '7d_click', '28d_click', '1d_view'. The view windows '7d_view' and '28d_view' were removed and now return empty data. If omitted, the account's default (Ads Manager) attribution is used. Source: constant enum chosen by the agent; defaults to account setting when omitted.", "example": [ "7d_click", "1d_view" ], "platform": [ "meta" ] }, { "name": "action_breakdowns", "type": "list", "required": false, "description": "Meta only: split the actions[] array by an action dimension. Values include 'action_type', 'action_destination', 'action_carousel_card_id', 'action_carousel_card_name', 'action_video_sound', 'action_video_type'. Default is 'action_type'. Source: constant enum chosen by the agent.", "example": [ "action_type", "action_destination" ], "platform": [ "meta" ] }, { "name": "time_ranges", "type": "list", "required": false, "description": "Meta only: list of {since,until} (YYYY-MM-DD) objects to compare multiple periods in one request. Mutually exclusive with date_preset (date_preset is dropped when this is set). Source: user-provided date ranges or agent-computed comparison windows.", "example": [ { "since": "2026-05-01", "until": "2026-05-31" }, { "since": "2026-06-01", "until": "2026-06-15" } ], "platform": [ "meta" ] }, { "name": "filtering", "type": "array", "required": false, "description": "Meta only: server-side row filter. List of {field, operator, value} objects; operator is one of EQUAL, NOT_EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL, IN_RANGE, NOT_IN_RANGE, CONTAIN, NOT_CONTAIN, IN, NOT_IN. Use to drop rows like spend=0. Source: agent-constructed from the user's threshold criteria. TikTok only: report filters.", "example": [ { "field": "spend", "operator": "GREATER_THAN", "value": 0 } ] }, { "name": "sort", "type": "list", "required": false, "description": "Meta only: server-side ordering for top-N queries. List of strings of the form '_descending' or '_ascending', e.g. 'spend_descending', 'impressions_descending'. Source: constant chosen by the agent from the requested ranking metric/direction.", "example": [ "spend_descending" ], "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor to fetch the next page beyond `limit`. Pass the value from the previous response's raw.paging.cursors.after to continue a multi-page pull. Source: output of a prior get_insights call: raw.paging.cursors.after.", "example": "MAZDZD", "platform": [ "meta" ] }, { "name": "pivot", "type": "string", "required": false, "description": "LinkedIn only: analytics grouping pivot; default CAMPAIGN.", "enum": [ "ACCOUNT", "CAMPAIGN_GROUP", "CAMPAIGN", "CREATIVE" ], "platform": [ "linkedin" ] }, { "name": "time_granularity", "type": "string", "required": false, "description": "LinkedIn only: bucket granularity; default DAILY.", "enum": [ "ALL", "DAILY", "MONTHLY", "YEARLY" ], "platform": [ "linkedin" ] }, { "name": "date_range", "type": "object", "required": false, "description": "LinkedIn only (required): date objects, e.g. {\"start\": {\"year\": 2026, \"month\": 7, \"day\": 1}, \"end\": {...}}.", "platform": [ "linkedin" ] }, { "name": "campaign_ids", "type": "array", "required": false, "description": "LinkedIn only: restrict to these campaign ids; omitted -> whole account.", "platform": [ "linkedin" ] }, { "name": "report_type", "type": "string", "required": false, "description": "TikTok only: report type (BASIC / AUDIENCE / ...).", "example": "BASIC", "platform": [ "tiktok" ] }, { "name": "data_level", "type": "string", "required": false, "description": "TikTok only: aggregation level (AUCTION_CAMPAIGN / AUCTION_ADGROUP / AUCTION_AD).", "example": "AUCTION_CAMPAIGN", "platform": [ "tiktok" ] }, { "name": "dimensions", "type": "list", "required": false, "description": "TikTok only: report dimensions.", "example": [ "campaign_id" ], "platform": [ "tiktok" ] }, { "name": "metrics", "type": "list", "required": false, "description": "TikTok only: report metrics.", "example": [ "spend", "impressions", "clicks" ], "platform": [ "tiktok" ] }, { "name": "start_date", "type": "string", "required": false, "description": "TikTok only: start date (YYYY-MM-DD).", "example": "2026-07-01", "platform": [ "tiktok" ] }, { "name": "end_date", "type": "string", "required": false, "description": "TikTok only: end date (YYYY-MM-DD).", "example": "2026-07-07", "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok only: page number (1-based).", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok only: page size (max 100).", "example": 100, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"campaign_id\": \"...\", \"campaign_name\": \"...\", \"impressions\": \"...\", \"clicks\": \"...\", \"spend\": \"...\", \"actions\": [{\"action_type\": \"link_click\", \"value\": \"...\"}], \"action_values\": [{\"action_type\": \"offsite_conversion.fb_pixel_purchase\", \"value\": \"...\"}]}], \"paging\": {...}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_leads", "namespace": "ads", "action": "get_leads", "description": "Read Meta leads collected by a lead form or ad; returns data[] with field_data. Smart ad-id fallback. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_lead_forms", "action:ads/create_lead_form", "action:ads/archive_lead_form", "action:ads/list_ads" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nFetch the actual lead submissions for a lead form or an ad. Handler tries the object_id directly and, if object_id is an ad_id whose direct call fails, falls back to resolving leadgen_form_id from the ad's creative and retrying.\n\nUse when:\n- The user wants the leads collected by a lead form or by a specific ad.\n- A workflow ingests lead field_data into a CRM.\n\nDo not use when:\n- The user wants lead form metadata only. Use action:ads/list_lead_forms.\n- The platform is Google.\n\nWorkflow:\n1. Pick object_id (either a lead_form_id from list_lead_forms or an ad_id from list_ads).\n2. Call this action.\n3. Page via limit / after if needed.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph paged response; field_data is a list of {name, values} per question.\n- If the original object_id call fails, the handler may retry against the resolved leadgen_form_id; if no form is found, the original error is re-raised.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: Capability docs never state the permission prerequisite (leads_retrieval) or that a Page-level token is typically required to read field_data PII; an agent hitting a permission error has no documented remedy. Also note: No mention of the 90-day lead retention window — agents may expect to retrieve historical leads older than 90 days and silently get nothing. Capability gap: filtering parameter (time_created GREATER_THAN ) for incremental / since-watermark lead retrieval — absent from handler, allowlist, and capability docs; the single biggest functional gap for CRM-ingest workflows that must avoid re-pulling the whole 90-day window each run.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "object_id", "type": "string", "required": true, "description": "Either a lead_form_id (from action:ads/list_lead_forms) or an ad_id (from action:ads/list_ads); handler falls back to resolving the form_id from the ad's creative on direct-call failure.", "platform": [ "meta" ] }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Pagination cursor.", "platform": [ "meta" ] }, { "name": "end_time", "type": "string", "required": false, "description": "TikTok end time.", "example": "value", "platform": [ "tiktok" ] }, { "name": "form_id", "type": "string", "required": false, "description": "Alias of TikTok page_id. Required for INSTANT_FORM; omit for DIRECT_MESSAGE.", "example": "form-1", "platform": [ "tiktok" ] }, { "name": "lead_source", "type": "string", "required": true, "description": "TikTok lead source. INSTANT_FORM reads Instant Form leads; DIRECT_MESSAGE reads leads generated by direct-message ads.", "example": "INSTANT_FORM", "enum": [ "INSTANT_FORM", "DIRECT_MESSAGE" ], "source": "constant", "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_id", "type": "string", "required": false, "description": "Required for INSTANT_FORM leads; omit for DIRECT_MESSAGE leads.", "example": "form-1", "source": "upstream:action:ads/list_lead_forms", "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] }, { "name": "start_time", "type": "string", "required": false, "description": "TikTok start time.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"lead_id\", \"created_time\": \"ISO\", \"field_data\": [{\"name\": \"email\", \"values\": [\"user@example.com\"]}], \"ad_id\": \"...\", \"form_id\": \"...\", \"platform\": \"...\"}], \"paging\": {...}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_offline_conversion_readiness", "namespace": "ads", "action": "get_offline_conversion_readiness", "description": "Read whether this brand's Google Ads conversion actions and Meta pixels are configured to receive offline (in-store) conversions, and what blocks each one.", "mode": "read", "platforms": [ "google", "meta" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_actions", "action:ads/list_ad_accounts" ], "long_description": "Primary use:\nAnswer 'can we send this customer's settled in-store orders into their own Google Ads / Meta account yet, and if not, what is missing'. Returns one row per addressable destination with an explicit blocker list.\n\nUse when:\n- The user asks whether offline / in-store conversion upload is set up.\n- You need to tell the user which setup step is outstanding, per account.\n- You need the eligible UPLOAD_CLICKS conversion actions or pixels for a brand.\n\nDo not use when:\n- You want to upload a conversion. This action never sends anything; no upload capability exists in this namespace.\n- You need live platform state. Google eligibility is read from the synced conversion-action snapshot, not from the Google Ads API.\n\nOutput semantics — read these carefully before reporting to a user:\n- ready=true means CONFIGURATION is complete. It does NOT mean an upload would succeed, and it says nothing about whether a conversion would be matched or attributed.\n- status='no_granted_accounts' means the brand holds no ad-account grant at all; status='credential_unavailable' means a grant exists but its connection is deactivated, so tell the user to reconnect rather than to re-grant.\n- google[].capability is the credential verdict: 'platform_setup_required' means Soku has not yet completed Google Data Manager onboarding, so asking the customer to reconnect would NOT help; 'reconnect_required' means the customer's own token predates the scope and reconnecting does help; 'service_account_pending_preflight' means a service-account credential cannot be judged without a live check.\n- google[].data_freshness is the age of the CONVERSION-action snapshot (the conversion sync pipeline), not of the ads hierarchy sync. 'freshness_unknown' means that snapshot has no known age and 'freshness_stale' means it is older than the staleness budget; both clear ready, because an ENABLED row in an old snapshot is not evidence the action is ENABLED now.\n- 'credential_unavailable' means the connection itself is not in a usable state (its verification failed or is still pending), so no destination behind it can receive anything until it is reconnected.\n- meta[] rows appear for every granted Meta account, including one with no pixel linked yet — that row carries 'no_linked_pixel' rather than being omitted, so 'not configured' is never silent.\n- meta[].blockers are per (account, pixel): one account's unresolved cross-Business-Manager binding never blocks a sibling account. 'pixel_permission_missing' means discovery already probed this pair and recorded that the connection cannot address that pixel.\n- meta[].data_freshness exists for the same reason as the Google one: pixel and link rows are snapshots, so an unknown or stale age clears ready rather than passing silently.\n- Destinations are derived from active grants; there is no input parameter that can name an account, conversion action, or pixel.\n- Discovery caveat: when a brand's every Google/Meta connection is deactivated, capability listing stops showing this action even though that is exactly the state it explains. It stays callable by id — invoke it directly if a user reports a broken Google/Meta connection and you do not see it listed.", "input_params": [], "output_shape": "{\"status\": \"ok\", \"google\": [{\"account_id\": \"1234567890\", \"account_name\": \"Acme\", \"credential_id\": 7, \"credential_source\": \"oauth\", \"capability\": \"platform_setup_required\", \"conversion_actions\": [{\"conversion_action_id\": \"987654321\", \"name\": \"In-store purchase\", \"action_type\": \"UPLOAD_CLICKS\", \"status\": \"ENABLED\"}], \"data_freshness\": \"ISO\", \"ready\": false, \"blockers\": [\"platform_setup_required\"]}], \"meta\": [{\"account_id\": \"act_123\", \"account_name\": \"Acme\", \"credential_id\": 8, \"credential_source\": \"oauth\", \"pixel_resource_id\": 4, \"pixel_id\": \"999\", \"pixel_name\": \"Store Pixel\", \"ready\": true, \"blockers\": []}]}" }, { "id": "action:ads/get_opportunity_score", "namespace": "ads", "action": "get_opportunity_score", "description": "Read Meta account opportunity_score and recommendations from Graph API.", "mode": "read", "platforms": [ "meta" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/get_insights", "action:ads/get_recommendations" ], "long_description": "Primary use:\nInspect Meta-native account opportunity score and recommendation payloads.\n\nUse when:\n- The user asks for Meta optimization opportunities similar to Google recommendations.\n- A live account audit needs Meta opportunity_score without querying synced metrics.\n\nDo not use when:\n- The user needs stable cross-platform KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs Google recommendations or Google change history. Use action:ads/get_recommendations or action:ads/get_change_history.\n- The diagnostic candidate has no public Graph API endpoint; such actions are not registered in this PR.\n\nWorkflow:\n1. Call action:ads/list_ad_accounts if account_id is unknown.\n2. Call this action with platform=meta and account_id.\n3. Use raw.opportunity_score and raw.recommendations to plan next diagnostic or optimization steps.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph diagnostic fields.\n- These diagnostics are live platform reads and are not written to synced metrics storage.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "include_recommendations", "type": "boolean", "required": false, "description": "Whether to also call the Graph recommendations edge; default true." }, { "name": "limit", "type": "integer", "required": false, "description": "Recommendation page size; default 25." }, { "name": "campaign_id", "type": "string", "required": false, "description": "Meta only: optional campaign id to read campaign-level opportunity_score instead of account-level. opportunity_score is officially available at account/campaign/adset level (0-100). Source: output of action:ads/list_campaigns (campaign id).", "example": "120210000000000000" }, { "name": "adset_id", "type": "string", "required": false, "description": "Meta only: optional ad set id to read ad-set-level opportunity_score instead of account-level. Takes effect only if campaign_id is not also supplied. Source: output of action:ads/list_ad_sets (ad set id).", "example": "120210000000000000" }, { "name": "object_id", "type": "string", "required": false, "description": "Meta only: generic Graph object id to read opportunity_score from (alternative to campaign_id/adset_id). Lowest precedence among entity selectors. Source: any Meta Graph entity id (account/campaign/adset).", "example": "120210000000000000" }, { "name": "recommendation_fields", "type": "string", "required": false, "description": "Meta only: comma-separated field list for the recommendations edge. Default 'title,message,importance,blame_field'. Previously read by the handler but undocumented. Source: constant enum of AdRecommendation fields.", "example": "title,message,importance,blame_field" }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor for the recommendations edge (paging.cursors.after from a prior response). Source: output of a prior get_opportunity_score call (recommendations.paging.cursors.after).", "example": "QVFIU...cursor..." } ], "output_shape": "{\"id\": \"act_...\", \"opportunity_score\": 82, \"recommendations\": {\"data\": [...]}, \"recommendations_error\": \"...optional, present only when the recommendations edge failed...\"} // opportunity_score is an integer 0-100 (not a 0-1 fraction); may be null/absent when not yet rolled out for the account or when the token lacks ads performance read scope." }, { "id": "action:ads/get_pmax_campaign", "namespace": "ads", "action": "get_pmax_campaign", "description": "Read a Google Performance Max campaign, asset-group state, attachments, and signal status.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_pmax_campaign", "action:ads/remove_campaign" ], "long_description": "Primary use:\nRead a Performance Max campaign and its persisted asset-group structure without changing delivery.\n\nUse when:\n- A create_pmax_campaign result must be verified through a live readback.\n- You need the asset attachments or signal approval state for an existing PMax campaign.\n\nDo not use when:\n- The campaign is a Search, Display, Shopping, or Video campaign; use action:ads/get_campaign.\n- You need to change assets or bidding; submit the relevant write action through HITL.\n\nWorkflow:\n1. Pass the numeric campaign_id returned by create_pmax_campaign.\n2. Inspect campaign status, asset-group final URLs, attachments, and signal records.\n\nOutput semantics:\n- Returns realtime campaign and asset-group state. Signal output contains resource identity and approval status because Google GAQL does not allow selecting audience or search-theme content.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Performance Max campaign id returned by create_pmax_campaign.", "example": "123" } ], "output_shape": "{\"success\": true, \"raw\": {\"campaign\": {\"campaign_id\": \"123\", \"status\": \"PAUSED\"}, \"asset_groups\": [{\"asset_group_id\": \"456\", \"status\": \"PAUSED\", \"final_urls\": [\"https://example.com\"], \"attachments\": [{\"asset_resource_name\": \"customers/.../assets/1\", \"field_type\": \"HEADLINE\"}], \"signals\": [{\"signal_resource_name\": \"customers/.../assetGroupSignals/1~2\", \"approval_status\": \"APPROVED\"}]}}]}}" }, { "id": "action:ads/get_product_catalog_details", "namespace": "ads", "action": "get_product_catalog_details", "description": "Get live details for one owned Meta product catalog. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/list_product_catalogs" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nInspect catalog metadata and ownership before feed, product, or diagnostic reads.\n\nUse when:\n- The user has a catalog_id and needs current catalog metadata.\n- A workflow must verify catalog ownership before using catalog object ids.\n\nDo not use when:\n- The user needs cached business-layer analytics or KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs to mutate catalog resources. Use a create_* catalog action through review-gated batch submission.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Get catalog_id from action:ads/list_product_catalogs.\n2. Call this action with catalog_id.\n3. Use the raw details to decide the next catalog operation.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph fields without L1.5 normalization.\n- For catalog object ids, the backend verifies ownership against the resolved Meta ad account business before returning data.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: account_id is declared required in the capability docs but is actually auto-resolved from the credential context when omitted (executor.py:51-52). The docs do not state this, so the agent may over-collect account_id when it is unnecessary. Also note: The \"Catalog not found\" / not_found envelope is overloaded: it is returned for (a) account has no business, (b) catalog id unreadable/forbidden, AND (c) catalog owned by a different business than account_id. The capability docs ('the backend verifies ownership against the resolved Meta ad account business') hint at this but the agent gets no distinguishing error, so on failure it cannot tell whether to fix catalog_id, fix account_id, or escalate a permissions issue. Capability gap: No agent-facing way to request additional ProductCatalog fields beyond the hardcoded _CATALOG_FIELDS set (e.g. da_display_settings, default_image_url, fallback_image_url, flight_catalog_settings, store_catalog_settings, catalog_store, commerce_merchant_settings). For a read action this is a deliberate simplification, not a break.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id. Required when platform=tiktok." }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"id\": \"...\", \"name\": \"...\", \"vertical\": \"...\", \"business\": {\"id\": \"...\"}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_product_feed_details", "namespace": "ads", "action": "get_product_feed_details", "description": "Get live details for a Meta product feed in an owned catalog. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/get_product_feed_rules", "action:ads/get_catalog_diagnostics" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nInspect product feed metadata, upload state, schedule, and latest ingestion details.\n\nUse when:\n- The user has product_feed_id and needs current feed status.\n- Catalog diagnostics indicate feed upload or schedule problems.\n\nDo not use when:\n- The user needs cached business-layer analytics or KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs to mutate catalog resources. Use a create_* catalog action through review-gated batch submission.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Get catalog_id from action:ads/list_product_catalogs.\n2. Call this action with catalog_id and product_feed_id.\n3. Use raw latest_upload/status fields to diagnose feed health.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph fields without L1.5 normalization.\n- For catalog object ids, the backend verifies ownership against the resolved Meta ad account business before returning data.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id. Required when platform=tiktok." }, { "name": "product_feed_id", "type": "string", "required": true, "description": "Meta product feed id.", "platform": [ "meta" ] }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "feed_id", "type": "string", "required": true, "description": "TikTok feed id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"id\": \"...\", \"name\": \"...\", \"latest_upload\": {...}, \"schedule\": {...}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_product_feed_rules", "namespace": "ads", "action": "get_product_feed_rules", "description": "Read rules configured on a Meta product feed.", "mode": "read", "platforms": [ "meta" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/get_product_feed_details" ], "long_description": "Primary use:\nInspect live product feed transformation rules before changing feed setup.\n\nUse when:\n- The user needs to understand how Meta transforms incoming feed fields.\n- Catalog diagnostics point to feed rule or mapping issues.\n\nDo not use when:\n- The user needs cached business-layer analytics or KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs to mutate catalog resources. Use a create_* catalog action through review-gated batch submission.\n- The platform is Google; this action is Meta-only.\n\nWorkflow:\n1. Get catalog_id from action:ads/list_product_catalogs.\n2. Get product_feed_id from action:ads/get_product_feed_details or Meta UI.\n3. Call this action with catalog_id and product_feed_id.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph fields without L1.5 normalization.\n- For catalog object ids, the backend verifies ownership against the resolved Meta ad account business before returning data.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: Doc workflow (_capability_docs.py:6234) says get product_feed_id from get_product_feed_details or Meta UI, but get_product_feed_details itself requires product_feed_id — circular; only real source is Meta UI. Also note: Doc does not say catalog_id is only an ownership guard (not sent to Meta) nor that a mismatched/cross-account catalog_id yields generic Catalog not found, masking a valid-but-unowned feed. Capability gap: No list_product_feeds action: agent cannot discover a product_feed_id. get_product_feed_details also requires the id as input, so both feed actions dead-end; the feed-rules dimension is effectively unreachable autonomously without Meta-UI input.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id." }, { "name": "product_feed_id", "type": "string", "required": true, "description": "Meta product feed id." }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100." }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from the previous response's paging.cursors.after; pass it back to fetch the next page.", "source": "upstream:action:ads/get_product_feed_rules" } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"attribute\": \"...\", \"operation\": \"...\"}], \"paging\": {...}}" }, { "id": "action:ads/get_recommendations", "namespace": "ads", "action": "get_recommendations", "description": "Live Google Ads recommendations with impact estimates; returns recommendations[].", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_keywords", "action:ads/update_campaign" ], "long_description": "Primary use:\nSurface Google Ads automated recommendations (e.g. add-keyword, raise-budget, switch-bidding-strategy) with baseline and potential impact metrics.\n\nUse when:\n- The user asks for optimization suggestions.\n- A workflow needs prioritized improvement ideas with quantified impact.\n\nDo not use when:\n- The user wants account-level optimization score. Use action:ads/gaql_search.\n- The platform is Meta. Use action:ads/list_ad_rules and Meta-side suggestions.\n\nWorkflow:\n1. Optionally filter by recommendation type (validated against Google's enum).\n2. Call this action.\n3. Apply chosen recommendations via the matching ads mutate (e.g. add_keywords).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.recommendations[].impact.baseMetrics vs potentialMetrics shows the estimated delta in impressions / clicks / costMicros.\n- raw.recommendations[].dismissed flags previously-dismissed items.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: include_dismissed boolean coercion: docs say type=boolean default false, but the handler does not parse stringified booleans. An agent emitting include_dismissed=\"false\" (a very common LLM serialization) will INVERT the intent and receive dismissed recommendations. Docs give no warning to pass a JSON-native boolean. Also note: type enum is not enumerated in the docs (only 3 examples given). With 50+ valid RecommendationType values and a hard ValueError on mismatch, the agent must guess valid spellings; docs should point to the enum or list the full set. Capability gap: No way to filter by attached resource (e.g. recommendation.campaign) — agent cannot scope recommendations to a single campaign; Google supports `WHERE recommendation.campaign = 'customers/X/campaigns/Y'` but it is not exposed.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "type", "type": "string", "required": false, "description": "Optional recommendation type filter; validated against the Google Ads RecommendationType enum (e.g. KEYWORD, CAMPAIGN_BUDGET, TARGET_CPA_OPT_IN).", "example": "KEYWORD", "enum": [ "CALLOUT_ASSET", "CALL_ASSET", "CAMPAIGN_BUDGET", "CUSTOM_AUDIENCE_OPT_IN", "DISPLAY_EXPANSION_OPT_IN", "DYNAMIC_IMAGE_EXTENSION_OPT_IN", "ENHANCED_CPC_OPT_IN", "FORECASTING_CAMPAIGN_BUDGET", "FORECASTING_SET_TARGET_CPA", "FORECASTING_SET_TARGET_ROAS", "IMPROVE_DEMAND_GEN_AD_STRENGTH", "IMPROVE_GOOGLE_TAG_COVERAGE", "IMPROVE_PERFORMANCE_MAX_AD_STRENGTH", "KEYWORD", "KEYWORD_MATCH_TYPE", "LEAD_FORM_ASSET", "LOWER_TARGET_ROAS", "MARGINAL_ROI_CAMPAIGN_BUDGET", "MAXIMIZE_CLICKS_OPT_IN", "MAXIMIZE_CONVERSIONS_OPT_IN", "MAXIMIZE_CONVERSION_VALUE_OPT_IN", "MIGRATE_DYNAMIC_SEARCH_ADS_CAMPAIGN_TO_PERFORMANCE_MAX", "MOVE_UNUSED_BUDGET", "OPTIMIZE_AD_ROTATION", "PERFORMANCE_MAX_FINAL_URL_OPT_IN", "PERFORMANCE_MAX_OPT_IN", "RAISE_TARGET_CPA", "RAISE_TARGET_CPA_BID_TOO_LOW", "REFRESH_CUSTOMER_MATCH_LIST", "RESPONSIVE_SEARCH_AD", "RESPONSIVE_SEARCH_AD_ASSET", "RESPONSIVE_SEARCH_AD_IMPROVE_AD_STRENGTH", "SEARCH_PARTNERS_OPT_IN", "SET_TARGET_CPA", "SET_TARGET_ROAS", "SHOPPING_ADD_AGE_GROUP", "SHOPPING_ADD_COLOR", "SHOPPING_ADD_GENDER", "SHOPPING_ADD_GTIN", "SHOPPING_ADD_MORE_IDENTIFIERS", "SHOPPING_ADD_PRODUCTS_TO_CAMPAIGN", "SHOPPING_ADD_SIZE", "SHOPPING_FIX_DISAPPROVED_PRODUCTS", "SHOPPING_FIX_MERCHANT_CENTER_ACCOUNT_SUSPENSION_WARNING", "SHOPPING_FIX_SUSPENDED_MERCHANT_CENTER_ACCOUNT", "SHOPPING_MIGRATE_REGULAR_SHOPPING_CAMPAIGN_OFFERS_TO_PERFORMANCE_MAX", "SHOPPING_TARGET_ALL_OFFERS", "SITELINK_ASSET", "TARGET_CPA_OPT_IN", "TARGET_ROAS_OPT_IN", "TEXT_AD", "UPGRADE_LOCAL_CAMPAIGN_TO_PERFORMANCE_MAX", "UPGRADE_SMART_SHOPPING_CAMPAIGN_TO_PERFORMANCE_MAX", "USE_BROAD_MATCH_KEYWORD" ], "source": "constant" }, { "name": "include_dismissed", "type": "boolean", "required": false, "description": "Include previously dismissed recommendations; default false.", "example": false }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap; default 50, max 1000." } ], "output_shape": "{\"success\": true, \"recommendations\": [{\"recommendation\": {\"resourceName\": \"...\", \"type\": \"...\", \"dismissed\": false, \"campaign\": \"customers/X/campaigns/Y\", \"impact\": {\"baseMetrics\": {\"impressions\": \"...\", \"clicks\": \"...\", \"costMicros\": \"...\"}, \"potentialMetrics\": {\"impressions\": \"...\", \"clicks\": \"...\", \"costMicros\": \"...\"}}}}]}" }, { "id": "action:ads/get_resource_metadata", "namespace": "ads", "action": "get_resource_metadata", "description": "Discover selectable, filterable, and sortable Google Ads fields for a GAQL resource before building gaql_search raw.* inputs.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/resolve_terms", "action:ads/gaql_search" ], "long_description": "FALLBACK PATH companion to gaql_search.\n\nPrimary use:\nGoogle Ads field discovery for a single GAQL resource such as campaign, ad_group, ad_group_criterion, or customer.\n\nUse when:\n- You plan to call gaql_search and need to know which native Google Ads fields exist.\n- You need to validate whether a field is selectable, filterable, or sortable.\n- A previous gaql_search failed because a raw field was unknown or not allowed.\n\nDo not use when:\n- You need cached business-layer analytics dimensions. Use list_dimensions.\n- You need conversion group business semantics. Use list_conversion_groups.\n- The platform is Meta. This action is Google-only.\n\nAvailability:\n- Native Google Ads connections declare and validate the v24 GoogleAdsFieldService.search_google_ads_fields RPC before I/O. The client parses the complete response with the official v24 protobuf type and preserve native pagination semantics.\n\nWorkflow:\n1. Choose the Google Ads GAQL resource name.\n2. Call this action to inspect fields.\n3. Use returned field names in gaql_search via raw..[....], raw.segments.X, or raw.metrics.X; pass resource_name to gaql_search when selecting related resources returned through selectable_with.\n\nOutput semantics:\n- selectable fields can be requested in gaql_search dimensions or metrics.\n- filterable fields can be used in gaql_search filters.\n- sortable fields can be used in gaql_search order_by.\n- data_type is the Google Ads API field data type.", "input_params": [ { "name": "platform", "type": "string", "required": false, "description": "Optional platform. Omit or use google; Meta is not supported." }, { "name": "account_id", "type": "string", "required": true, "description": "Google account id from list_ad_accounts.rows[].account_id. Use digits only with no dashes." }, { "name": "resource_name", "type": "string", "required": true, "description": "Google Ads resource lowercase name, such as campaign or ad_group. Must match ^[a-z_]+$.", "example": "ad_group" } ], "output_shape": "{\"account\": {\"platform\": \"google\", \"account_id\": \"123\", \"data_freshness\": \"ISO\"}, \"resource\": \"ad_group\", \"fields\": [{\"name\": \"...\", \"category\": \"ATTRIBUTE\", \"selectable\": true, \"filterable\": true, \"sortable\": true, \"data_type\": \"STRING\"}]}" }, { "id": "action:ads/get_smart_plus_campaign", "namespace": "ads", "action": "get_smart_plus_campaign", "description": "Read a TikTok Smart+ campaign.", "mode": "read", "platforms": [ "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nRead a TikTok Smart+ campaign.\n\nUse when:\nReading back a known Smart+ campaign's configuration and state.\n\nDo not use when:\nYou want performance metrics — use get_smart_plus_material_report.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Call this read directly (no review needed).\n3. Use the returned ids / metrics in the next step.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "campaign_id", "type": "string", "required": true, "description": "TikTok campaign id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "filtering", "type": "object", "required": false, "description": "TikTok filtering.", "example": {}, "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_smart_plus_material_report", "namespace": "ads", "action": "get_smart_plus_material_report", "description": "Read TikTok Smart+ material overview or breakdown reporting.", "mode": "read", "platforms": [ "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nRead TikTok Smart+ material overview or breakdown reporting.\n\nUse when:\nPulling Smart+ overview or per-material breakdown performance.\n\nDo not use when:\nYou want campaign configuration — use get_smart_plus_campaign.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Call this read directly (no review needed).\n3. Use the returned ids / metrics in the next step.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "campaign_id", "type": "string", "required": true, "description": "TikTok campaign id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "dimensions", "type": "list", "required": true, "description": "At least two Upgraded Smart+ report dimensions. Overview example: ['campaign_id', 'main_material_id']; breakdown example: ['main_material_id', 'stat_time_day'].", "example": [ "campaign_id", "main_material_id" ], "platform": [ "tiktok" ] }, { "name": "start_date", "type": "string", "required": false, "description": "TikTok start date (YYYY-MM-DD). Required for breakdown reports and for overview reports unless query_lifetime=true.", "example": "value", "platform": [ "tiktok" ] }, { "name": "end_date", "type": "string", "required": false, "description": "TikTok end date (YYYY-MM-DD). Required for breakdown reports and for overview reports unless query_lifetime=true.", "example": "value", "platform": [ "tiktok" ] }, { "name": "breakdown", "type": "boolean", "required": false, "description": "Local route selector. true uses the breakdown endpoint and requires start_date/end_date; false uses overview.", "example": false, "platform": [ "tiktok" ] }, { "name": "filtering", "type": "object", "required": false, "description": "TikTok filtering.", "example": {}, "platform": [ "tiktok" ] }, { "name": "metrics", "type": "list", "required": false, "description": "TikTok metrics.", "example": [], "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] }, { "name": "query_lifetime", "type": "boolean", "required": false, "description": "Overview only. Set true to query lifetime data without start_date/end_date; omit for breakdown reports.", "example": true, "platform": [ "tiktok" ] }, { "name": "sort_field", "type": "string", "required": false, "description": "TikTok sort field.", "example": "value", "platform": [ "tiktok" ] }, { "name": "sort_type", "type": "string", "required": false, "description": "TikTok sort type.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/get_tree_structure", "namespace": "ads", "action": "get_tree_structure", "description": "Read the Google MCC customer tree under a manager account; returns hierarchical customers[].", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_customers", "action:ads/list_ad_accounts" ], "long_description": "Primary use:\nTree view of customers below a Google Ads MCC manager account. Use this to navigate sub-accounts and their currencies / timezones.\n\nUse when:\n- The customer_id supplied is an MCC manager account.\n- You need currency, timezone, or manager flag per sub-account.\n\nDo not use when:\n- The customer_id is a leaf (non-manager) account. The query returns just that account.\n- The platform is Meta. Meta does not expose an analogous tree.\n\nWorkflow:\n1. Call this action with the MCC customer_id.\n2. Use the sub-accounts as the account_id for later actions.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.root_customer_id matches the supplied MCC.\n- raw.customers[].level indicates depth in the manager hierarchy.\n- Filters out status='CANCELED'.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: The capability docs advertise account_ids as 'Optional list of customer IDs to narrow the tree response', but the get_tree_structure handler does not implement any narrowing — the agent will believe it can filter and silently receive the full tree. This is a guidance/contract mismatch that will cause the agent to mis-plan. Also note: Docs do not state that there is no depth limit or pagination, so the agent cannot anticipate large responses on big MCCs. Capability gap: No level/depth filter: the handler always returns the entire descendant hierarchy (no WHERE customer_client.level <= N). The official hierarchy example uses customer_client.level <= 1 for direct children; large MCCs return very large unbounded result sets with no pagination or limit param.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "MCC manager account id from ads.list_ad_accounts. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "account_ids", "type": "list", "required": false, "description": "Optional list of customer IDs to narrow the tree response." } ], "output_shape": "{\"success\": true, \"root_customer_id\": \"1234567890\", \"customers\": [{\"customer_id\": 1234567890, \"name\": \"...\", \"resource_name\": \"customers/1234567890\", \"currency\": \"USD\", \"timezone\": \"America/Los_Angeles\", \"manager\": true, \"level\": 0}]}" }, { "id": "action:ads/list_account_negative_keywords", "namespace": "ads", "action": "list_account_negative_keywords", "description": "List account-level negative keywords from Google shared sets; read-only.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_account_negative_keywords", "action:ads/remove_account_negative_keywords", "action:ads/list_campaign_negative_keywords" ], "long_description": "Primary use:\nQuery account-level negative keywords via GAQL over shared_criterion joined with shared_set (type NEGATIVE_KEYWORDS). Returns keyword text, match type, the shared_criterion resource_name, and the owning shared set id/name.\n\nUse when:\n- Auditing account-level negative keyword shared sets, or deduplicating candidates before action:ads/add_account_negative_keywords.\n- Collecting shared_criterion resource_name values to feed action:ads/remove_account_negative_keywords.\n\nDo not use when:\n- You need campaign-level negatives. Use action:ads/list_campaign_negative_keywords.\n- You need ad-group-level negatives. Use action:ads/list_ad_group_negative_keywords.\n\nWorkflow:\n1. Call with account_id; optionally pass shared_set_id to scope to one shared set.\n2. Returns all negative keywords across non-removed NEGATIVE_KEYWORDS shared sets.\n\nOutput semantics:\n- negative_keywords: list of {criterion_id, text, match_type, resource_name, shared_set_id, shared_set_name}.\n- resource_name identifies the shared_criterion and is the exact value action:ads/remove_account_negative_keywords expects.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "shared_set_id", "type": "string", "required": false, "description": "Optional shared set id to scope results to a single negative keyword list. Omit to list all NEGATIVE_KEYWORDS shared sets.", "example": "9876543210" } ], "output_shape": "{\"success\": true, \"negative_keywords\": [{\"criterion_id\": \"...\", \"text\": \"free\", \"match_type\": \"BROAD\", \"resource_name\": \"customers/123/sharedCriteria/456~789\", \"shared_set_id\": \"...\", \"shared_set_name\": \"...\"}]}" }, { "id": "action:ads/list_ad_accounts", "namespace": "ads", "action": "list_ad_accounts", "description": "Discover brand-granted Google, Meta, TikTok, and ChatGPT Ads accounts; returns account_id values required by later ads queries and ads actions.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_dimensions", "action:ads/query_single_dimension", "action:ads/query_multi_dimension" ], "long_description": "Primary use:\nAccount discovery for the current brand. Use the returned account_id values as the only valid account_id inputs for later ads analytics or ads calls.\n\nUse when:\n- The user did not provide an account_id.\n- The user asks which ad accounts are available.\n- You need to choose a Google, Meta, TikTok, or ChatGPT Ads account before querying synced metrics or running supported ads actions.\n\nDo not use when:\n- You already have an account_id from this response in the same workflow.\n- You need campaign/ad/keyword metrics. Use list_dimensions plus query_* instead.\n\nWorkflow:\n1. Call this action, optionally filtering by platform.\n2. Pick one row's account_id and platform.\n3. Reuse that account_id in query_single_dimension, list_operation_records, or supported platform actions. Use query_multi_dimension, gaql_search, and get_resource_metadata only when the selected platform supports them.\n\nOutput semantics:\n- rows[].account_id is the external platform account id expected by later calls.\n- rows[].data_freshness is the latest completed ads sync run finish time for that account.\n\nConstraints:\n- Rows are filtered by ResourceBrandGrant for the sandbox brand.\n- An account absent from this response cannot be used in ads or ads calls; the dispatcher returns 403.", "input_params": [ { "name": "platform", "type": "string", "required": false, "description": "Optional platform filter. Use google, meta, tiktok, or chatgpt_ads; omit to list all granted accounts.", "example": "google" } ], "output_shape": "{\"rows\": [{\"account_id\": \"1234567890\", \"platform\": \"google\", \"name\": \"...\", \"currency\": \"USD\", \"timezone\": \"America/Los_Angeles\", \"data_freshness\": \"ISO\"}]}" }, { "id": "action:ads/list_ad_creatives", "namespace": "ads", "action": "list_ad_creatives", "description": "List Meta ad creatives under an account; returns data[] with creative content and object_story_spec.", "mode": "read", "platforms": [ "meta" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/get_ad_creative", "action:ads/get_ad_creatives", "action:ads/create_ad_creative", "action:ads/remove_ad_creative", "action:ads/generate_ad_preview" ], "long_description": "Primary use:\nAccount-wide creative inventory. Use this to discover creative_id values for reuse in action:ads/create_ad or update_ad, including creatives not attached to any ad yet.\n\nUse when:\n- You need a creative_id but do not know which ad (if any) uses the creative.\n- The user asks what creatives exist in a Meta account.\n- A workflow wants to reuse an existing creative instead of creating a duplicate via create_ad_creative.\n\nDo not use when:\n- You want the creatives wired to one specific ad. Use action:ads/get_ad_creatives with ad_id.\n- The user wants creative-level metrics. Use action:ads/get_insights with level=ad.\n- The platform is Google. Use action:ads/list_ads (headlines/descriptions on RSAs).\n\nWorkflow:\n1. Call action:ads/list_ad_accounts if account_id is unknown.\n2. Call this action with platform=meta and account_id.\n3. Page with 'after' from raw.paging.cursors.after until raw.paging.next is absent.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph paged response; object_story_spec and asset_feed_spec are nested creative-content objects.\n- Default limit=25; pagination cursor is raw.paging.cursors.after — pass it back as 'after' to fetch the next page.\n\nSide effects:\n- None. Read-only Graph API call.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 25." }, { "name": "after", "type": "string", "required": false, "description": "Meta pagination cursor; pass it back to fetch the next page. Source: output of a prior list_ad_creatives call: raw.paging.cursors.after.", "example": "QVFIU..." } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"name\": \"...\", \"title\": \"...\", \"body\": \"...\", \"image_url\": \"...\", \"thumbnail_url\": \"...\", \"object_story_spec\": {...}, \"asset_feed_spec\": {...}, \"status\": \"...\"}], \"paging\": {\"cursors\": {\"after\": \"...\"}}}" }, { "id": "action:ads/list_ad_group_negative_keywords", "namespace": "ads", "action": "list_ad_group_negative_keywords", "description": "List negative keywords for a Google ad group; read-only.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_ad_group_negative_keywords", "action:ads/remove_ad_group_negative_keywords", "action:ads/list_campaign_negative_keywords" ], "long_description": "Primary use:\nQuery ad-group-level negative keywords via GAQL. Returns keyword text, match type, status, and parent ad group/campaign info.\n\nUse when:\n- Deduplicating negative keyword candidates before adding new ones.\n- Auditing existing ad-group negatives for coverage gaps.\n- Collecting ad_group_criterion resource_name values to feed action:ads/remove_ad_group_negative_keywords.\n\nDo not use when:\n- You need campaign-level negatives. Use GAQL query on campaign_criterion.\n- You need account-level shared sets. Use GAQL query on shared_criterion.\n\nWorkflow:\n1. Call with account_id and ad_group_id.\n2. Returns all negative keywords for the specified ad group.\n\nOutput semantics:\n- negative_keywords: list of {criterion_id, text, match_type, status, resource_name, ad_group_id, ad_group_name, campaign_id, campaign_name}.\n- resource_name identifies the ad_group_criterion and is the exact value action:ads/remove_ad_group_negative_keywords expects.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Capability-docs output_shape example omits resource_name, but the handler does return resource_name in every negative_keywords item (negatives.py:232) — the example undersells the response. Also note: ad_group_id input_params description ('Ad group id to list negatives for.') does not state the digits-only/no-dashes expectation; account_id description states it but ad_group_id does not. Dashed input is tolerated (validate_numeric_id strips dashes) but agents are not told the format. Capability gap: No pagination/limit/page_token parameter exposed. GoogleAdsService.search auto-paginates internally so all rows are still returned, but a very large ad group could be slow/heavy with no caller-side cap; sibling list_ads accepts a 'limit' extra and this action does not.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Ad group id to list negatives for." } ], "output_shape": "{\"success\": true, \"negative_keywords\": [{\"criterion_id\": \"...\", \"text\": \"free\", \"match_type\": \"BROAD\", \"status\": \"ENABLED\", \"resource_name\": \"customers/123/adGroupCriteria/111~222\", \"ad_group_id\": \"...\", \"ad_group_name\": \"...\", \"campaign_id\": \"...\", \"campaign_name\": \"...\"}]}" }, { "id": "action:ads/list_ad_groups", "namespace": "ads", "action": "list_ad_groups", "description": "List ChatGPT Ads Ad Groups, optionally scoped to one Campaign; read-only.", "mode": "read", "platforms": [ "chatgpt_ads" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/get_ad_group", "action:ads/get_campaign" ], "long_description": "Primary use:\nRead Ad Groups and their current lifecycle state without changing delivery.\n\nUse when:\n- You need to inspect a Campaign hierarchy or reconcile a write.\n- You need include_archived=true before a legacy migration.\n\nDo not use when:\n- You already know one ad_group_id and need its complete embedded Ads window; use get_ad_group.\n\nWorkflow:\n1. Resolve account_id and optionally campaign_id.\n2. Call directly; no HITL review is required.\n3. Follow the returned collection and inspect each group independently.\n\nOutput semantics:\n- Returns the platform collection envelope containing Ad Group objects.\n- This read does not prove a previous create unless exact ids and signatures match.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant", "platform": [ "chatgpt_ads" ] }, { "name": "account_id", "type": "string", "required": true, "description": "Advertiser account id.", "example": "account_123", "platform": [ "chatgpt_ads" ] }, { "name": "campaign_id", "type": "string", "required": false, "description": "Optional Campaign filter.", "example": "campaign_123", "platform": [ "chatgpt_ads" ] }, { "name": "include_archived", "type": "boolean", "required": false, "description": "Include archived groups; required for migration reconciliation.", "example": true, "platform": [ "chatgpt_ads" ] } ], "output_shape": "{\"ad_groups\": [{\"id\": \"ag_123\", \"campaign_id\": \"campaign_123\", \"status\": \"paused\", \"group_type\": \"manual\", \"ads\": [...], \"ads_total\": 1}]}" }, { "id": "action:ads/list_ad_labels", "namespace": "ads", "action": "list_ad_labels", "description": "Read Meta ad labels on an account; returns data[] with id and name.", "mode": "read", "platforms": [ "meta" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad_label", "action:ads/apply_label" ], "long_description": "Primary use:\nInventory of Meta ad labels on the account. Use the name (NOT id) when applying labels via action:ads/apply_label.\n\nUse when:\n- The user asks which ad labels exist.\n- Before action:ads/apply_label to confirm the label name is correct.\n\nDo not use when:\n- The platform is Google. Use action:ads/gaql_search on label resources.\n\nWorkflow:\n1. Call this action.\n2. Capture the exact label name (case-sensitive) for apply_label.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: Capability docs say limit 'default 100' which matches the handler but diverges from Meta's native edge default of 25; harmless but could confuse anyone reasoning from raw Graph API behavior. Also note: output_shape advertises a paging object, implying the agent can paginate, but there is no after/cursor input wired — the agent could mistakenly attempt to page and have no parameter to do so. Capability gap: fields selector: the handler hardcodes id,name so AdLabel fields created_time, updated_time, and account are permanently unreachable. Auditing 'newest label' or 'who created' use cases cannot be served.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100." } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"name\": \"...\"}], \"paging\": {...}}" }, { "id": "action:ads/list_ad_rules", "namespace": "ads", "action": "list_ad_rules", "description": "Read Meta or TikTok automated ad rules and their provider-native rule specs.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad_rule", "action:ads/update_ad_rule", "action:ads/remove_ad_rule" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nInventory of automated rules on a Meta ad account, including their evaluation filters and execution actions.\n\nUse when:\n- The user asks which automated rules are active.\n- An audit needs to confirm no rule will fight a planned mutation.\n\nDo not use when:\n- The platform is Google.\n\nWorkflow:\n1. Call this action.\n2. Inspect rules with status=ENABLED before mutating related objects.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph paged response; Meta status values include ENABLED, DISABLED, DELETED, HAS_ISSUES. Follow paging.cursors.after until absent when a complete Meta inventory is required.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "tiktok", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from paging.cursors.after.", "platform": [ "meta" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"name\": \"...\", \"evaluation_spec\": {...}, \"execution_spec\": {...}, \"schedule_spec\": {...}, \"status\": \"ENABLED|DISABLED|DELETED|HAS_ISSUES\"}], \"paging\": {\"cursors\": {\"after\": \"...\"}}}\nTikTok output: {\"success\":true,\"raw\":{\"success\":true,\"data\":[...],\"page_info\":{\"page\":1,\"page_size\":N,\"total_number\":N,\"total_page\":N}}}" }, { "id": "action:ads/list_ads", "namespace": "ads", "action": "list_ads", "description": "Read ads from Google/Meta Ads; returns ads[] or data[] with id, name, status, creative fields. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/get_ad", "action:ads/create_ad", "action:ads/update_ad", "action:ads/remove_ad", "action:ads/query_single_dimension" ], "long_description": "Supported platforms: google, meta, tiktok, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nLive ad inventory under an account, campaign, ad group, or ad set. Use this to discover ad_id values for updates, removes, creative inspection, or lead retrieval.\n\nUse when:\n- You need an ad_id for action:ads/update_ad, remove_ad, get_ad_creatives, or get_leads.\n- The user asks what ads are running.\n\nDo not use when:\n- The user needs cached analytics by ad. Use action:ads/query_single_dimension with dimension=ad.\n- The user needs ad strength or policy approval. Use action:ads/gaql_search.\n\nWorkflow:\n1. Call action:ads/list_ad_accounts if account_id is unknown.\n2. Optionally narrow by campaign_id, ad_group_id (Google), or adset_id (Meta).\n3. Call this action.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Google raw.ads[] uses snake_case; RSAs include headlines[] and descriptions[]; ad_group_ad_resource_name is required for remove_ad.\n- Meta raw.data[] is the Graph response; creative is a nested object with id, name, title, body, image_url, thumbnail_url.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: limit semantics: capability doc says 'Page size; Google default 100' but on Google there is no paging cursor and limit is a one-shot GAQL LIMIT (truncation), not a page size. Calling it 'page size' may mislead the agent into expecting an 'after'-style continuation on Google, which does not exist. Also note: No guidance that list_ads only returns non-REMOVED ads and only fully decodes RSA creatives; an agent auditing 'all ad copy' on a mixed-ad-type ad group could wrongly conclude ads have empty headlines/descriptions. Capability gap: No status_filter parameter: the handler hard-codes status != 'REMOVED' and exposes no way to filter by ENABLED/PAUSED only, or to include REMOVED. Google list_campaigns DOES expose status_filter; list_ads does not. The agent cannot list only paused/enabled ads or audit removed ads without falling back to gaql_search.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta, tiktok, linkedin.", "example": "google", "enum": [ "google", "meta", "tiktok", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": false, "description": "Optional campaign filter." }, { "name": "ad_group_id", "type": "string", "required": false, "description": "Google only: optional ad group filter.", "platform": [ "google", "meta" ] }, { "name": "adset_id", "type": "string", "required": false, "description": "Meta only: optional ad set filter; the endpoint is scoped to the most specific filter." }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; Google default 100, Meta default 25.", "platform": [ "google", "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor for the next page. Pass the value of paging.cursors.after returned by the previous list_ads call to fetch the next page. Without it you only ever see the first page. Source: output of a prior list_ads response (paging.cursors.after).", "example": "QVFIUjh...cursor", "platform": [ "google", "meta" ] }, { "name": "effective_status", "type": "list", "required": false, "description": "Meta only: array of effective statuses to include, e.g. [\"ACTIVE\",\"PAUSED\",\"ARCHIVED\",\"DISAPPROVED\"]. Needed to surface ARCHIVED/PAUSED ads that are hidden from the default listing. Source: constant enum chosen by the agent.", "example": [ "ACTIVE", "PAUSED", "ARCHIVED" ], "platform": [ "google", "meta" ] }, { "name": "filtering", "type": "list", "required": false, "description": "Meta only: general-purpose edge filter, an array of {field, operator, value} objects matching Graph API filtering syntax. Source: constructed by the agent from field/operator/value.", "example": [ { "field": "name", "operator": "CONTAIN", "value": "summer" } ], "platform": [ "google", "meta" ] }, { "name": "fields", "type": "string", "required": false, "description": "Meta only: comma-separated Graph field selector to override the default ad fields (e.g. to also request creative{object_story_spec,asset_feed_spec} or tracking_specs). Source: constant field list chosen by the agent.", "example": "id,name,status,effective_status,creative{id,object_story_spec}", "platform": [ "google", "meta" ] }, { "name": "date_preset", "type": "string", "required": false, "description": "Meta only: relative time window for the listing (e.g. last_7d, last_30d). Source: constant enum.", "example": "last_30d", "platform": [ "google", "meta" ] }, { "name": "time_range", "type": "object", "required": false, "description": "Meta only: explicit {since, until} (YYYY-MM-DD) window; mutually exclusive with date_preset. Source: user-provided date range.", "example": { "since": "2026-06-01", "until": "2026-06-16" }, "platform": [ "google", "meta" ] }, { "name": "updated_since", "type": "integer", "required": false, "description": "Meta only: Unix timestamp; only return ads updated after this time. Source: user-provided timestamp.", "example": 1717200000, "platform": [ "google", "meta" ] }, { "name": "ad_ids", "type": "list", "required": false, "description": "TikTok only: filter to these ad ids.", "example": [ "17840000000000000" ], "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok only: page number (1-based).", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok only: page size (max 100).", "example": 100, "platform": [ "tiktok" ] }, { "name": "adgroup_id", "type": "string", "required": false, "description": "TikTok adgroup id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true, \"ads\": [{\"ad_id\": 123, \"ad_name\": \"...\", \"ad_type\": \"RESPONSIVE_SEARCH_AD\", \"status\": \"ENABLED|PAUSED|REMOVED\", \"final_urls\": [\"...\"], \"headlines\": [\"...\"], \"descriptions\": [\"...\"], \"ad_resource_name\": \"...\", \"ad_group_ad_resource_name\": \"customers/X/adGroupAds/AG~AD\", \"ad_group_id\": 456, \"ad_group_name\": \"...\", \"campaign_id\": 789, \"campaign_name\": \"...\"}]} // Meta: {\"data\": [{\"id\": \"23...\", \"name\": \"...\", \"adset_id\": \"...\", \"campaign_id\": \"...\", \"status\": \"...\", \"effective_status\": \"...\", \"creative\": {\"id\": \"...\", \"title\": \"...\", \"body\": \"...\", \"image_url\": \"...\", \"thumbnail_url\": \"...\"}, \"created_time\": \"ISO\", \"updated_time\": \"ISO\"}], \"paging\": {...}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/list_adsets", "namespace": "ads", "action": "list_adsets", "description": "Read Meta ad sets; returns data[] with id, name, campaign_id, status, daily_budget, targeting. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/get_adset", "action:ads/create_adset", "action:ads/update_adset", "action:ads/remove_adset", "action:ads/query_single_dimension" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nLive ad set inventory under a Meta account or campaign. Use this to discover adset_id values before ad, targeting, or budget writes.\n\nUse when:\n- You need an adset_id for action:ads/create_ad, update_adset, or remove_adset.\n- The user asks what ad sets exist under a Meta campaign.\n\nDo not use when:\n- The platform is Google. Google uses ad groups; use action:ads/gaql_search.\n- The user needs cached analytics. Use action:ads/query_single_dimension with dimension=adgroup.\n\nWorkflow:\n1. Call action:ads/list_ad_accounts if account_id is unknown.\n2. Optionally call action:ads/list_campaigns to pick a campaign_id filter.\n3. Call this action with platform=meta, account_id, optional campaign_id.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API paged response; daily_budget and lifetime_budget are strings in cents; targeting is a nested object.\n- Ad sets have no spend_cap field (spend_cap is campaign/account-level); a missing field is never a confirmed value — read get_campaign or get_account_info to confirm a spend cap.\n- Default limit=25; use 'after' for pagination.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": false, "description": "Optional Meta campaign filter; if set, the endpoint is scoped to the campaign." }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 25.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from a prior response's paging.cursors.after; pass it back to fetch the next page (now actually forwarded by the handler). Source: output of a prior list_adsets call: raw.paging.cursors.after.", "example": "QVFIU...", "platform": [ "meta" ] }, { "name": "effective_status", "type": "list", "required": false, "description": "Meta only: filter ad sets by effective status. Values: ACTIVE, PAUSED, DELETED, CAMPAIGN_PAUSED, ARCHIVED, IN_PROCESS, WITH_ISSUES (per AdSet.EffectiveStatus). Use [\"ARCHIVED\"] to retrieve archived ad sets hidden from the default listing. Source: constant enum list chosen by the agent based on what the user wants to see.", "example": [ "ARCHIVED" ], "platform": [ "meta" ] }, { "name": "adgroup_ids", "type": "list", "required": false, "description": "TikTok only: filter to these ad group ids.", "example": [ "17840000000000000" ], "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok only: page number (1-based).", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok only: page size (max 100).", "example": 100, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"23...\", \"name\": \"...\", \"campaign_id\": \"23...\", \"status\": \"ACTIVE|PAUSED|ARCHIVED|DELETED\", \"daily_budget\": \"5000\", \"lifetime_budget\": \"...\", \"targeting\": {...}, \"bid_amount\": 100, \"bid_strategy\": \"...\", \"optimization_goal\": \"...\", \"billing_event\": \"...\", \"start_time\": \"ISO\", \"end_time\": \"ISO\"}], \"paging\": {\"cursors\": {\"after\": \"...\"}}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/list_bidding_strategies", "namespace": "ads", "action": "list_bidding_strategies", "description": "List reusable Google portfolio Target CPA and Target ROAS strategies.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/update_campaign" ], "long_description": "Primary use:\nList reusable Google portfolio Target CPA and Target ROAS strategies.\n\nUse when:\n- You need a reusable Google Target CPA or Target ROAS portfolio strategy.\n\nDo not use when:\n- You need a campaign-specific standard bidding strategy; use action:ads/update_campaign.\n\nWorkflow:\n1. Read the current strategy or campaign state when changing an existing resource.\n2. For write operations, submit through action:_batch/submit for HITL review.\n3. Verify the resulting state with action:ads/get_bidding_strategy.\n\nOutput semantics:\n- Returns the created, read, updated, listed, or removed Google portfolio strategy result.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id without dashes.", "example": "1234567890" } ], "output_shape": "{\"success\":true,\"raw\":{}}" }, { "id": "action:ads/list_brand_campaigns", "namespace": "ads", "action": "list_brand_campaigns", "description": "List brand-visible campaigns for manually linking a campaign to a conversion group via set_conversion_group_manual_campaign.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/set_conversion_group_manual_campaign", "action:ads/list_conversion_groups" ], "long_description": "Primary use:\nFind candidate campaigns and their internal campaign_id when you need to manually include or exclude a campaign on a conversion group.\n\nOutput semantics:\n- campaigns[].campaign_id is the INTERNAL id required by set/clear_conversion_group_manual_campaign (not the platform id).\n- campaigns[].platform_campaign_id is the platform's campaign id.\n- source/included are always manual/true here; auto and excluded states only appear inside a group's resolved campaigns from list_conversion_groups.\n- Set include_metadata=true to include campaign-level UTM params when the synced campaign metadata has them.", "input_params": [ { "name": "platform", "type": "string", "required": false, "description": "Optional platform filter (google/meta/chatgpt_ads).", "example": "google" }, { "name": "search", "type": "string", "required": false, "description": "Optional name/id substring filter." }, { "name": "limit", "type": "integer", "required": false, "description": "Max rows (default 100, max 500)." }, { "name": "include_metadata", "type": "boolean", "required": false, "description": "When true, include campaign metadata such as utm_params when available.", "example": true } ], "output_shape": "{\"campaigns\": [{\"campaign_id\": \"456\", \"platform_campaign_id\": \"789\", \"name\": \"Holiday Sale\", \"platform\": \"chatgpt_ads\", \"source\": \"manual\", \"included\": true, \"utm_params\": {\"utm_source\": \"chatgpt\", \"utm_campaign\": \"launch\"}}]}" }, { "id": "action:ads/list_campaign_negative_keywords", "namespace": "ads", "action": "list_campaign_negative_keywords", "description": "List negative keywords for a Google campaign; read-only.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_campaign_negative_keywords", "action:ads/remove_campaign_negative_keywords", "action:ads/list_ad_group_negative_keywords" ], "long_description": "Primary use:\nQuery campaign-level negative keywords via GAQL. Returns keyword text, match type, status, and parent campaign info.\n\nUse when:\n- Deduplicating campaign-level negative keyword candidates before adding new ones.\n- Auditing existing campaign negatives for search-term waste workflows.\n- Collecting campaign_criterion resource_name values to feed action:ads/remove_campaign_negative_keywords.\n\nDo not use when:\n- You need ad-group-level negatives. Use action:ads/list_ad_group_negative_keywords.\n- You need account-level shared sets. Use GAQL query on shared_criterion.\n\nWorkflow:\n1. Call with account_id and campaign_id.\n2. Returns all negative keywords for the specified campaign.\n\nOutput semantics:\n- negative_keywords: list of {criterion_id, text, match_type, status, resource_name, campaign_id, campaign_name}.\n- resource_name identifies the campaign_criterion and is the exact value action:ads/remove_campaign_negative_keywords expects.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: account_id description says 'digits only without dashes' but the handler/sanitizer actually accepts the dashed form too (it strips dashes). Harmless over-restriction in the doc; the agent will not pass dashed values and that is fine. Also note: Output_shape and long_description omit that 'status' values come from CampaignCriterionStatus (ENABLED/PAUSED) and that REMOVED is filtered out server-side; an agent auditing 'all' negatives might assume removed ones are included. Minor doc polish. Capability gap: No pagination/limit parameter. The handler returns all matching rows from client.search without a LIMIT or page-size control. Sibling read actions (e.g. list_ads) expose a 'limit' extra; this action does not. For campaigns with very large negative-keyword sets the agent cannot page or cap results. Low impact (negative-keyword sets are usually small) but it is a genuine capability gap vs the GAQL LIMIT clause Google supports.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id to list negatives for." } ], "output_shape": "{\"success\": true, \"negative_keywords\": [{\"criterion_id\": \"...\", \"text\": \"free\", \"match_type\": \"BROAD\", \"status\": \"ENABLED\", \"resource_name\": \"...\", \"campaign_id\": \"...\", \"campaign_name\": \"...\"}]}" }, { "id": "action:ads/list_campaigns", "namespace": "ads", "action": "list_campaigns", "description": "Read campaigns from Google, Meta, TikTok, or ChatGPT Ads; returns platform-native campaign rows.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_campaign", "action:ads/get_campaign", "action:ads/update_campaign", "action:ads/query_single_dimension" ], "long_description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nLive campaign inventory directly from Google Ads or Meta Marketing API. Use this to confirm what exists on the platform before mutating, or to pick a campaign_id for a downstream write.\n\nUse when:\n- You need the current status, budget, or platform-side metadata of campaigns.\n- You are about to call update_campaign, remove_campaign, list_adsets, or set_geo_targeting and need a campaign_id.\n- The user asks what campaigns exist on the platform right now.\n\nDo not use when:\n- The user needs cached business-layer analytics or KPIs. Use action:ads/query_single_dimension instead.\n- The user needs Nex-side audit of agent actions. Use action:ads/list_operation_records instead.\n- The user needs platform-side change history. Use action:ads/get_change_history instead.\n\nWorkflow:\n1. Call action:ads/list_ad_accounts if account_id is unknown.\n2. Call this action with platform and account_id.\n3. Use status_filter (Google or Meta) or objective_filter (Meta) to narrow.\n4. Pick a campaign_id for the next mutation.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Google raw.campaigns[] uses snake_case with budget_amount_micros (micros).\n- Meta raw.data[] is the Graph API body with daily_budget in cents and string IDs; effective_status reflects platform-side delivery state (WITH_ISSUES, IN_REVIEW, etc.) and may differ from status.\n- Google excludes REMOVED by default; pass status_filter to include it.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Docs do not state status_filter REMOVED returns only removed campaigns; no single call for all-including-removed Also note: Docs do not state ENABLED PAUSED REMOVED are the only accepted values; validate_status hard-errors otherwise Capability gap: No agent-facing limit or pagination for Google: returns all non-REMOVED campaigns, no SELECT LIMIT, no page token; relies on SDK implicit 10k-row paging with no cursor surfaced\n- meta: Guidance: Capability doc for status_filter says only 'Meta: effective_status filter' without enumerating allowed values; official effective_status enum is ACTIVE, PAUSED, ARCHIVED, DELETED, IN_PROCESS, WITH_ISSUES. Agent must guess valid strings. Also note: Doc does not state status_filter accepts only ONE value (single-element array), so an agent may try a comma list or array and get unexpected results. Capability gap: before cursor (reverse pagination) is not supported (handler only reads after); agents cannot page backward.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin.", "example": "chatgpt_ads", "enum": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "status_filter", "type": "string", "required": false, "description": "Google: campaign.status filter (ENABLED, PAUSED, REMOVED). Meta: effective_status filter.", "example": "ENABLED", "platform": [ "google", "meta", "chatgpt_ads" ] }, { "name": "objective_filter", "type": "string", "required": false, "description": "Meta only: filter by objective such as OUTCOME_TRAFFIC or OUTCOME_SALES.", "example": "OUTCOME_TRAFFIC", "platform": [ "google", "meta" ] }, { "name": "filtering", "type": "list", "required": false, "description": "Meta only: raw Graph filtering list, e.g. [{field, operator, value}].", "platform": [ "google", "meta" ] }, { "name": "limit", "type": "integer", "required": false, "description": "Meta only: page size; default 25.", "platform": [ "google", "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor returned in paging.cursors.after.", "platform": [ "google", "meta" ] }, { "name": "campaign_ids", "type": "list", "required": false, "description": "TikTok only: filter to these campaign ids.", "example": [ "17840000000000000" ], "platform": [ "tiktok" ] }, { "name": "objective_type", "type": "string", "required": false, "description": "TikTok only: filter by objective.", "example": "TRAFFIC", "platform": [ "tiktok" ] }, { "name": "primary_status", "type": "string", "required": false, "description": "TikTok only: filter by primary status.", "example": "STATUS_ALL", "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok only: page number (1-based).", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok only: page size (max 100).", "example": 100, "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true, \"campaigns\": [{\"campaign_id\": 123, \"name\": \"...\", \"status\": \"ENABLED|PAUSED|REMOVED\", \"channel_type\": \"SEARCH|DISPLAY|...\", \"bidding_strategy_type\": \"...\", \"budget_amount_micros\": 50000000, \"budget_name\": \"...\", \"start_date\": \"YYYY-MM-DD\", \"end_date\": \"YYYY-MM-DD\", \"resource_name\": \"customers/X/campaigns/Y\"}]} // Meta: {\"data\": [{\"id\": \"23...\", \"name\": \"...\", \"objective\": \"...\", \"status\": \"ACTIVE|PAUSED|ARCHIVED\", \"effective_status\": \"...\", \"daily_budget\": \"5000\", \"lifetime_budget\": \"...\", \"bid_strategy\": \"...\", \"buying_type\": \"AUCTION\", \"created_time\": \"ISO\", \"updated_time\": \"ISO\"}], \"paging\": {\"cursors\": {\"after\": \"...\"}}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/list_conversion_actions", "namespace": "ads", "action": "list_conversion_actions", "description": "List synced platform conversion actions that can be mapped into conversion groups for the caller's brand-granted ad accounts.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_groups", "action:ads/create_conversion_action", "action:ads/update_conversion_action", "action:ads/remove_conversion_action" ], "long_description": "Primary use:\nConversion action candidate discovery for maintaining conversion_group rules. Use this action when you need the platform conversion_action_id values that can be bound to a conversion group.\n\nUse when:\n- You are preparing to create or update conversion_group rules.\n- You need to know whether a platform conversion action is already mapped.\n- You need account-scoped conversion action metadata such as platform, customer_id, category, type, or status.\n\nDo not use when:\n- You need business conversion group slugs or derived metric labels. Use list_conversion_groups.\n- You need live platform inventory from ads. This action returns synced Nex data limited to the current brand's granted ad accounts.\n\nOutput semantics:\n- conversion_actions[].account_id is the internal ad account id used by conversion mapping rules.\n- conversion_actions[].conversion_action_id is the platform action id used by rules.\n- conversion_actions[].data_freshness is the latest completed ads sync run finish time for the action's account.\n- mapped_to lists conversion_group slugs that already include the action.", "input_params": [ { "name": "platform", "type": "string", "required": false, "description": "Optional platform filter. Use google, meta, or chatgpt_ads; omit to list all synced actions.", "example": "google" } ], "output_shape": "{\"conversion_actions\": [{\"id\": 11, \"account_id\": 5, \"customer_id\": \"1234567890\", \"platform\": \"google\", \"conversion_action_id\": \"act_purchase\", \"conversion_action_name\": \"Purchase\", \"category\": \"PURCHASE\", \"action_type\": \"WEBPAGE\", \"status\": \"ENABLED\", \"customer_name\": \"Account\", \"is_mapped\": true, \"data_freshness\": \"ISO\", \"mapped_to\": [\"payment-success\"]}]}" }, { "id": "action:ads/list_conversion_goals", "namespace": "ads", "action": "list_conversion_goals", "description": "List platform conversion goals visible to the brand. A goal is the bridge that links a conversion group to the campaigns optimizing for it.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_groups", "action:ads/bind_conversion_group_goal" ], "long_description": "Primary use:\nDiscover platform conversion goals (Google/Meta) that can be linked to a conversion group via bind_conversion_group_goal. ChatGPT Ads has conversion actions but no conversion-goal catalog, so it returns an empty list here. A goal carries the campaign_ids that optimize for it, so linking a goal is how a group inherits those campaigns.\n\nOutput semantics:\n- conversion_goals[].id is the internal goal id used by bind/unbind_conversion_group_goal.\n- conversion_goals[].conversion_action_ids are platform action ids the goal covers.\n- conversion_goals[].campaign_ids are platform campaign ids optimizing for the goal. Known limitation: Google does not expose a campaign->custom goal edge so this may be empty for Google goals; Meta only resolves campaigns bound via custom_conversion_id.", "input_params": [ { "name": "platform", "type": "string", "required": false, "description": "Optional platform filter. Use google, meta, or chatgpt_ads. ChatGPT Ads currently has no conversion-goal catalog and returns an empty list.", "example": "google" } ], "output_shape": "{\"conversion_goals\": [{\"id\": 12, \"platform\": \"google\", \"account_id\": 5, \"platform_goal_id\": \"abc\", \"name\": \"Purchase\", \"type\": \"custom_conversion_goal\", \"status\": \"ENABLED\", \"conversion_action_ids\": [\"act_purchase\"], \"campaign_ids\": [\"123\"], \"last_synced_at\": \"ISO\"}]}" }, { "id": "action:ads/list_conversion_groups", "namespace": "ads", "action": "list_conversion_groups", "description": "Discover conversion group slugs and derived CPA/ROAS labels needed for cg.{slug}.* columns in query_single_dimension. Scoped to the caller's brand.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/resolve_terms", "action:ads/list_dimensions", "action:ads/query_single_dimension", "action:ads/list_conversion_actions" ], "long_description": "Primary use:\nConversion Mapping Group discovery for business metrics such as CPA, CPP, ROAS, Reg Rate, and Pur Rate. Use the returned group slugs to build legal cg.{slug}.* column references for query_single_dimension.\n\nUse when:\n- The user asks for CPA, CPP, ROAS, registration rate, purchase rate, or other mapped conversion metrics.\n- You plan to request or interpret cg.{slug}.* fan-out columns.\n- You need to know the default attribution_window, time_field, count_field, value_field, or display labels for conversion-derived metrics.\n\nDo not use when:\n- You only need platform raw metrics such as clicks, impressions, spend_micros, ctr, or conversions.\n- You are using query_multi_dimension. Multi-dimensional queries do not support conversion-group fan-out or mapping-aware derived metrics.\n\nWorkflow:\n1. Call list_dimensions to confirm the dimension supports cg fan-out.\n2. Call this action to get legal group slugs, labels, metadata, and version tokens.\n3. Call query_single_dimension with cg.{slug}.* columns implied by the selected metrics.\n\nOutput semantics:\n- groups[].slug is the only legal slug in cg.{slug}.* column references.\n- groups[].brand_id echoes the caller's brand; same slug can mean a different configuration in another brand of the same org.\n- groups[].version is an opaque precondition token for conversion_group write actions.\n- derived_metrics explains the display label and denominator/value semantics.\n- metadata is a customer-defined list of key/value/description fields for agent interpretation.\n- attribution_window is the group default unless query_single_dimension overrides it.\n- Admin-Web fields such as color, sort_order, owner_admin_user_id, and level are intentionally not exposed.", "input_params": [], "output_shape": "{\"groups\": [{\"id\": 1, \"slug\": \"qualified-new-user\", \"name\": \"Qualified New User\", \"brand_id\": \"...\", \"version\": \"opaque\", \"time_field\": \"click_time\", \"count_field\": \"all_conversions\", \"value_field\": \"conversions_value\", \"attribution_window\": \"7d_click\", \"derived_metrics\": {\"cost_per_label\": \"CPA\", \"rate_label\": \"Reg Rate\", \"rate_denominator\": \"clicks\", \"value_label\": null, \"roas_label\": null}, \"metadata\": [{\"key\": \"business_stage\", \"value\": \"activation\", \"description\": \"How the agent should interpret this field.\"}]}]}" }, { "id": "action:ads/list_conversions", "namespace": "ads", "action": "list_conversions", "description": "Deprecated alias for list_conversion_groups. Returns conversion group business fields under rows for short-term compatibility.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_groups", "action:ads/list_conversion_actions", "action:ads/query_single_dimension" ], "long_description": "Deprecated compatibility alias.\n\nUse list_conversion_groups for new workflows. This action exists only so older agents that learned the legacy list_conversions name can still discover conversion group slugs and derived metric labels.\n\nOutput semantics:\n- rows[] contains conversion_group specs with the same field set as list_conversion_groups.groups[].\n- This action does not list platform conversion actions. Use list_conversion_actions for that.", "input_params": [], "output_shape": "{\"rows\": [{\"id\": 1, \"slug\": \"qualified-new-user\", \"name\": \"Qualified New User\", \"brand_id\": \"...\", \"version\": \"opaque\", \"derived_metrics\": {\"cost_per_label\": \"CPA\", \"rate_label\": \"Reg Rate\", \"rate_denominator\": \"clicks\", \"value_label\": null, \"roas_label\": null}, \"metadata\": []}]}" }, { "id": "action:ads/list_countries", "namespace": "ads", "action": "list_countries", "description": "Read Google geo target constants for countries; returns countries[] with location_id, name, country_code.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/set_geo_targeting", "action:ads/list_languages", "action:ads/search_geo_locations" ], "long_description": "Primary use:\nLookup table for Google geo target constants of type 'Country'. Use this to get location_id values for action:ads/set_geo_targeting.\n\nUse when:\n- You need a country location_id before set_geo_targeting.\n- The user asks which countries Google supports as targeting.\n\nDo not use when:\n- The user needs sub-country regions or cities. Use action:ads/gaql_search on geo_target_constant with target_type='Region' / 'City'.\n- The platform is Meta. Use action:ads/search_geo_locations.\n\nWorkflow:\n1. Call this action to discover location_id and country_code.\n2. Pass the chosen IDs to action:ads/set_geo_targeting.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.countries[].status may be ENABLED or REMOVAL_PLANNED.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Capability docs mark account_id required=True, which matches runtime, but the docs do not explain WHY a customer_id is needed for a global constant lookup (any owned Google account works; the choice of account_id does not change the country list). An agent may wrongly assume it must pick a 'relevant' account. Also note: output_shape example shows status:'ENABLED' but long_description says status may be ENABLED or REMOVAL_PLANNED — yet the handler filters out REMOVAL_PLANNED in the WHERE clause, so REMOVAL_PLANNED countries are NEVER returned. The doc 'raw.countries[].status may be ENABLED or REMOVAL_PLANNED' is misleading; in practice only ENABLED rows appear. Capability gap: No way to query sub-country geo levels (Region/City/Postal Code/Metro) through this action — target_type is hardcoded to 'Country'. The agent must fall back to action:ads/gaql_search for other target_type values (the long_description already directs this), so this is a deliberate scope limit, not a defect.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap; default 250, max 1000." } ], "output_shape": "{\"success\": true, \"countries\": [{\"location_id\": \"2840\", \"name\": \"United States\", \"country_code\": \"US\", \"target_type\": \"Country\", \"status\": \"ENABLED\", \"resource_name\": \"geoTargetConstants/2840\"}]}" }, { "id": "action:ads/list_custom_audiences", "namespace": "ads", "action": "list_custom_audiences", "description": "Read Meta custom audiences; returns data[] with id, name, subtype, delivery_status. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_custom_audience", "action:ads/create_lookalike_audience", "action:ads/update_custom_audience", "action:ads/remove_custom_audience", "action:ads/list_saved_audiences" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nInventory of custom audiences on a Meta account. Use this to discover audience_id values for ad set targeting or for lookalike source selection.\n\nUse when:\n- The user asks which custom audiences exist.\n- A workflow needs source_audience_id for action:ads/create_lookalike_audience.\n\nDo not use when:\n- The user wants saved (targeting-spec) audiences. Use action:ads/list_saved_audiences.\n- The platform is Google.\n\nWorkflow:\n1. Call this action with optional limit / after.\n2. Pick an audience_id for downstream targeting or lookalike use.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph paged response; delivery_status indicates populating / ready status.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: delivery_status semantics: long_description says it 'indicates populating / ready status' but does not enumerate the structure ({code, description}) or the common codes (e.g. 200 ready, 300/414 populating/too-small). The output_shape shows code 200 but the agent has no code-to-meaning map, so it must guess when judging readiness. Also note: Pagination loop guidance is implicit: docs say 'optional limit / after' but do not tell the agent how to continue paging (read raw.paging.cursors.after and re-call), so multi-page inventories may be silently truncated at the first 100. Capability gap: business_id read filter (official optional) — not exposed; cannot scope listing to a specific Business Manager. Low impact.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Pagination cursor.", "platform": [ "meta" ] }, { "name": "custom_audience_ids", "type": "list", "required": false, "description": "TikTok custom audience ids.", "example": [], "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"name\": \"...\", \"subtype\": \"CUSTOM|WEBSITE|...\", \"description\": \"...\", \"delivery_status\": {\"code\": 200, \"description\": \"...\"}}], \"paging\": {...}}\nTikTok output: {\"success\":true,\"raw\":{\"success\":true,\"data\":[...],\"page_info\":{\"page\":1,\"page_size\":N,\"total_number\":N,\"total_page\":N}}}" }, { "id": "action:ads/list_customers", "namespace": "ads", "action": "list_customers", "description": "Read Google Ads accessible customers; returns customers[] with customer_id and resource_name.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_accounts", "action:ads/get_tree_structure" ], "long_description": "Primary use:\nList Google Ads customers accessible to the current OAuth identity via the CustomerService.list_accessible_customers RPC.\n\nUse when:\n- The user wants to discover which Google customer_id values are reachable.\n- You need to validate the agent has access before any later mutate.\n\nDo not use when:\n- The user wants the brand's granted accounts via Nex. Use action:ads/list_ad_accounts.\n- The user wants the MCC manager tree. Use action:ads/get_tree_structure.\n- The platform is Meta. Use action:ads/get_account_info or list_ad_accounts.\n\nWorkflow:\n1. Call this action with the seed customer_id (the OAuth root).\n2. Optionally pass account_ids to narrow the response.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.customers[].customer_id is dash-stripped; resource_name is 'customers/X'.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Capability docs mark account_id required=True and describe it as the OAuth-root seed, implying it scopes/affects the result set. Officially the result set is fixed by the OAuth identity and the request ignores any supplied login-customer-id. The agent may wrongly believe changing account_id changes which customers are returned. account_id is required by Nex only as a credential-selection key, not by Google. Also note: long_description Workflow step 2 ('Optionally pass account_ids to narrow the response') does not make clear this is a CLIENT-SIDE filter applied after fetching all accessible customers, nor that account_ids items must be customer ids that already appear in the accessible set (ids not accessible are silently filtered out, yielding an empty list with no error). Capability gap: None at the Google API level: ListAccessibleCustomers has no other parameters (no pagination, no page_size, no filter) to expose. The action surfaces the full official contract (empty request -> resource_names[]).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Seed account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "account_ids", "type": "list", "required": false, "description": "Optional list (or JSON string) of customer IDs to narrow the response." } ], "output_shape": "{\"success\": true, \"customers\": [{\"customer_id\": \"1234567890\", \"resource_name\": \"customers/1234567890\"}]}" }, { "id": "action:ads/list_dimensions", "namespace": "ads", "action": "list_dimensions", "description": "Discover cached ads dimensions, metrics, filters, and scope caveats before building query_single_dimension or query_multi_dimension calls.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/resolve_terms", "action:ads/query_single_dimension", "action:ads/query_multi_dimension", "action:ads/list_conversion_groups", "action:ads/gaql_search" ], "long_description": "Primary use:\nSchema discovery for cached ads analytics. This is the primary way to learn which business-layer dimensions, metrics, filters, scopes, and Parquet-backed views can be queried.\n\nUse when:\n- Before the first query_single_dimension or query_multi_dimension call in a workflow.\n- You need valid dimension slugs for a granted ads platform. ChatGPT Ads currently supports account, date, campaign, ad, landing_page, and the system-managed UTM dimensions (utm_source, utm_medium, utm_campaign, utm_content) only.\n- UTM dimensions are derived from campaign metadata (ChatGPT Ads utm_params, Google final_url_suffix, Meta creative url_tags) and are available on google, meta, and chatgpt_ads. Campaigns without a value fall into the '(not set)' bucket, so UTM totals match the campaign scope. Value filters and campaign filters are not supported on UTM scope yet.\n- You need filterable_fields, parent_dimensions, scope_caveat, or cg fan-out support.\n- You need to know which raw and derived metrics are available.\n\nDo not use when:\n- You need Google Ads native fields not listed here. Use get_resource_metadata then gaql_search.\n- You need conversion group slugs for cg.{slug}.* columns. Use list_conversion_groups.\n\nWorkflow:\n1. Call list_ad_accounts if account_id is unknown.\n2. Call this action with platform when possible. If platform is omitted and multiple ads platforms are granted, read by_platform[platform].\n3. Use query_single_dimension for one dimension or mapping-aware metrics.\n4. Use query_multi_dimension for valid multi-dimensional raw breakdowns.\n\nOutput semantics:\n- dimensions[].name is the dimension slug to pass as dimension or dimensions[].\n- dimensions[].filterable_fields are the only supported filter keys for that dimension.\n- dimensions[].supports_cg_fanout tells whether cg.{slug}.* columns can be requested.\n- metrics[].slug is the metric name to read from query output columns.\n- When multiple ads platforms are granted and platform is omitted, the response uses by_platform instead of a misleading cross-platform dimension list.", "input_params": [ { "name": "platform", "type": "string", "required": false, "description": "Optional platform filter. Use google, meta, tiktok, or chatgpt_ads. If omitted, a single granted ads platform auto-filters the schema; multiple granted ads platforms return by_platform.", "example": "google" } ], "output_shape": "{\"platform\": \"chatgpt_ads\", \"dimensions\": [{\"name\": \"campaign\", \"scope\": \"campaign\", \"source_view\": \"campaign_daily\", \"parquet_table\": \"campaign_daily\", \"filterable_fields\": [...], \"supports_cg_fanout\": true, \"caveat\": \"...\"}], \"metrics\": [{\"slug\": \"ctr\", \"unit\": \"ratio\", \"formula\": \"...\", \"depends_on\": [...], \"platform_support\": [...]}]} or {\"platforms\": [\"google\", \"chatgpt_ads\"], \"by_platform\": {\"google\": {...}, \"chatgpt_ads\": {...}}}" }, { "id": "action:ads/list_languages", "namespace": "ads", "action": "list_languages", "description": "Read Google language constants; returns languages[] with language_id, name, ISO code.", "mode": "read", "platforms": [ "google" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/set_language_targeting", "action:ads/list_countries" ], "long_description": "Primary use:\nLookup table for Google Ads language constants. Use this to get language_id values for action:ads/set_language_targeting.\n\nUse when:\n- You need a language_id before set_language_targeting.\n- The user asks which languages Google supports for targeting.\n\nDo not use when:\n- The platform is Meta. Meta uses locales inside the ad set targeting spec.\n\nWorkflow:\n1. Call this action.\n2. Pass the chosen IDs to action:ads/set_language_targeting.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.languages[].code is ISO 639-1.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Capability docs say raw.languages[].code is ISO 639-1, but the official language_constant.code values are frequently locale-tagged (e.g. \"en_US\", \"en_AU\", \"zh_CN\", \"pt_BR\") rather than pure ISO 639-1 two-letter codes. An agent matching on a bare ISO 639-1 code (e.g. \"en\") may fail to find the intended row or pick the wrong locale variant. The doc's ISO 639-1 claim is inaccurate/misleading. Also note: The agent-facing param is account_id but the handler/native field is customer_id; this rename is invisible to the agent. Not a functional gap (normalize handles it) but the description never states that this id becomes the Google customer_id, which can confuse cross-action reasoning. Capability gap: language_constant.targetable is not selected by the handler nor exposed in output_shape/docs. Agents cannot filter out non-targetable languages before calling set_language_targeting; selecting a non-targetable language_id could cause a downstream set_language_targeting failure. Low impact (most common languages are targetable) but it is a genuinely missing dimension.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap; default 250, max 1000." } ], "output_shape": "{\"success\": true, \"languages\": [{\"language_id\": \"1000\", \"name\": \"English\", \"code\": \"en\", \"resource_name\": \"languageConstants/1000\"}]}" }, { "id": "action:ads/list_lead_forms", "namespace": "ads", "action": "list_lead_forms", "description": "Read Meta lead forms on a Page; returns data[] with id, name, status, questions. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_lead_form", "action:ads/archive_lead_form", "action:ads/get_leads", "action:ads/get_account_pages" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nInventory of leadgen forms attached to a Facebook Page.\n\nUse when:\n- You need a lead_form_id before action:ads/get_leads.\n- The user asks which lead forms exist on a page.\n- Pairing a new lead ad with an EXISTING form: enumerate here (page with 'after' until exhausted) and match on content — status=ACTIVE plus the intended questions/privacy_policy/follow_up_action_url, not name alone — instead of guessing an id or creating a duplicate form. (Exception: the deploy-ads copy mode intentionally RECREATES each source form per its own procedure; do not substitute this pairing flow there.)\n\nDo not use when:\n- The user wants leads themselves. Use action:ads/get_leads.\n- The platform is Google.\n\nWorkflow:\n1. Get a page_id via action:ads/get_account_pages.\n2. Call this action.\n3. Pick the form: prefer status=ACTIVE and an exact/near name match from the user's brief; when ambiguous, compare questions and created_time, or ask the user rather than guessing.\n4. To confirm which form an EXISTING ad uses, read the ad's creative via action:ads/get_ad_creatives — the form id lives at object_story_spec.link_data|video_data.call_to_action.value.lead_gen_form_id (action:ads/get_leads also accepts an ad id directly and resolves the form).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph paged response; status values include ACTIVE, ARCHIVED.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: Capability docs do not state the returned field set is fixed/hardcoded; an agent that needs leads_count or category cannot get them and the docs give no hint that fields is non-controllable. Also note: No mention of the leads_retrieval / pages_manage_ads permission prerequisite. If a Page lacks the right token scope or admin role, the call fails with a Graph permission error; the docs only point the agent to get_account_pages for page_id and give no troubleshooting guidance for permission failures. Capability gap: before cursor (backward pagination) — absent from all three layers; only forward paging via after is possible.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "page_id", "type": "string", "required": true, "description": "Facebook Page id from action:ads/get_account_pages.", "platform": [ "meta" ] }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Pagination cursor.", "platform": [ "meta" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"lead_form_id\", \"name\": \"...\", \"status\": \"ACTIVE|ARCHIVED\", \"questions\": [...], \"privacy_policy_url\": \"...\", \"created_time\": \"ISO\"}], \"paging\": {...}}\nTikTok output: {\"success\":true,\"raw\":{\"success\":true,\"data\":[...],\"page_info\":{\"page\":1,\"page_size\":N,\"total_number\":N,\"total_page\":N}}}" }, { "id": "action:ads/list_operation_records", "namespace": "ads", "action": "list_operation_records", "description": "List Nex-side ads action audit records for an account and date range, including before/after payloads and operator metadata.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_accounts", "action:ads/get_change_history" ], "long_description": "Primary use:\nAudit Nex-side ads actions that were recorded for a specific ad account and date range. This helps explain what Nex or an agent attempted to change.\n\nUse when:\n- The user asks what actions Nex or an agent performed on an ad account.\n- You need before/after payloads for Nex-recorded ads writes.\n- You need operator_user_id, operator_display_name, action_type, target entity, and timestamp for internal troubleshooting.\n\nDo not use when:\n- The user asks for platform-side Google/Meta change history. Use action:ads/get_change_history.\n- The user asks for analytics metrics. Use query_single_dimension or query_multi_dimension.\n\nWorkflow:\n1. Call list_ad_accounts if account_id is unknown.\n2. Call this action with platform, account_id, date_start, and date_end.\n3. Use page and page_size for pagination when the result is large.\n\nOutput semantics:\n- rows[] contains Nex internal action records, not platform-native history.\n- before and after are the recorded operation payload snapshots when available.\n- Operator email is never returned to avoid PII leakage; use operator_user_id and operator_display_name only.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Ad platform for the selected account. Use google, meta, tiktok, or chatgpt_ads." }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from list_ad_accounts.rows[].account_id. For Google, use digits only with no dashes." }, { "name": "date_start", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format." }, { "name": "date_end", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format." }, { "name": "page", "type": "integer", "required": false, "description": "1-indexed page number. Defaults to 1." }, { "name": "page_size", "type": "integer", "required": false, "description": "Rows per page. Omit to use the service default." } ], "output_shape": "{\"account\": {\"platform\": \"google\", \"account_id\": \"123\", \"data_freshness\": \"ISO\"}, \"rows\": [{\"operator_user_id\": \"...\", \"operator_display_name\": \"...\", \"action_type\": \"create_campaign\", \"target_entity_type\": \"campaign\", \"target_entity_id\": \"...\", \"before\": null, \"after\": {...}, \"detail\": null, \"timestamp\": \"ISO\"}]}" }, { "id": "action:ads/list_pixels", "namespace": "ads", "action": "list_pixels", "description": "Read Meta or TikTok Pixels on an ad account; returns data[] with id, name, code, last_fired_time.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/create_pixel", "action:ads/update_pixel", "action:ads/send_conversion_event" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nList the Meta or TikTok pixels on the resolved ad account.\n\nUse when:\nDiscovering an existing pixel id before create_pixel / update_pixel or send_conversion_event.\n\nDo not use when:\nYou already hold the pixel id.\n\nWorkflow:\n1. Resolve the advertiser account (meta or tiktok).\n2. Call this read directly (no review needed).\n3. Pick a pixel id for the next step.\n\nOutput semantics:\nReturns provider-native pixel records; no cross-provider field coercion.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"pixel_id\", \"name\": \"...\", \"code\": \"\", \"last_fired_time\": \"ISO\"}], \"paging\": {...}}\nTikTok output: {\"success\":true,\"raw\":{\"success\":true,\"data\":[...],\"page_info\":{\"page\":1,\"page_size\":N,\"total_number\":N,\"total_page\":N}}}" }, { "id": "action:ads/list_product_catalogs", "namespace": "ads", "action": "list_product_catalogs", "description": "List Meta product catalogs owned by the account business. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/get_product_catalog_details" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nDiscover live product catalogs available for Meta commerce and product ads workflows.\n\nUse when:\n- The user asks what Meta product catalogs are available now.\n- A workflow needs catalog_id before reading feeds, products, product sets, or diagnostics.\n\nDo not use when:\n- The user needs cached business-layer analytics or KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs to mutate catalog resources. Use a create_* catalog action through review-gated batch submission.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Call action:ads/list_ad_accounts if account_id is unknown.\n2. Call this action with platform=meta and account_id.\n3. Use returned catalog ids for downstream catalog actions.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph fields without L1.5 normalization.\n- For catalog object ids, the backend verifies ownership against the resolved Meta ad account business before returning data.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from the previous response's paging.cursors.after; pass it back to fetch the next page.", "source": "upstream:action:ads/list_product_catalogs", "platform": [ "meta" ] }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"name\": \"...\", \"business\": {\"id\": \"...\"}}], \"paging\": {...}}\nTikTok output: {\"success\":true,\"raw\":{\"success\":true,\"data\":[...],\"page_info\":{\"page\":1,\"page_size\":N,\"total_number\":N,\"total_page\":N}}}" }, { "id": "action:ads/list_product_set_products", "namespace": "ads", "action": "list_product_set_products", "description": "List products included in a Meta product set. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/list_product_sets", "action:ads/list_products" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nInspect which catalog products are included in a specific product set.\n\nUse when:\n- The user has product_set_id and needs the items in that set.\n- A catalog segmentation issue requires checking product set membership.\n\nDo not use when:\n- The user needs cached business-layer analytics or KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs to mutate catalog resources. Use a create_* catalog action through review-gated batch submission.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Get catalog_id from action:ads/list_product_catalogs.\n2. Get product_set_id from action:ads/list_product_sets.\n3. Call this action with catalog_id and product_set_id.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph fields without L1.5 normalization.\n- For catalog object ids, the backend verifies ownership against the resolved Meta ad account business before returning data.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: Capability doc does not state that limit is the ONLY way to control page size and that there is no pagination/'after' support, so the agent may assume it can fetch all products of a large set and silently get a truncated list. Also note: Doc does not warn that fields are fixed; an agent asking 'what is the sale price / custom_label of products in this set' will not get those fields and has no parameter to request them. Capability gap: filter: official optional JSON filter on the /products edge is not exposed; agent cannot narrow returned products (e.g. by availability or custom_label).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id. Required when platform=tiktok." }, { "name": "product_set_id", "type": "string", "required": true, "description": "Meta product set id. Required when platform=tiktok." }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from the previous response's paging.cursors.after; pass it back to fetch the next page.", "source": "upstream:action:ads/list_product_set_products", "platform": [ "meta" ] }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"retailer_id\": \"...\", \"name\": \"...\"}], \"paging\": {...}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/list_product_sets", "namespace": "ads", "action": "list_product_sets", "description": "List product sets configured in an owned Meta product catalog. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/list_product_set_products" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nDiscover catalog product sets used for product ads targeting or segmentation.\n\nUse when:\n- The user needs product_set_id for product set inspection.\n- A product ads workflow needs to understand catalog segmentation.\n\nDo not use when:\n- The user needs cached business-layer analytics or KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs to mutate catalog resources. Use a create_* catalog action through review-gated batch submission.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Get catalog_id from action:ads/list_product_catalogs.\n2. Call this action with catalog_id.\n3. Use returned product_set_id values for downstream product set reads.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph fields without L1.5 normalization.\n- For catalog object ids, the backend verifies ownership against the resolved Meta ad account business before returning data.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: Capability docs mark account_id as required, but the executor auto-resolves it from the credential account when omitted. The agent is told it must supply account_id even though catalog_id alone is sufficient. Not harmful (supplying it is fine and normalization handles act_ prefixing), but the 'required' label is stricter than runtime behavior. Also note: limit default is documented as 100 in capability docs, which matches the handler but differs from Meta's native default of 25. Agents reasoning about result completeness should know the effective default is 100 here. Capability gap: Pagination: 'after' cursor is not accepted by the handler or documented, so the agent can only ever read the first page (default 100). For catalogs with >100 product sets the rest are unreachable through this action — a partial-coverage gap rather than a hard break.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id. Required when platform=tiktok." }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from the previous response's paging.cursors.after; pass it back to fetch the next page.", "source": "upstream:action:ads/list_product_sets", "platform": [ "meta" ] }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "product_set_id", "type": "string", "required": false, "description": "TikTok product set id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "return_product_count", "type": "boolean", "required": false, "description": "TikTok return product count.", "example": true, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"name\": \"...\", \"filter\": {...}, \"product_count\": 123}], \"paging\": {...}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/list_products", "namespace": "ads", "action": "list_products", "description": "List products in an owned Meta catalog, or inspect one product by product_id. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/get_catalog_diagnostics" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nRead live product/item records from a Meta product catalog.\n\nUse when:\n- The user needs current item status, retailer ids, names, availability, or prices.\n- A catalog diagnostic points to specific products that need inspection.\n\nDo not use when:\n- The user needs cached business-layer analytics or KPI reporting. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The user needs to mutate catalog resources. Use a create_* catalog action through review-gated batch submission.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Get catalog_id from action:ads/list_product_catalogs.\n2. Call this action with catalog_id and optional product_id.\n3. Use returned retailer/product ids for issue triage or feed QA.\n\nOutput semantics:\n- Top-level envelope includes success, platform, action, raw, and freshness=realtime.\n- raw is the native Meta Graph API response and preserves Graph fields without L1.5 normalization.\n- For catalog object ids, the backend verifies ownership against the resolved Meta ad account business before returning data.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: The product_id path changes the response shape from a paged list ({data:[],paging:{}}) to a single object ({id,}), but the doc's output_shape only shows the list shape. An agent passing product_id will get a differently-shaped payload than advertised. Also note: The capability doc does not state that field selection, filtering, and pagination are unavailable. The output_shape advertises a 'paging' object, implying the agent can paginate, but there is no 'after' input param — an agent will believe it can page and cannot. Capability gap: fields selection: the field set is hardcoded (_PRODUCT_FIELDS). The agent cannot retrieve common catalog QA fields Meta exposes on Product Item such as sale_price, sale_price_start_date/end_date, inventory, gtin, mpn, color, size, custom_label_0..4, additional_image_urls, applinks, errors/review_status, or product_group — these are unreachable without code change. For a 'why is this item rejected' workflow, the errors/review_status field is the most consequential omission.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id. Required when platform=tiktok." }, { "name": "product_id", "type": "string", "required": false, "description": "Optional Meta product item id for single-item details." }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Meta only: pagination cursor from the previous response's paging.cursors.after; pass it back to fetch the next page.", "source": "upstream:action:ads/list_products", "platform": [ "meta" ] }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"retailer_id\": \"...\", \"name\": \"...\", \"availability\": \"...\"}], \"paging\": {...}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/list_saved_audiences", "namespace": "ads", "action": "list_saved_audiences", "description": "Read Meta saved audiences (saved targeting specs); returns data[] with id, name, targeting. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_custom_audiences", "action:ads/create_adset" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nInventory of saved Meta audiences (reusable targeting specs).\n\nUse when:\n- The user asks which saved targeting templates exist.\n- A workflow needs to reuse a saved targeting spec for action:ads/create_adset.\n\nDo not use when:\n- The user wants custom audiences (pixel / file based). Use action:ads/list_custom_audiences.\n- The platform is Google.\n\nWorkflow:\n1. Call this action.\n2. Reuse targeting in action:ads/create_adset.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph paged response with id, name, targeting, description.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: long_description and the output_shape advertise a 'targeting' field but do not warn that the returned spec may need light reshaping (e.g. SavedAudience targeting can include flexible_spec / geo_locations objects) before it is valid as the create_adset 'targeting' payload — the doc says 'Reuse targeting in action:ads/create_adset' as if it is drop-in. Also note: No mention that run_status is NOT returned, so 'inventory of saved audiences' may silently include deleted ones depending on account state; the agent cannot tell which are usable. Capability gap: No way to request run_status (active vs deleted) — the handler hard-codes fields without it, so the agent cannot distinguish/filter live vs deleted saved audiences. There is no server-side status filter on this edge, but exposing fields would at least surface run_status for client-side filtering.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "limit", "type": "integer", "required": false, "description": "Page size; default 100.", "platform": [ "meta" ] }, { "name": "after", "type": "string", "required": false, "description": "Pagination cursor.", "platform": [ "meta" ] }, { "name": "page", "type": "integer", "required": false, "description": "TikTok page.", "example": 1, "platform": [ "tiktok" ] }, { "name": "page_size", "type": "integer", "required": false, "description": "TikTok page size.", "example": 1, "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"...\", \"name\": \"...\", \"targeting\": {...}, \"description\": \"...\"}], \"paging\": {...}}\nTikTok output: {\"success\":true,\"raw\":{\"success\":true,\"data\":[...],\"page_info\":{\"page\":1,\"page_size\":N,\"total_number\":N,\"total_page\":N}}}" }, { "id": "action:ads/migrate_legacy_ad_units", "namespace": "ads", "action": "migrate_legacy_ad_units", "description": "Migrate explicit legacy Ad Unit records into paused Ad Groups and authored Ads; review-gated risk action.", "mode": "risk", "platforms": [ "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_groups", "action:ads/get_ad_group", "action:_batch/submit" ], "long_description": "Primary use:\nPerform an explicit, idempotent HITL migration from caller-supplied legacy Ad Unit records into paused manual Ad Groups and Ads without changing legacy resources.\n\nUse when:\n- The user approved migration for a known Campaign and supplied complete legacy records.\n\nDo not use when:\n- The caller expects automatic legacy discovery, deletion, archival, or activation.\n- The caller wants to repair existing Ad names; the separate guarded runner is plan-only by default, pauses and reads back active targets, orders by canonical ad_id, and never activates or submits review.\n- Any prior result is ambiguous and has not been reconciled.\n\nWorkflow:\n1. Supply stable ad_unit_id identities and approved content for every record.\n2. Submit one migration through HITL review.\n3. Inspect records[] and require materialization_verified=true for full success.\n4. Reconcile existing/created/ambiguous/failed records before retry.\n\nOutput semantics:\n- Success returns ordered records[], ad_group_ids, ad_ids, materialization_verified=true, and legacy_resources_modified=false.\n- A blocked, partial_failure, or unknown outcome returns per-record status and reconcile_required=true; known ids are retained.\n\nSide effects:\n- May create paused Ad Groups and authored Ads.\n- Never modifies, archives, or deletes the supplied legacy resources.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant", "platform": [ "chatgpt_ads" ] }, { "name": "account_id", "type": "string", "required": true, "description": "Advertiser account id.", "example": "account_123", "platform": [ "chatgpt_ads" ] }, { "name": "campaign_id", "type": "string", "required": true, "description": "Existing Campaign id that will own the migrated groups.", "example": "campaign_123", "platform": [ "chatgpt_ads" ] }, { "name": "legacy_ad_units", "type": "list", "required": true, "description": "Non-empty explicit records. Every record requires stable ad_unit_id, headline, static_ad_text, static_cta, landing_page, and brand_name; optional fields include description, ad_name, image_url, and utm_params. When ad_name is omitted, migration derives 'Legacy migration Ad'; explicit null or blank is rejected.", "example": [ { "ad_unit_id": "legacy_123", "ad_name": "Legacy migration legacy_123 Ad", "headline": "Approved headline", "static_ad_text": "Approved body copy.", "static_cta": "Learn more", "landing_page": "https://example.com", "brand_name": "Example", "utm_params": { "utm_source": "nexad_c" } } ], "platform": [ "chatgpt_ads" ] } ], "output_shape": "{\"success\": true|false, \"campaign_id\": \"campaign_123\", \"records\": [{\"index\": 0, \"ad_unit_id\": \"legacy_123\", \"status\": \"existing|created|ambiguous|failed\", \"ad_group_id\": \"ag_123\", \"ad_ids\": [\"ad_123\"]}], \"materialization_verified\": true, \"legacy_resources_modified\": false}" }, { "id": "action:ads/preview_automation_baseline", "namespace": "ads", "action": "preview_automation_baseline", "description": "Read-only Google, Meta, TikTok, or ChatGPT Ads automation observation and identity-bound approval digest.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ads/establish_automation_baseline" ], "long_description": "Primary use:\nRead a provider entity's current automation state and prepare an identity-bound digest for human baseline approval.\n\nUse when:\n- A pre-existing Google, Meta, TikTok, or ChatGPT Ads entity has no authoritative Soku-written baseline.\n\nDo not use when:\n- The entity already has an authoritative Soku-write baseline that should be preserved.\n- The caller wants to provide expected state, source, or provenance; those fields are server-derived.\n\nWorkflow:\n1. Resolve the brand-scoped account and provider entity.\n2. Fresh-read and canonicalize its automation state.\n3. Present observation and preview_digest to the user.\n4. Submit establish_automation_baseline with the unchanged identity and digest for HITL approval.\n\nOutput semantics:\n- observation is fresh provider state; observed_at records read time.\n- preview_digest binds organization, account, platform, entity identity, observation, and schema version and cannot be replayed across identities.\n\nSide effects:\n- None. This action reads provider state and does not persist a baseline.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "google, meta, tiktok, or chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "google", "meta", "tiktok", "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Brand-scoped ad account id from ads.list_ad_accounts.", "example": "adv_123", "source": "upstream:action:ads/list_ad_accounts" }, { "name": "entity_type", "type": "string", "required": true, "description": "Audited entity type: meta=creative; google/tiktok=campaign; chatgpt_ads=campaign or ad_group.", "example": "campaign", "enum": [ "campaign", "creative", "ad_group" ], "source": "constant" }, { "name": "entity_id", "type": "string", "required": true, "description": "Provider entity id in the selected account and entity type.", "example": "campaign_123" } ], "output_shape": "{\"success\":true,\"platform\":\"...\",\"account_id\":\"...\",\"entity_type\":\"...\",\"entity_id\":\"...\",\"observation\":{...},\"observed_at\":\"...\",\"schema_version\":1,\"preview_digest\":\"...\",\"read_only\":true}\nTikTok output: the same canonical baseline preview envelope." }, { "id": "action:ads/query_multi_dimension", "namespace": "ads", "action": "query_multi_dimension", "description": "Cached ads breakdown across multiple dimensions; returns column-oriented raw platform metrics for supported dimension combinations.", "mode": "read", "platforms": [ "google", "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/query_single_dimension", "action:ads/gaql_search", "action:ads/list_dimensions" ], "long_description": "PRIMARY PATH for cached multi-dimensional breakdowns.\n\nPrimary use:\nMulti-dimensional reporting over synced Nex ads data, such as campaign x country or campaign x device. Use it when the user needs more than one group-by dimension.\n\nUse when:\n- The user asks for a breakdown by two or more supported dimensions.\n- The requested combination is valid for the selected platform according to the service viable-combo table.\n- Raw platform metrics are sufficient.\n\nDo not use when:\n- The user needs CPA, CPP, ROAS, Reg Rate, Pur Rate, or cg.{slug}.* columns. Use query_single_dimension.\n- A single dimension is enough. Use query_single_dimension for richer semantics.\n- The selected platform is chatgpt_ads. ChatGPT Ads has no multi-dimensional breakdown support; use query_single_dimension instead.\n- The dimension combination is unsupported and must use Google-native fields. Use get_resource_metadata then gaql_search for Google only.\n\nWorkflow:\n1. Call list_ad_accounts if account_id is unknown.\n2. Call list_dimensions to choose legal dimension slugs and filters.\n3. Prefer the most specific child dimension; the handler may drop redundant parent dimensions and report that in notice.\n4. Call this action with platform, account_id, date range, and dimensions[].\n\nOutput semantics:\n- Returns column-oriented data. Read data.columns.[i], not data.rows.\n- Only platform-raw conversion metrics are available.\n- requested_dimensions preserves the original request when normalization occurs.\n- notice describes dropped parent dimensions; unsupported combinations return 400 unsupported_dimension_combination. Google errors include nearest viable combos and a GAQL fallback hint; platforms without live fallback return supported_combos.\n- For id-keyed dimensions (campaign / account / adgroup / ad), the response includes both the raw id column (e.g., adset_id) and the resolved name column (e.g., adset_name) for agent display. Use the id column for filters / drills; use the name column for user-facing output.\nFreshly-onboarded accounts (first-time sync in progress):\n- If the requested window has NO data yet, this returns 409 data_syncing with details.readiness (bootstrap_state, per-window status, eta_hint) instead of an empty result — an empty window that is still syncing is NOT genuinely-zero spend.\n- If the requested range spans BOTH ready and not-yet-ready windows and returns rows, you still get the rows plus a top-level readiness object with partial=true — treat the result as incomplete, not a full-range total.\n- For google, the readiness/fallback body names ads/gaql_search for live data while the window syncs; switch back once ready (vacuum-only, never the default). meta/tiktok have no live fallback — report the eta_hint and retry later.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Ad platform for the selected account. Use google, meta, or tiktok. chatgpt_ads is intentionally unsupported for multi-dimensional queries." }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from list_ad_accounts.rows[].account_id. For Google, use digits only with no dashes." }, { "name": "date_start", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format." }, { "name": "date_end", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format." }, { "name": "dimensions", "type": "list", "required": true, "description": "Dimension slug list from list_dimensions.dimensions[].name. The service enforces valid combinations.", "example": [ "campaign", "country" ] }, { "name": "filters", "type": "object", "required": false, "description": "Map of field -> allowed values; field must appear in list_dimensions.filterable_fields for the selected dimensions." }, { "name": "sort", "type": "list", "required": false, "description": "[{field, order}] with order in {asc, desc}; field must exist in output columns." }, { "name": "mode", "type": "string", "required": false, "description": "rows (default) or summary." }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap. Omit to use the service default; hard budget still applies." } ], "output_shape": "{\"account\": {\"platform\": \"google\", \"account_id\": \"123\", \"data_freshness\": \"ISO\"}, \"dimensions\": [\"campaign\", \"country\"], \"data_basis\": {...}, \"scope_caveat\": \"...\", \"row_count\": N, \"columns\": {: [...]}, \"summary\": null, \"readiness\": null | {\"partial\": true, \"readiness\": {...}, \"fallback\": {...}}}" }, { "id": "action:ads/query_single_dimension", "namespace": "ads", "action": "query_single_dimension", "description": "Primary cached ads KPI query for one dimension; returns ROAS, CPA, CPP, conversion-group metrics, derived metrics, and cg.{slug}.* fan-out.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/resolve_terms", "action:ads/query_multi_dimension", "action:ads/gaql_search", "action:ads/list_conversion_groups" ], "long_description": "PRIMARY PATH for cached ads analytics.\n\nPrimary use:\nSingle-dimension reporting over synced Nex business-layer ads data. Use this for metrics report KPIs, platform-supported breakdowns, cross-platform-consistent metrics, and mapping-aware derived metrics. ChatGPT Ads supports account, date, campaign, ad, landing_page, and the UTM dimensions (utm_source, utm_medium, utm_campaign, utm_content) only.\n\nUTM dimensions are system-managed campaign-metadata breakdowns available on google, meta, and chatgpt_ads. They return the same spend/impressions/clicks/conversions/derived metrics as any other scope; campaigns with no value for the key aggregate into the '(not set)' bucket so totals reconcile with the campaign scope. Values reflect current campaign metadata — history outside the cache refresh window keeps earlier values until a full rebuild. UTM value filters and campaign filters are not supported yet.\n\nUse when:\n- The analysis has exactly one group-by dimension.\n- The user asks for CPA, CPP, ROAS, Reg Rate, Pur Rate, or cg.{slug}.* metrics.\n- The result should use the Nex metrics report cache first and fall back to synced Parquet data when the cache cannot exactly represent the request.\n\nDo not use when:\n- The user needs multiple group-by dimensions. Use query_multi_dimension.\n- The field or segment is not exposed by list_dimensions. Use get_resource_metadata then gaql_search for Google-native exploration; ChatGPT Ads has no GAQL fallback.\n- The user needs platform-side change history. Use get_change_history.\n- The user needs Nex-side agent action records. Use list_operation_records.\n\nWorkflow:\n1. Call list_ad_accounts if account_id is unknown.\n2. If the user's metric/dimension wording is ambiguous, call resolve_terms.\n3. Call list_dimensions to choose a valid dimension, filters, and metrics.\n4. Call list_conversion_groups only if conversion-group fan-out or CPA/ROAS-like metrics are needed.\n5. Call this action with platform, account_id, date_start, date_end, and dimension.\n\nOutput semantics:\n- Returns column-oriented data. Read data.columns.[i], not data.rows.\n- spend_micros and cpc_micros are micros; ctr is a 0-1 ratio.\n- scope_caveat explains aggregation limits for the selected scope.\n- mode=summary returns summary data such as top rows, totals, and p95 where supported.\n- truncated or warning means the agent must mention partial result limits.\n- No production Agent action currently exposes row-level conversion events. list_conversion_groups returns definitions, not event records.\n- For id-keyed dimensions (campaign / account / adgroup / ad), dimension_label is the resolved entity name (e.g., adset name for adgroup dimension); falls back to the platform id when the entity record is missing or has no name.\nFreshly-onboarded accounts (first-time sync in progress):\n- If the requested window has NO data yet, this returns 409 data_syncing with details.readiness (bootstrap_state, per-window status, eta_hint) instead of an empty result — an empty window that is still syncing is NOT genuinely-zero spend. (409, not 5xx, so the structured body survives the public proxy.)\n- If the requested range spans BOTH ready and not-yet-ready windows and returns rows, you still get the rows plus a top-level readiness object with partial=true — treat the result as incomplete, not a full-range total.\n- For google, the readiness/fallback body names ads/gaql_search: use it for live Google Ads data while the window is syncing, then switch back to this action once the window is ready. Offered ONLY during the bootstrap vacuum; once ready the 409 stops firing, so do not keep using gaql for synced windows. meta/tiktok/chatgpt_ads have no live fallback — report the eta_hint and retry later.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Ad platform for the selected account. Use google, meta, tiktok, or chatgpt_ads.", "example": "google" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from list_ad_accounts.rows[].account_id. For Google, use digits only with no dashes.", "example": "1234567890" }, { "name": "date_start", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format.", "example": "2026-05-01" }, { "name": "date_end", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-05-18" }, { "name": "dimension", "type": "string", "required": true, "description": "Single dimension slug from list_dimensions.dimensions[].name.", "example": "campaign" }, { "name": "filters", "type": "object", "required": false, "description": "Map of field -> allowed values; field must appear in the selected dimension's list_dimensions.filterable_fields." }, { "name": "sort", "type": "list", "required": false, "description": "[{field, order}] with order in {asc, desc}; default desc by total_spend_micros. Sort field must exist in output columns." }, { "name": "mode", "type": "string", "required": false, "description": "rows (default) or summary; summary returns top rows, totals, and p95." }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap. Omit to use the service default; hard budget still applies." }, { "name": "attribution_window", "type": "string", "required": false, "description": "Override conversion-mapping default; one of 1d_click, 7d_click, 1d_view, 7d_view, 28d_view, 1d_click_1d_view, 7d_click_1d_view, 28d_click_1d_view, default" } ], "output_shape": "{\"account\": {\"platform\": \"google\", \"account_id\": \"123\", \"data_freshness\": \"ISO\"}, \"dimension\": \"campaign\", \"data_basis\": {\"view\": \"...\", \"parquet_table\": \"...\", \"selected_scope\": \"campaign\", \"cg_fan_out_available\": true}, \"scope_caveat\": \"...\", \"row_count\": 128, \"truncated\": false, \"warning\": null, \"columns\": {\"campaign_id\": [...], \"spend_micros\": [...], \"ctr\": [...], \"cg.qualified-new-user.all_conv\": [...]}, \"summary\": null, \"readiness\": null | {\"partial\": true, \"readiness\": {...}, \"fallback\": {...}}}" }, { "id": "action:ads/register_automation_copy", "namespace": "ads", "action": "register_automation_copy", "description": "HITL register a Google, Meta, TikTok, or ChatGPT Ads copy from one authoritative automation intent.", "mode": "risk", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:ads/preview_automation_baseline", "action:ads/audit_creative_automation" ], "long_description": "Primary use:\nRegister the verified lineage of an externally copied Google, Meta, TikTok, or ChatGPT Ads entity and inherit the source entity's authoritative Soku-written automation intent.\n\nUse when:\n- A provider UI or external tool created a new entity ID from a source with an authoritative Soku-write baseline.\n\nDo not use when:\n- The source has only a human baseline or no baseline.\n- Source and copied identities are in different accounts, platforms, or entity types.\n- The caller is guessing lineage, expected state, or source authority.\n\nWorkflow:\n1. Submit source and new identities through action:_batch/submit.\n2. Review preparation resolves source authority and injects a digest bound to both identities.\n3. After approval, execution revalidates account scope and both provider identities, then reloads source authority.\n4. Inherit the intent atomically and fresh-read the copy for an immediate canonical diff.\n\nOutput semantics:\n- registration_status confirms inheritance; expected is copied from the authoritative source and current comes from provider readback.\n- findings immediately report OK or DRIFTED for the copied entity.\n\nSide effects:\n- Inserts the copied entity's local authoritative intent baseline after HITL approval.\n- Does not mutate either provider entity.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "google, meta, tiktok, or chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "google", "meta", "tiktok", "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Brand-scoped ad account id from ads.list_ad_accounts.", "example": "adv_123", "source": "upstream:action:ads/list_ad_accounts" }, { "name": "entity_type", "type": "string", "required": true, "description": "Audited entity type: meta=creative; google/tiktok=campaign; chatgpt_ads=campaign or ad_group.", "example": "campaign", "enum": [ "campaign", "creative", "ad_group" ], "source": "constant" }, { "name": "source_entity_id", "type": "string", "required": true, "description": "Provider id with an authoritative Soku-written baseline.", "example": "campaign_source" }, { "name": "new_entity_id", "type": "string", "required": true, "description": "Copied provider entity id in the same account and entity type.", "example": "campaign_copy" } ], "output_shape": "{\"success\":true,\"registration_status\":\"registered\",\"source_entity_id\":\"...\",\"new_entity_id\":\"...\",\"expected\":{...},\"current\":{...},\"findings\":[...],\"summary\":{\"ok\":N,\"drifted\":N,\"no_baseline\":0}}\nTikTok output: the same canonical immediate-diff envelope." }, { "id": "action:ads/remove_account_negative_keywords", "namespace": "ads", "action": "remove_account_negative_keywords", "description": "Remove account-level negative keywords (shared criteria) from a Google NEGATIVE_KEYWORDS shared set by resource_name (atomic batch, type-checked pre-flight); review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_account_negative_keywords", "action:ads/add_account_negative_keywords", "action:ads/remove_campaign_negative_keywords", "action:ads/remove_ad_group_negative_keywords", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-remove account-level negative keywords by shared_criterion resource_name in one atomic mutate. Removes SharedCriterion rows only — NEVER the shared set itself (shared-set deletion is intentionally not exposed; an emptied set stays attached to its campaigns and simply excludes nothing). Before mutating, a GAQL pre-flight fetches every requested resource_name and rejects the whole batch unless each one is a KEYWORD shared_criterion inside a NEGATIVE_KEYWORDS shared set.\n\nUse when:\n- Account-wide negatives are over-blocking valid queries across campaigns.\n- A shared negative-keyword list needs pruning (stale/duplicate terms).\n\nDo not use when:\n- You want to delete or detach the shared set itself. Not exposed; use the Google Ads UI.\n- The negatives live on one campaign. Use action:ads/remove_campaign_negative_keywords.\n- The negatives live on one ad group. Use action:ads/remove_ad_group_negative_keywords.\n\nWorkflow:\n1. Call action:ads/list_account_negative_keywords immediately before, and copy resource_name values verbatim from its output. resource_names MUST come from an immediately-preceding list call — never from memory or an earlier conversation turn (criteria may have changed since).\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/list_account_negative_keywords.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.removed_keywords echoes the pre-flight-verified {resource_name, text, match_type, shared_set_id} per removed criterion, so the review summary shows exactly what was deleted and from which set.\n\nSide effects:\n- The whole batch is atomic: one stale or invalid resource_name fails the entire request and nothing is removed — re-list and retry.\n- Pre-flight verification rejects anything that is not a KEYWORD criterion in a NEGATIVE_KEYWORDS shared set (e.g. placement criteria or criteria from other shared-set types), so the type-blind criterion-remove path cannot delete the wrong criterion kind.\n- Removal takes effect in every campaign the shared set is attached to; re-add via action:ads/add_account_negative_keywords if needed.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "criterion_resource_names", "type": "list", "required": true, "description": "List (or JSON string) of shared_criterion resource_name values, format 'customers/{customer_id}/sharedCriteria/{shared_set_id}~{criterion_id}'. Copy verbatim from an immediately-preceding action:ads/list_account_negative_keywords call. No duplicates; every entry must belong to the given account.", "example": [ "customers/1234567890/sharedCriteria/456~789" ] } ], "output_shape": "{\"success\": true, \"message\": \"Removed 2 account negative keyword(s)\", \"removed_keywords\": [{\"resource_name\": \"customers/123/sharedCriteria/456~789\", \"text\": \"free\", \"match_type\": \"BROAD\", \"shared_set_id\": \"456\"}], \"results\": [...]}" }, { "id": "action:ads/remove_ad", "namespace": "ads", "action": "remove_ad", "description": "Remove an ad on Google (terminal) or archive on Meta; review-gated. TikTok: soft delete via status/update DELETE — irreversible; the object can no longer change status, though it stays queryable via the get/list action.", "mode": "risk", "platforms": [ "google", "meta", "tiktok", "linkedin" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ads", "action:ads/update_ad", "action:_batch/submit" ], "long_description": "Supported platforms: google, meta, tiktok, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action sends operation_status=DELETE through TikTok's ad status-update endpoint. It is a soft delete in storage (the resource remains queryable), but its status can no longer change, so treat it as irreversible and recreate the resource to restore delivery. The returned resource_id is the deleted TikTok resource id.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nTerminate or archive an ad. On Google this is a terminal mutate (status=REMOVED); on Meta it sets status=ARCHIVED.\n\nUse when:\n- The user wants to retire a specific ad without touching the rest of the ad group / ad set.\n\nDo not use when:\n- The user wants to retire the whole ad group / ad set. Use action:ads/remove_ad_group or action:ads/remove_adset.\n- The user wants to pause temporarily. Use action:ads/update_ad with status=PAUSED.\n\nWorkflow:\n1. Call action:ads/list_ads to confirm ad_id (Google also needs ad_group_ad_resource_name which the dispatcher auto-synthesizes from customer_id, ad_group_id, ad_id).\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via list_ads.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Google raw.resource_name is the removed ad_group_ad resource path.\n- Meta raw is {success: true}.\n\nSide effects:\n- Google: terminal mutate(remove); not reversible.\n- Meta: POSTs status=ARCHIVED; can be un-archived to PAUSED via update_ad.\n- Stops spend immediately for that ad. TikTok: soft delete via status/update DELETE — irreversible; the object can no longer change status, though it stays queryable via the get/list action.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta, tiktok, linkedin.", "example": "google", "enum": [ "google", "meta", "tiktok", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google only: required to scope the customer (digits only without dashes); from ads.list_ad_accounts.rows[].account_id. Meta only: not required for remove_ad (only ad_id is used) -- the executor injects account_id automatically and the handler ignores it. Source: user-provided account id or back-filled from the resolved credential.", "example": "1234567890" }, { "name": "ad_id", "type": "string", "required": true, "description": "Meta only: archives the ad (POST /{ad_id} status=ARCHIVED). IMPORTANT: on Meta, ARCHIVED is effectively terminal - it cannot be reverted to PAUSED/ACTIVE via update_ad; to restore, re-create the ad with create_ad. Archive is also capped at ~50k archived objects per type per account. Google: ad id from action:ads/list_ads. Source: output of list_ads / create_ad (the ad id). Required when platform=tiktok.", "example": "123" }, { "name": "ad_group_id", "type": "string", "required": false, "description": "Google only (required for remove on Google): combined with ad_id to synthesize the ad_group_ad resource name customers/{cid}/adGroupAds/{ad_group_id}~{ad_id}. Omitting it on Google returns a clear validation error (no platform call). Not used by Meta. Source: output of list_ads (ad_group_id field).", "example": "1234567890", "platform": [ "google", "meta" ] } ], "output_shape": "{\"success\": true, \"message\": \"Ad removed\", \"resource_name\": \"customers/X/adGroupAds/AG~AD\", \"results\": [...]} // Meta: {\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/remove_ad_creative", "namespace": "ads", "action": "remove_ad_creative", "description": "Hard-delete a Meta ad creative; review-gated, IRREVERSIBLE (true Graph DELETE, not an archive).", "mode": "risk", "platforms": [ "meta" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_creatives", "action:ads/get_ad_creative", "action:ads/get_ad_creatives", "action:ads/create_ad_creative", "action:_batch/submit" ], "long_description": "Primary use:\nPermanently delete a Meta ad creative that is no longer needed, e.g. to clean up drafts or wrong-content creatives produced by create_ad_creative.\n\nUse when:\n- The user explicitly wants a creative gone for good and it is not referenced by any ad.\n- A workflow created a creative with wrong content and wants to delete it before it is ever attached to an ad.\n\nDo not use when:\n- The creative is referenced by live ads. Meta rejects the delete with a platform error; detach or remove the ads first (check references via action:ads/get_ad_creatives per ad or action:ads/list_ads creative.id).\n- The user only wants to stop delivery. Pause or remove the ad instead (action:ads/update_ad / action:ads/remove_ad); the creative itself does not spend.\n- The platform is Google. Google creatives are managed through action:ads/update_ad / remove_ad.\n\nWorkflow:\n1. Confirm creative_id via action:ads/list_ad_creatives or action:ads/get_ad_creative.\n2. Check no ad still uses the creative (action:ads/list_ads, action:ads/get_ad_creatives).\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/get_ad_creative (expect not_found).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body: {success: true}.\n- Returns success=false with error_code=not_found when the creative does not exist OR belongs to a different ad account than the resolved one (ownership pre-check).\n\nSide effects:\n- Sends DELETE {creative_id} — a TRUE hard delete. Unlike action:ads/remove_campaign / remove_adset / remove_ad, which archive and are reversible, this cannot be undone; the creative content is gone and the id becomes unusable.\n- Fails with a platform error (passed through verbatim) if the creative is still referenced by ads, including archived ones in some cases.\n- Ownership is verified against the active account before deletion: the handler reads the creative's account_id and rejects mismatches as not_found, so creatives of other workspaces cannot be deleted.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id. Used for the ownership pre-check: the creative must belong to this account or the action fails with not_found.", "example": "1234567890" }, { "name": "creative_id", "type": "string", "required": true, "description": "Meta ad creative id to delete, from action:ads/list_ad_creatives, action:ads/get_ad_creative, or action:ads/create_ad_creative (raw.id).", "example": "120251905634300081" } ], "output_shape": "{\"success\": true}" }, { "id": "action:ads/remove_ad_group", "namespace": "ads", "action": "remove_ad_group", "description": "Remove a Google Ads ad group with terminal REMOVED status; review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/update_ad_group", "action:_batch/submit" ], "long_description": "Primary use:\nTerminate a Google ad group. This is a terminal mutate; the ad group cannot be restored.\n\nUse when:\n- The user wants to permanently retire an ad group along with its keywords and ads.\n\nDo not use when:\n- The user wants to temporarily pause the ad group. Use action:ads/update_ad_group with status=PAUSED.\n- The platform is Meta. Use action:ads/remove_adset.\n\nWorkflow:\n1. Call action:ads/gaql_search to confirm ad_group_id.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify removal via action:ads/gaql_search (excluded by default).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.resource_name is the removed ad group resource name.\n\nSide effects:\n- Terminal mutate(remove); sets status=REMOVED.\n- Not reversible.\n- Stops spend immediately for the ad group's child ads.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: account_id is declared required=True in capability docs but is absent from registry.required_common; the executor._validate_required guard therefore does not enforce it, so the agent gets a less-clean late ValueError (\"customer_id is required\") instead of the standard 400 'missing required field(s)' if it omits account_id. The docs and the enforced contract disagree. Also note: remove_ad_group is terminal/irreversible (status REMOVED) yet there is no validate_only path; the long_description tells the agent to verify via gaql_search AFTER removal, but offers no pre-flight confirmation that the ad_group_id is valid/exists before the destructive mutate. Removing an already-REMOVED or non-existent ad group will surface as a raw Google Ads error with no partial_failure cushioning -- guidance does not warn about this failure mode. Capability gap: validate_only dry-run flag is not exposed at any layer; a HITL review-gated terminal delete would benefit from a server-side validation pass before commit, but is currently impossible to request.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Ad group id from action:ads/gaql_search.", "example": "987654" } ], "output_shape": "{\"success\": true, \"message\": \"Ad group removed\", \"resource_name\": \"customers/X/adGroups/Y\", \"results\": [...]}" }, { "id": "action:ads/remove_ad_group_label", "namespace": "ads", "action": "remove_ad_group_label", "description": "Detach a Google label from an ad group; review-gated, reversible via re-apply.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/apply_ad_group_label", "action:ads/remove_campaign_label", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nRemove the ad_group_label join row that attaches a label to a Google ad group. The resource name is built server-side from ad_group_id + label_id, so there is no type-blind remove and no pre-flight is needed.\n\nUse when:\n- A label was applied to the wrong ad group or is no longer wanted there.\n- Re-organizing ad-group-level labels.\n\nDo not use when:\n- The label sits on a campaign. Use action:ads/remove_campaign_label.\n- The user wants to delete the label itself account-wide. Label deletion is not exposed at L1.5; only the ad group link is removed here.\n- The platform is Meta (Google-only action).\n\nWorkflow:\n1. Discover the attached label via action:ads/gaql_search on ad_group_label (e.g. SELECT ad_group.id, label.id, label.name FROM ad_group_label WHERE ad_group.id = ), or reuse the ad_group_id + label_id from the earlier apply workflow.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/gaql_search on ad_group_label.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- If the (ad group, label) link does not exist, the Google API error is passed through unchanged (no local pre-flight).\n\nSide effects:\n- Removes the ad_group_label join row only; the label and the ad group are untouched.\n- Reversible via action:ads/apply_ad_group_label.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Ad group id from action:ads/gaql_search." }, { "name": "label_id", "type": "string", "required": true, "description": "Label id currently attached to the ad group; discover via action:ads/gaql_search on ad_group_label or label." } ], "output_shape": "{\"success\": true, \"message\": \"Ad group label removed successfully\", \"results\": [...]}" }, { "id": "action:ads/remove_ad_group_negative_keywords", "namespace": "ads", "action": "remove_ad_group_negative_keywords", "description": "Remove ad-group-level negative keywords from a Google ad group by resource_name (atomic batch, type-checked pre-flight); review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_group_negative_keywords", "action:ads/add_ad_group_negative_keywords", "action:ads/remove_campaign_negative_keywords", "action:ads/remove_account_negative_keywords", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-remove ad-group-level negative keywords by ad_group_criterion resource_name in one atomic mutate. Before mutating, a GAQL pre-flight fetches every requested resource_name and rejects the whole batch unless each one is a negative KEYWORD criterion.\n\nUse when:\n- Ad-group negatives added earlier are over-blocking valid queries.\n- Restructuring negatives (e.g. moving them up to campaign level) requires deleting the ad-group copies.\n\nDo not use when:\n- The negatives live on the campaign. Use action:ads/remove_campaign_negative_keywords.\n- The negatives live in an account-level shared set. Use action:ads/remove_account_negative_keywords.\n- You want to remove positive keywords. Use action:ads/remove_keyword.\n\nWorkflow:\n1. Call action:ads/list_ad_group_negative_keywords immediately before, and copy resource_name values verbatim from its output. resource_names MUST come from an immediately-preceding list call — never from memory or an earlier conversation turn (criteria may have changed since).\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/list_ad_group_negative_keywords.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.removed_keywords echoes the pre-flight-verified {resource_name, text, match_type} per removed criterion, so the review summary shows exactly what was deleted.\n\nSide effects:\n- The whole batch is atomic: one stale or invalid resource_name fails the entire request and nothing is removed — re-list and retry.\n- Pre-flight verification rejects anything that is not a negative KEYWORD criterion (positive keywords, audience criteria, bid modifiers), so the type-blind criterion-remove path cannot delete the wrong criterion kind.\n- After removal the ad group may serve on the previously blocked queries again; re-add via action:ads/add_ad_group_negative_keywords if needed.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "criterion_resource_names", "type": "list", "required": true, "description": "List (or JSON string) of ad_group_criterion resource_name values, format 'customers/{customer_id}/adGroupCriteria/{ad_group_id}~{criterion_id}'. Copy verbatim from an immediately-preceding action:ads/list_ad_group_negative_keywords call. No duplicates; every entry must belong to the given account.", "example": [ "customers/1234567890/adGroupCriteria/111~222" ] } ], "output_shape": "{\"success\": true, \"message\": \"Removed 2 ad group negative keyword(s)\", \"removed_keywords\": [{\"resource_name\": \"customers/123/adGroupCriteria/111~222\", \"text\": \"free\", \"match_type\": \"BROAD\"}], \"results\": [...]}" }, { "id": "action:ads/remove_ad_rule", "namespace": "ads", "action": "remove_ad_rule", "description": "Hard-delete a Meta or TikTok automated ad rule; review-gated and IRREVERSIBLE.", "mode": "risk", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_rules", "action:ads/create_ad_rule", "action:ads/update_ad_rule", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nPermanently delete an automated rule that is no longer wanted, e.g. an obsolete spend-guard or a rule superseded by a new one.\n\nUse when:\n- The user explicitly wants the rule gone for good.\n\nDo not use when:\n- The user only wants to stop the rule from running. Use action:ads/update_ad_rule with status=DISABLED (reversible) instead.\n- The user wants to change what the rule does. Use action:ads/update_ad_rule.\n- The platform is Google.\n\nWorkflow:\n1. Confirm rule_id via action:ads/list_ad_rules.\n2. Prefer disabling first (action:ads/update_ad_rule status=DISABLED) unless permanent deletion is explicitly requested.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/list_ad_rules (rule gone).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body: {success: true}.\n- Returns success=false with error_code=not_found when the rule does not exist OR belongs to a different ad account than the resolved one (ownership pre-check).\n\nSide effects:\n- Meta sends DELETE {rule_id}; TikTok sends operate_type=DELETE to /optimizer/rule/update/status/. Unlike action:ads/remove_campaign / remove_adset / remove_ad, which archive and are reversible, this cannot be undone; the rule stops evaluating immediately and its id becomes unusable.\n- Ownership is verified against the active account before deletion: the handler reads the rule's account_id and rejects mismatches as not_found, so rules of other workspaces cannot be deleted.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "tiktok", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id. Used for the ownership pre-check: the rule must belong to this account or the action fails with not_found.", "example": "1234567890" }, { "name": "rule_id", "type": "string", "required": true, "description": "Ad rule id to delete, from action:ads/list_ad_rules or action:ads/create_ad_rule (raw.id). Required when platform=tiktok.", "example": "97800000000000000" } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/remove_adset", "namespace": "ads", "action": "remove_adset", "description": "Archive a Meta ad set; review-gated, reversible by un-archiving to PAUSED. TikTok: soft delete via status/update DELETE — irreversible; the object can no longer change status, though it stays queryable via the get/list action.", "mode": "risk", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_adsets", "action:ads/update_adset", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action sends operation_status=DELETE through TikTok's ad group status-update endpoint. It is a soft delete in storage (the resource remains queryable), but its status can no longer change, so treat it as irreversible and recreate the resource to restore delivery. The returned resource_id is the deleted TikTok resource id.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nArchive a Meta ad set so it stops spending and disappears from default listings. Reversible by un-archiving to PAUSED.\n\nUse when:\n- The user wants to retire a Meta ad set but keep the option to restore.\n\nDo not use when:\n- The user wants a temporary pause. Use action:ads/update_adset with status=PAUSED.\n- The platform is Google. Use action:ads/remove_ad_group.\n\nWorkflow:\n1. Call action:ads/list_adsets to confirm adset_id.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via list_adsets (ARCHIVED status hidden from default listings).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body: {success: true}.\n\nSide effects:\n- POSTs status=ARCHIVED to {adset_id}; not a true delete.\n- Stops spend immediately for ads under the ad set.\n- Reversible: update_adset with status=PAUSED (to re-enter the funnel), then update_adset with status=ACTIVE to resume. TikTok: soft delete via status/update DELETE — irreversible; the object can no longer change status, though it stays queryable via the get/list action.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": false, "description": "Meta only: optional for remove_adset -- the account is resolved from adset_id alone, so account_id is not strictly required. Source: ads.list_ad_accounts.rows[].account_id (optional).", "example": "4040704529502487" }, { "name": "adset_id", "type": "string", "required": true, "description": "Ad set id from action:ads/list_adsets. Required when platform=tiktok.", "example": "23..." } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/remove_audience_targeting", "namespace": "ads", "action": "remove_audience_targeting", "description": "Remove ad-group audience criteria from a Google ad group by resource_name (atomic batch, type-checked pre-flight, negative flag echoed); review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_audience_targeting", "action:ads/remove_campaign_audience_targeting", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-remove ad-group-level audience criteria by ad_group_criterion resource_name in one atomic mutate. Before mutating, a GAQL pre-flight fetches every requested resource_name and rejects the whole batch unless each one is an AUDIENCE criterion (USER_LIST, USER_INTEREST, CUSTOM_INTENT, CUSTOM_AUDIENCE, COMBINED_AUDIENCE) that is not already REMOVED.\n\nUse when:\n- An audience layer added earlier no longer matches the targeting strategy.\n- Restructuring ad-group audiences (e.g. moving them up to campaign level) requires deleting the ad-group copies.\n\nDo not use when:\n- The audience criteria live on the campaign. Use action:ads/remove_campaign_audience_targeting.\n- You want to remove keywords or negative keywords. Use action:ads/remove_keyword or the remove_*_negative_keywords actions.\n- You want to delete the user list itself. Not exposed; removing the criterion only detaches the audience from the ad group.\n\nWorkflow:\n1. Discover criteria via action:ads/gaql_search immediately before (there is no dedicated list action), e.g.: SELECT ad_group_criterion.resource_name, ad_group_criterion.type, ad_group_criterion.negative, ad_group_criterion.status, ad_group_criterion.user_list.user_list FROM ad_group_criterion WHERE ad_group.id = {ad_group_id} AND ad_group_criterion.type IN ('USER_LIST', 'USER_INTEREST', 'CUSTOM_INTENT', 'CUSTOM_AUDIENCE', 'COMBINED_AUDIENCE'). Copy resource_name values verbatim from raw.ad_group_criterion — never from memory or an earlier conversation turn (criteria may have changed since).\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via the same action:ads/gaql_search query.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.removed_criteria echoes the pre-flight-verified {resource_name, type, negative} per removed criterion. Check the negative flag in review: negative=true means an EXCLUSION was removed, which WIDENS delivery to the previously excluded audience.\n\nSide effects:\n- The whole batch is atomic: one stale or invalid resource_name fails the entire request and nothing is removed — re-discover via gaql_search and retry.\n- Pre-flight verification rejects anything that is not an audience criterion (keywords, geo/language criteria, bid modifiers) and anything already REMOVED, so the type-blind criterion-remove path cannot delete the wrong criterion kind.\n- Removing a positive criterion narrows targeting; removing a negative (excluded) criterion WIDENS delivery — the flag is echoed in the result so the HITL reviewer can see which is which.\n- The underlying audience resource (user list etc.) is untouched; re-attach via action:ads/add_audience_targeting if needed.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "criterion_resource_names", "type": "list", "required": true, "description": "List (or JSON string) of ad_group_criterion resource_name values, format 'customers/{customer_id}/adGroupCriteria/{ad_group_id}~{criterion_id}'. Copy verbatim from an immediately-preceding action:ads/gaql_search discovery query on ad_group_criterion. No duplicates; every entry must belong to the given account.", "example": [ "customers/1234567890/adGroupCriteria/111~222" ] } ], "output_shape": "{\"success\": true, \"message\": \"Removed 2 ad group audience criterion(s)\", \"removed_criteria\": [{\"resource_name\": \"customers/123/adGroupCriteria/111~222\", \"type\": \"USER_LIST\", \"negative\": false}], \"results\": [...]}" }, { "id": "action:ads/remove_bidding_strategy", "namespace": "ads", "action": "remove_bidding_strategy", "description": "Remove an unused Google portfolio bidding strategy.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/update_campaign" ], "long_description": "Primary use:\nRemove an unused Google portfolio bidding strategy.\n\nUse when:\n- You need a reusable Google Target CPA or Target ROAS portfolio strategy.\n\nDo not use when:\n- You need a campaign-specific standard bidding strategy; use action:ads/update_campaign.\n\nWorkflow:\n1. Read the current strategy or campaign state when changing an existing resource.\n2. For write operations, submit through action:_batch/submit for HITL review.\n3. Verify the resulting state with action:ads/get_bidding_strategy.\n\nOutput semantics:\n- Returns the created, read, updated, listed, or removed Google portfolio strategy result.\n\nSide effects:\n- Creates, updates, or removes a Google portfolio strategy. Removing a strategy is irreversible and requires it to be unused.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id without dashes.", "example": "1234567890" }, { "name": "bidding_strategy_id", "type": "string", "required": true, "description": "Google portfolio bidding strategy id.", "example": "123" } ], "output_shape": "{\"success\":true,\"raw\":{}}" }, { "id": "action:ads/remove_campaign", "namespace": "ads", "action": "remove_campaign", "description": "Remove a campaign on Google/TikTok or archive on Meta/ChatGPT Ads; review-gated and stops spend. TikTok: soft delete via status/update DELETE — irreversible; the object can no longer change status, though it stays queryable via the get/list action.", "mode": "risk", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_campaigns", "action:ads/update_campaign", "action:_batch/submit", "action:ads/list_operation_records" ], "long_description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action sends operation_status=DELETE through TikTok's campaign status-update endpoint. It is a soft delete in storage (the resource remains queryable), but its status can no longer change, so treat it as irreversible and recreate the resource to restore delivery. The returned resource_id is the deleted TikTok resource id.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nTerminate or archive a campaign so it stops spending and disappears from default listings. Google/TikTok removal is irreversible; Meta is archive-style. ChatGPT Ads uses a provider toggle guarded by a pre-read, at-most-once mutation, and post-read proof.\n\nUse when:\n- The user wants to permanently retire a Google campaign.\n- The user wants to archive a Meta or ChatGPT Ads campaign.\n\nDo not use when:\n- The user wants to temporarily pause spend. Use action:ads/update_campaign with status=PAUSED instead.\n- The user wants to delete only an ad or ad group. Use action:ads/remove_ad or action:ads/remove_ad_group / remove_adset.\n\nWorkflow:\n1. Call action:ads/list_campaigns to confirm the target campaign_id.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify removal via action:ads/list_campaigns with status_filter.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Google raw.resource_name is the removed campaign resource name.\n- Meta raw is {success: true}; the campaign moves to ARCHIVED status.\n\nSide effects:\n- Google: terminal — sets status=REMOVED via mutate(remove=resource_name); not reversible.\n- Meta: not a true delete — POSTs status=ARCHIVED. The campaign disappears from default listings; it can be un-archived to PAUSED (not ACTIVE) via action:ads/update_campaign.\n- Stops spend immediately on both platforms.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Capability docs do not state the customer_id is derived from account_id; harmless but the input_params omit customer_id while the handler reads it — an agent reading only handler source could be confused about which key to send. Also note: Docs say Google removal is 'terminal/irreversible' (correct) but do not warn that re-removing an already-REMOVED campaign returns a raw API error rather than an idempotent success; agent may retry and surface a confusing error. Capability gap: validate_only / partial_failure (Google Ads mutate options) are not exposed at this layer; a remove cannot be dry-run via the agent contract (acceptable for a destructive op but worth noting).\n- meta: Guidance: capability docs declare account_id required:True for remove_campaign, but for Meta it is not needed (handler ignores it; archive targets the campaign-id node). This forces the agent to fetch/pass an account_id that has no effect on Meta and risks confusing it about what is load-bearing. Also note: Docs say Meta archive 'can be un-archived to PAUSED (not ACTIVE) via update_campaign' but do not warn about the 50,000 ARCHIVED-objects-per-type-per-account ceiling that can make the operation fail; the agent cannot anticipate this failure mode. Capability gap: No way to perform a TRUE delete (status=DELETED) on Meta via remove_campaign. The handler is hard-coded to ARCHIVED, so the agent cannot evacuate archived objects when an account hits the 50,000-archived-per-type limit, and cannot satisfy a user request to permanently delete a Meta campaign. capability docs even claim Meta is 'archive-style can be un-archived', acknowledging this gap as intentional. TikTok: soft delete via status/update DELETE — irreversible; the object can no longer change status, though it stays queryable via the get/list action.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin.", "example": "google", "enum": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns. Required when platform=tiktok.", "example": "123456" } ], "output_shape": "{\"success\": true, \"message\": \"Campaign removed\", \"resource_name\": \"customers/X/campaigns/Y\", \"results\": [...]} // Meta: {\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/remove_campaign_assets", "namespace": "ads", "action": "remove_campaign_assets", "description": "Detach Google extension assets (sitelink/callout/structured snippet) from a campaign by asset id (atomic batch, link pre-flight, detach only — the asset survives); review-gated.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_sitelinks", "action:ads/add_callouts", "action:ads/add_structured_snippets", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-remove CampaignAsset link rows — the attachment between a campaign and extension assets of one field_type (SITELINK, CALLOUT or STRUCTURED_SNIPPET) — in one atomic mutate. DETACH ONLY: the Asset itself is never deleted; it stays in the account's asset library and can be re-attached later via the add_* extension actions.\n\nUse when:\n- Sitelinks / callouts / structured snippets added earlier should no longer show on this campaign.\n- Replacing extensions: detach the old assets, then add the new ones via action:ads/add_sitelinks, action:ads/add_callouts or action:ads/add_structured_snippets.\n\nDo not use when:\n- You want to delete the Asset itself from the account. Not exposed; this action only detaches the campaign link.\n- You want to remove audience criteria. Use action:ads/remove_audience_targeting or action:ads/remove_campaign_audience_targeting.\n- The platform is Meta. Meta does not use campaign extension assets.\n\nWorkflow:\n1. Discover attached assets via action:ads/gaql_search immediately before (there is no dedicated list action), e.g.: SELECT campaign_asset.asset, campaign_asset.field_type, campaign_asset.status, asset.id, asset.name FROM campaign_asset WHERE campaign.id = {campaign_id} AND campaign_asset.field_type IN ('SITELINK', 'CALLOUT', 'STRUCTURED_SNIPPET'). Take asset ids from raw.asset.id — never from memory or an earlier conversation turn (links may have changed since).\n2. Submit via action:_batch/submit for HITL review with asset_ids and the matching field_type.\n3. Verify via the same action:ads/gaql_search query.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.removed_links echoes the pre-flight-verified {resource_name, asset, field_type} per detached link.\n\nSide effects:\n- Detach only: removes CampaignAsset link rows; the underlying Asset rows survive in the account and can be re-attached via the add_* extension actions.\n- The whole batch is atomic: one asset id that is not attached to the campaign with the given field_type (or whose link is already REMOVED) fails the entire request and nothing is detached — re-discover via gaql_search and retry.\n- A GAQL pre-flight resolves each (campaign, asset, field_type) link to its live resource_name; the remove operations use those verbatim, so a stale or mistyped asset id can never detach the wrong link.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns." }, { "name": "asset_ids", "type": "list", "required": true, "description": "List (or JSON string) of numeric asset ids to detach. Copy verbatim from an immediately-preceding action:ads/gaql_search discovery query on campaign_asset (raw.asset.id). No duplicates; every asset must be attached to the campaign with the given field_type.", "example": [ "111222333", "444555666" ] }, { "name": "field_type", "type": "string", "required": true, "description": "Link field type to detach: one of SITELINK, CALLOUT, STRUCTURED_SNIPPET. Must match campaign_asset.field_type from the discovery query.", "example": "SITELINK" } ], "output_shape": "{\"success\": true, \"message\": \"Detached 2 sitelink asset(s) from campaign 123 (assets remain in the account)\", \"removed_links\": [{\"resource_name\": \"customers/1234567890/campaignAssets/123~111222333~SITELINK\", \"asset\": \"customers/1234567890/assets/111222333\", \"field_type\": \"SITELINK\"}], \"results\": [...]}" }, { "id": "action:ads/remove_campaign_audience_targeting", "namespace": "ads", "action": "remove_campaign_audience_targeting", "description": "Remove campaign-level audience criteria from a Google campaign by resource_name (atomic batch, type-checked pre-flight, negative flag echoed); review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_campaign_audience_targeting", "action:ads/remove_audience_targeting", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-remove campaign-level audience criteria by campaign_criterion resource_name in one atomic mutate. Before mutating, a GAQL pre-flight fetches every requested resource_name and rejects the whole batch unless each one is an AUDIENCE criterion (USER_LIST, USER_INTEREST, CUSTOM_AUDIENCE, COMBINED_AUDIENCE) that is not already REMOVED.\n\nUse when:\n- A campaign-level audience layer (or exclusion) added earlier no longer matches the targeting strategy.\n- An audience exclusion set via add_campaign_audience_targeting negative=true must be lifted.\n\nDo not use when:\n- The audience criteria live on an ad group. Use action:ads/remove_audience_targeting.\n- You want to remove negative keywords. Use action:ads/remove_campaign_negative_keywords.\n- You want to delete the user list itself. Not exposed; removing the criterion only detaches the audience from the campaign.\n\nWorkflow:\n1. Discover criteria via action:ads/gaql_search immediately before (there is no dedicated list action), e.g.: SELECT campaign_criterion.resource_name, campaign_criterion.type, campaign_criterion.negative, campaign_criterion.status, campaign_criterion.user_list.user_list FROM campaign_criterion WHERE campaign.id = {campaign_id} AND campaign_criterion.type IN ('USER_LIST', 'USER_INTEREST', 'CUSTOM_AUDIENCE', 'COMBINED_AUDIENCE'). Copy resource_name values verbatim from raw.campaign_criterion — never from memory or an earlier conversation turn (criteria may have changed since).\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via the same action:ads/gaql_search query.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.removed_criteria echoes the pre-flight-verified {resource_name, type, negative} per removed criterion. Check the negative flag in review: negative=true means an EXCLUSION was removed, which WIDENS delivery to the previously excluded audience.\n\nSide effects:\n- The whole batch is atomic: one stale or invalid resource_name fails the entire request and nothing is removed — re-discover via gaql_search and retry.\n- Pre-flight verification rejects anything that is not an audience criterion (keywords, geo/language criteria, bid modifiers) and anything already REMOVED, so the type-blind criterion-remove path cannot delete the wrong criterion kind. Exclusion (negative) campaign criteria carry no status, so the stale check applies only where the field exists.\n- Removing a positive criterion narrows targeting; removing a negative (excluded) criterion WIDENS delivery — the flag is echoed in the result so the HITL reviewer can see which is which.\n- The underlying audience resource (user list etc.) is untouched; re-attach via action:ads/add_campaign_audience_targeting if needed.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "criterion_resource_names", "type": "list", "required": true, "description": "List (or JSON string) of campaign_criterion resource_name values, format 'customers/{customer_id}/campaignCriteria/{campaign_id}~{criterion_id}'. Copy verbatim from an immediately-preceding action:ads/gaql_search discovery query on campaign_criterion. No duplicates; every entry must belong to the given account.", "example": [ "customers/1234567890/campaignCriteria/111~222" ] } ], "output_shape": "{\"success\": true, \"message\": \"Removed 2 campaign audience criterion(s)\", \"removed_criteria\": [{\"resource_name\": \"customers/123/campaignCriteria/111~222\", \"type\": \"USER_LIST\", \"negative\": true}], \"results\": [...]}" }, { "id": "action:ads/remove_campaign_label", "namespace": "ads", "action": "remove_campaign_label", "description": "Detach a Google label from a campaign; review-gated, reversible via re-apply.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/apply_campaign_label", "action:ads/remove_ad_group_label", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nRemove the campaign_label join row that attaches a label to a Google campaign. The resource name is built server-side from campaign_id + label_id, so there is no type-blind remove and no pre-flight is needed.\n\nUse when:\n- A label was applied to the wrong campaign or is no longer wanted there.\n- Re-organizing labels (e.g. replacing 'Q1 Promo' with 'Q2 Promo').\n\nDo not use when:\n- The label sits on an ad group. Use action:ads/remove_ad_group_label.\n- The user wants to delete the label itself account-wide. Label deletion is not exposed at L1.5; only the campaign link is removed here.\n- The platform is Meta (Google-only action).\n\nWorkflow:\n1. Discover the attached label via action:ads/gaql_search on campaign_label (e.g. SELECT campaign.id, label.id, label.name FROM campaign_label WHERE campaign.id = ), or reuse the campaign_id + label_id from the earlier apply workflow.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/gaql_search on campaign_label.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- If the (campaign, label) link does not exist, the Google API error is passed through unchanged (no local pre-flight).\n\nSide effects:\n- Removes the campaign_label join row only; the label and the campaign are untouched.\n- Reversible via action:ads/apply_campaign_label.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns." }, { "name": "label_id", "type": "string", "required": true, "description": "Label id currently attached to the campaign; discover via action:ads/gaql_search on campaign_label or label." } ], "output_shape": "{\"success\": true, \"message\": \"Campaign label removed successfully\", \"results\": [...]}" }, { "id": "action:ads/remove_campaign_negative_keywords", "namespace": "ads", "action": "remove_campaign_negative_keywords", "description": "Remove campaign-level negative keywords from a Google campaign by resource_name (atomic batch, type-checked pre-flight); review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_campaign_negative_keywords", "action:ads/add_campaign_negative_keywords", "action:ads/remove_ad_group_negative_keywords", "action:ads/remove_account_negative_keywords", "action:_batch/submit" ], "long_description": "Primary use:\nBatch-remove campaign-level negative keywords by campaign_criterion resource_name in one atomic mutate. Before mutating, a GAQL pre-flight fetches every requested resource_name and rejects the whole batch unless each one is a negative KEYWORD criterion.\n\nUse when:\n- Negatives added earlier are over-blocking valid queries and must be undone.\n- A negative-keyword audit found stale or duplicate campaign negatives to clean up.\n\nDo not use when:\n- The negatives live on an ad group. Use action:ads/remove_ad_group_negative_keywords.\n- The negatives live in an account-level shared set. Use action:ads/remove_account_negative_keywords.\n- You want to remove positive keywords. Use action:ads/remove_keyword.\n\nWorkflow:\n1. Call action:ads/list_campaign_negative_keywords immediately before, and copy resource_name values verbatim from its output. resource_names MUST come from an immediately-preceding list call — never from memory or an earlier conversation turn (criteria may have changed since).\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/list_campaign_negative_keywords.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.removed_keywords echoes the pre-flight-verified {resource_name, text, match_type} per removed criterion, so the review summary shows exactly what was deleted.\n\nSide effects:\n- The whole batch is atomic: one stale or invalid resource_name fails the entire request and nothing is removed — re-list and retry.\n- Pre-flight verification rejects anything that is not a negative KEYWORD criterion (positive keywords, geo/audience criteria, bid modifiers), so the type-blind criterion-remove path cannot delete the wrong criterion kind.\n- After removal the campaign may serve on the previously blocked queries again; re-add via action:ads/add_campaign_negative_keywords if needed.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "criterion_resource_names", "type": "list", "required": true, "description": "List (or JSON string) of campaign_criterion resource_name values, format 'customers/{customer_id}/campaignCriteria/{campaign_id}~{criterion_id}'. Copy verbatim from an immediately-preceding action:ads/list_campaign_negative_keywords call. No duplicates; every entry must belong to the given account.", "example": [ "customers/1234567890/campaignCriteria/111~222" ] } ], "output_shape": "{\"success\": true, \"message\": \"Removed 2 campaign negative keyword(s)\", \"removed_keywords\": [{\"resource_name\": \"customers/123/campaignCriteria/111~222\", \"text\": \"free\", \"match_type\": \"BROAD\"}], \"results\": [...]}" }, { "id": "action:ads/remove_conversion_action", "namespace": "ads", "action": "remove_conversion_action", "description": "PERMANENTLY remove a Google conversion action (status -> REMOVED, irreversible); GAQL pre-flight echoes name+status; risk-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/update_conversion_action", "action:ads/create_conversion_action", "action:ads/list_conversion_actions", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nRemove a Google Ads conversion action by conversion_action_id.\n\nPERMANENT — this cannot be undone:\nGoogle sets the action's status to REMOVED forever. A removed conversion action can never be re-enabled, and its historical conversion data stays in reports, but the action stops recording new conversions immediately. If campaigns are still optimizing toward it, Smart Bidding loses that signal. When the goal is to merely hide or pause the action, use action:ads/update_conversion_action with status=HIDDEN instead.\n\nUse when:\n- The user explicitly wants to permanently retire a conversion action (e.g. duplicate, mis-configured, or obsolete tracking).\n\nDo not use when:\n- The user only wants to hide/pause it. Use action:ads/update_conversion_action with status=HIDDEN (reversible).\n- The user wants to rename or re-configure it. Use action:ads/update_conversion_action.\n- The platform is Meta. Meta conversion config lives on pixels/events.\n\nWorkflow:\n1. Discover the conversion_action_id via action:ads/list_conversion_actions (the cached multi-platform list) or action:ads/gaql_search (e.g. SELECT conversion_action.id, conversion_action.name, conversion_action.status FROM conversion_action).\n2. Call this action; the pre-flight re-fetches the action so the reviewer sees the verified name and status.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/gaql_search (status becomes REMOVED).\n\nPre-flight semantics:\n- The handler runs a GAQL lookup by id before the mutate.\n- Id not found -> fails with 'not found (stale?)'; re-run discovery and retry. Nothing is removed.\n- Status already REMOVED -> fails with 'already REMOVED'; nothing to do.\n- The verified name and status are echoed into the success result so the human reviewer approves a named action, not an opaque id.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.removed_conversion_action carries the pre-flight-verified resource_name, name, and prior status.\n\nSide effects:\n- Sets the conversion action status to REMOVED. PERMANENT; not reversible via any API or UI.\n- Historical conversion data is retained in reports; new conversions stop recording immediately.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "conversion_action_id", "type": "string", "required": true, "description": "Numeric conversion action id from action:ads/list_conversion_actions or action:ads/gaql_search on conversion_action. The pre-flight rejects ids that do not exist or are already REMOVED.", "example": "123456" } ], "output_shape": "{\"success\": true, \"message\": \"Conversion action 'Purchase' removed (was ENABLED); removal is permanent\", \"removed_conversion_action\": {\"resource_name\": \"customers/X/conversionActions/Y\", \"name\": \"Purchase\", \"status\": \"ENABLED\"}, \"results\": [{\"conversionActionResult\": {\"resourceName\": \"customers/X/conversionActions/Y\"}}]}" }, { "id": "action:ads/remove_conversion_group_rule", "namespace": "ads", "action": "remove_conversion_group_rule", "description": "Review-gated remove of a conversion_group rule.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_groups" ], "long_description": "Soft-deletes a rule from an Admin View conversion_group after human approval. Requires expected_version from list_conversion_groups; stale versions are rejected.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "group_id", "type": "integer", "required": true, "description": "Conversion group id." }, { "name": "expected_version", "type": "string", "required": true, "description": "Opaque version from list_conversion_groups.groups[].version." }, { "name": "rule_id", "type": "integer", "required": true, "description": "Conversion group rule id." } ], "output_shape": "{\"removed\": true, \"rule_id\": 1, \"conversion_group\": {\"version\": \"opaque\"}}" }, { "id": "action:ads/remove_custom_audience", "namespace": "ads", "action": "remove_custom_audience", "description": "Hard-delete a Meta custom audience; review-gated, IRREVERSIBLE (true Graph DELETE, not an archive). Also supports TikTok through its provider-native contract.", "mode": "risk", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_custom_audiences", "action:ads/create_custom_audience", "action:ads/update_custom_audience", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nPermanently delete a Meta custom (or lookalike) audience that is no longer needed, e.g. stale seed lists or abandoned test audiences.\n\nUse when:\n- The user explicitly wants an audience gone for good and it is not targeted by active ad sets.\n\nDo not use when:\n- The audience is still used in ad set targeting. Meta rejects the delete with a platform error; update the ad sets' targeting first (action:ads/update_adset).\n- The user only wants to rename or re-describe the audience. Use action:ads/update_custom_audience.\n- The platform is Google.\n\nWorkflow:\n1. Confirm audience_id via action:ads/list_custom_audiences.\n2. Check the audience is not referenced by ad set targeting (action:ads/get_adset targeting).\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/list_custom_audiences (audience gone).\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body: {success: true}.\n- Returns success=false with error_code=not_found when the audience does not exist OR belongs to a different ad account than the resolved one (ownership pre-check).\n\nSide effects:\n- Sends DELETE {audience_id} — a TRUE hard delete. Unlike action:ads/remove_campaign / remove_adset / remove_ad, which archive and are reversible, this cannot be undone; uploaded/collected membership data is discarded and the id becomes unusable (including as a lookalike source).\n- Fails with a platform error (passed through verbatim) if the audience is still in use by ad sets.\n- Ownership is verified against the active account before deletion: the handler reads the audience's account_id and rejects mismatches as not_found, so audiences of other workspaces cannot be deleted.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id. Used for the ownership pre-check: the audience must belong to this account or the action fails with not_found.", "example": "1234567890" }, { "name": "audience_id", "type": "string", "required": true, "description": "Custom audience id to delete, from action:ads/list_custom_audiences or action:ads/create_custom_audience (raw.id). Required when platform=tiktok.", "example": "23845000000000000" } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/remove_keyword", "namespace": "ads", "action": "remove_keyword", "description": "Remove a Google keyword with terminal REMOVED status; review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/update_keyword", "action:_batch/submit" ], "long_description": "Primary use:\nTerminate a single Google ad group keyword. Terminal mutate; not reversible.\n\nUse when:\n- The user wants to permanently drop a keyword.\n\nDo not use when:\n- The user wants to temporarily pause. Use action:ads/update_keyword with status=PAUSED.\n- The user wants to remove a negative keyword. The negative remove handler is not exposed; use the Google Ads UI.\n\nWorkflow:\n1. Call action:ads/gaql_search to confirm criterion_id.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/gaql_search.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.resource_name is the removed criterion resource path.\n\nSide effects:\n- Terminal mutate(remove); sets status=REMOVED.\n- Not reversible.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Capability docs do not state the action is non-idempotent: removing an already-REMOVED keyword returns a server error (RESOURCE_NOT_FOUND). The agent could retry-loop or mis-handle. Workflow step 1 ('confirm criterion_id via gaql_search') implicitly mitigates only if the agent also filters status!=REMOVED. Also note: Docs say criterion_id comes 'from action:ads/gaql_search' but do not warn that ad_group_id and criterion_id must come from the SAME row (same ad group) or the composite resource_name is invalid. An agent that takes criterion_id from one query and ad_group_id from a different context can build a bad path. Capability gap: validate_response_content_setting / partial_failure: not exposed. mutate is all-or-nothing; no partial_failure flag is surfaced, so a single bad criterion errors the whole call. Acceptable for a single-op remove but worth noting vs. add_keywords batch.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Parent ad group id." }, { "name": "criterion_id", "type": "string", "required": true, "description": "Keyword criterion id from action:ads/gaql_search." } ], "output_shape": "{\"success\": true, \"message\": \"Keyword removed\", \"resource_name\": \"customers/X/adGroupCriteria/AG~CID\", \"results\": [...]}" }, { "id": "action:ads/remove_label", "namespace": "ads", "action": "remove_label", "description": "Remove a Meta ad label (by NAME) from a campaign/adset/ad/creative; review-gated, reversible (re-apply).", "mode": "write", "platforms": [ "meta" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/apply_label", "action:ads/list_ad_labels", "action:_batch/submit" ], "long_description": "Primary use:\nDetach a Meta ad label from an object (campaign, ad set, ad, or creative). Reverse of action:ads/apply_label. Like apply, Meta resolves the label by NAME (case-sensitive), not by id.\n\nUse when:\n- The user wants to untag an object previously labeled via action:ads/apply_label.\n\nDo not use when:\n- The label is not attached to the object. The handler verifies attachment first and returns error_code=not_found without writing.\n- The platform is Google. No Google unlabel action is exposed here (removal only via the Google Ads UI).\n\nWorkflow:\n1. Look up the exact label name via action:ads/list_ad_labels.\n2. Identify object_id (campaign / adset / ad / creative id).\n3. Submit via action:_batch/submit for HITL review.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body of the field write: {success: true}.\n- If the label is not attached: {success: false, error_code: 'not_found'}.\n\nSide effects:\n- The DELETE {object_id}/adlabels edge is deprecated (Meta 2020-02-03 changelog), so the handler does a read-modify-write: reads the object's adlabels field, drops the named label, and POSTs the remaining set back (a field write replaces the full list).\n- Case-sensitive name match against the currently attached labels.\n- Reversible via action:ads/apply_label (re-apply the same label).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta.", "example": "meta", "enum": [ "meta" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "object_id", "type": "string", "required": true, "description": "Target object id (campaign / ad set / ad / creative id) to unlabel." }, { "name": "label_name", "type": "string", "required": true, "description": "Label name (case-sensitive) from action:ads/list_ad_labels. Meta resolves by name, not id." } ], "output_shape": "{\"success\": true}" }, { "id": "action:ads/remove_product_catalog", "namespace": "ads", "action": "remove_product_catalog", "description": "Hard-delete an owned Meta product catalog; review-gated, IRREVERSIBLE (true Graph DELETE, not an archive). Also supports TikTok through its provider-native contract.", "mode": "risk", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:ads/list_product_catalogs", "action:ads/create_product_catalog", "action:ads/update_product_catalog", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nPermanently delete a Meta product catalog that is no longer needed, e.g. an abandoned test catalog.\n\nUse when:\n- The user explicitly wants the catalog gone for good and none of its products or product sets are used by live ads.\n\nDo not use when:\n- The user only needs to inspect catalog state. Use the matching list/get action first.\n- The user needs cached ads performance metrics. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Confirm catalog_id via action:ads/list_product_catalogs.\n2. Check for live usage first (action:ads/list_products, action:ads/list_product_sets, and product ads referencing its product sets); Meta rejects the delete while they are in active use.\n3. Submit through action:_batch/submit for HITL approval.\n4. After approval, call action:ads/list_product_catalogs to confirm the catalog is gone.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Meta Graph API mutation response.\n- The action is review-gated through the ads HITL path.\n\nSide effects:\n- Sends DELETE {catalog_id} — a TRUE hard delete. Unlike action:ads/remove_campaign / remove_adset / remove_ad, which archive and are reversible, this cannot be undone; the catalog's feeds, products, and product sets are deleted with it.\n- Fails with a platform error (passed through verbatim) when products or product sets in the catalog are in active use (e.g. referenced by live product ads). Meta's allow_delete_catalog_with_live_product_set override is intentionally NOT exposed on this surface — stop the referencing ads first instead of force-deleting.\n- Catalog ownership is verified against the resolved account business before deletion; mismatches return error_code=not_found, so catalogs of other workspaces cannot be deleted.\n", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id to delete. Required when platform=tiktok." }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/remove_product_feed", "namespace": "ads", "action": "remove_product_feed", "description": "Hard-delete a Meta product feed from an owned catalog; review-gated, IRREVERSIBLE (true Graph DELETE, not an archive). Also supports TikTok through its provider-native contract.", "mode": "risk", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:ads/get_product_feed_details", "action:ads/create_product_feed", "action:ads/update_product_feed", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nPermanently delete a product feed whose ingestion source is no longer wanted, e.g. a superseded or misconfigured feed.\n\nUse when:\n- The user explicitly wants the feed gone for good and understands its ingested products leave the catalog with it.\n\nDo not use when:\n- The user only needs to inspect catalog state. Use the matching list/get action first.\n- The user needs cached ads performance metrics. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Get catalog_id from action:ads/list_product_catalogs.\n2. Confirm product_feed_id via action:ads/get_product_feed_details.\n3. Check which products came from this feed (action:ads/list_products) — they are removed from the catalog with it, which can empty product sets used by live product ads.\n4. Submit through action:_batch/submit for HITL approval.\n5. After approval, call action:ads/get_catalog_diagnostics or action:ads/list_products to confirm.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Meta Graph API mutation response.\n- The action is review-gated through the ads HITL path.\n\nSide effects:\n- Sends DELETE {product_feed_id} — a TRUE hard delete. Unlike action:ads/remove_campaign / remove_adset / remove_ad, which archive and are reversible, this cannot be undone.\n- Products ingested by this feed are removed from the catalog, which can empty product sets and affect product ads that depend on them; Meta rejects the delete with a platform error (passed through verbatim) when the feed's products are in active use.\n- Catalog ownership AND the feed's membership in that catalog are verified before deletion; mismatches return error_code=not_found, so feeds of other workspaces cannot be deleted.\n", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id that contains the feed. Required when platform=tiktok." }, { "name": "product_feed_id", "type": "string", "required": true, "description": "Meta product feed id to delete.", "platform": [ "meta" ] }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "feed_id", "type": "string", "required": true, "description": "TikTok feed id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/replace_campaign_ad_units", "namespace": "ads", "action": "replace_campaign_ad_units", "description": "Deprecated legacy compatibility: Replace the full ChatGPT Ads ad-unit set on a campaign.", "mode": "risk", "platforms": [ "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_ad_unit", "action:ads/get_campaign" ], "long_description": "Deprecated compatibility path. New deployments use inline Campaign -> Ad Group -> Ad.\n\nPrimary use:\nReplace the complete ChatGPT Ads ad-unit set attached to a campaign.\n\nUse when:\n- The user approved an exact final list of campaign ad units.\n- Old campaign ad units should be detached from the campaign.\n\nDo not use when:\n- The user only wants to add variants. Use add_campaign_ad_units.\n- The provided ad_unit_ids list is empty.\n\nWorkflow:\n1. Resolve campaign_id and the exact non-empty ad_unit_ids list.\n2. Submit the replace operation for HITL review.\n3. Read the campaign back to confirm the final binding.\n\nSide effects:\n- Replaces the campaign's full ad-unit binding set.\n- Does not archive detached ad units and does not activate the campaign.\n\nOutput semantics:\n- raw is the ChatGPT Ads platform response for the replacement mutation.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: chatgpt_ads.", "example": "chatgpt_ads", "enum": [ "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "" }, { "name": "campaign_id", "type": "string", "required": true, "description": "", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "ad_unit_ids", "type": "list", "required": true, "description": "", "source": "upstream:action:ads/create_ad_unit" } ], "output_shape": "{\"success\": true, \"raw\": {...}}" }, { "id": "action:ads/resolve_terms", "namespace": "ads", "action": "resolve_terms", "description": "Resolve ads words like ROAS, CPA, conversion group, search term, asset group, or change history to the right ads action and payload hints.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_dimensions", "action:ads/list_conversion_groups", "action:ads/query_single_dimension", "action:ads/get_resource_metadata", "action:ads/gaql_search", "action:ads/get_change_history", "action:ads/list_operation_records" ], "long_description": "Primary use:\nSemantic routing for ambiguous ads terms before choosing a data action. This is a lightweight resolver: it maps user-facing KPI, dimension, conversion group, change-history, and Google-native vocabulary to action ids and payload hints. It does not query metrics or platform APIs.\n\nUse when:\n- The user names a metric or dimension and you are unsure whether it is cached, conversion-group derived, or Google-native.\n- The user asks for ROAS, CPA, CPP, registration rate, purchase rate, or cg.* columns and you need the correct conversion group workflow.\n- The user asks for change history and you need to choose platform-side get_change_history vs Nex-side list_operation_records.\n- The user mentions asset group, PMax asset, ad strength, geo target constants, or another Google-native field that may require get_resource_metadata + gaql_search.\n\nDo not use when:\n- You already know the exact action and parameters. Call that action directly.\n- You need the full available schema. Use list_dimensions or list_conversion_groups.\n- You need live Google Ads field metadata. Use get_resource_metadata.\n\nWorkflow:\n1. Pass a short list of user terms in terms[].\n2. Read rows[].matches[0].recommended_action as the first route.\n3. Follow followup_actions and parameter_hints to fill the next action payload.\n\nOutput semantics:\n- rows[].recommended_action is the best first action for that term.\n- matches[].kind classifies dimension, metric, conversion_group_metric, platform_change_history, nex_audit, gaql_native_hint, or unknown.\n- parameter_hints are routing hints, not an executable schema.", "input_params": [ { "name": "terms", "type": "string|list", "required": true, "description": "One term or a list of terms to resolve. Examples: ROAS, CPA, search term, asset group, change history.", "example": [ "ROAS", "search term", "change history" ] }, { "name": "platform", "type": "string", "required": false, "description": "Optional platform context: google, meta, or tiktok. Defaults to google.", "example": "google" } ], "output_shape": "{\"platform\": \"google\", \"rows\": [{\"term\": \"ROAS\", \"matches\": [{\"kind\": \"conversion_group_metric\", \"canonical\": \"cg.{slug}.roas\", \"recommended_action\": \"action:ads/list_conversion_groups\", \"followup_actions\": [\"action:ads/query_single_dimension\"], \"parameter_hints\": {...}}], \"recommended_action\": \"action:ads/list_conversion_groups\"}]}" }, { "id": "action:ads/search_geo_locations", "namespace": "ads", "action": "search_geo_locations", "description": "Search Meta ad geo location keys; returns data[] with location key, name, type. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/search_interests", "action:ads/estimate_audience_size", "action:ads/create_adset" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nLook up Meta geo location keys for targeting.geo_locations. Use the returned 'key' values inside ad set targeting.\n\nUse when:\n- The user provides a country / region / city name and needs Meta's canonical key.\n- A workflow builds targeting from human-readable place names.\n\nDo not use when:\n- The user wants Google geo target constants. Use action:ads/list_countries or action:ads/gaql_search on geo_target_constant.\n- The platform is Google.\n\nWorkflow:\n1. Call this action with the user-supplied query and optional location_types.\n2. Embed the chosen keys into targeting.geo_locations for action:ads/create_adset.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.data[].key is the targeting key (e.g. 'US', '2421422'); raw.data[].type is the location type (country, region, city, ...).\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: The official q caveat — q must contain ONLY the locality name (no state/region/country text) and disambiguation must come from country_code — is NOT stated in the capability long_description. Example in docs is 'San Francisco' (fine) but nothing warns against 'San Francisco, CA' or 'Paris, Texas', so the agent may craft poor queries. Also note: No way to disambiguate same-named places: docs say 'multiple ad geo locations with same name/type can be returned' but provide no field/param (region_id) for the agent to narrow down — the agent must guess which returned key is correct. Capability gap: region_id: official optional param to disambiguate same-named cities (e.g. multiple 'Springfield'); absent from handler, whitelist, and capability docs — the agent cannot resolve ambiguous localities precisely.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "query", "type": "string", "required": true, "description": "Free-text place name. Required when platform=tiktok.", "example": "San Francisco" }, { "name": "location_types", "type": "list", "required": false, "description": "Optional list of location types to include. One or more of country, region, city, zip, geo_market, electoral_district, neighborhood, country_group, subneighborhood.", "platform": [ "meta" ] }, { "name": "country_code", "type": "string", "required": false, "description": "ISO-2 country code to constrain the search.", "example": "US", "platform": [ "meta" ] }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap; default 25.", "platform": [ "meta" ] }, { "name": "objective_type", "type": "string", "required": true, "description": "TikTok objective type.", "example": "value", "platform": [ "tiktok" ] }, { "name": "placements", "type": "list", "required": true, "description": "TikTok placements.", "example": [], "platform": [ "tiktok" ] }, { "name": "region_codes", "type": "list", "required": false, "description": "TikTok region codes.", "example": [], "platform": [ "tiktok" ] }, { "name": "search_type", "type": "string", "required": false, "description": "TikTok search type.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"key\": \"location_key\", \"name\": \"...\", \"type\": \"country|region|city|...\", \"country_code\": \"US\", \"country_name\": \"United States\", \"region\": \"...\", \"supports_region\": true, \"supports_city\": true}]}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/search_interests", "namespace": "ads", "action": "search_interests", "description": "Search Meta ad interest taxonomy; returns data[] with interest_id, name, audience size bounds. Also supports TikTok through its provider-native contract.", "mode": "read", "platforms": [ "meta", "tiktok" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/search_geo_locations", "action:ads/estimate_audience_size", "action:ads/create_adset" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nLook up Meta ad interest IDs and audience-size bounds by free-text query. Use the returned interest_id values inside targeting.interests on ad sets.\n\nUse when:\n- The user provides interest keywords and needs Meta's canonical interest IDs.\n- A workflow builds a targeting spec from human-readable interest names.\n\nDo not use when:\n- The user provides locations. Use action:ads/search_geo_locations.\n- The platform is Google. Google audience interests are not exposed here.\n\nWorkflow:\n1. Call this action with the user-supplied query.\n2. Pick interests with sufficient audience_size_lower_bound.\n3. Embed the interest IDs into targeting.interests for action:ads/create_adset.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.data[] contains id, name, audience_size_lower_bound, audience_size_upper_bound, topic, path (category breadcrumbs).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta only: ad account id from ads.list_ad_accounts.rows[].account_id. Used ONLY to resolve the access token; the interest search itself is not scoped to the account. Source: output of ads/list_ad_accounts (rows[].account_id).", "example": "4040704529502487" }, { "name": "query", "type": "string", "required": true, "description": "Free-text interest keyword. Required when platform=tiktok.", "example": "yoga" }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap; default 25." }, { "name": "locale", "type": "string", "required": false, "description": "Meta only: locale code (e.g. en_US, ja_JP) to localize the returned interest taxonomy names/IDs. Omit for default locale. Source: constant enum of Meta locale codes (retrievable via Targeting Search type=adlocale); typically derived from the advertiser's market/language.", "example": "en_US", "platform": [ "meta" ] }, { "name": "language", "type": "string", "required": false, "description": "TikTok language.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"data\": [{\"id\": \"interest_id\", \"name\": \"...\", \"audience_size_lower_bound\": N, \"audience_size_upper_bound\": N, \"topic\": \"...\", \"path\": [\"...\"]}]}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/send_conversion_event", "namespace": "ads", "action": "send_conversion_event", "description": "Send a review-gated server-side conversion to Meta CAPI or TikTok Events API 2.0.", "mode": "risk", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_pixels", "action:ads/create_pixel", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nSend a server-side conversion to Meta CAPI or TikTok Events API 2.0.\n\nUse when:\nReporting an offline / server conversion for measurement and optimization.\n\nDo not use when:\nThe event can be captured by the browser / app pixel, or PII in user_data is not yet hashed.\n\nWorkflow:\n1. Resolve the advertiser + pixel binding.\n2. For Meta, supply action_source, event_time, Meta user_data and optional custom_data; for TikTok, set event_id for deduplication and use the Events API properties / page / context schema. Hash PII in user_data first.\n3. Submit through the review queue, then verify the provider response before retrying.\n\nOutput semantics:\nReturns the provider-native event response; no cross-provider field coercion.\n\nSide effects:\nSends an irreversible server-side event to the provider that feeds attribution and Smart Bidding / optimization. TikTok's separate Events API token is configured and encrypted on the server for the advertiser+pixel binding; it must never be placed in the Agent payload.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "tiktok", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "pixel_id", "type": "string", "required": true, "description": "Meta pixel id from action:ads/list_pixels.", "platform": [ "meta" ] }, { "name": "event_name", "type": "string", "required": true, "description": "Meta standard event name (Purchase, AddToCart, ViewContent, Lead, CompleteRegistration) or a custom string. Required when platform=tiktok.", "example": "Purchase" }, { "name": "event_time", "type": "integer", "required": false, "description": "Unix seconds; required for Meta. Optional for TikTok, where the handler defaults to the current time." }, { "name": "action_source", "type": "string", "required": true, "description": "Meta CAPI event source. One of website, email, app, phone_call, chat, physical_store, system_generated, business_messaging, other.", "example": "other", "enum": [ "app", "business_messaging", "chat", "email", "other", "phone_call", "physical_store", "system_generated", "website" ], "source": "constant", "platform": [ "meta" ] }, { "name": "user_data", "type": "object", "required": true, "description": "Provider-native identity object. For TikTok use email, phone_number, or external_id with SHA-256 lowercase-hex values; Meta uses different key names.", "example": { "external_id": [ "0000000000000000000000000000000000000000000000000000000000000000" ] } }, { "name": "custom_data", "type": "object", "required": false, "description": "Meta only: custom event payload (value, currency, content_ids, content_type, order_id, etc.). For Purchase events, value AND currency are required by Meta. Source: user-provided / derived from the order or conversion record.", "example": { "value": 1.0, "currency": "USD" }, "platform": [ "meta" ] }, { "name": "event_source_url", "type": "string", "required": false, "description": "Meta only: URL of the page where the event occurred. REQUIRED when action_source='website'. Validated as a public https URL. Source: user-provided page URL where the conversion happened.", "example": "https://example.com/checkout", "platform": [ "meta" ] }, { "name": "event_id", "type": "string", "required": false, "description": "Deduplication key; required for TikTok and recommended for Meta browser/CAPI dedupe." }, { "name": "app_data", "type": "object", "required": false, "description": "Meta only: app-event context. REQUIRED when action_source='app'. Contains advertiser_tracking_enabled (0/1), application_tracking_enabled (0/1) and extinfo (ordered array of device/app fields per Meta's extinfo spec). Source: captured from the mobile SDK / app at event time.", "example": { "advertiser_tracking_enabled": 1, "application_tracking_enabled": 1, "extinfo": [ "i2", "", "", "", "16.0", "", "", "", "", "", "", "", "", "", "", "" ] }, "platform": [ "meta" ] }, { "name": "opt_out", "type": "boolean", "required": false, "description": "Meta only: per-event flag; when true the event is used for attribution but excluded from ads optimization. Source: user-provided privacy preference.", "example": false, "platform": [ "meta" ] }, { "name": "data_processing_options", "type": "list", "required": false, "description": "Meta only: Limited Data Use / privacy controls. Pass [\"LDU\"] to enable Limited Data Use, or [] to disable. Source: constant enum chosen for US-privacy compliance.", "example": [ "LDU" ], "platform": [ "meta" ] }, { "name": "data_processing_options_country", "type": "integer", "required": false, "description": "Meta only: country for Limited Data Use (0 = let Meta geolocate, 1 = United States). Used with data_processing_options. Source: constant enum.", "example": 1, "platform": [ "meta" ] }, { "name": "data_processing_options_state", "type": "integer", "required": false, "description": "Meta only: state for Limited Data Use (0 = let Meta geolocate, 1000 = California). Used with data_processing_options. Source: constant enum.", "example": 1000, "platform": [ "meta" ] }, { "name": "attribution_data", "type": "object", "required": false, "description": "Meta only: optional CAPI attribution optimization payload (e.g. attribution_share) forwarded verbatim. Source: user-provided / advanced attribution config per Meta docs.", "example": { "attribution_share": "0.5" }, "platform": [ "meta" ] }, { "name": "original_event_data", "type": "object", "required": false, "description": "Meta only: original event reference (event_name, event_time) used to link a derived event back to its source event for optimization. Source: derived from the originating event record.", "example": { "event_name": "Purchase", "event_time": 1716000000 }, "platform": [ "meta" ] }, { "name": "context", "type": "object", "required": false, "description": "TikTok Events API context object.", "example": { "ad": { "callback": "provider-callback" } }, "platform": [ "tiktok" ] }, { "name": "page", "type": "object", "required": false, "description": "TikTok Events API page object.", "example": { "url": "https://example.com/checkout" }, "platform": [ "tiktok" ] }, { "name": "pixel_code", "type": "string", "required": true, "description": "TikTok only: Alphanumeric pixel_code used as Events API event_source_id. Do not pass the numeric pixel_id used by update_pixel.", "example": "D9GS9MBC77UCJG7151AG", "source": "upstream:action:ads/list_pixels", "platform": [ "tiktok" ] }, { "name": "properties", "type": "object", "required": false, "description": "TikTok Events API properties object.", "example": { "currency": "USD", "value": 99.0 }, "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{\"provider_response\":\"...\"}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/set_ad_schedule", "namespace": "ads", "action": "set_ad_schedule", "description": "Replace the Google campaign ad schedule criteria (remove-all then create-new); review-gated, non-atomic at criterion level.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_campaigns", "action:ads/set_geo_targeting", "action:ads/set_language_targeting", "action:_batch/submit" ], "long_description": "Primary use:\nReplace all AD_SCHEDULE criteria on a Google campaign with a new list of day-of-week + hour windows.\n\nUse when:\n- The user wants to restrict ad serving to specific hours or days.\n- The user wants to remove all existing schedules and start over.\n\nDo not use when:\n- The user wants to add a single schedule without removing the others. There is no append handler; replace is the only path.\n- The platform is Meta. Use the Meta ad set start_time / end_time fields.\n\nWorkflow:\n1. Capture the current schedule via action:ads/gaql_search if rollback may be needed.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via gaql_search on campaign_criterion where type='AD_SCHEDULE'.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message reports the count of schedules applied.\n\nSide effects:\n- Removes ALL existing AD_SCHEDULE criteria on the campaign, then creates new ones, all in one mutate.\n- Non-atomic at the criterion level: if a create fails after removes succeed, the campaign may have zero schedules. Caller must re-run after fixing the input.\n- Reversible: re-call with the previously captured schedule list.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Capability docs say start_minute/end_minute default to ZERO and hours have ranges, but do NOT warn that the official API treats all five sub-fields as required-at-creation; an agent may assume omitting start_hour leaves the window 'open' rather than defaulting to 00:00-24:00. The silent defaulting (start_hour=0, end_hour=24) can produce an unintended all-day schedule. Also note: No guidance that end must be after start and that end is exclusive of end_minute. The agent could build a zero-length or inverted window (e.g. start_hour=17,end_hour=9) and only discover the failure at the API, with no local hint. Capability gap: bid_modifier per AdSchedule criterion: AdSchedule CampaignCriterion supports an optional bid_modifier (dayparting bid adjustment) to raise/lower bids during a window. This is a core dayparting use case and is absent from all three layers (handler hard-creates criteria with no bid_modifier, no whitelist key, no capability-docs param). The agent cannot do bid dayparting via this action.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "schedules", "type": "list", "required": true, "description": "List (or JSON string) of {day_of_week, start_hour, start_minute, end_hour, end_minute}. day_of_week: MONDAY..SUNDAY. start_minute / end_minute: ZERO, FIFTEEN, THIRTY, FORTY_FIVE (default ZERO). start_hour: 0-23. end_hour: 0-24.", "example": [ { "day_of_week": "MONDAY", "start_hour": 9, "end_hour": 17 } ] } ], "output_shape": "{\"success\": true, \"message\": \"Ad schedule replaced with 5 schedule(s)\", \"results\": [...]}" }, { "id": "action:ads/set_bid_modifier", "namespace": "ads", "action": "set_bid_modifier", "description": "Set a Google campaign device bid modifier (replaces existing DEVICE criteria); review-gated.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_campaigns", "action:ads/set_ad_schedule", "action:_batch/submit" ], "long_description": "Primary use:\nReplace all DEVICE-type bid modifiers on a Google campaign with a single new modifier for one device.\n\nUse when:\n- The user wants to raise or lower bids for mobile / desktop / tablet / connected TV.\n\nDo not use when:\n- The user wants to add modifiers for multiple devices independently. There is no append handler; calling this action again replaces the prior modifier.\n- The platform is Meta. Meta does not expose this concept; use ad set targeting.\n\nWorkflow:\n1. Choose the device and modifier_value (1.0 = no change).\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via gaql_search on campaign_criterion where type='DEVICE'.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message echoes the device and modifier value.\n\nSide effects:\n- Removes ALL existing DEVICE criteria on the campaign, then creates one new bid modifier, all in one mutate.\n- Non-atomic at criterion level: same partial-failure risk as set_ad_schedule.\n- Reversible: re-call with the prior device + modifier_value.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "device", "type": "string", "required": true, "description": "Device to adjust: one of MOBILE, DESKTOP, TABLET, CONNECTED_TV, OTHER. CONNECTED_TV is only valid on Video/Demand-Gen campaigns and will be rejected by Google on Search/Display campaigns. Source: constant enum chosen by the agent.", "example": "MOBILE", "enum": [ "CONNECTED_TV", "DESKTOP", "MOBILE", "OTHER", "TABLET" ], "source": "constant" }, { "name": "modifier_value", "type": "number", "required": true, "description": "Bid multiplier. 1.0 = no change; valid range 0.1-10.0 (handler now rejects out-of-range clean). 0 is NOT accepted (use a separate opt-out path); e.g. 1.2 = +20%, 0.8 = -20%. Source: user/agent-chosen multiplier.", "example": 1.2 } ], "output_shape": "{\"success\": true, \"message\": \"MOBILE bid modifier set to 1.2\", \"results\": [...]}" }, { "id": "action:ads/set_conversion_group_manual_campaign", "namespace": "ads", "action": "set_conversion_group_manual_campaign", "description": "Review-gated manual include/exclude of a campaign on a conversion group.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_brand_campaigns", "action:ads/clear_conversion_group_manual_campaign", "action:ads/list_conversion_groups" ], "long_description": "Manually includes or excludes a campaign on an Admin View conversion group after human approval. include adds a campaign goals did not auto-resolve; exclude removes an auto campaign. Requires expected_version and the INTERNAL campaign_id from list_brand_campaigns.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "group_id", "type": "integer", "required": true, "description": "Conversion group id." }, { "name": "expected_version", "type": "string", "required": true, "description": "Opaque version from list_conversion_groups.groups[].version." }, { "name": "campaign_id", "type": "integer", "required": true, "description": "Internal campaign id from list_brand_campaigns." }, { "name": "mode", "type": "string", "required": true, "description": "include or exclude." } ], "output_shape": "{\"set\": true, \"conversion_group\": {\"id\": 1, \"slug\": \"...\", \"version\": \"opaque\"}}" }, { "id": "action:ads/set_final_url_suffix", "namespace": "ads", "action": "set_final_url_suffix", "description": "Set the Google campaign final URL suffix (UTM tracking template); review-gated, reversible.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_campaigns", "action:ads/update_campaign", "action:_batch/submit" ], "long_description": "Primary use:\nSet or replace the final_url_suffix on a Google campaign for tracking-template parameter injection.\n\nUse when:\n- The user wants to install UTM tracking parameters on every ad URL.\n- A workflow needs to standardize tracking across all ads in the campaign.\n\nDo not use when:\n- The platform is Meta. Meta uses ad-level URL tags via tracking_specs.\n- The user wants per-ad URL overrides. Use action:ads/update_ad with final_urls.\n\nWorkflow:\n1. Build the suffix string without a leading '?' or '&'.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/list_campaigns or the Google Ads UI.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message echoes the applied suffix string.\n\nSide effects:\n- Campaign update with field-mask ['final_url_suffix'].\n- Reversible: re-call with the prior value (or empty string to clear).\n\nAudit guidance (batch 4 P2):\n- google: Guidance: Empty-string-to-clear contradiction: long_description says 'Reversible: re-call with the prior value (or empty string to clear)', but suffix is declared required=True in both capability docs and registry. The agent may believe it cannot clear. In practice passing suffix=\"\" works (key present -> required-check passes; .strip() -> \"\" -> Google clears the field), but this is implicit and the agent must guess that '' is acceptable. Also note: No campaign-type guardrail or warning: docs do not tell the agent that Video campaigns reject this mutation, so the agent may attempt it and surface a raw Google error. Add a 'do not use on Video campaigns' note. Capability gap: No missing required Google Ads API parameters for THIS specific field update - final_url_suffix is a single optional string and the action correctly sets only it. (Related but out-of-scope: tracking_url_template and custom_parameters / url_custom_parameters are sibling URL-options fields not covered by this action; an agent wanting full tracking-template control would need a separate capability, but that is a scope decision, not a defect of set_final_url_suffix.)", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "suffix", "type": "string", "required": true, "description": "Query-string fragment without a leading '?' or '&'. Supports Google value-track parameters like {_campaign}.", "example": "utm_source=google&utm_medium=cpc&utm_campaign={_campaign}" } ], "output_shape": "{\"success\": true, \"message\": \"Final URL suffix set to: \", \"results\": [...]}" }, { "id": "action:ads/set_geo_targeting", "namespace": "ads", "action": "set_geo_targeting", "description": "Replace Google campaign location criteria (remove-all + create-new); review-gated, non-atomic at criterion level.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_countries", "action:ads/set_language_targeting", "action:_batch/submit" ], "long_description": "Primary use:\nReplace all LOCATION criteria on a Google campaign with a new list of geo target constants. Use the IDs from action:ads/list_countries or Google's geo target constants table.\n\nUse when:\n- The user wants to limit the campaign to specific countries, regions, or cities.\n- The user wants to clear and reset all geographic targeting.\n\nDo not use when:\n- The user wants to add one location without removing the others. There is no append handler; replace is the only path.\n- The platform is Meta. Use targeting.geo_locations in action:ads/create_adset or action:ads/update_adset.\n\nWorkflow:\n1. Call action:ads/list_countries to look up country location_id values.\n2. Capture current criteria via gaql_search if rollback may be needed.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via gaql_search on campaign_criterion where type='LOCATION'.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message reports the count of locations applied.\n\nSide effects:\n- Removes ALL existing LOCATION criteria, then creates new criteria referencing geo_target_constant_path(location_id), all in one mutate.\n- Non-atomic at criterion level: partial-failure risk.\n- Reversible: re-call with the prior location_id list.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "location_ids", "type": "list", "required": true, "description": "Google only: list of geo target constant IDs to REPLACE the campaign's location targeting (removes all existing LOCATION criteria first). Each item is either a bare ID (positive include) or an object {\"id\": , \"negative\": true|false, \"bid_modifier\": <0.1-10.0>}. negative=true EXCLUDES the location; bid_modifier raises/lowers the bid for that location (1.5 = +50%). A negative location cannot have a bid_modifier (API constraint, returns an error). Source: geo target constant IDs from action:ads/list_countries (countries) or Google's geo target constants table for regions/cities/postal codes.", "example": [ 2840, { "id": 2826, "bid_modifier": 1.2 }, { "id": 1022762, "negative": true } ] }, { "name": "location_type", "type": "string", "required": false, "description": "Google only: DEPRECATED alias of geo_target_type. Sets the campaign-level positive geo target type. Valid: PRESENCE (people physically in the locations) or PRESENCE_OR_INTEREST (also people interested in them). AREA_OF_INTEREST/INTEREST map to PRESENCE_OR_INTEREST; SEARCH_INTEREST is deprecated and rejected. Prefer geo_target_type. Source: constant enum chosen by the agent based on user intent.", "example": "PRESENCE" }, { "name": "geo_target_type", "type": "string", "required": false, "description": "Google only: campaign-level positive geo target type applied via Campaign.geo_target_type_setting in the same mutate. Valid: PRESENCE (people physically in the targeted locations) or PRESENCE_OR_INTEREST (also people who searched for / showed interest in them). SEARCH_INTEREST is deprecated and not settable. Source: constant enum chosen by the agent based on user intent (physical presence vs presence-or-interest).", "example": "PRESENCE_OR_INTEREST" }, { "name": "limit", "type": "integer", "required": false, "description": "Google only: maximum number of locations allowed in location_ids. If the list exceeds this, the action returns an error (it does NOT silently truncate). Source: user-provided cap.", "example": 50 } ], "output_shape": "{\"success\": true, \"message\": \"Geo targeting replaced with 2 location(s)\", \"results\": [...]}" }, { "id": "action:ads/set_language_targeting", "namespace": "ads", "action": "set_language_targeting", "description": "Replace Google campaign language criteria (remove-all + create-new); review-gated, non-atomic at criterion level.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_languages", "action:ads/set_geo_targeting", "action:_batch/submit" ], "long_description": "Primary use:\nReplace all LANGUAGE criteria on a Google campaign with a new list of language constants. Use the IDs from action:ads/list_languages.\n\nUse when:\n- The user wants to restrict the campaign to specific Google Ads languages.\n- The user wants to clear and reset language targeting.\n\nDo not use when:\n- The user wants to append one language. There is no append handler; replace is the only path.\n- The platform is Meta. Use targeting locales inside the Meta ad set targeting spec.\n\nWorkflow:\n1. Call action:ads/list_languages to look up language_id values.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via gaql_search on campaign_criterion where type='LANGUAGE'.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message reports the count of languages applied.\n\nSide effects:\n- Removes ALL existing LANGUAGE criteria, then creates new criteria referencing language_constant_path(language_id), all in one mutate.\n- Non-atomic at criterion level.\n- Reversible: re-call with the prior language_id list.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: The 'limit' param is documented as 'Optional cap on the number of languages applied' but the handler ignores it entirely. An agent that passes limit=1 expecting only the first language to be applied will instead get ALL languages applied — silent contract violation. Either implement the cap (language_ids[:limit]) or remove limit from docs+allowlist. Also note: Docs example uses integer language IDs (example=[1000,1003]) while description says 'List of Google language constant IDs (or a JSON string)'. _parse_json_array + validate_numeric_id coerce both ints and strings, so this works, but the mixed int/string guidance is mildly ambiguous. Capability gap: No way to set CampaignCriterion.negative — language criteria are positive-only here, which is the normal case, but the API does support negative; not exposed (acceptable, low value).", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "language_ids", "type": "list", "required": true, "description": "List of Google language constant IDs (or a JSON string). Use action:ads/list_languages to look them up.", "example": [ 1000, 1003 ] }, { "name": "limit", "type": "integer", "required": false, "description": "Optional cap on the number of languages applied." } ], "output_shape": "{\"success\": true, \"message\": \"Language targeting replaced with 2 language(s)\", \"results\": [...]}" }, { "id": "action:ads/set_network_settings", "namespace": "ads", "action": "set_network_settings", "description": "Toggle Google campaign search-partners or display-network distribution; review-gated, reversible.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_campaigns", "action:ads/update_campaign", "action:_batch/submit" ], "long_description": "Primary use:\nEnable or disable search partners and display network distribution on a Google Search campaign.\n\nUse when:\n- The user wants to enable / disable Google Search Partners.\n- The user wants to enable / disable Display Network for a Search campaign.\n\nDo not use when:\n- The platform is Meta. Meta uses publisher_platforms inside the ad set targeting spec.\n- The user wants to toggle YouTube or other channels. This action handles only the four Google campaign network_settings booleans.\n\nWorkflow:\n1. Decide which network booleans to flip.\n Prefer the native target_* parameters. search_partners maps to target_search_network and display_network maps to target_content_network.\n Enabling target_search_network may require target_google_search=true in the same update.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/list_campaigns and/or gaql_search.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message lists the field-mask paths that were updated.\n\nSide effects:\n- Campaign update with field-mask for the supplied native network_settings fields.\n- Raises ValueError if neither boolean is provided.\n- Reversible: re-call with the prior boolean values.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns.", "source": "upstream:action:ads/create_campaign,action:ads/list_campaigns" }, { "name": "search_partners", "type": "boolean", "required": false, "description": "Enable search-partner distribution. At least one of this or display_network is required.", "example": true }, { "name": "display_network", "type": "boolean", "required": false, "description": "Enable display-network distribution.", "example": false }, { "name": "target_google_search", "type": "boolean", "required": false, "description": "Native Google flag for Google Search distribution.", "example": true }, { "name": "target_search_network", "type": "boolean", "required": false, "description": "Native Google flag for search-partner distribution; preferred over legacy search_partners.", "example": true }, { "name": "target_content_network", "type": "boolean", "required": false, "description": "Native Google flag for Display Network distribution; preferred over legacy display_network.", "example": false }, { "name": "target_partner_search_network", "type": "boolean", "required": false, "description": "Native Google flag for partner Search Network distribution.", "example": false } ], "output_shape": "{\"success\": true, \"message\": \"Network settings updated: network_settings.target_search_network\", \"results\": [...]}" }, { "id": "action:ads/suggest_conversion_group_setup", "namespace": "ads", "action": "suggest_conversion_group_setup", "description": "Suggest onboarding templates for creating conversion groups from synced platform conversion actions.", "mode": "read", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_groups", "action:ads/list_conversion_actions", "action:ads/apply_conversion_group_template" ], "long_description": "Primary use:\nOnboarding assistant for brands with no or incomplete conversion_group setup. Call this after list_conversion_groups returns empty, or when the user asks for CPA/ROAS/Reg Rate/Pur Rate but no business conversion groups are configured.\n\nThe action inspects brand-visible synced conversion actions and returns recommended templates such as qualified-new-user and payment-success, including suggested rules with confidence and a human-readable reason.\n\nThis action is read-only. Applying the recommended setup requires the review-gated apply_conversion_group_template action.", "input_params": [ { "name": "platform", "type": "string", "required": false, "description": "Optional platform filter. Use google, meta, or chatgpt_ads; omit to inspect all.", "example": "google" }, { "name": "template_slugs", "type": "list", "required": false, "description": "Optional subset of template slugs. Supported values: qualified-new-user, payment-success.", "example": [ "qualified-new-user", "payment-success" ] } ], "output_shape": "{\"configured\": false, \"existing_group_slugs\": [], \"templates\": [{\"slug\": \"qualified-new-user\", \"name\": \"Qualified New User\", \"already_configured\": false, \"derived_metrics\": {\"cost_per_label\": \"CPA\", \"rate_label\": \"Reg Rate\", \"rate_denominator\": \"clicks\", \"value_label\": null, \"roas_label\": null}, \"suggested_rules\": [{\"account_id\": 5, \"conversion_action_id\": \"sign_up\", \"conversion_action_name\": \"Sign Up\", \"confidence\": 0.82, \"reason\": \"...\"}], \"missing_required_mapping\": false}]}" }, { "id": "action:ads/unbind_conversion_group_goal", "namespace": "ads", "action": "unbind_conversion_group_goal", "description": "Review-gated unlink of a conversion goal from a conversion group.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/bind_conversion_group_goal", "action:ads/list_conversion_groups" ], "long_description": "Removes a goal link from an Admin View conversion group after human approval. Idempotent: unlinking a goal that is not linked still succeeds. Requires expected_version.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "group_id", "type": "integer", "required": true, "description": "Conversion group id." }, { "name": "expected_version", "type": "string", "required": true, "description": "Opaque version from list_conversion_groups.groups[].version." }, { "name": "goal_id", "type": "integer", "required": true, "description": "Internal goal id to unlink." } ], "output_shape": "{\"unbound\": true, \"conversion_group\": {\"id\": 1, \"slug\": \"...\", \"version\": \"opaque\"}}" }, { "id": "action:ads/update_ad", "namespace": "ads", "action": "update_ad", "description": "Update ad fields on Google/Meta; review-gated. Google RSA headlines/descriptions are immutable. Also supports TikTok through its provider-native contract.", "mode": "risk", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ads", "action:ads/create_ad", "action:ads/remove_ad", "action:_batch/submit" ], "long_description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nMutate an existing ad's status, URLs (Google), or creative wiring (Meta).\n\nUse when:\n- The user wants to activate, pause, or remove an ad.\n- The user wants to update final_urls on a Google RSA.\n- The user wants to swap a Meta ad's creative_id or tracking_specs.\n\nDo not use when:\n- The user wants to update Google RSA headlines or descriptions. These are NOT mutable; use action:ads/remove_ad then action:ads/create_ad.\n- The user wants to update the ad group or ad set. Use action:ads/update_ad_group or action:ads/update_adset.\n\nWorkflow:\n1. Call action:ads/list_ads to capture prior state.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/list_ads.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Google raw.message lists the fields updated; warnings[] may explain that RSA creative fields were skipped.\n- Google returns success=false with the warning message if only RSA creative fields are supplied (nothing actually mutable).\n- Meta raw is {success: true}.\n\nSide effects:\n- Google: status update targets AdGroupAd; final_urls update targets the Ad. Headlines/descriptions are silently skipped with a warning.\n- Meta: POST {ad_id} with supplied fields. If creative_id is provided it is wrapped as creative={creative_id: ...}.\n- Setting status to ENABLED/ACTIVE resumes spend immediately.\n- Reversible if prior values captured.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin.", "example": "google", "enum": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_id", "type": "string", "required": true, "description": "Ad id from action:ads/list_ads. Required when platform=tiktok.", "example": "123" }, { "name": "status", "type": "string", "required": false, "description": "Provider-specific lifecycle state (no global enum). TikTok and Google: ENABLED, PAUSED, or REMOVED; TikTok maps these to ENABLE, DISABLE, or DELETE. Meta: ACTIVE, PAUSED, or ARCHIVED; unified ENABLED/REMOVED map to ACTIVE/ARCHIVED. ChatGPT Ads: lowercase active or paused.", "example": "PAUSED", "source": "constant" }, { "name": "final_urls", "type": "list", "required": false, "description": "Google only: list of final landing URLs (or a JSON string).", "platform": [ "google", "meta" ] }, { "name": "headlines", "type": "list", "required": false, "description": "Google only: RSA headlines ARE mutable in place (correcting prior docs). Provide the FULL replacement set of 3-15 headlines, each <=30 chars; replaces (not merges) existing headlines. Items may be strings or {text, pinned_field=HEADLINE_1|2|3}. Do NOT remove+recreate the ad just to change copy. Source: agent-generated replacement ad copy.", "example": [ "New Headline A", "New Headline B", "New Headline C" ], "platform": [ "google", "meta" ] }, { "name": "descriptions", "type": "list", "required": false, "description": "Google only: RSA descriptions ARE mutable in place. Provide the FULL replacement set of 2-4 descriptions, each <=90 chars; replaces existing. Items may be strings or {text, pinned_field=DESCRIPTION_1|2}. Source: agent-generated replacement ad copy.", "example": [ "Updated description one.", "Updated description two." ], "platform": [ "google", "meta" ] }, { "name": "final_mobile_urls", "type": "list", "required": false, "description": "Google only: replacement set of mobile-specific landing URLs on the Ad resource. Replace-not-merge. Source: user-provided mobile landing URL(s).", "example": [ "https://m.example.com/lp" ], "platform": [ "google", "meta" ] }, { "name": "tracking_url_template", "type": "string", "required": false, "description": "Google only: set/replace the ad-level click-tracking URL template. Pass an empty string to clear it. Source: user-provided tracking template.", "example": "{lpurl}?utm_source=google", "platform": [ "google", "meta" ] }, { "name": "creative_id", "type": "string", "required": false, "description": "Meta only: swap to a different creative id.", "platform": [ "google", "meta" ] }, { "name": "creative", "type": "object", "required": false, "description": "Meta only: inline AdCreative spec OR {\"creative_id\": \"...\"} to swap the ad's creative. If creative_id is also supplied, creative_id wins and the inline object is ignored. Swapping a creative re-enters ad review. Source: constructed by the agent or output of create_ad_creative.", "example": { "creative_id": "23847900000000123" }, "platform": [ "google", "meta" ] }, { "name": "tracking_specs", "type": "list", "required": false, "description": "Meta only: list of conversion-tracking spec objects, e.g. [{\"action.type\": [\"offsite_conversion\"], \"fb_pixel\": [\"\"]}]. Source: pixel id from the account's tracking setup.", "example": [ { "action.type": [ "offsite_conversion" ], "fb_pixel": [ "1234567890" ] } ], "platform": [ "google", "meta" ] }, { "name": "conversion_domain", "type": "string", "required": false, "description": "Meta only: the attribution domain for a conversion/sales/leads ad (off-site optimization). Conditionally REQUIRED by Meta for those objectives; setting/repairing it is otherwise impossible. Must be a verified domain associated with the pixel event. Source: user-provided verified domain (must match Business Manager domain verification).", "example": "example.com", "platform": [ "google", "meta" ] }, { "name": "name", "type": "string", "required": false, "description": "Meta only: new name for the ad (renames the Ad node). Google only: not applicable. Source: user-provided ad name. ChatGPT Ads: Optional replacement authored Ad name.", "example": "Summer sale - v2", "platform": [ "google", "meta", "chatgpt_ads", "linkedin" ] }, { "name": "creatives", "type": "array", "required": false, "description": "TikTok only: partial creative updates (each carrying its ad_id) submitted as an ad-group-scoped array. Omitted fields retain their current values. If identity/material fields are supplied, they must satisfy create_ad's compatibility rules.", "example": [ { "ad_id": "178", "ad_text": "New copy" } ], "platform": [ "tiktok" ] }, { "name": "adgroup_id", "type": "string", "required": false, "description": "TikTok only: ad group id (falls back to adset_id).", "example": "17840000000000000", "platform": [ "tiktok" ] }, { "name": "headline", "type": "string", "required": false, "description": "Authored Ad headline.", "platform": [ "chatgpt_ads" ] }, { "name": "copy", "type": "string", "required": false, "description": "Authored Ad body copy.", "platform": [ "chatgpt_ads" ] }, { "name": "cta", "type": "string", "required": false, "description": "Explicitly approved call to action.", "platform": [ "chatgpt_ads" ] }, { "name": "description", "type": "string", "required": false, "description": "Optional authored Ad description.", "platform": [ "chatgpt_ads" ] }, { "name": "landing_page", "type": "string", "required": false, "description": "Final HTTP(S) destination URL.", "platform": [ "chatgpt_ads" ] }, { "name": "image_url", "type": "string", "required": false, "description": "Optional durable public image URL.", "platform": [ "chatgpt_ads" ] }, { "name": "ad_group_id", "type": "string", "required": false, "description": "Canonical parent Ad Group id. Do not send adset_id for ChatGPT Ads.", "example": "ag_123", "source": "upstream:action:ads/list_ad_groups", "platform": [ "tiktok", "chatgpt_ads" ] }, { "name": "adset_id", "type": "string", "required": false, "description": "Provider-native parent alias. Do not send this field for ChatGPT Ads; send exactly one canonical ad_group_id instead.", "example": "value", "platform": [ "google", "meta", "tiktok" ] } ], "output_shape": "{\"success\": true, \"message\": \"Ad updated: status, final_urls\", \"results\": [...], \"warnings\": [\"Headlines and descriptions cannot be updated on an existing RSA...\"]} // Meta: {\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_ad_group", "namespace": "ads", "action": "update_ad_group", "description": "Update a Google or ChatGPT Ads Ad Group; review-gated. Enabling/activating may resume spend.", "mode": "risk", "platforms": [ "google", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/create_ad_group", "action:ads/remove_ad_group", "action:_batch/submit" ], "long_description": "Primary use:\nUpdate caller-controlled fields on an existing Google or ChatGPT Ads Ad Group.\n\nUse when:\n- The user approved a name, lifecycle, bid, content, targeting, budget, or tracking change.\n- A ChatGPT Ads manual group needs a persisted field correction.\n\nDo not use when:\n- The platform is Meta or TikTok; use action:ads/update_adset.\n- A ChatGPT Ads manual group still has authored Ads and the requested type is generative.\n- The update would activate delivery without separate explicit approval.\n\nWorkflow:\n1. Read the current Ad Group and capture the before state.\n2. Present the exact presence-aware patch and submit it for HITL review.\n3. Read the same id back and verify every supplied field, including explicit null.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- ChatGPT Ads returns the exact persisted Ad Group after readback proof.\n\nSide effects:\n- Mutates only supplied fields. Google lifecycle values are ENABLED, PAUSED, or REMOVED; ChatGPT Ads lifecycle values are lowercase active or paused.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, chatgpt_ads.", "example": "google", "enum": [ "google", "chatgpt_ads" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Ad group id from action:ads/gaql_search.", "example": "987654" }, { "name": "name", "type": "string", "required": false, "description": "New ad group display name." }, { "name": "status", "type": "string", "required": false, "description": "Provider-specific lifecycle value: Google uses ENABLED, PAUSED, or REMOVED; ChatGPT Ads uses lowercase active or paused. Activation requires separate approval.", "source": "constant" }, { "name": "cpc_bid_micros", "type": "integer", "required": false, "description": "Default CPC bid in micros (1 USD = 1_000_000).", "platform": [ "google" ] }, { "name": "group_type", "type": "string", "required": false, "description": "manual (default) or generative.", "example": "manual", "enum": [ "manual", "generative" ], "source": "constant", "platform": [ "chatgpt_ads" ] }, { "name": "context", "type": "string", "required": false, "description": "Conversation context used by the Ad Group.", "platform": [ "chatgpt_ads" ] }, { "name": "negative_context", "type": "string", "required": false, "description": "Explicitly approved contexts where the Ad Group must not appear.", "platform": [ "chatgpt_ads" ] }, { "name": "ad_prompt", "type": "string", "required": false, "description": "Explicitly approved generation prompt; required by the deployment workflow for generative groups.", "platform": [ "chatgpt_ads" ] }, { "name": "image_url", "type": "string", "required": false, "description": "Optional durable public Ad Group image URL.", "platform": [ "chatgpt_ads" ] }, { "name": "daily_budget", "type": "number", "required": false, "description": "Optional Ad Group daily budget.", "platform": [ "chatgpt_ads" ] }, { "name": "auto_optimize_budget", "type": "boolean", "required": false, "description": "Whether the platform automatically optimizes the Ad Group budget. Defaults to true.", "platform": [ "chatgpt_ads" ] }, { "name": "min_daily_budget", "type": "number", "required": false, "description": "Optional minimum daily-spend bound.", "platform": [ "chatgpt_ads" ] }, { "name": "max_daily_budget", "type": "number", "required": false, "description": "Optional maximum daily-spend bound.", "platform": [ "chatgpt_ads" ] }, { "name": "max_cpc", "type": "number", "required": false, "description": "Optional maximum CPC bound.", "platform": [ "chatgpt_ads" ] }, { "name": "sort_order", "type": "integer", "required": false, "description": "Optional stable Ad Group display order.", "platform": [ "chatgpt_ads" ] }, { "name": "allowed_countries", "type": "list", "required": false, "description": "Explicitly approved country targeting.", "platform": [ "chatgpt_ads" ] }, { "name": "allowed_devices", "type": "list", "required": false, "description": "Explicitly approved device targeting.", "platform": [ "chatgpt_ads" ] }, { "name": "include_audience_ids", "type": "list", "required": false, "description": "Audience ids explicitly included.", "platform": [ "chatgpt_ads" ] }, { "name": "exclude_audience_ids", "type": "list", "required": false, "description": "Audience ids explicitly excluded.", "platform": [ "chatgpt_ads" ] }, { "name": "example_query", "type": "string", "required": false, "description": "Example query that should match the group.", "platform": [ "chatgpt_ads" ] }, { "name": "utm_params", "type": "list", "required": false, "description": "Ordered UTM objects with key and value fields.", "example": [ { "key": "utm_source", "value": "chatgpt" } ], "platform": [ "chatgpt_ads" ] }, { "name": "preview", "type": "object", "required": false, "description": "Structured advertiser-dashboard preview metadata.", "platform": [ "chatgpt_ads" ] }, { "name": "landing_page", "type": "string", "required": false, "description": "Replacement final HTTP(S) destination URL for the Ad Group.", "example": "https://example.com", "platform": [ "chatgpt_ads" ] }, { "name": "brand_name", "type": "string", "required": false, "description": "Replacement brand display name shown by the advertiser dashboard.", "example": "Acme", "platform": [ "chatgpt_ads" ] } ], "output_shape": "{\"success\": true, \"message\": \"Ad group updated: name, status, cpc_bid_micros\", \"results\": [...]}" }, { "id": "action:ads/update_ad_rule", "namespace": "ads", "action": "update_ad_rule", "description": "Update a Meta or TikTok automated ad rule (spec or status); review-gated, reversible by writing back the previous values.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_ad_rules", "action:ads/create_ad_rule", "action:ads/remove_ad_rule", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nModify an existing automated rule in place: rename it, replace its evaluation/execution/schedule specs, or turn it on/off via status.\n\nUse when:\n- The user wants to pause (disable) or re-enable an automated rule. Pausing a rule = status=DISABLED.\n- Thresholds, filters, or the executed action of a rule need changing.\n- A rule's schedule cadence needs adjusting.\n\nDo not use when:\n- The user wants the rule permanently gone. Use action:ads/remove_ad_rule.\n- The user wants a new rule alongside the old one. Use action:ads/create_ad_rule.\n- The platform is Google.\n\nWorkflow:\n1. Confirm rule_id via action:ads/list_ad_rules.\n2. Provide only the fields to change among name / evaluation_spec / execution_spec / schedule_spec / status (at least one). Spec objects REPLACE the stored spec wholesale — send the complete new spec, not a partial patch.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/list_ad_rules.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body: {success: true}.\n- Returns success=false with error_code=not_found when the rule does not exist OR belongs to a different ad account than the resolved one (ownership pre-check).\n- Returns success=false with error_code=invalid_param when no updatable field is provided.\n\nSide effects:\n- Meta POSTs provided fields to {rule_id}. TikTok replaces the official rules[] spec through /optimizer/rule/update/ or maps ACTIVE/ENABLED and PAUSED/DISABLED to /optimizer/rule/update/status/.\n- STATUS ENUM WARNING: the status enum here is the AdRule enum ENABLED/DISABLED (plus HAS_ISSUES), NOT the generic ACTIVE/PAUSED lifecycle enum — the platform maps generic values (ENABLED/ACTIVE->ENABLED, PAUSED->DISABLED) before the write. status=DELETED is rejected with error_code=invalid_param: deleting a rule is irreversible and goes through action:ads/remove_ad_rule.\n- Ownership is verified against the active account before the write.\n- Reversible: write the previous name/specs/status back.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "tiktok", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id. Used for the ownership pre-check: the rule must belong to this account or the action fails with not_found.", "example": "1234567890" }, { "name": "rule_id", "type": "string", "required": true, "description": "Ad rule id from action:ads/list_ad_rules or action:ads/create_ad_rule (raw.id). Required when platform=tiktok.", "example": "97800000000000000" }, { "name": "name", "type": "string", "required": false, "description": "New rule display name. At least one updatable field must be provided.", "platform": [ "meta" ] }, { "name": "evaluation_spec", "type": "object", "required": false, "description": "Meta only: replacement evaluation spec (same shape as action:ads/create_ad_rule). REPLACES the stored spec wholesale — send the full spec including the entity_type scoping filter, not just the changed filters.", "example": { "evaluation_type": "SCHEDULE", "filters": [ { "field": "entity_type", "operator": "EQUAL", "value": "AD" }, { "field": "spend", "operator": "GREATER_THAN", "value": 1000000 }, { "field": "time_preset", "operator": "EQUAL", "value": "LAST_7_DAYS" } ] }, "platform": [ "meta" ] }, { "name": "execution_spec", "type": "object", "required": false, "description": "Meta only: replacement execution spec (same shape as action:ads/create_ad_rule; execution_type enum uses NOTIFICATION, not NOTIFY). REPLACES the stored spec wholesale.", "example": { "execution_type": "PAUSE" }, "platform": [ "meta" ] }, { "name": "schedule_spec", "type": "object", "required": false, "description": "Meta only: replacement schedule spec (same shape as action:ads/create_ad_rule). REPLACES the stored spec wholesale.", "example": { "schedule_type": "DAILY" }, "platform": [ "meta" ] }, { "name": "status", "type": "string", "required": false, "description": "New rule status. For Meta the enum is the AdRule enum ENABLED / DISABLED (plus HAS_ISSUES), NOT the generic ACTIVE/PAUSED lifecycle enum; the platform maps generic values (PAUSED->DISABLED, ACTIVE->ENABLED). Pausing a rule = status=DISABLED. DELETED is rejected (invalid_param) — deletion is irreversible and goes through action:ads/remove_ad_rule. For TikTok, ENABLED/ACTIVE maps to TURN_ON and DISABLED/PAUSED maps to TURN_OFF. Source: constant enum chosen by the agent.", "example": "DISABLED", "platform": [ "meta" ] }, { "name": "rules", "type": "array", "required": false, "description": "TikTok only: complete replacement rules array. Each rule_id must match the top-level rule_id. Omit when only changing status." }, { "name": "lang", "type": "string", "required": false, "description": "TikTok lang.", "example": "value", "platform": [ "tiktok" ] }, { "name": "operate_type", "type": "string", "required": false, "description": "TikTok operate type.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_adset", "namespace": "ads", "action": "update_adset", "description": "Update Meta ad set fields; review-gated, setting status=ACTIVE resumes spend immediately. Also supports TikTok through its provider-native contract.", "mode": "risk", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_adsets", "action:ads/get_adset", "action:ads/create_adset", "action:ads/remove_adset", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nMutate an existing Meta ad set's status, targeting, budget, billing, or optimization goal.\n\nUse when:\n- The user wants to activate (status=ACTIVE) or pause an ad set.\n- The user wants to change targeting, budget, bid_amount, or optimization_goal.\n\nDo not use when:\n- The platform is Google. Use action:ads/update_ad_group.\n- The ad set is archived and the user wants to re-activate. Meta requires un-archive to PAUSED first via update_adset status=PAUSED, then a second update_adset with status=ACTIVE.\n\nWorkflow:\n1. Call action:ads/list_adsets to capture the prior state.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/list_adsets.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body: {success: true}.\n\nSide effects:\n- POSTs the supplied fields to {adset_id}; Meta merges into the existing ad set.\n- Setting status=ACTIVE resumes spend immediately if budget is non-zero.\n- Reversible if prior values captured.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "adset_id", "type": "string", "required": true, "description": "Ad set id from action:ads/list_adsets. Required when platform=tiktok.", "example": "23..." }, { "name": "name", "type": "string", "required": false, "description": "New ad set display name." }, { "name": "status", "type": "string", "required": false, "description": "Provider-specific lifecycle state (no global enum). TikTok: ENABLED, PAUSED, or REMOVED, mapped to ENABLE, DISABLE, or DELETE. Meta: ACTIVE, PAUSED, or ARCHIVED; unified ENABLED/REMOVED map to ACTIVE/ARCHIVED.", "example": "PAUSED", "source": "constant" }, { "name": "billing_event", "type": "string", "required": false, "description": "See create_adset for legal values.", "platform": [ "meta" ] }, { "name": "optimization_goal", "type": "string", "required": false, "description": "See create_adset for legal values.", "platform": [ "meta" ] }, { "name": "targeting", "type": "object", "required": false, "description": "Meta targeting spec object (replaces the existing targeting).", "platform": [ "meta" ] }, { "name": "bid_amount", "type": "integer", "required": false, "description": "Bid in cents.", "platform": [ "meta" ] }, { "name": "daily_budget", "type": "integer", "required": false, "description": "Daily budget in cents.", "platform": [ "meta" ] }, { "name": "lifetime_budget", "type": "integer", "required": false, "description": "Lifetime budget in cents.", "platform": [ "meta" ] }, { "name": "promoted_object", "type": "object", "required": false, "description": "Promoted object spec.", "platform": [ "meta" ] }, { "name": "start_time", "type": "string", "required": false, "description": "ISO-8601 start time.", "platform": [ "meta" ] }, { "name": "end_time", "type": "string", "required": false, "description": "ISO-8601 end time.", "platform": [ "meta" ] }, { "name": "bid_strategy", "type": "string", "required": false, "description": "Meta only: change the ad-set bid strategy. One of LOWEST_COST_WITHOUT_CAP, LOWEST_COST_WITH_BID_CAP, COST_CAP, LOWEST_COST_WITH_MIN_ROAS. Cap strategies require bid_amount. Source: constant enum chosen by the agent from user intent.", "example": "LOWEST_COST_WITH_BID_CAP", "platform": [ "meta" ] }, { "name": "frequency_control_specs", "type": "list", "required": false, "description": "Meta only: frequency capping rules (REACH/awareness), list of {event, interval_days, max_frequency} objects. Source: constant config from the user's frequency cap request.", "example": [ { "event": "IMPRESSIONS", "interval_days": 7, "max_frequency": 2 } ], "platform": [ "meta" ] }, { "name": "is_dynamic_creative", "type": "boolean", "required": false, "description": "Meta only: toggle Dynamic Creative on the ad set. Source: user intent.", "example": true, "platform": [ "meta" ] }, { "name": "attribution_spec", "type": "list", "required": false, "description": "Meta only: conversion attribution windows, list of {event_type, window_days}. Source: constant config chosen by the agent.", "example": [ { "event_type": "CLICK_THROUGH", "window_days": 7 } ], "platform": [ "meta" ] }, { "name": "destination_type", "type": "string", "required": false, "description": "Meta only: conversion destination, e.g. WEBSITE, MESSENGER, WHATSAPP, INSTAGRAM_DIRECT. Source: constant enum chosen from objective/user intent.", "example": "WEBSITE", "platform": [ "meta" ] }, { "name": "dsa_beneficiary", "type": "string", "required": false, "description": "Meta only: EU DSA beneficiary name (required for EU-regulated targeting). Source: user-provided beneficiary name.", "example": "Acme Inc", "platform": [ "meta" ] }, { "name": "dsa_payor", "type": "string", "required": false, "description": "Meta only: EU DSA payer name (required alongside dsa_beneficiary for EU-regulated targeting). Source: user-provided payer name.", "example": "Acme Inc", "platform": [ "meta" ] }, { "name": "adgroup_name", "type": "string", "required": false, "description": "TikTok only: new ad group name.", "example": "AG 1 v2", "platform": [ "tiktok" ] }, { "name": "budget", "type": "number", "required": false, "description": "TikTok only: new budget as a float in the account currency.", "example": 25.0, "platform": [ "tiktok" ] }, { "name": "location_ids", "type": "list", "required": false, "description": "TikTok only: Target location ids; provide this or zipcode_ids when geography is required.", "example": [ "6252001" ], "platform": [ "tiktok" ] }, { "name": "zipcode_ids", "type": "list", "required": false, "description": "TikTok only: Target ZIP/postal code ids; provide this or location_ids when geography is required. ZIP targeting requires PLACEMENT_TYPE_NORMAL and PLACEMENT_TIKTOK.", "example": [ "123456789" ], "platform": [ "tiktok" ] }, { "name": "age_groups", "type": "list", "required": false, "description": "TikTok only: Target age group enums.", "example": [ "AGE_25_34" ], "platform": [ "tiktok" ] }, { "name": "gender", "type": "string", "required": false, "description": "TikTok only: Target gender.", "example": "GENDER_UNLIMITED", "platform": [ "tiktok" ] }, { "name": "languages", "type": "list", "required": false, "description": "TikTok only: Target language codes.", "example": [ "en" ], "platform": [ "tiktok" ] }, { "name": "interest_category_ids", "type": "list", "required": false, "description": "TikTok only: Interest category ids.", "example": [ "100" ], "platform": [ "tiktok" ] }, { "name": "interest_keyword_ids", "type": "list", "required": false, "description": "TikTok only: Interest keyword ids.", "example": [ "200" ], "platform": [ "tiktok" ] }, { "name": "audience_ids", "type": "list", "required": false, "description": "TikTok only: Included custom-audience ids.", "example": [ "300" ], "platform": [ "tiktok" ] }, { "name": "excluded_audience_ids", "type": "list", "required": false, "description": "TikTok only: Excluded custom-audience ids.", "example": [ "400" ], "platform": [ "tiktok" ] }, { "name": "operating_systems", "type": "list", "required": false, "description": "TikTok only: Target operating systems.", "example": [ "ANDROID" ], "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_bidding_strategy", "namespace": "ads", "action": "update_bidding_strategy", "description": "Update a reusable Google portfolio Target CPA or Target ROAS strategy.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/update_campaign" ], "long_description": "Primary use:\nUpdate a reusable Google portfolio Target CPA or Target ROAS strategy.\n\nUse when:\n- You need a reusable Google Target CPA or Target ROAS portfolio strategy.\n\nDo not use when:\n- You need a campaign-specific standard bidding strategy; use action:ads/update_campaign.\n\nWorkflow:\n1. Read the current strategy or campaign state when changing an existing resource.\n2. For write operations, submit through action:_batch/submit for HITL review.\n3. Verify the resulting state with action:ads/get_bidding_strategy.\n\nOutput semantics:\n- Returns the created, read, updated, listed, or removed Google portfolio strategy result.\n\nSide effects:\n- Creates, updates, or removes a Google portfolio strategy. Removing a strategy is irreversible and requires it to be unused.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id without dashes.", "example": "1234567890" }, { "name": "name", "type": "string", "required": false, "description": "Portfolio bidding strategy name; optional when updating.", "example": "Target CPA portfolio" }, { "name": "bidding_strategy_id", "type": "string", "required": true, "description": "Google portfolio bidding strategy id.", "example": "123" }, { "name": "bidding_strategy_type", "type": "string", "required": false, "description": "Portfolio strategy type; optional for a rename-only update.", "example": "TARGET_CPA", "enum": [ "TARGET_CPA", "TARGET_ROAS" ] }, { "name": "target_cpa_micros", "type": "integer", "required": false, "description": "Required when bidding_strategy_type is TARGET_CPA.", "example": 5000000 }, { "name": "target_roas", "type": "number", "required": false, "description": "Required when bidding_strategy_type is TARGET_ROAS.", "example": 3.5 } ], "output_shape": "{\"success\":true,\"raw\":{}}" }, { "id": "action:ads/update_campaign", "namespace": "ads", "action": "update_campaign", "description": "Update a Google, Meta, TikTok, or ChatGPT Ads campaign; review-gated. Activation can resume or begin spend immediately.", "mode": "risk", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_campaigns", "action:ads/create_campaign", "action:ads/remove_campaign", "action:_batch/submit", "action:ads/list_operation_records" ], "long_description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nMutate an existing campaign's name, status, budget, bidding strategy, or platform-specific settings. This is the canonical way to activate, pause, rename, rebudget, or change campaign-level bidding.\n\nUse when:\n- The user wants to activate a paused campaign (status -> ENABLED on Google or ACTIVE on Meta).\n- The user wants to pause an active campaign or change its daily budget.\n- The user wants to change a Google campaign bidding strategy (MAXIMIZE_CLICKS, TARGET_CPA, TARGET_ROAS, etc.).\n- The user wants to retarget the Meta objective, bid_strategy, or spend_cap.\n\nDo not use when:\n- The campaign does not exist yet. Use action:ads/create_campaign.\n- The change touches ad groups, ad sets, ads, or keywords. Use the matching action:ads/update_ad_group / update_adset / update_ad / update_keyword.\n- The user wants targeting changes. Use action:ads/set_geo_targeting, set_language_targeting, set_network_settings, or set_bid_modifier.\n\nWorkflow:\n1. Call action:ads/list_campaigns to fetch the current state and capture old values for rollback.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/list_campaigns or action:ads/gaql_search with dimensions=['campaign','bidding_strategy'].\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- Google raw.message lists the fields actually updated (name, status, start_date, end_date, budget_amount_micros, bidding_strategy, target_cpa_micros, target_roas, max_cpc_bid_micros, asset_automation). Returns success=false with 'No fields to update were provided.' if no mutable field is supplied.\n- When asset_automation is supplied, raw.asset_automation_settings is the COMPLETE merged list that was written (including entries you did not name), so it can be snapshotted as the approved intent.\n- Meta raw is the Graph API body: {success: true}.\n\nSide effects:\n- Modifies platform-side campaign settings; reversible only if the caller captured the prior value before the update.\n- Setting status to ENABLED (Google) or ACTIVE (Meta) resumes spend immediately if the campaign has non-zero budget.\n- Google performs at most two MutateOperations in one mutate: a Campaign update plus an optional CampaignBudget update for amount_micros. Google bidding changes update the Campaign bidding oneof; TARGET_CPA requires target_cpa_micros and TARGET_ROAS requires target_roas. MAXIMIZE_CONVERSIONS accepts an optional target_cpa_micros and MAXIMIZE_CONVERSION_VALUE an optional target_roas (Google's standard tCPA/tROAS forms); a target value that does not apply to the chosen strategy is rejected instead of silently dropped. Switching to MAXIMIZE_CONVERSIONS/MAXIMIZE_CONVERSION_VALUE without a target clears any existing target on the campaign (warning emitted).\n- Google asset_automation adds a third read (the campaign's current asset_automation_settings) before the mutate, and writes the merged list back under the bare 'asset_automation_settings' mask path. Two invariants are asserted before the mutate is sent: no automation type you did not name changes status, and no type appears twice; either failure returns success=false without touching the platform.\n- Reversibility: re-call with prior values to roll back.\n\nChatGPT Ads activation:\n- Keep creation paused. status=active is a separate risk write and requires its own explicit user approval.\n- Creation still sends the server-owned network_mode=openai under the private account-capability contract. A public generic advertiser-dashboard contract that describes server-managed allocation does not prove or replace that private contract.\n- Read action:ads/get_account_info first. Its activation_preflight is conservative and non-authoritative. Immediately before PATCH, Soku reads the exact Campaign and requires the same id and an explicitly non-active state. Identity-mismatched, active, or failed readback stops with no activation PATCH. Provider network_mode read metadata is unfinished and does not gate activation.\n- An activation-only HTTP 402 returns CHATGPT_ADS_BILLING_REQUIRED with a safe account-side funding handoff. Retry the same approved activation only after the account-side binding is complete.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google, meta, tiktok, chatgpt_ads, linkedin.", "example": "chatgpt_ads", "enum": [ "google", "meta", "tiktok", "chatgpt_ads", "linkedin" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "campaign_id", "type": "string", "required": true, "description": "Campaign id from action:ads/list_campaigns. Numeric on both platforms. Required when platform=tiktok.", "example": "123456" }, { "name": "name", "type": "string", "required": false, "description": "New campaign name." }, { "name": "status", "type": "string", "required": false, "description": "Provider-specific lifecycle state (no global enum because the shared action has different provider vocabularies). TikTok: ENABLED, PAUSED, or REMOVED, mapped to ENABLE, DISABLE, or DELETE. ChatGPT Ads: DRAFT, ACTIVE, or PAUSED. Google/Meta follow their provider contracts. For ChatGPT Ads ACTIVE, exact Campaign GET must prove the same id and a non-active state; identity mismatch, active state, and read failures send no activation PATCH. Provider network_mode read metadata is unfinished and does not gate activation. REMOVED is allowed only for update where supported.", "example": "PAUSED", "source": "constant" }, { "name": "start_date", "type": "string", "required": false, "description": "Google only: new campaign start date in YYYY-MM-DD. Cannot be edited once the campaign has started. Source: user-provided date.", "example": "2026-07-05", "platform": [ "google", "meta" ] }, { "name": "end_date", "type": "string", "required": false, "description": "Google only: new campaign end date in YYYY-MM-DD. Source: user-provided date.", "example": "2026-08-31", "platform": [ "google", "meta" ] }, { "name": "budget_daily_micros", "type": "integer", "required": false, "description": "Daily budget in micros (1 USD = 1_000_000). Google updates the linked CampaignBudget; Meta converts to daily_budget in cents.", "example": 50000000, "platform": [ "google", "meta" ] }, { "name": "bidding_strategy", "type": "string", "required": false, "description": "Google only: campaign bidding strategy. One of MANUAL_CPC, MAXIMIZE_CLICKS, MAXIMIZE_CONVERSIONS, MAXIMIZE_CONVERSION_VALUE, TARGET_CPA, TARGET_ROAS, TARGET_SPEND. Required when updating target_cpa_micros, target_roas, or max_cpc_bid_micros. Source: constant enum chosen from the user's bidding intent.", "example": "MAXIMIZE_CLICKS", "enum": [ "MANUAL_CPC", "MAXIMIZE_CLICKS", "MAXIMIZE_CONVERSIONS", "MAXIMIZE_CONVERSION_VALUE", "TARGET_CPA", "TARGET_ROAS", "TARGET_SPEND" ], "source": "constant", "platform": [ "google" ] }, { "name": "portfolio_bidding_strategy_id", "type": "string", "required": false, "description": "Google only: reusable portfolio Target CPA/ROAS strategy id. Mutually exclusive with bidding_strategy and its target fields.", "example": "123456789", "platform": [ "google" ] }, { "name": "target_cpa_micros", "type": "integer", "required": false, "description": "Google only: target cost-per-action in micros (1 USD = 1_000_000). Required companion when bidding_strategy=TARGET_CPA; optional target when bidding_strategy=MAXIMIZE_CONVERSIONS (Google's standard tCPA form). Rejected for other strategies.", "example": 12000000, "platform": [ "google" ] }, { "name": "target_roas", "type": "number", "required": false, "description": "Google only: return on ad spend as a ratio, e.g. 4.0 for 400%. Required companion when bidding_strategy=TARGET_ROAS; optional target when bidding_strategy=MAXIMIZE_CONVERSION_VALUE (Google's standard tROAS form). Rejected for other strategies.", "example": 4.0, "platform": [ "google" ] }, { "name": "max_cpc_bid_micros", "type": "integer", "required": false, "description": "Google only: optional CPC bid ceiling for MAXIMIZE_CLICKS / TARGET_SPEND. Ignored with a warning for other bidding strategies.", "example": 2500000, "platform": [ "google" ] }, { "name": "asset_automation", "type": "object", "required": false, "description": "Google only: opt in/out of Google's automatically generated ad assets. Object mapping automation type -> OPTED_IN | OPTED_OUT, e.g. {'text_asset_automation': 'OPTED_OUT', 'final_url_expansion': 'OPTED_OUT'}. Campaign-level types: text_asset_automation, final_url_expansion, generate_enhanced_youtube_videos, generate_image_enhancement, generate_image_extraction (all five are ON by default for Performance Max; the two text ones are also supported on Search, where they default OFF). Ad-level Demand Gen types are rejected here. Read-modify-write: the campaign's current settings are read and merged first, because the underlying repeated field can only be written as a whole list — types you do not name keep their status. Trap: with final_url_expansion left OPTED_IN, Google still generates text for expanded URLs and that generation cannot be opted out, so OPTED_OUT on text_asset_automation alone does not stop all text generation. Source: user/agent decision.", "example": { "text_asset_automation": "OPTED_OUT" }, "platform": [ "google" ] }, { "name": "objective", "type": "string", "required": false, "description": "Meta only: campaign objective (see create_campaign for legal values).", "platform": [ "google", "meta" ] }, { "name": "special_ad_categories", "type": "list", "required": false, "description": "Meta only: special category list.", "platform": [ "google", "meta" ] }, { "name": "buying_type", "type": "string", "required": false, "description": "Meta only: AUCTION or RESERVED.", "platform": [ "google", "meta" ] }, { "name": "bid_strategy", "type": "string", "required": false, "description": "Meta only: bid strategy enum.", "platform": [ "google", "meta" ] }, { "name": "spend_cap", "type": "integer", "required": false, "description": "Meta only: campaign lifetime spend cap in account-currency cents (NOT micros). budget_daily_micros is in micros and auto-converted; spend_cap is raw cents -- do not confuse the two units. Source: user-provided cap converted to cents.", "example": 100000, "platform": [ "google", "meta" ] }, { "name": "special_ad_category_country", "type": "list", "required": false, "description": "Meta only: list of ISO country codes; required when special_ad_categories is updated to include a regulated category. Note: objective/buying_type/special_ad_categories are creation-time fields and Meta may reject changing them on an existing campaign. Source: user-provided target country/countries.", "example": [ "US" ], "platform": [ "google", "meta" ] }, { "name": "lifetime_budget", "type": "integer", "required": false, "description": "Meta only: campaign-level lifetime budget in account-currency cents (CBO). Mutually exclusive with the daily budget from budget_daily_micros. Only valid on Advantage Campaign Budget (CBO) campaigns; setting budget on an ad-set-budget campaign returns a Meta error. Source: user-provided total spend cap converted to cents.", "example": 50000, "platform": [ "google", "meta" ] }, { "name": "start_time", "type": "string", "required": false, "description": "Meta only: ISO-8601 start time.", "platform": [ "google", "meta", "chatgpt_ads" ] }, { "name": "stop_time", "type": "string", "required": false, "description": "Meta only: ISO-8601 stop time.", "platform": [ "google", "meta" ] }, { "name": "campaignGroup", "type": "string", "required": false, "description": "LinkedIn only: sponsoredCampaignGroup URN to attach the campaign to; omitted -> a per-account 'Soku Default' group is found or created.", "example": "urn:li:sponsoredCampaignGroup:635137195", "platform": [ "linkedin" ] }, { "name": "costType", "type": "string", "required": false, "description": "LinkedIn only: bid cost type.", "example": "CPC", "enum": [ "CPM", "CPC", "CPV" ], "platform": [ "linkedin" ] }, { "name": "creativeSelection", "type": "string", "required": false, "description": "LinkedIn only: creative rotation strategy.", "enum": [ "ROUND_ROBIN", "OPTIMIZED" ], "platform": [ "linkedin" ] }, { "name": "dailyBudget", "type": "object", "required": false, "description": "LinkedIn only: daily budget as a decimal-string MoneyAmount in the account currency, e.g. {\"amount\": \"25\", \"currencyCode\": \"USD\"}. Amounts are strings, never floats.", "platform": [ "linkedin" ] }, { "name": "totalBudget", "type": "object", "required": false, "description": "LinkedIn only: lifetime budget MoneyAmount (decimal-string amount).", "platform": [ "linkedin" ] }, { "name": "unitCost", "type": "object", "required": false, "description": "LinkedIn only: bid amount MoneyAmount (decimal-string amount).", "platform": [ "linkedin" ] }, { "name": "locale", "type": "object", "required": false, "description": "LinkedIn only: campaign locale, e.g. {\"country\": \"US\", \"language\": \"en\"}.", "platform": [ "linkedin" ] }, { "name": "objectiveType", "type": "string", "required": false, "description": "LinkedIn only: campaign objective.", "enum": [ "BRAND_AWARENESS", "ENGAGEMENT", "JOB_APPLICANTS", "LEAD_GENERATION", "WEBSITE_CONVERSIONS", "WEBSITE_VISITS", "VIDEO_VIEWS" ], "platform": [ "linkedin" ] }, { "name": "offsiteDeliveryEnabled", "type": "boolean", "required": false, "description": "LinkedIn only: serve on the LinkedIn Audience Network beyond the feed.", "platform": [ "linkedin" ] }, { "name": "runSchedule", "type": "object", "required": false, "description": "LinkedIn only: epoch-ms schedule, e.g. {\"start\": 1755000000000}; start is required by LinkedIn even when end is open.", "platform": [ "linkedin" ] }, { "name": "targetingCriteria", "type": "object", "required": false, "description": "LinkedIn only: AND/OR targeting expression over adTargetingFacet URNs (include/exclude clauses).", "platform": [ "linkedin" ] }, { "name": "type", "type": "string", "required": false, "description": "LinkedIn only: campaign format type.", "enum": [ "TEXT_AD", "SPONSORED_UPDATES", "SPONSORED_INMAILS", "DYNAMIC" ], "platform": [ "linkedin" ] }, { "name": "audienceExpansionEnabled", "type": "boolean", "required": false, "description": "LinkedIn only: enable audience expansion for the targeting criteria.", "platform": [ "linkedin" ] }, { "name": "optimizationTargetType", "type": "string", "required": false, "description": "LinkedIn only: spend optimization target; default NONE.", "platform": [ "linkedin" ] }, { "name": "pacingStrategy", "type": "string", "required": false, "description": "LinkedIn only: budget pacing.", "enum": [ "LIFETIME", "ACCELERATED" ], "platform": [ "linkedin" ] }, { "name": "politicalIntent", "type": "string", "required": false, "description": "LinkedIn only: political-advertising declaration; defaults to NOT_POLITICAL. EU-targeted campaigns must surface the non-political confirmation to the human during HITL review and pass their answer here.", "enum": [ "POLITICAL", "NOT_POLITICAL", "NOT_DECLARED" ], "platform": [ "linkedin" ] }, { "name": "budget", "type": "number", "required": false, "description": "TikTok only: new budget as a float in the account currency (NOT micros).", "example": 80.0, "platform": [ "tiktok" ] }, { "name": "budget_mode", "type": "string", "required": false, "description": "TikTok only: new budget mode.", "example": "BUDGET_MODE_DAY", "platform": [ "tiktok" ] }, { "name": "daily_budget", "type": "number", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "landing_page", "type": "string", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "campaign_objective", "type": "string", "required": false, "description": "", "enum": [ "clicks", "reach", "conversions", "leads" ], "source": "constant", "platform": [ "chatgpt_ads" ] }, { "name": "end_time", "type": "string", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "utm_params", "type": "list", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "allowed_countries", "type": "list", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "allowed_devices", "type": "list", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "custom_favicon_url", "type": "string", "required": false, "description": "", "platform": [ "chatgpt_ads" ] }, { "name": "lead_form_config", "type": "object", "required": false, "description": "", "platform": [ "chatgpt_ads" ] } ], "output_shape": "{\"success\": true, \"message\": \"Campaign updated: name, status, budget_amount_micros\", \"results\": [...]} // Meta: {\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_conversion_action", "namespace": "ads", "action": "update_conversion_action", "description": "Update a Google conversion action (name, status ENABLED|HIDDEN, category, value settings) via field mask; risk-gated — value/category steer Smart Bidding.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_conversion_action", "action:ads/remove_conversion_action", "action:ads/list_conversion_actions", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nUpdate an existing Google Ads conversion action by conversion_action_id. Field-mask update: only the fields provided are written; everything else is left untouched.\n\nRISK — Smart Bidding input:\ncategory and value settings (default_value / always_use_default_value) are direct Smart Bidding inputs. Changing them on a conversion action that is in use immediately changes bidding across every campaign optimizing toward it — same blast radius as a bid change, hence the risk gating.\n\nUse when:\n- The user wants to rename a conversion action.\n- The user wants to pause reporting visibility (status=HIDDEN) or restore it (status=ENABLED).\n- The user wants to re-categorize the action or change its default conversion value settings.\n\nDo not use when:\n- The action does not exist yet. Use action:ads/create_conversion_action.\n- The user wants to remove/delete the action. status accepts ENABLED or HIDDEN only — REMOVED is rejected here because removal is permanent; use action:ads/remove_conversion_action.\n- The user wants to change type. It is immutable after create; create a new action instead.\n- The platform is Meta. Meta conversion config lives on pixels/events.\n\nWorkflow:\n1. Get conversion_action_id from action:ads/create_conversion_action raw.resource_id, from action:ads/list_conversion_actions (synced multi-platform list), or via action:ads/gaql_search on conversion_action.\n2. Provide at least one of name / status / category / default_value / always_use_default_value.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/gaql_search on conversion_action.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- The message lists exactly which fields were updated.\n- Providing none of the updatable fields fails fast with a validation error before any mutate.\n\nSide effects:\n- category / value settings changes take effect immediately for Smart Bidding on all campaigns using this conversion action.\n- Reversible by updating the fields back to their previous values (record them before the change).\n- status=HIDDEN keeps recording conversions; it only hides them from default report views.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "conversion_action_id", "type": "string", "required": true, "description": "Numeric conversion action id from action:ads/create_conversion_action raw.resource_id, action:ads/list_conversion_actions, or action:ads/gaql_search on conversion_action.", "example": "123456" }, { "name": "name", "type": "string", "required": false, "description": "New display name; must stay unique within the account. At least one of name / status / category / default_value / always_use_default_value is required." }, { "name": "status", "type": "string", "required": false, "description": "New status: ENABLED or HIDDEN only. REMOVED (or any other value) is rejected — removal is permanent and must go through action:ads/remove_conversion_action. Source: constant enum.", "example": "HIDDEN", "enum": [ "ENABLED", "HIDDEN" ], "source": "constant" }, { "name": "category", "type": "string", "required": false, "description": "New conversion category; same enum as action:ads/create_conversion_action (DEFAULT, PAGE_VIEW, PURCHASE, SIGNUP, ...). RISK: category feeds Smart Bidding goal grouping immediately. Source: constant enum.", "example": "PURCHASE", "enum": [ "ADD_TO_CART", "BEGIN_CHECKOUT", "BOOK_APPOINTMENT", "CONTACT", "DEFAULT", "DOWNLOAD", "GET_DIRECTIONS", "IMPORTED_LEAD", "OUTBOUND_CLICK", "PAGE_VIEW", "PHONE_CALL_LEAD", "PURCHASE", "REQUEST_QUOTE", "SIGNUP", "SUBMIT_LEAD_FORM", "SUBSCRIBE_PAID" ], "source": "constant" }, { "name": "default_value", "type": "number", "required": false, "description": "New default monetary value per conversion (value_settings.default_value). RISK: on value-based bidding (tROAS / Maximize Conversion Value) this immediately changes what every conversion is worth to the bidder. Source: user-provided business value.", "example": 25.0 }, { "name": "always_use_default_value", "type": "boolean", "required": false, "description": "Whether every conversion is recorded with default_value instead of a transaction-specific value (value_settings.always_use_default_value). RISK: flipping this on an ecommerce action replaces real order values with the flat default for bidding. Source: user-provided. NOTE: to set this back to false, use `soku call ads update_conversion_action` with a JSON payload — the typed CLI renders booleans as valueless flags, so the typed command can only send true (omitting sends no field-mask update at all).", "example": true } ], "output_shape": "{\"success\": true, \"message\": \"Conversion action updated: status, default_value\", \"results\": [{\"conversionActionResult\": {\"resourceName\": \"customers/X/conversionActions/Y\"}}]}" }, { "id": "action:ads/update_conversion_group", "namespace": "ads", "action": "update_conversion_group", "description": "Review-gated update of an Admin View conversion_group.", "mode": "write", "platforms": [ "google", "meta", "tiktok", "chatgpt_ads" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_conversion_groups" ], "long_description": "Updates a brand-scoped Admin View conversion_group after human approval. Requires expected_version from list_conversion_groups; stale versions are rejected at approval execution time.", "input_params": [ { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." }, { "name": "group_id", "type": "integer", "required": true, "description": "Conversion group id." }, { "name": "expected_version", "type": "string", "required": true, "description": "Opaque version from list_conversion_groups.groups[].version." }, { "name": "name", "type": "string", "required": false, "description": "Updated display name." }, { "name": "slug", "type": "string", "required": false, "description": "Updated stable slug." }, { "name": "metadata", "type": "list", "required": false, "description": "Replacement [{key, value, description}] metadata fields." } ], "output_shape": "{\"conversion_group\": {\"id\": 1, \"slug\": \"...\", \"version\": \"opaque\"}}" }, { "id": "action:ads/update_custom_audience", "namespace": "ads", "action": "update_custom_audience", "description": "Update a Meta custom audience's name/description metadata; review-gated, reversible by writing back the previous values. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/list_custom_audiences", "action:ads/create_custom_audience", "action:ads/remove_custom_audience", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nRename a Meta custom audience or refresh its description without touching how the audience is populated.\n\nUse when:\n- The user wants to fix a typo in an audience name or align naming conventions.\n- A workflow needs to annotate an audience with an updated description.\n\nDo not use when:\n- The user wants to change the audience rule, subtype, retention, or membership. Those are create-time properties; build a new audience via action:ads/create_custom_audience instead.\n- The user wants to delete the audience. Use action:ads/remove_custom_audience.\n- The platform is Google.\n\nWorkflow:\n1. Confirm audience_id via action:ads/list_custom_audiences.\n2. Provide the new name and/or description (at least one).\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/list_custom_audiences.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Graph API body: {success: true}.\n- Returns success=false with error_code=not_found when the audience does not exist OR belongs to a different ad account than the resolved one (ownership pre-check).\n- Returns success=false with error_code=invalid_param when neither name nor description is provided.\n\nSide effects:\n- POSTs only the provided fields among {name, description} to {audience_id}; targeting/population behavior is unchanged.\n- Ownership is verified against the active account before the write.\n- Reversible: write the previous name/description back.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id. Used for the ownership pre-check: the audience must belong to this account or the action fails with not_found.", "example": "1234567890" }, { "name": "audience_id", "type": "string", "required": true, "description": "Custom audience id from action:ads/list_custom_audiences or action:ads/create_custom_audience (raw.id). Required when platform=tiktok.", "example": "23845000000000000" }, { "name": "name", "type": "string", "required": false, "description": "New audience display name. At least one of name / description must be provided. Required when platform=tiktok." }, { "name": "description", "type": "string", "required": false, "description": "New audience description. At least one of name / description must be provided.", "platform": [ "meta" ] } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_gmv_max_campaign", "namespace": "ads", "action": "update_gmv_max_campaign", "description": "Update a TikTok Shop GMV Max campaign.", "mode": "risk", "platforms": [ "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nUpdate a TikTok Shop GMV Max campaign.\n\nUse when:\nChanging settings on an existing GMV Max campaign.\n\nDo not use when:\nYou only need eligibility checks or reporting.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nMutates a live GMV Max campaign; changes apply on TikTok immediately. Requires shop_exclusivity_acknowledged.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "campaign_id", "type": "string", "required": true, "description": "TikTok campaign id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "shop_exclusivity_acknowledged", "type": "boolean", "required": true, "description": "Explicit acknowledgement required before this side effect.", "example": true, "platform": [ "tiktok" ] }, { "name": "affiliate_posts_enabled", "type": "boolean", "required": false, "description": "TikTok only: Enable affiliate posts.", "example": true, "platform": [ "tiktok" ] }, { "name": "auto_budget_enabled", "type": "boolean", "required": false, "description": "TikTok only: Enable automatic budget allocation.", "example": true, "platform": [ "tiktok" ] }, { "name": "budget", "type": "number", "required": false, "description": "TikTok only: Updated GMV Max campaign budget.", "example": 100.0, "platform": [ "tiktok" ] }, { "name": "campaign_name", "type": "string", "required": false, "description": "TikTok only: Updated GMV Max campaign name.", "example": "GMV Max", "platform": [ "tiktok" ] }, { "name": "custom_anchor_video_list", "type": "list", "required": false, "description": "TikTok only: Updated custom anchor video objects.", "example": [ { "item_id": "item-1" } ], "platform": [ "tiktok" ] }, { "name": "item_group_ids", "type": "list", "required": false, "description": "TikTok only: Updated item group ids.", "example": [ "item-group-1" ], "platform": [ "tiktok" ] }, { "name": "item_list", "type": "list", "required": false, "description": "TikTok only: Updated item objects.", "example": [ { "item_id": "item-1" } ], "platform": [ "tiktok" ] }, { "name": "promotion_days", "type": "object", "required": false, "description": "TikTok only: Updated promotion-day settings.", "example": { "is_enabled": false }, "platform": [ "tiktok" ] }, { "name": "roas_bid", "type": "number", "required": false, "description": "TikTok only: Updated ROAS bid.", "example": 2.0, "platform": [ "tiktok" ] }, { "name": "schedule_end_time", "type": "string", "required": false, "description": "TikTok only: Updated schedule end time in the advertiser time zone.", "example": "2026-07-31 23:59:59", "platform": [ "tiktok" ] }, { "name": "schedule_type", "type": "string", "required": false, "description": "TikTok only: Updated schedule type.", "example": "SCHEDULE_START_END", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_keyword", "namespace": "ads", "action": "update_keyword", "description": "Update a Google keyword's bid or status; review-gated, reversible if prior value known.", "mode": "risk", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/gaql_search", "action:ads/add_keywords", "action:ads/remove_keyword", "action:_batch/submit" ], "long_description": "Primary use:\nMutate a single Google ad group keyword's cpc_bid_micros or status.\n\nUse when:\n- The user wants to raise/lower a keyword's CPC bid.\n- The user wants to pause or re-enable a specific keyword.\n\nDo not use when:\n- The user wants to add new keywords. Use action:ads/add_keywords.\n- The user wants to remove keywords permanently. Use action:ads/remove_keyword.\n- The user wants to change match_type. match_type is not mutable; remove and re-add.\n\nWorkflow:\n1. Call action:ads/gaql_search to capture criterion_id and prior values.\n2. Submit via action:_batch/submit for HITL review.\n3. Verify via action:ads/gaql_search.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.message lists the fields actually updated.\n- Returns success=false if no mutable field is supplied or status is invalid.\n\nSide effects:\n- Field-mask update of (cpc_bid_micros, status) on the ad_group_criterion.\n- Status validated against {ENABLED, PAUSED, REMOVED}.\n- Reversible if prior value captured.\n\nAudit guidance (batch 4 P2):\n- google: Guidance: input_params marks both cpc_bid_micros and status as required=False with no note that AT LEAST ONE is mandatory; an agent reading only the param table could submit neither and get success=false. The rule lives only in long_description output semantics. Also note: No guidance that cpc_bid_micros only works under Manual CPC bidding. An agent could lower/raise a bid on an automated-bidding ad group and receive an opaque platform error with no recovery hint. Capability gap: final_url_overrides / keyword-level final URLs and tracking_url_template: AdGroupCriterion supports per-keyword final URLs and url tracking template; not exposable here (handler only edits cpc_bid_micros/status). Acceptable scope choice but worth noting as an unreachable mutable dimension.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "ad_group_id", "type": "string", "required": true, "description": "Parent ad group id from action:ads/gaql_search.", "example": "987654" }, { "name": "criterion_id", "type": "string", "required": true, "description": "Keyword criterion id from action:ads/gaql_search.criterion_id.", "example": "12345" }, { "name": "cpc_bid_micros", "type": "integer", "required": false, "description": "New CPC bid in micros (1 USD = 1_000_000)." }, { "name": "status", "type": "string", "required": false, "description": "One of ENABLED, PAUSED, REMOVED.", "example": "ENABLED", "enum": [ "ENABLED", "PAUSED", "REMOVED" ], "source": "constant" } ], "output_shape": "{\"success\": true, \"message\": \"Keyword updated: cpc_bid_micros, status\", \"results\": [...]}" }, { "id": "action:ads/update_pixel", "namespace": "ads", "action": "update_pixel", "description": "Rename a Meta or TikTok Pixel; review-gated, reversible by writing the previous name back.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_pixel", "action:ads/list_pixels", "action:ads/send_conversion_event", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nUpdate a Meta or TikTok pixel on the resolved ad account.\n\nUse when:\nRenaming or reconfiguring an existing pixel.\n\nDo not use when:\nNo pixel exists yet — create one with create_pixel; never reuse a pixel id across providers.\n\nWorkflow:\n1. Resolve the advertiser account and pixel id.\n2. Submit through the review queue.\n3. Read back the updated pixel.\n\nOutput semantics:\nReturns the provider-native pixel resource; no fake cross-provider parity.\n\nSide effects:\nMutates an existing pixel's configuration; changes apply on the provider immediately. Reversible via the provider UI.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id. Used for the ownership pre-check: the pixel must belong to this account or the action fails with not_found.", "example": "1234567890" }, { "name": "pixel_id", "type": "string", "required": true, "description": "provider pixel id from action:ads/list_pixels or action:ads/create_pixel (raw.id). Required when platform=tiktok." }, { "name": "name", "type": "string", "required": true, "description": "New pixel display name. The only updatable field on this surface; the handler rejects the call when it is missing. Required when platform=tiktok." } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_product_catalog", "namespace": "ads", "action": "update_product_catalog", "description": "Rename an owned Meta product catalog; review-gated platform write. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:ads/get_product_catalog_details", "action:ads/create_product_catalog", "action:ads/remove_product_catalog", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nRename an existing Meta product catalog without touching its feeds, products, or product sets.\n\nUse when:\n- The user wants to fix or standardize a catalog display name.\n- Catalog naming drifted from conventions across accounts.\n\nDo not use when:\n- The user only needs to inspect catalog state. Use the matching list/get action first.\n- The user needs cached ads performance metrics. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Confirm catalog_id via action:ads/list_product_catalogs or action:ads/get_product_catalog_details.\n2. Provide the new name; it is the only updatable field on this surface (vertical is create-only on the Graph API).\n3. Submit through action:_batch/submit for HITL approval.\n4. After approval, call action:ads/get_product_catalog_details to confirm the rename.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Meta Graph API mutation response.\n- The action is review-gated through the ads HITL path.\n\nSide effects:\n- POSTs only name to {catalog_id}; feeds, products, and product sets are unchanged.\n- Catalog ownership is verified against the resolved account business before the write; a mismatched or unreadable catalog returns error_code=not_found.\n- Reversible: write the previous name back.\n", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id to rename. Required when platform=tiktok." }, { "name": "name", "type": "string", "required": true, "description": "New catalog display name. The only updatable field on this surface — vertical is create-only on the Graph API — and the handler rejects the call with error_code=invalid_param when it is missing. Required when platform=tiktok." }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_product_feed", "namespace": "ads", "action": "update_product_feed", "description": "Update a Meta product feed's name or fetch schedule; review-gated platform write. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:ads/get_product_feed_details", "action:ads/create_product_feed", "action:ads/remove_product_feed", "action:_batch/submit" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nRename a product feed or replace its scheduled-fetch configuration in an owned catalog.\n\nUse when:\n- The user wants to change the feed's fetch cadence or source URL (schedule.url).\n- A feed display name needs fixing or standardizing.\n\nDo not use when:\n- The user only needs to inspect catalog state. Use the matching list/get action first.\n- The user needs cached ads performance metrics. Use action:ads/query_single_dimension or action:ads/query_multi_dimension instead.\n- The platform is Google; use a Google-specific action instead.\n\nWorkflow:\n1. Get catalog_id from action:ads/list_product_catalogs.\n2. Get product_feed_id from action:ads/create_product_feed (raw.id) or action:ads/get_product_feed_details.\n3. Provide at least one of name / schedule. schedule REPLACES the stored scheduled-fetch config wholesale — send the complete object (url + interval), not a partial patch.\n4. Submit through action:_batch/submit for HITL approval.\n5. After approval, call action:ads/get_product_feed_details to confirm.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw is the Meta Graph API mutation response.\n- The action is review-gated through the ads HITL path.\n\nSide effects:\n- POSTs only the provided fields among {name, schedule} to {product_feed_id}; already-ingested products are unchanged.\n- A schedule change alters when and from where Meta fetches the feed file.\n- Returns error_code=invalid_param when neither name nor schedule is provided, or when schedule.interval is outside the official enum.\n- Catalog ownership AND the feed's membership in that catalog are verified before the write; mismatches return error_code=not_found.\n", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "catalog_id", "type": "string", "required": true, "description": "Owned Meta product catalog id that contains the feed. Required when platform=tiktok." }, { "name": "product_feed_id", "type": "string", "required": true, "description": "Meta product feed id to update.", "platform": [ "meta" ] }, { "name": "name", "type": "string", "required": false, "description": "New feed display name. At least one of name / schedule must be provided.", "platform": [ "meta" ] }, { "name": "schedule", "type": "object", "required": false, "description": "Meta only: replacement scheduled-fetch config; same shape as action:ads/create_product_feed schedule (url, interval enum HOURLY|DAILY|WEEKLY|MONTHLY, hour, minute, day_of_week). REPLACES the stored schedule wholesale. At least one of name / schedule must be provided.", "example": { "interval": "DAILY", "url": "https://example.com/feed.tsv", "hour": 22 }, "platform": [ "meta" ] }, { "name": "bc_id", "type": "string", "required": true, "description": "TikTok bc id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "feed_id", "type": "string", "required": true, "description": "TikTok feed id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "feed_name", "type": "string", "required": true, "description": "TikTok feed name.", "example": "value", "platform": [ "tiktok" ] }, { "name": "schedule_param", "type": "object", "required": false, "description": "TikTok only: Feed schedule source; source.uri is the provider feed URL.", "example": { "source": { "uri": "https://example.com/feed.csv" } }, "source": "user", "platform": [ "tiktok" ] }, { "name": "update_mode", "type": "string", "required": true, "description": "TikTok update mode.", "example": "value", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_smart_plus_campaign", "namespace": "ads", "action": "update_smart_plus_campaign", "description": "Update a TikTok Smart+ campaign.", "mode": "write", "platforms": [ "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nUpdate a TikTok Smart+ campaign.\n\nUse when:\nChanging budget / name / settings on an existing Smart+ campaign.\n\nDo not use when:\nYou only want to pause or resume delivery — use update_smart_plus_campaign_status.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nMutates a live Smart+ campaign; changes take effect on TikTok immediately. Requires smart_plus_acknowledged.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "campaign_id", "type": "string", "required": true, "description": "TikTok campaign id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "smart_plus_acknowledged", "type": "boolean", "required": true, "description": "Explicit acknowledgement required before this side effect.", "example": true, "platform": [ "tiktok" ] }, { "name": "budget", "type": "number", "required": false, "description": "TikTok only: Updated Smart+ campaign budget.", "example": 100.0, "platform": [ "tiktok" ] }, { "name": "campaign_name", "type": "string", "required": false, "description": "TikTok only: Updated Smart+ campaign name.", "example": "Smart Launch", "platform": [ "tiktok" ] }, { "name": "po_number", "type": "string", "required": false, "description": "TikTok only: Updated purchase-order number.", "example": "PO-123", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_smart_plus_campaign_status", "namespace": "ads", "action": "update_smart_plus_campaign_status", "description": "Update TikTok Smart+ campaign delivery status.", "mode": "risk", "platforms": [ "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nUpdate TikTok Smart+ campaign delivery status.\n\nUse when:\nEnabling, pausing, or deleting delivery of a Smart+ campaign.\n\nDo not use when:\nYou want budget / setting edits — use update_smart_plus_campaign.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nChanges live delivery status (starts / stops spend) on TikTok. Requires smart_plus_acknowledged.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "campaign_id", "type": "string", "required": true, "description": "TikTok campaign id.", "example": "value", "platform": [ "tiktok" ] }, { "name": "operation_status", "type": "string", "required": true, "description": "TikTok only: Smart+ delivery operation.", "example": "DISABLE", "enum": [ "ENABLE", "DISABLE", "DELETE" ], "platform": [ "tiktok" ] }, { "name": "smart_plus_acknowledged", "type": "boolean", "required": true, "description": "Explicit acknowledgement required before this side effect.", "example": true, "platform": [ "tiktok" ] }, { "name": "postback_window_mode", "type": "string", "required": false, "description": "TikTok only: Optional Smart+ postback-window mode for the status update.", "example": "UNSET", "enum": [ "POSTBACK_WINDOW_MODE1", "POSTBACK_WINDOW_MODE2", "POSTBACK_WINDOW_MODE3", "UNSET" ], "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/update_user_list", "namespace": "ads", "action": "update_user_list", "description": "Update a Google user list's metadata (name, description, membership_lifespan_days) via field mask; review-gated, reversible.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_user_list", "action:ads/add_audience_targeting", "action:ads/gaql_search", "action:_batch/submit" ], "long_description": "Primary use:\nUpdate the metadata of an existing Google user list (Customer Match audience) by user_list_id. Field-mask update: only the fields provided are written; everything else is left untouched.\n\nUse when:\n- The user wants to rename a user list or fix its description.\n- The user wants to change how long members stay in the list (membership_lifespan_days).\n\nDo not use when:\n- The list does not exist yet. Use action:ads/create_user_list.\n- The user wants to delete a user list. User lists cannot be deleted via API.\n- The user wants to change upload_key_type or app_id. Those are immutable after create; create a new list instead.\n- The platform is Meta. Use action:ads/update_custom_audience.\n\nWorkflow:\n1. Get user_list_id from action:ads/create_user_list raw.resource_id, or discover it via action:ads/gaql_search (e.g. SELECT user_list.id, user_list.name, user_list.membership_life_span FROM user_list).\n2. Provide at least one of name / description / membership_lifespan_days.\n3. Submit via action:_batch/submit for HITL review.\n4. Verify via action:ads/gaql_search on user_list.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- The message lists exactly which fields were updated.\n- Providing none of the updatable fields fails fast with a validation error before any mutate.\n\nSide effects:\n- Metadata-only change; list membership and targeting links are untouched.\n- Reversible by updating the fields back to their previous values.\n- Shortening membership_lifespan_days can expire existing members earlier.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "user_list_id", "type": "string", "required": true, "description": "Numeric user list id from action:ads/create_user_list raw.resource_id or action:ads/gaql_search on user_list.", "example": "123456" }, { "name": "name", "type": "string", "required": false, "description": "New display name; must stay unique within the account. At least one of name / description / membership_lifespan_days is required." }, { "name": "description", "type": "string", "required": false, "description": "New description. An explicit empty string clears the existing description. Source: user-provided text.", "example": "Newsletter subscribers" }, { "name": "membership_lifespan_days", "type": "integer", "required": false, "description": "Days a contact stays in the list. Valid range: 0-540 inclusive, OR exactly 10000 for no expiration (CRM-based lists). Values 541-9999 are invalid. Same param name and validation as action:ads/create_user_list; maps to the UserList.membership_life_span proto field. Source: agent/user choice within the allowed range; constant integer.", "example": 540 } ], "output_shape": "{\"success\": true, \"message\": \"User list updated: name, membership_lifespan_days\", \"results\": [{\"userListResult\": {\"resourceName\": \"customers/X/userLists/Y\"}}]}" }, { "id": "action:ads/upload_custom_audience_file", "namespace": "ads", "action": "upload_custom_audience_file", "description": "Upload a base64-encoded TikTok custom-audience data file.", "mode": "write", "platforms": [ "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [], "long_description": "Supported platforms: tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nPrimary use:\nUpload a base64-encoded TikTok custom-audience data file.\n\nUse when:\nYou have a hashed customer list (emails / phone / IDFA) to seed or refresh a TikTok custom audience.\n\nDo not use when:\nBuilding an audience from engagement or web/app rules instead of a file — use create_rule_custom_audience.\n\nWorkflow:\n1. Resolve the TikTok advertiser grant.\n2. Submit the mutation through the review queue.\n3. Re-read the resource or report after execution.\n\nOutput semantics:\nReturns the TikTok Marketing API response without fabricating Meta-equivalent fields.\n\nSide effects:\nCreates or refreshes a TikTok custom audience from the uploaded file; the audience becomes targetable subject to TikTok's minimum-size and match-rate rules. PII must be hashed before upload.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: tiktok.", "example": "tiktok", "enum": [ "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Resolved TikTok advertiser id.", "example": "123456789" }, { "name": "file_name", "type": "string", "required": true, "description": "TikTok file name.", "example": "value", "platform": [ "tiktok" ] }, { "name": "file_base64", "type": "string", "required": true, "description": "TikTok file base64.", "example": "value", "platform": [ "tiktok" ] }, { "name": "calculate_type", "type": "string", "required": true, "description": "TikTok only: Hash scheme matching the uploaded identifier column.", "example": "EMAIL_SHA256", "enum": [ "EMAIL_SHA256", "PHONE_SHA256", "IDFA_MD5", "IDFA_SHA256", "GAID_MD5", "GAID_SHA256" ], "source": "constant", "platform": [ "tiktok" ] }, { "name": "file_signature", "type": "string", "required": false, "description": "TikTok only: Optional lowercase MD5 of decoded file bytes. Soku computes it when omitted and rejects a supplied mismatch before upload.", "example": "f3e40b9481d2d140b9e8b380c4e9c59a", "source": "user", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\":true,\"raw\":{}}\nTikTok output: read top-level resource_id for a normalized resource id when present; raw preserves the provider-native TikTok response. When the preceding shape describes another provider, ignore its Meta/Google-only fields." }, { "id": "action:ads/upload_image", "namespace": "ads", "action": "upload_image", "description": "Upload an image to a Meta or TikTok ad account from a public URL after HITL review and return its provider-native asset ID.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/upload_images", "action:ads/upload_video", "action:ads/create_ad_creative" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nDownload a public https image, validate it, and POST the bytes to Meta's {account_id}/adimages. Returns image_hash for action:ads/create_ad_creative.\n\nUse when:\n- The user wants to ingest a new image asset for ad creatives.\n\nDo not use when:\n- The source URL is private, http (non-https), or an IP literal. The handler rejects such URLs before any upload.\n- The image is larger than 25 MB. The handler returns an error.\n- The platform is Google. Use action:ads/upload_image_asset (returns asset_id).\n\nWorkflow:\n1. Get a public https image URL <= 25 MB without redirects.\n2. Submit this write through action:_batch/submit for HITL review.\n3. Use the returned image_hash in action:ads/create_ad_creative.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.image_hash is the value to feed into create_ad_creative.image_hash.\n- Errors: 'Image exceeds 25 MB byte limit.' or 'Image URL returned a redirect.'\n\nSide effects:\n- Downloads the URL (rejects redirects, enforces 25 MB cap, validates public https).\n- Base64-encodes and POSTs to {account_id}/adimages; image is stored against the account.\n- Consumes Meta storage quota but no ad spend.\n- Reversible only via the Meta UI; no L1.5 remove handler.\n\nAudit guidance (batch 4 P2):\n- meta: Guidance: Capability docs describe image_url as the input but do not state the internal mechanism is base64 'bytes' upload (not a Meta-fetched URL), nor that the 25MB cap is stricter than Meta's ~30MB native limit — an agent could wrongly assume a <30MB image will succeed. Also note: Docs do not mention image format constraints (Meta accepts JPG/PNG; GIF/WEBP/other may be rejected by Meta with code 100), so the agent cannot pre-validate format. Capability gap: copy_from: cross-account image reuse (clone an existing hash into another account) is unreachable in all three layers; agent must re-upload bytes per account.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "image_url", "type": "string", "required": true, "description": "Public https URL of the image; rejects http, redirects, IP literals, and files > 25 MB. Required when platform=tiktok.", "example": "https://example.com/banner.jpg" }, { "name": "name", "type": "string", "required": false, "description": "Optional file name. Default \"upload.jpg\".", "platform": [ "meta" ] }, { "name": "upload_type", "type": "string", "required": false, "description": "TikTok only: this adapter supports UPLOAD_BY_URL only.", "example": "UPLOAD_BY_URL", "enum": [ "UPLOAD_BY_URL" ], "platform": [ "tiktok" ] }, { "name": "file_name", "type": "string", "required": false, "description": "TikTok only: optional file name label.", "example": "hero.jpg", "platform": [ "tiktok" ] } ], "output_shape": "{\"success\": true, \"account_id\": \"act_...\", \"image_hash\": \"hex_md5\", \"images\": [{\"hash\": \"...\", \"url\": \"...\", \"width\": 1200, \"height\": 628, \"name\": \"...\"}]}\nTikTok output: resource_id is the uploaded image_id; raw preserves image_id and id. image_hash belongs to the Meta response only." }, { "id": "action:ads/upload_image_asset", "namespace": "ads", "action": "upload_image_asset", "description": "Upload an image to a Google Ads account asset library from a public https URL after HITL review and return the asset_id.", "mode": "write", "platforms": [ "google" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/create_text_asset", "action:ads/create_youtube_video_asset", "action:ads/create_pmax_campaign" ], "long_description": "Primary use:\nDownload a public https image and create a Google ImageAsset via the Google Ads API generic mutate. Returns asset_id (+ resource_name) to reference in create_pmax_campaign's asset_group or Display/Video creatives.\n\nUse when:\n- A Google PMax / Display / Video campaign needs an image asset the account does not have yet.\n\nDo not use when:\n- The image URL is not public https, or is larger than 5 MB.\n- The platform is Meta. Use action:ads/upload_image (returns image_hash).\n\nWorkflow:\n1. Get a public https image URL <= 5 MB.\n2. Submit this write through action:_batch/submit for HITL review.\n3. Feed the returned asset_id into the asset_group / creative.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- resource_id is the numeric asset id; raw.resource_name is the full path.\n- Google content-deduplicates images: re-uploading returns the same asset.\n\nSide effects:\n- Downloads the URL (enforces public https + 5 MB cap).\n- Creates an account-level ImageAsset; no ad spend.\n- Reversible only via the Google Ads UI; no L1.5 remove handler.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: google.", "example": "google", "enum": [ "google" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Google customer id (digits only, no dashes).", "example": "1234567890" }, { "name": "image_url", "type": "string", "required": true, "description": "Public https URL of the image; rejects http and files > 5 MB.", "example": "https://example.com/banner.jpg" }, { "name": "name", "type": "string", "required": false, "description": "Optional asset name (omit to let Google de-duplicate).", "example": "hero-image" } ], "output_shape": "{\"success\": true, \"resource_id\": \"456\", \"resource_name\": \"customers/123/assets/456\"}" }, { "id": "action:ads/upload_images", "namespace": "ads", "action": "upload_images", "description": "Upload multiple images to a Meta ad account from direct base64 bytes or public URLs with bounded concurrency after HITL review and return per-item image_hash values. Also supports TikTok through its provider-native contract.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/upload_image", "action:ads/create_ad_creative", "action:ads/deploy_image_ads_batch" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nBulk-ingest local image bytes or public https image URLs into Meta's {account_id}/adimages edge. Use this instead of many serial upload_image calls when the user has multiple assets for one account.\n\nUse when:\n- The user wants to upload more than one image asset for Meta creatives.\n- A batch campaign workflow has local files or public URLs and needs image_hash values before creative creation.\n\nDo not use when:\n- A URL source is private, http (non-https), an IP literal, redirects, or points to an image larger than 25 MB. Invalid items fail individually.\n- The platform is Google. Use a Google-specific action instead.\n\nWorkflow:\n1. Build images[] with {client_ref, name, image_url} for public URLs or {client_ref, name, bytes_base64} for local files. client_ref should be stable so the returned item can be mapped back to the source asset.\n2. Submit this write through action:_batch/submit for HITL review. When image count is greater than 2, the handler uses bounded concurrency (default 6, max 10) plus transient Meta retry/backoff per image.\n3. Feed each successful items[].image_hash into create_ad_creative or deploy_image_ads_batch.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.items[] preserves input order and includes index, client_ref, image_hash, retry_count, and per-item error/error_code.\n- raw.success=false means at least one item failed; successful items remain usable.\n\nSide effects:\n- Downloads each URL (rejects redirects, enforces 25 MB cap, validates public https) or decodes bytes_base64 directly.\n- POSTs successful items to {account_id}/adimages using Meta's bytes field.\n- Consumes Meta storage quota but no ad spend.\n- Reversible only via the Meta UI; no L1.5 remove handler.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Meta ad account id from ads.list_ad_accounts.rows[].account_id.", "example": "1234567890" }, { "name": "images", "type": "list", "required": true, "description": "List of image upload objects. Meta accepts image_url or bytes_base64; TikTok accepts a public HTTPS image_url only (an object with image_url/url or a bare URL string). name and stable client_ref are optional metadata. Required when platform=tiktok.", "example": [ { "client_ref": "asset-001", "image_url": "https://example.com/asset-001.jpg", "name": "asset-001.jpg" } ] }, { "name": "concurrency", "type": "integer", "required": false, "description": "Optional concurrency limit for >2 images. Default 6, max 10. Values outside the range are clamped by the handler.", "example": 6, "platform": [ "meta" ] } ], "output_shape": "{\"success\": true, \"account_id\": \"act_...\", \"total\": 50, \"succeeded\": 50, \"failed\": 0, \"concurrency\": 6, \"items\": [{\"index\": 0, \"client_ref\": \"asset-001\", \"image_hash\": \"hex_md5\", \"retry_count\": 0, \"images\": [{\"hash\": \"...\"}]}]}\nTikTok output: raw.results[] contains each upload result; read image_id or id from each result. image_hash belongs to Meta results only." }, { "id": "action:ads/upload_video", "namespace": "ads", "action": "upload_video", "description": "Upload a video to a Meta or TikTok ad account from a public URL after HITL review and return video_id for async encoding or readiness checks.", "mode": "write", "platforms": [ "meta", "tiktok" ], "requires_review": true, "freshness_kind": "synced", "see_also": [ "action:ads/upload_image", "action:ads/create_ad_creative" ], "long_description": "Supported platforms: meta, tiktok. For TikTok, use platform=tiktok and the parameters tagged for TikTok. Provider-specific guidance below is conditional and does not narrow the supported-platform list.\n\nTikTok behavior:\nThis action calls its provider-native TikTok Marketing API handler and returns the native TikTok response; do not infer Meta Graph fields.\n\nOther-platform guidance (not applicable when platform=tiktok):\nPrimary use:\nSubmit a public https video URL to Meta's {account_id}/advideos. Meta downloads + encodes asynchronously; the returned video_id may not be immediately usable in creatives.\n\nUse when:\n- The user wants to ingest a new video asset for ad creatives.\n\nDo not use when:\n- The source URL is private, http, or an IP literal. The handler rejects it.\n- The platform is Google.\n\nWorkflow:\n1. Get a public https video URL.\n2. Submit this write through action:_batch/submit for HITL review.\n3. After Meta finishes encoding, use the video_id in action:ads/create_ad_creative.\n\nOutput semantics:\n- Top-level envelope: see Cross-cutting envelope in capabilities-api.md.\n- raw.id is the new video_id; raw.success indicates upload accepted, not encoding done.\n\nSide effects:\n- POSTs file_url to {account_id}/advideos; Meta pulls and encodes the file asynchronously.\n- Encoding is async on Meta's side — the video may not be ready for creatives immediately.\n- No immediate spend; consumes Meta storage quota.\n- Reversible only via the Meta UI.", "input_params": [ { "name": "platform", "type": "string", "required": true, "description": "Supported platforms: meta, tiktok.", "example": "meta", "enum": [ "meta", "tiktok" ], "source": "constant" }, { "name": "account_id", "type": "string", "required": true, "description": "Account id from ads.list_ad_accounts.rows[].account_id. For Google, digits only without dashes.", "example": "1234567890" }, { "name": "file_url", "type": "string", "required": true, "description": "Meta only: public https URL of the video to ingest. Required -- this is the only supported upload path (Meta's resumable chunked/source upload for very large files is NOT supported by this action). Must be a direct https URL (no http, no IP literal, no redirect); the handler SSRF-validates it and rejects redirects. Prefer reasonably sized MP4/H.264 clips; multi-GB assets that require chunked upload will fail. Source: user-provided public https URL of the video file.", "example": "https://example.com/clip.mp4", "platform": [ "meta" ] }, { "name": "title", "type": "string", "required": false, "description": "Optional video title.", "platform": [ "meta" ] }, { "name": "description", "type": "string", "required": false, "description": "Optional video description.", "platform": [ "meta" ] }, { "name": "name", "type": "string", "required": false, "description": "Meta only: optional asset name for the uploaded video in the ad account media library. Distinct from title/description (which annotate the video). Free-form, agent- or user-provided. Source: agent-provided or user-provided label (e.g. a campaign/asset naming convention).", "example": "summer-sale-hero-15s", "platform": [ "meta" ] }, { "name": "video_url", "type": "string", "required": true, "description": "TikTok only: required — public video URL (UPLOAD_BY_URL). Awaits transcoding.", "example": "https://example.com/v.mp4", "platform": [ "tiktok" ] }, { "name": "upload_type", "type": "string", "required": false, "description": "TikTok only: this adapter supports UPLOAD_BY_URL only.", "example": "UPLOAD_BY_URL", "enum": [ "UPLOAD_BY_URL" ], "platform": [ "tiktok" ] }, { "name": "file_name", "type": "string", "required": false, "description": "TikTok only: optional file name label.", "example": "promo.mp4", "platform": [ "tiktok" ] } ], "output_shape": "{\"id\": \"video_id\", \"success\": true}\nTikTok output: resource_id is the uploaded video_id; raw preserves video_id, id, ready, and any transcoding warnings." }, { "id": "action:appsflyer/list_accounts", "namespace": "appsflyer", "action": "list_accounts", "description": "List the AppsFlyer accounts this brand can query.", "mode": "read", "platforms": [ "appsflyer" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the AppsFlyer accounts granted to the current brand (account_id + name). START HERE for any AppsFlyer task: every other appsflyer action requires one of these account_ids. Do NOT ask the user for an account id — this action is the source of truth. A single AppsFlyer MCP token maps to one account; individual apps within it are chosen via tool arguments, not as separate resources.", "input_params": [], "output_shape": "data.rows[].{account_id, name}" }, { "id": "action:appsflyer/list_tools", "namespace": "appsflyer", "action": "list_tools", "description": "List the AppsFlyer attribution/analytics tools available for an account.", "mode": "read", "platforms": [ "appsflyer" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the live AppsFlyer MCP tool catalog for one granted account, each annotated with `requires_review`. The default response is a slim catalog without input schemas. Pass `tool` to fetch one exact tool's full input schema before calling `appsflyer/query`. Tools with `requires_review=true` (anything not on our read-only allowlist) must go through `appsflyer/request_change`, not `appsflyer/query`. Data is live from AppsFlyer (realtime, no cached pipeline); AppsFlyer MCP is Open Beta, so treat results as best-effort.", "input_params": [ { "name": "project_id", "type": "string", "required": false, "description": "AppsFlyer account id (from appsflyer/list_accounts).", "example": "af_acct_demo" }, { "name": "tool", "type": "string", "required": false, "description": "Exact AppsFlyer tool name; when provided, returns that tool's full input schema.", "example": "fetch_aggregated_data" } ], "output_shape": "data.project_id, data.tools[].{name, description, requires_review[, input_schema when tool is provided]}" }, { "id": "action:appsflyer/query", "namespace": "appsflyer", "action": "query", "description": "Run a read-only AppsFlyer tool (aggregated performance, SKAN, cost & ad-revenue integrations, OneLink, audiences, app settings).", "mode": "read", "platforms": [ "appsflyer" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Forwards a read-only AppsFlyer tool call for one granted account and returns live data (realtime — there is no cached sync pipeline). The hosted MCP exposes aggregated attribution/performance (`fetch_aggregated_data`: installs, ROAS, LTV, retention by media-source/campaign/date), SKAN aggregates (`skan_get_app_data`), cost & ad-revenue integration health, OneLink templates/links, active audiences, and app settings. Choose the app via tool arguments. Get the account_id from `appsflyer/list_accounts` (never ask the user for it). Only allowlisted read tools run immediately; anything else returns `requires_review` — call `appsflyer/request_change`. The response echoes which account was queried.", "input_params": [ { "name": "project_id", "type": "string", "required": true, "description": "AppsFlyer account id (from appsflyer/list_accounts).", "example": "af_acct_demo" }, { "name": "tool", "type": "string", "required": true, "description": "Exact AppsFlyer tool name from appsflyer/list_tools.", "example": "fetch_aggregated_data" }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments object per the tool's input schema.", "example": { "app_id": "id123456789", "from": "2026-06-01", "to": "2026-06-30", "metrics": [ "installs", "roas" ] } } ], "output_shape": "data.{project_id, tool, is_error, content[], structured, truncated}" }, { "id": "action:appsflyer/request_change", "namespace": "appsflyer", "action": "request_change", "description": "Request a non-allowlisted AppsFlyer tool (or a future write) via human approval.", "mode": "risk", "platforms": [ "appsflyer" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "Submits an AppsFlyer tool call that is not on our read-only allowlist for human approval (HITL). Returns a pending_review_id immediately; the call is forwarded to AppsFlyer only after a human approves. Use this for anything `appsflyer/query` rejected as requires_review. Note: account-wide user listing (`get_users`) and product feedback (`send_feedback`) are hard-hidden and never available, even here.", "input_params": [ { "name": "project_id", "type": "string", "required": true, "description": "AppsFlyer account id (from appsflyer/list_accounts).", "example": "af_acct_demo" }, { "name": "tool", "type": "string", "required": true, "description": "Exact AppsFlyer tool name.", "example": "fetch_aggregated_data" }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments object per the tool's input schema.", "example": { "app_id": "id123456789" } } ], "output_shape": "202 -> {ok, pending_review_id}; on approval data.{project_id, tool, is_error, content[], structured}" }, { "id": "action:bigquery/get_table_schema", "namespace": "bigquery", "action": "get_table_schema", "description": "Read one BigQuery table or view's column list (name, type, mode) plus its row and byte counts.", "mode": "read", "platforms": [ "bigquery" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:bigquery/query", "action:bigquery/list_tables" ], "long_description": "Primary use:\nThe column vocabulary you must have before writing SQL. Column names and types come from here, never from a guess or from another table's schema.\n\nUse when:\n- You are about to call bigquery/query against this table.\n- A query failed with an unrecognized-name error.\n\nOutput semantics:\n- fields[].type is the upstream spelling verbatim; BigQuery mixes the legacy names (INTEGER / FLOAT / BOOLEAN) with the GoogleSQL ones (INT64 / FLOAT64 / BOOL) in the same response, and both mean the same type.\n- fields[].mode is NULLABLE / REQUIRED / REPEATED.\n- A RECORD/STRUCT column carries a nested fields list, expanded one level deep only.\n- table.num_rows / table.num_bytes are REST string-encoded int64 and are absent (null) for a view.\n- truncated=true means the table has more columns than were returned.\n\nConstraints:\n- Reading a schema does not prove the data is readable; SELECT permission is checked when the query runs.\n\nConnection selection:\n- project_id is the selector. A connection that was granted for exactly that GCP project serves it; otherwise the brand's Google-account connection (which reaches whatever projects that account can see) does.\n- Because of that fallback, a successful call is NOT evidence that the brand was granted the project you named. Do not report reachability as permission, and do not enumerate projects by probing ids.\n- On the three read actions you may omit project_id when the brand has a single connected project — the error message lists the candidates when it is ambiguous. bigquery/query ALWAYS requires it: a human approves the query against a named project, so it is never inferred there.\n- connection_id is only needed to break a tie the error message asks you to break. Copy it verbatim from that message; never invent one.", "input_params": [ { "name": "dataset_id", "type": "string", "required": true, "description": "Dataset holding the table, from bigquery/list_datasets.", "example": "sales", "source": "upstream:action:bigquery/list_datasets" }, { "name": "table_id", "type": "string", "required": true, "description": "Table or view to describe, from bigquery/list_tables.", "example": "orders", "source": "upstream:action:bigquery/list_tables" }, { "name": "project_id", "type": "string", "required": false, "description": "GCP project holding the dataset. Required unless the brand has exactly one connected BigQuery project.", "example": "my-analytics-prod", "source": "upstream:action:bigquery/list_datasets" }, { "name": "connection_id", "type": "string", "required": false, "description": "Tie-breaker between several connections that could serve the project. Only pass a value an error message listed.", "source": "user" } ], "output_shape": "{\"table\": {\"project_id\": \"my-analytics-prod\", \"dataset_id\": \"sales\", \"table_id\": \"orders\", \"num_rows\": \"12345\", \"num_bytes\": \"678900\"}, \"fields\": [{\"name\": \"country\", \"type\": \"STRING\", \"mode\": \"NULLABLE\"}], \"truncated\": false, \"connection_source\": \"service_account\"}" }, { "id": "action:bigquery/list_datasets", "namespace": "bigquery", "action": "list_datasets", "description": "List the BigQuery datasets in a connected GCP project; returns the dataset_id values the other bigquery.* actions take.", "mode": "read", "platforms": [ "bigquery" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:bigquery/list_tables", "action:bigquery/get_table_schema" ], "long_description": "Primary use:\nDataset discovery inside one BigQuery project. This is the entry point of every BigQuery workflow — the ids it returns are the only dataset_id values later bigquery.* calls accept.\n\nUse when:\n- You do not yet know which datasets the connection can read.\n- A previous call failed with not_found on a dataset id you guessed.\n\nDo not use when:\n- You already listed this project's datasets in the same workflow.\n\nWorkflow:\n1. Call this action.\n2. Pick a dataset_id.\n3. Call bigquery/list_tables for that dataset.\n\nOutput semantics:\n- rows[].dataset_id / project_id are the exact ids later calls take.\n- rows[].location is the dataset's region; a query that spans regions fails, and a non-US/EU dataset may need the query's location parameter.\n- truncated=true means the project has more datasets than were returned.\n\nConstraints:\n- Only datasets the connected account itself can read are returned; an empty list means the account has no dataset access in that project, not that the project is empty.\n\nConnection selection:\n- project_id is the selector. A connection that was granted for exactly that GCP project serves it; otherwise the brand's Google-account connection (which reaches whatever projects that account can see) does.\n- Because of that fallback, a successful call is NOT evidence that the brand was granted the project you named. Do not report reachability as permission, and do not enumerate projects by probing ids.\n- On the three read actions you may omit project_id when the brand has a single connected project — the error message lists the candidates when it is ambiguous. bigquery/query ALWAYS requires it: a human approves the query against a named project, so it is never inferred there.\n- connection_id is only needed to break a tie the error message asks you to break. Copy it verbatim from that message; never invent one.", "input_params": [ { "name": "project_id", "type": "string", "required": false, "description": "GCP project to list datasets in. Required unless the brand has exactly one connected BigQuery project.", "example": "my-analytics-prod", "source": "user" }, { "name": "connection_id", "type": "string", "required": false, "description": "Tie-breaker between several connections that could serve the project. Only pass a value an error message listed.", "source": "user" } ], "output_shape": "{\"project_id\": \"my-analytics-prod\", \"connection_source\": \"service_account\", \"rows\": [{\"project_id\": \"my-analytics-prod\", \"dataset_id\": \"sales\", \"location\": \"US\"}], \"truncated\": false}" }, { "id": "action:bigquery/list_tables", "namespace": "bigquery", "action": "list_tables", "description": "List the tables and views in one BigQuery dataset; returns table_id values plus each entry's type.", "mode": "read", "platforms": [ "bigquery" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:bigquery/get_table_schema", "action:bigquery/query" ], "long_description": "Primary use:\nTable discovery inside one dataset, before reading a schema or writing SQL.\n\nUse when:\n- You have a dataset_id and need the table names in it.\n\nDo not use when:\n- You need column names or types. Use bigquery/get_table_schema.\n\nOutput semantics:\n- rows[].type is reported verbatim: TABLE, VIEW, MATERIALIZED_VIEW or EXTERNAL. A VIEW is queried exactly like a table and is often the curated, intended entry point — do not skip views or prefer tables.\n- truncated=true means the dataset holds more entries than were returned.\n\nConstraints:\n- Listing a table does not prove it is readable; SELECT permission is checked when the query runs.\n\nConnection selection:\n- project_id is the selector. A connection that was granted for exactly that GCP project serves it; otherwise the brand's Google-account connection (which reaches whatever projects that account can see) does.\n- Because of that fallback, a successful call is NOT evidence that the brand was granted the project you named. Do not report reachability as permission, and do not enumerate projects by probing ids.\n- On the three read actions you may omit project_id when the brand has a single connected project — the error message lists the candidates when it is ambiguous. bigquery/query ALWAYS requires it: a human approves the query against a named project, so it is never inferred there.\n- connection_id is only needed to break a tie the error message asks you to break. Copy it verbatim from that message; never invent one.", "input_params": [ { "name": "dataset_id", "type": "string", "required": true, "description": "Dataset to list, from bigquery/list_datasets.", "example": "sales", "source": "upstream:action:bigquery/list_datasets" }, { "name": "project_id", "type": "string", "required": false, "description": "GCP project holding the dataset. Required unless the brand has exactly one connected BigQuery project.", "example": "my-analytics-prod", "source": "upstream:action:bigquery/list_datasets" }, { "name": "connection_id", "type": "string", "required": false, "description": "Tie-breaker between several connections that could serve the project. Only pass a value an error message listed.", "source": "user" } ], "output_shape": "{\"project_id\": \"my-analytics-prod\", \"dataset_id\": \"sales\", \"connection_source\": \"composio\", \"rows\": [{\"project_id\": \"my-analytics-prod\", \"dataset_id\": \"sales\", \"table_id\": \"orders\", \"type\": \"TABLE\"}], \"truncated\": false}" }, { "id": "action:bigquery/query", "namespace": "bigquery", "action": "query", "description": "Run one read-only GoogleSQL query against the connected BigQuery project. Always human-reviewed: a person reads the SQL before it runs.", "mode": "risk", "platforms": [ "bigquery" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [ "action:bigquery/get_table_schema", "action:bigquery/list_tables" ], "long_description": "Primary use:\nAnswering a question from the customer's own warehouse with SQL you wrote. The SQL is forwarded verbatim, so it is also exactly what the reviewing human sees.\n\nHuman review:\n- This action ALWAYS goes through human approval. The first call returns 202 with a pending_review_id and runs nothing; it executes only after a person approves it.\n- Requires chat_id and a _summary describing, in the user's words, what the query answers — that sentence is the card header the reviewer reads.\n- Write one correct query rather than a batch of exploratory ones: every attempt costs a human interruption.\n\nUse when:\n- You already read the relevant schema with bigquery/get_table_schema.\n\nDo not use when:\n- You are still exploring what exists. Use list_datasets / list_tables / get_table_schema, which run immediately and need no approval.\n\nCost and limits:\n- Every query is capped server-side at 1 GiB billed bytes; a query that would scan more is killed by BigQuery before it bills. Narrow it (partition filters, explicit columns instead of SELECT *) rather than asking for the cap to be raised.\n- dry_run=true validates the SQL and returns the byte estimate WITHOUT running it or returning rows — use it when a scan looks expensive.\n- The query must finish inside a short synchronous window; a query_timeout error means narrow the query, not retry it unchanged.\n\nOutput semantics:\n- rows are decoded by the response schema: INT64/FLOAT64 columns become numbers, every other type (BOOL, DATE, TIMESTAMP, NUMERIC, ...) keeps BigQuery's string encoding, and a RECORD/REPEATED column keeps BigQuery's raw {\"f\": [...]} / [{\"v\": ...}] wire encoding — unnest or select scalar fields if you want flat values.\n- truncated=true means more rows matched than were returned; raise max_results or aggregate in SQL rather than paging.\n- total_bytes_processed / total_bytes_billed are string int64.\n\nConstraints:\n- Read-only by intent and by the reviewer's judgement: write DML/DDL is not what this action is for, and a reviewer will reject it.\n- Legacy SQL is disabled; write GoogleSQL.\n\nConnection selection:\n- project_id is the selector. A connection that was granted for exactly that GCP project serves it; otherwise the brand's Google-account connection (which reaches whatever projects that account can see) does.\n- Because of that fallback, a successful call is NOT evidence that the brand was granted the project you named. Do not report reachability as permission, and do not enumerate projects by probing ids.\n- On the three read actions you may omit project_id when the brand has a single connected project — the error message lists the candidates when it is ambiguous. bigquery/query ALWAYS requires it: a human approves the query against a named project, so it is never inferred there.\n- connection_id is only needed to break a tie the error message asks you to break. Copy it verbatim from that message; never invent one.", "input_params": [ { "name": "sql", "type": "string", "required": true, "description": "The GoogleSQL query, forwarded verbatim. Fully qualify tables as `project`.`dataset`.`table`.", "example": "SELECT country, SUM(revenue) AS revenue FROM `my-analytics-prod`.`sales`.`orders` WHERE order_date >= '2026-01-01' GROUP BY country", "source": "user" }, { "name": "project_id", "type": "string", "required": true, "description": "GCP project the query job runs in and is billed to. Always required here (unlike the read actions): it is part of what the human approves, so it is never inferred.", "example": "my-analytics-prod", "source": "upstream:action:bigquery/list_datasets" }, { "name": "max_results", "type": "integer", "required": false, "description": "Rows to return. Default 100, hard cap 1000.", "example": 100, "source": "constant" }, { "name": "dry_run", "type": "boolean", "required": false, "description": "Validate and estimate only: returns the byte estimate, runs no query and returns no rows.", "example": false, "source": "constant" }, { "name": "location", "type": "string", "required": false, "description": "Dataset region (from bigquery/list_datasets) when it is neither US nor EU. Omit and BigQuery infers it.", "example": "asia-northeast1", "source": "upstream:action:bigquery/list_datasets" }, { "name": "connection_id", "type": "string", "required": false, "description": "Tie-breaker between several connections that could serve the project. Only pass a value an error message listed.", "source": "user" } ], "output_shape": "{\"project_id\": \"my-analytics-prod\", \"connection_source\": \"service_account\", \"rows\": [{\"country\": \"US\", \"revenue\": 1234.5}], \"truncated\": false, \"total_bytes_processed\": \"2097152\", \"total_bytes_billed\": \"10485760\", \"cache_hit\": false} — or {\"dry_run\": true, \"total_bytes_processed\": \"2097152\", ...} when dry_run was set" }, { "id": "action:composio/append_doc_from_markdown", "namespace": "composio", "action": "append_doc_from_markdown", "description": "Append richly formatted Markdown to an existing Google Doc through human review.", "mode": "risk", "platforms": [ "composio_managed" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "Use only with a googledocs account from composio/list_accounts. Nothing is read or written before approval. On approval, the server reads all tabs, appends native formatting to the first tab, and uses the returned revision id as write control so collaborator changes are merged by Google Docs. Native tables use server-returned cell indexes rather than agent-computed offsets.", "input_params": [ { "name": "account_id", "type": "string", "required": true, "description": "A googledocs account id from composio/list_accounts.", "example": "googledocs:google_user:1163774059..." }, { "name": "toolkit", "type": "string", "required": true, "description": "Must be the literal toolkit slug googledocs.", "example": "googledocs", "enum": [ "googledocs" ] }, { "name": "document_id", "type": "string", "required": true, "description": "Existing Google Docs document id.", "example": "1AbCdEfGhIjKlMn" }, { "name": "markdown", "type": "string", "required": true, "description": "Non-empty Markdown content to append.", "example": "## Update\n\nNew data received at 14:00 UTC." } ], "output_shape": "202 -> {ok, pending_review_id}; on approval data.{document_id, url}; partial failure details include {stage, document_id, document_url}" }, { "id": "action:composio/create_doc_from_markdown", "namespace": "composio", "action": "create_doc_from_markdown", "description": "Create a richly formatted Google Doc from Markdown through one human-reviewed semantic action.", "mode": "risk", "platforms": [ "composio_managed" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "Use only with a googledocs account copied from composio/list_accounts. The dispatcher requests human approval before any Google API call. On approval, the server compiles the Markdown into native Google Docs headings, text styles, links, lists, code, images, and tables, then performs the required multi-stage official API calls through the same granted Composio account. Do not build batchUpdate indexes yourself. If creation succeeds but formatting fails, the error includes the document id and recovery URL for the empty or partially updated document.", "input_params": [ { "name": "account_id", "type": "string", "required": true, "description": "A googledocs account id from composio/list_accounts.", "example": "googledocs:google_user:1163774059..." }, { "name": "toolkit", "type": "string", "required": true, "description": "Must be the literal toolkit slug googledocs.", "example": "googledocs", "enum": [ "googledocs" ] }, { "name": "title", "type": "string", "required": true, "description": "Title for the new Google Doc.", "example": "Weekly Performance Report" }, { "name": "markdown", "type": "string", "required": true, "description": "Complete Markdown content to compile and write.", "example": "# Summary\n\n- Spend: $150\n- Conversions: 12" } ], "output_shape": "202 -> {ok, pending_review_id}; on approval data.{document_id, url, title}; partial failure details include {stage, document_id, document_url}" }, { "id": "action:composio/graphql_mutate", "namespace": "composio", "action": "graphql_mutate", "description": "Write to a GraphQL toolkit's official API by POSTing a mutation GraphQL document via human approval.", "mode": "risk", "platforms": [ "composio_managed" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "The write action for GraphQL toolkits (e.g. Monday). GraphQL uses one POST endpoint for reads and writes, so writes cannot use request_change; this action POSTs a mutation document for human approval (HITL). Returns a pending_review_id immediately; the request is forwarded to the vendor only after a human approves. The document must contain a top-level mutation (a pure query is steered to composio/graphql_query; subscriptions are rejected). Writes are opt-in PER TOOLKIT: a GraphQL toolkit that is not write-enabled — and every toolkit whose api_shape is 'unknown' — is refused here. That refusal is not retryable; tell the user the toolkit needs to be write-enabled in Soku. body accepts ONLY query, variables and operationName — any other key is rejected (400), so REST fields cannot ride along with a mutation — and the endpoint must be the toolkit's own GraphQL endpoint (monday /v2) written as a PLAIN path with no query string or fragment, so a '..' traversal onto a REST path is rejected too, as are percent escapes (%2e, %3f, %2f ...), ';', '\\' and '?_method=DELETE'. Get the (account_id, toolkit) from composio/list_accounts. REFERENCE FIRST — before your FIRST call against a toolkit, open its docs_url (web_search the provider's OFFICIAL API reference when docs_url is null) and copy the exact path, method, query parameters and body shape from it. Do NOT reconstruct an endpoint from memory, from another vendor's API, or from a Composio tool name (e.g. GMAIL_SEND_EMAIL) — those slugs do not exist on this raw path; never guess. If a call fails with a 4xx, the error carries the toolkit's docs_url, api_hosts and base_path: re-read the reference and fix the request from it — do not retry a guess. Never guess a mutation shape: a human approves this request.", "input_params": [ { "name": "account_id", "type": "string", "required": true, "description": "Connected account id from composio/list_accounts.", "example": "monday:ca_9hk2..." }, { "name": "toolkit", "type": "string", "required": true, "description": "GraphQL toolkit of the account (from composio/list_accounts).", "example": "monday" }, { "name": "endpoint", "type": "string", "required": true, "description": "The toolkit's GraphQL endpoint — a relative path (recommended) or an absolute https URL on the toolkit's official host. A declared GraphQL toolkit only accepts its own (monday /v2, fireflies /graphql, linear /graphql); any other path is rejected (400), so a REST write goes through composio/request_change instead. It must be a BARE path: a query string or fragment is rejected. Endpoint spelling is strict: a relative path starts with EXACTLY one '/' ('/v1/users/me' — '//v1/users', '///v1/users' and a bare 'v1/users' are all rejected), an absolute URL is 'https://host/path' with no userinfo ('https://user@host/...' is rejected), a backslash or control character anywhere is rejected, and a space in the HOST or PATH is rejected (percent-encode it as %20). A space inside a QUERY string is fine — write \"?q=from:a is:unread\" as-is, no encoding needed. These are 400s raised before the call, not vendor errors — fix the spelling rather than retrying.", "example": "https://api.monday.com/v2" }, { "name": "body", "type": "object", "required": true, "description": "GraphQL request body: a 'query' string (must contain a mutation operation) and optional 'variables' object.", "example": { "query": "mutation ($b: ID!, $g: String!, $n: String!) { create_item(board_id: $b, group_id: $g, item_name: $n) { id } }", "variables": { "b": "123", "g": "topics", "n": "New item" } } }, { "name": "parameters", "type": "list", "required": false, "description": "Optional Composio proxy parameter objects." } ], "output_shape": "202 -> {ok, pending_review_id}; on approval data.{toolkit, endpoint, method, status, data}" }, { "id": "action:composio/graphql_query", "namespace": "composio", "action": "graphql_query", "description": "Read from a GraphQL toolkit's official API by POSTing a query-only GraphQL document (relayed through Composio; runs immediately).", "mode": "read", "platforms": [ "composio_managed" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "The read action for GraphQL toolkits (e.g. Fireflies). GraphQL uses one POST endpoint for reads and writes, so it cannot use proxy_get; this action POSTs a query-only document to the toolkit's GraphQL endpoint and runs immediately (no HITL). Mutations and subscriptions are rejected here — writes go through composio/graphql_mutate where the toolkit has them enabled. Also usable for a toolkit whose api_shape is 'unknown' once its official reference shows the API is GraphQL. Four guards keep this action from becoming a generic POST tunnel — it is not HITL-gated, so a REST write smuggled into the request would reach the vendor without approval. (1) body accepts ONLY query, variables and operationName, for EVERY toolkit including declared GraphQL ones; any other key is rejected (400), because the body is forwarded to the vendor verbatim. (2) the endpoint must be the toolkit's OWN GraphQL endpoint: a DECLARED GraphQL toolkit is pinned to it exactly (monday /v2, fireflies /graphql, linear /graphql — relative or absolute, both accepted), and an 'unknown' toolkit must pass a path with a segment named 'graphql' (/graphql, /v1/graphql, /api/graphql and /graphql/v1 all qualify; /api/v2/tickets does not). The endpoint path must be a PLAIN path — letters, digits, '-', '.', '_' and '~' only — so '..' traversal, percent escapes (%2e, %3f, %2f ...), ';' and '\\' are all rejected outright, and the path this check reads is the exact path the vendor routes on. (3) the endpoint carries NO query string and NO fragment: '/graphql?_method=DELETE' is rejected (400), because '_method' is the Rails/Laravel/Symfony write override and this action is a POST — put everything in body. (4) parameters is not accepted at all here — a GraphQL request's arguments belong in body.variables, and a proxy parameter would set an upstream header or query parameter on an unreviewed request. To write to a REST endpoint on an unknown-shape toolkit use composio/request_change, which IS approval-gated. Get the (account_id, toolkit) from composio/list_accounts. REFERENCE FIRST — before your FIRST call against a toolkit, open its docs_url (web_search the provider's OFFICIAL API reference when docs_url is null) and copy the exact path, method, query parameters and body shape from it. Do NOT reconstruct an endpoint from memory, from another vendor's API, or from a Composio tool name (e.g. GMAIL_SEND_EMAIL) — those slugs do not exist on this raw path; never guess. If a call fails with a 4xx, the error carries the toolkit's docs_url, api_hosts and base_path: re-read the reference and fix the request from it — do not retry a guess. For GraphQL that means the schema itself (types, queries, variables) — an invented field name fails the whole document. Data is realtime (no cached pipeline). Bulk-read rule: GraphQL is natively batch-friendly — fetch many items with only the fields you need in ONE query (list field + selection set + limit/pagination arguments from the official schema); never loop item-by-item over a list you could select in a single document. Size pages so one response fits in one tool result.", "input_params": [ { "name": "account_id", "type": "string", "required": true, "description": "Connected account id from composio/list_accounts.", "example": "fireflies:ca_3kf9..." }, { "name": "toolkit", "type": "string", "required": true, "description": "GraphQL toolkit of the account (from composio/list_accounts).", "example": "fireflies" }, { "name": "endpoint", "type": "string", "required": true, "description": "The toolkit's GraphQL endpoint — a relative path (recommended, e.g. /graphql) or an absolute https URL on the toolkit's official host. It must be THE GraphQL endpoint of this toolkit: a declared GraphQL toolkit only accepts its own (monday /v2, fireflies /graphql, linear /graphql), and an 'unknown' toolkit only accepts a path with a 'graphql' segment. Any other path is rejected (400) — read it with composio/proxy_get, write it with composio/request_change. It must be a BARE path: a query string or fragment ('/graphql?x=1', '/graphql#y') is rejected — a GraphQL request takes no URL parameters. Endpoint spelling is strict: a relative path starts with EXACTLY one '/' ('/v1/users/me' — '//v1/users', '///v1/users' and a bare 'v1/users' are all rejected), an absolute URL is 'https://host/path' with no userinfo ('https://user@host/...' is rejected), a backslash or control character anywhere is rejected, and a space in the HOST or PATH is rejected (percent-encode it as %20). A space inside a QUERY string is fine — write \"?q=from:a is:unread\" as-is, no encoding needed. These are 400s raised before the call, not vendor errors — fix the spelling rather than retrying.", "example": "https://api.fireflies.ai/graphql" }, { "name": "body", "type": "object", "required": true, "description": "GraphQL request body: a 'query' string (query operation only) and optional 'variables' object. No other key is accepted, and this action takes no 'parameters' — put every argument in 'variables'.", "example": { "query": "query { transcripts(limit: 5) { id title } }", "variables": {} } } ], "output_shape": "data.{toolkit, endpoint, method, status, data}" }, { "id": "action:composio/list_accounts", "namespace": "composio", "action": "list_accounts", "description": "List the connected third-party accounts (Gmail, Notion, Google Sheets, ...) this brand can use.", "mode": "read", "platforms": [ "composio_managed" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the Composio connected accounts granted to the current brand: each row is (account_id, toolkit, name, status, execution_available, api_shape, docs_url, api_hosts, base_path, recommended). START HERE for any third-party task — every proxy action needs an (account_id, toolkit) pair from this list; neither is guessable. Do NOT ask the user for an account id; this action is the source of truth. Every connected toolkit is executable (execution_available is true). api_shape is 'rest' (use proxy_get / request_change), 'graphql' (use graphql_query / graphql_mutate), or 'unknown' — meaning Soku has NOT classified this toolkit, not that it is REST. On 'unknown', let the official reference decide the shape: both read actions are open (proxy_get for a REST API, graphql_query for a GraphQL one) and REST writes still go through request_change, but graphql_mutate is refused until the toolkit is explicitly write-enabled — so for a GraphQL write on an unknown toolkit, tell the user it must be enabled by Soku rather than retrying. REFERENCE FIRST — before your FIRST call against a toolkit, open its docs_url (web_search the provider's OFFICIAL API reference when docs_url is null) and copy the exact path, method, query parameters and body shape from it. Do NOT reconstruct an endpoint from memory, from another vendor's API, or from a Composio tool name (e.g. GMAIL_SEND_EMAIL) — those slugs do not exist on this raw path; never guess. If a call fails with a 4xx, the error carries the toolkit's docs_url, api_hosts and base_path: re-read the reference and fix the request from it — do not retry a guess. api_hosts lists the toolkit's curated official hosts: a relative endpoint resolves against the toolkit's PRIMARY host, so reaching any OTHER host in api_hosts needs an absolute https URL. An EMPTY api_hosts means the toolkit has no host allowlist — use a relative endpoint ONLY; an absolute URL is rejected (400). base_path, when non-null, is a path segment Composio's base URL for that toolkit ALREADY includes (e.g. apollo '/api/v1') — your relative endpoint must OMIT it (use '/mixed_people/search', NOT '/api/v1/mixed_people/search'), or the URL double-prefixes and 404s.", "input_params": [], "output_shape": "data.rows[].{account_id, toolkit, name, status, execution_available, api_shape, docs_url, api_hosts, base_path, recommended}" }, { "id": "action:composio/proxy_get", "namespace": "composio", "action": "proxy_get", "description": "Read from a connected account's official API (HTTP GET, relayed through Composio with the account credential injected).", "mode": "read", "platforms": [ "composio_managed" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Forwards a GET request to the toolkit's official API; Composio injects the connected account's OAuth credential server-side (you never see a token). Get the (account_id, toolkit) from composio/list_accounts FIRST. REFERENCE FIRST — before your FIRST call against a toolkit, open its docs_url (web_search the provider's OFFICIAL API reference when docs_url is null) and copy the exact path, method, query parameters and body shape from it. Do NOT reconstruct an endpoint from memory, from another vendor's API, or from a Composio tool name (e.g. GMAIL_SEND_EMAIL) — those slugs do not exist on this raw path; never guess. If a call fails with a 4xx, the error carries the toolkit's docs_url, api_hosts and base_path: re-read the reference and fix the request from it — do not retry a guess. Put query parameters directly in the endpoint URL. Only the toolkit's official API hosts are allowed. Anything that mutates state (POST/PUT/PATCH/DELETE) must go through composio/request_change. Data is realtime (no cached pipeline). Bulk-read rule: before fetching more than a few similar items, check the official API reference for a cheaper request shape, in priority order: (1) server-side filtering/search parameters, (2) a list/bulk endpoint returning many items with the fields you need in one call, (3) field projection / partial responses (fields= / format= style parameters) so you fetch only what you need, (4) pagination sized so one page fits in one tool result. Loop item-by-item only when the official API offers none of these, and say so. A vendor batch endpoint that requires POST is a write here (HITL) even if it only reads — for reads, prefer GET-shaped list/search endpoints.", "input_params": [ { "name": "account_id", "type": "string", "required": true, "description": "Connected account id, copied verbatim from composio/list_accounts — never constructed by hand.", "example": "googledocs:google_user:1163774059..." }, { "name": "toolkit", "type": "string", "required": true, "description": "Toolkit of the account (from composio/list_accounts).", "example": "gmail" }, { "name": "endpoint", "type": "string", "required": true, "description": "Prefer a RELATIVE path (e.g. /gmail/v1/users/me/profile): it works for EVERY toolkit — Composio pins it to the toolkit's primary host, so when the official reference shows a full URL on that primary host, copy just the path (and omit any base_path the toolkit lists in list_accounts). Pass an absolute https URL ONLY to reach a secondary official host listed in api_hosts (from list_accounts) — e.g. from a googledocs account, listing Google Docs/Sheets files is a Drive API call: https://www.googleapis.com/drive/v3/files?q=..., and reading a presentation is a Slides API call: https://slides.googleapis.com/v1/presentations/{presentationId} (reference: https://developers.google.com/slides/api/reference/rest). Keep such URLs absolute; do NOT strip them to relative paths. When api_hosts is EMPTY the toolkit has no host allowlist, so a relative path is the ONLY valid form — an absolute URL is rejected (400). (Exception: slack — always pass an absolute https://slack.com/api/... URL; slack's registered base URL already ends in /api, so a docs-style relative /api/... path double-prefixes and 404s.) Query parameters may be inlined here (this is a GET). Endpoint spelling is strict: a relative path starts with EXACTLY one '/' ('/v1/users/me' — '//v1/users', '///v1/users' and a bare 'v1/users' are all rejected), an absolute URL is 'https://host/path' with no userinfo ('https://user@host/...' is rejected), a backslash or control character anywhere is rejected, and a space in the HOST or PATH is rejected (percent-encode it as %20). A space inside a QUERY string is fine — write \"?q=from:a is:unread\" as-is, no encoding needed. These are 400s raised before the call, not vendor errors — fix the spelling rather than retrying.", "example": "/gmail/v1/users/me/profile" }, { "name": "parameters", "type": "list", "required": false, "description": "Optional Composio proxy parameter objects; prefer inlining query params in the endpoint URL instead. Each item is {'name': ..., 'value': ..., 'type': 'query'|'header'} and 'type' is REQUIRED and lowercase — 'Query', the OpenAPI spelling 'in', and a missing type are all rejected (400), because Composio would then place the parameter wherever it defaults to rather than where you meant. Header parameters ARE allowed here for the request headers a vendor requires on a read (e.g. Notion-Version, Accept); the one refused family is the method override (X-HTTP-Method-Override, X-Method-Override, X-HTTP-Method), which would turn this GET into an unapproved write — send the real method through composio/request_change instead." } ], "output_shape": "data.{toolkit, endpoint, method, status, data}" }, { "id": "action:composio/replace_doc_with_markdown", "namespace": "composio", "action": "replace_doc_with_markdown", "description": "Replace the first tab of an existing Google Doc with richly formatted Markdown through human review.", "mode": "risk", "platforms": [ "composio_managed" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "This is destructive and always requires human approval. Use only with a googledocs account from composio/list_accounts. On approval, the server reads all tabs, deletes only the editable content in the first tab, writes the compiled Markdown, and carries the read revision id into write control. An empty Markdown string clears the first tab while preserving its required trailing paragraph and all other tabs.", "input_params": [ { "name": "account_id", "type": "string", "required": true, "description": "A googledocs account id from composio/list_accounts.", "example": "googledocs:google_user:1163774059..." }, { "name": "toolkit", "type": "string", "required": true, "description": "Must be the literal toolkit slug googledocs.", "example": "googledocs", "enum": [ "googledocs" ] }, { "name": "document_id", "type": "string", "required": true, "description": "Existing Google Docs document id.", "example": "1AbCdEfGhIjKlMn" }, { "name": "markdown", "type": "string", "required": true, "description": "Replacement Markdown. An empty string intentionally clears the first tab.", "example": "# Replaced Report\n\nFull replacement content." } ], "output_shape": "202 -> {ok, pending_review_id}; on approval data.{document_id, url}; partial failure details include {stage, document_id, document_url}" }, { "id": "action:composio/request_change", "namespace": "composio", "action": "request_change", "description": "Write to a connected account's official API (POST/PUT/PATCH/DELETE) via human approval.", "mode": "risk", "platforms": [ "composio_managed" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "Submits an official-API write for human approval (HITL). Returns a pending_review_id immediately; the request is forwarded to the vendor only after a human approves. Provide method + endpoint + body (e.g. send a Gmail message, append a Google Sheets row, update a Notion page). Get the (account_id, toolkit) from composio/list_accounts. REFERENCE FIRST — before your FIRST call against a toolkit, open its docs_url (web_search the provider's OFFICIAL API reference when docs_url is null) and copy the exact path, method, query parameters and body shape from it. Do NOT reconstruct an endpoint from memory, from another vendor's API, or from a Composio tool name (e.g. GMAIL_SEND_EMAIL) — those slugs do not exist on this raw path; never guess. If a call fails with a 4xx, the error carries the toolkit's docs_url, api_hosts and base_path: re-read the reference and fix the request from it — do not retry a guess. A write payload is the LAST thing to guess: a human reviews this request, so an invented body wastes their approval.", "input_params": [ { "name": "account_id", "type": "string", "required": true, "description": "Connected account id, copied verbatim from composio/list_accounts — never constructed by hand.", "example": "gmail:google_user:1163774059..." }, { "name": "toolkit", "type": "string", "required": true, "description": "Toolkit of the account (from composio/list_accounts).", "example": "gmail" }, { "name": "method", "type": "string", "required": true, "description": "HTTP write method.", "example": "POST", "enum": [ "POST", "PUT", "PATCH", "DELETE" ] }, { "name": "endpoint", "type": "string", "required": true, "description": "Prefer a RELATIVE path: it works for EVERY toolkit — Composio pins it to the toolkit's primary host, so when the official reference shows a full URL on that primary host, copy just the path (and omit any base_path the toolkit lists in list_accounts). Pass an absolute https URL ONLY to reach a secondary official host listed in api_hosts (from list_accounts) — keep such a URL absolute (e.g. from a googledocs account, a Drive write: https://www.googleapis.com/drive/v3/..., or a Slides write: https://slides.googleapis.com/v1/presentations/{presentationId}:batchUpdate per https://developers.google.com/slides/api/reference/rest). When api_hosts is EMPTY the toolkit has no host allowlist, so a relative path is the ONLY valid form — an absolute URL is rejected (400). (Exception: slack — always pass an absolute https://slack.com/api/... URL; a relative /api/... path double-prefixes and 404s.) Endpoint spelling is strict: a relative path starts with EXACTLY one '/' ('/v1/users/me' — '//v1/users', '///v1/users' and a bare 'v1/users' are all rejected), an absolute URL is 'https://host/path' with no userinfo ('https://user@host/...' is rejected), a backslash or control character anywhere is rejected, and a space in the HOST or PATH is rejected (percent-encode it as %20). A space inside a QUERY string is fine — write \"?q=from:a is:unread\" as-is, no encoding needed. These are 400s raised before the call, not vendor errors — fix the spelling rather than retrying.", "example": "/gmail/v1/users/me/drafts" }, { "name": "body", "type": "object", "required": false, "description": "JSON request body per the official API.", "example": { "message": { "raw": "" } } }, { "name": "parameters", "type": "list", "required": false, "description": "Optional Composio proxy parameter objects." } ], "output_shape": "202 -> {ok, pending_review_id}; on approval data.{toolkit, endpoint, method, status, data}" }, { "id": "action:doordash/get_sales_summary", "namespace": "doordash", "action": "get_sales_summary", "description": "DoorDash sales aggregates (orders, fees, marketing, net) — no rows.", "mode": "read", "platforms": [ "doordash" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "AGGREGATES ONLY over the TRANSACTION_DETAIL report (v4): row count and sums of subtotal, tax, commission, marketing fees, error charges and net total for the selected granted stores and window. Optional group_by='store' or group_by='date' adds per-group aggregates alongside the overall one. Use doordash/list_transactions when you need the underlying rows. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got. start_date and end_date are required ISO dates (YYYY-MM-DD), inclusive on both ends; the window may span at most 366 days counting both ends (so end_date is at most 365 days after start_date). store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores. Money columns are never guessed: a column absent from the CSV is listed under aggregates.missing_columns, a column present but with NO parseable number in any row is listed under aggregates.unreadable_columns and is left OUT of aggregates.sums (a missing sum means 'could not read it', never 'it is zero' — report it as unavailable, do not tell the user it was 0), and per-column cell counts are in aggregates.parsed_values / aggregates.unparsed_values so you can see the coverage behind a partially readable column. Rows are re-checked locally against the queried stores before aggregation. If any come back that do not belong to them, the response carries out_of_scope_rows (a different store's rows) and/or unattributed_rows (no usable store id) plus a scope_note: those rows are in NEITHER the echoed rows nor the sums, so the result is a subset of the report — say so when you report the numbers. store_scope_unverified=true means the report had no store id column and the rows could not be re-checked at all. A report_columns_ambiguous error means the report carried two columns that both read as the same one, so the scope could not be decided: it reproduces on every retry, with any date range and any store list — report it and tell the user to contact support rather than retrying. Reports generate asynchronously (up to ~5 minutes upstream). When a response has status='pending', re-invoke the SAME action with the SAME arguments after retry_after_seconds (~2 minutes): report ids are cached server-side, so an identical re-call RESUMES the same report instead of creating a new one. Changing any argument starts a fresh report.", "input_params": [ { "name": "store_ids", "type": "list", "required": false, "description": "store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores.", "example": [ 901, 902 ], "source": "upstream:action:doordash/list_stores" }, { "name": "start_date", "type": "string", "required": true, "description": "Start of the window, ISO date (YYYY-MM-DD), inclusive. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got.", "example": "2026-08-01" }, { "name": "end_date", "type": "string", "required": true, "description": "End of the window, ISO date (YYYY-MM-DD), inclusive. At most 365 days after start_date (366 days counting both ends). Use yesterday to cover 'today' (data is D-1).", "example": "2026-08-10" }, { "name": "group_by", "type": "string", "required": false, "description": "Optional grouping: 'store' (per-store aggregates) or 'date' (per-day aggregates). Omit for the overall aggregate only.", "example": "store", "enum": [ "store", "date" ] } ], "output_shape": "{store_ids, start_date, end_date, data_freshness, aggregates: {row_count, sums: {column: decimal-string}, missing_columns, unreadable_columns, parsed_values, unparsed_values}, group_by?, groups?: [{key, aggregates}], missing_group_column?, out_of_scope_rows?, unattributed_rows?, store_scope_unverified?, scope_note?} OR, while the report is still generating: {status: 'pending', report_id, retry_after_seconds, note, store_ids, start_date, end_date, data_freshness}" }, { "id": "action:doordash/list_cancelled_orders", "namespace": "doordash", "action": "list_cancelled_orders", "description": "List DoorDash cancelled-order rows with aggregates.", "mode": "read", "platforms": [ "doordash" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Cancelled orders from the CANCELLED_ORDERS report across the selected granted stores — cancellation time and category plus the money involved — with aggregate sums. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got. start_date and end_date are required ISO dates (YYYY-MM-DD), inclusive on both ends; the window may span at most 366 days counting both ends (so end_date is at most 365 days after start_date). store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores. Aggregates are computed over the FULL report before any row truncation (check `truncated` and `total_rows`). Money columns are never guessed: a column absent from the CSV is listed under aggregates.missing_columns, a column present but with NO parseable number in any row is listed under aggregates.unreadable_columns and is left OUT of aggregates.sums (a missing sum means 'could not read it', never 'it is zero' — report it as unavailable, do not tell the user it was 0), and per-column cell counts are in aggregates.parsed_values / aggregates.unparsed_values so you can see the coverage behind a partially readable column. Rows are re-checked locally against the queried stores before aggregation. If any come back that do not belong to them, the response carries out_of_scope_rows (a different store's rows) and/or unattributed_rows (no usable store id) plus a scope_note: those rows are in NEITHER the echoed rows nor the sums, so the result is a subset of the report — say so when you report the numbers. store_scope_unverified=true means the report had no store id column and the rows could not be re-checked at all. A report_columns_ambiguous error means the report carried two columns that both read as the same one, so the scope could not be decided: it reproduces on every retry, with any date range and any store list — report it and tell the user to contact support rather than retrying. Reports generate asynchronously (up to ~5 minutes upstream). When a response has status='pending', re-invoke the SAME action with the SAME arguments after retry_after_seconds (~2 minutes): report ids are cached server-side, so an identical re-call RESUMES the same report instead of creating a new one. Changing any argument starts a fresh report.", "input_params": [ { "name": "store_ids", "type": "list", "required": false, "description": "store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores.", "example": [ 901, 902 ], "source": "upstream:action:doordash/list_stores" }, { "name": "start_date", "type": "string", "required": true, "description": "Start of the window, ISO date (YYYY-MM-DD), inclusive. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got.", "example": "2026-08-01" }, { "name": "end_date", "type": "string", "required": true, "description": "End of the window, ISO date (YYYY-MM-DD), inclusive. At most 365 days after start_date (366 days counting both ends). Use yesterday to cover 'today' (data is D-1).", "example": "2026-08-10" }, { "name": "limit", "type": "integer", "required": false, "description": "Maximum rows echoed back, 1-1000. Default 200. Values above 1000 are rejected rather than clamped. Aggregates always cover the FULL report regardless of limit.", "example": 200 } ], "output_shape": "{store_ids, start_date, end_date, data_freshness, cancelled_orders: [], total_rows, returned_rows, truncated, aggregates: {row_count, sums: {column: decimal-string}, missing_columns, unreadable_columns, parsed_values, unparsed_values}, out_of_scope_rows?, unattributed_rows?, store_scope_unverified?, scope_note?, note?} OR, while the report is still generating: {status: 'pending', report_id, retry_after_seconds, note, store_ids, start_date, end_date, data_freshness}" }, { "id": "action:doordash/list_orders", "namespace": "doordash", "action": "list_orders", "description": "List DoorDash order rows (ORDER_DETAIL report) with aggregates.", "mode": "read", "platforms": [ "doordash" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Order-level rows from the ORDER_DETAIL report across the selected granted stores, plus aggregate sums of the money columns. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got. start_date and end_date are required ISO dates (YYYY-MM-DD), inclusive on both ends; the window may span at most 366 days counting both ends (so end_date is at most 365 days after start_date). store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores. Aggregates are computed over the FULL report before any row truncation (check `truncated` and `total_rows`). Money columns are never guessed: a column absent from the CSV is listed under aggregates.missing_columns, a column present but with NO parseable number in any row is listed under aggregates.unreadable_columns and is left OUT of aggregates.sums (a missing sum means 'could not read it', never 'it is zero' — report it as unavailable, do not tell the user it was 0), and per-column cell counts are in aggregates.parsed_values / aggregates.unparsed_values so you can see the coverage behind a partially readable column. Rows are re-checked locally against the queried stores before aggregation. If any come back that do not belong to them, the response carries out_of_scope_rows (a different store's rows) and/or unattributed_rows (no usable store id) plus a scope_note: those rows are in NEITHER the echoed rows nor the sums, so the result is a subset of the report — say so when you report the numbers. store_scope_unverified=true means the report had no store id column and the rows could not be re-checked at all. A report_columns_ambiguous error means the report carried two columns that both read as the same one, so the scope could not be decided: it reproduces on every retry, with any date range and any store list — report it and tell the user to contact support rather than retrying. Reports generate asynchronously (up to ~5 minutes upstream). When a response has status='pending', re-invoke the SAME action with the SAME arguments after retry_after_seconds (~2 minutes): report ids are cached server-side, so an identical re-call RESUMES the same report instead of creating a new one. Changing any argument starts a fresh report.", "input_params": [ { "name": "store_ids", "type": "list", "required": false, "description": "store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores.", "example": [ 901, 902 ], "source": "upstream:action:doordash/list_stores" }, { "name": "start_date", "type": "string", "required": true, "description": "Start of the window, ISO date (YYYY-MM-DD), inclusive. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got.", "example": "2026-08-01" }, { "name": "end_date", "type": "string", "required": true, "description": "End of the window, ISO date (YYYY-MM-DD), inclusive. At most 365 days after start_date (366 days counting both ends). Use yesterday to cover 'today' (data is D-1).", "example": "2026-08-10" }, { "name": "limit", "type": "integer", "required": false, "description": "Maximum rows echoed back, 1-1000. Default 200. Values above 1000 are rejected rather than clamped. Aggregates always cover the FULL report regardless of limit.", "example": 200 } ], "output_shape": "{store_ids, start_date, end_date, data_freshness, orders: [], total_rows, returned_rows, truncated, aggregates: {row_count, sums: {column: decimal-string}, missing_columns, unreadable_columns, parsed_values, unparsed_values}, out_of_scope_rows?, unattributed_rows?, store_scope_unverified?, scope_note?, note?} OR, while the report is still generating: {status: 'pending', report_id, retry_after_seconds, note, store_ids, start_date, end_date, data_freshness}" }, { "id": "action:doordash/list_payouts", "namespace": "doordash", "action": "list_payouts", "description": "List DoorDash payout rows (PAYOUT_SUMMARY report) with aggregates.", "mode": "read", "platforms": [ "doordash" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Payout-level rows from the PAYOUT_SUMMARY report (v3) across the selected granted stores — what DoorDash actually paid out, with the commission / marketing / error-charge components — plus aggregate sums. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got. start_date and end_date are required ISO dates (YYYY-MM-DD), inclusive on both ends; the window may span at most 366 days counting both ends (so end_date is at most 365 days after start_date). store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores. Aggregates are computed over the FULL report before any row truncation (check `truncated` and `total_rows`). Money columns are never guessed: a column absent from the CSV is listed under aggregates.missing_columns, a column present but with NO parseable number in any row is listed under aggregates.unreadable_columns and is left OUT of aggregates.sums (a missing sum means 'could not read it', never 'it is zero' — report it as unavailable, do not tell the user it was 0), and per-column cell counts are in aggregates.parsed_values / aggregates.unparsed_values so you can see the coverage behind a partially readable column. Rows are re-checked locally against the queried stores before aggregation. If any come back that do not belong to them, the response carries out_of_scope_rows (a different store's rows) and/or unattributed_rows (no usable store id) plus a scope_note: those rows are in NEITHER the echoed rows nor the sums, so the result is a subset of the report — say so when you report the numbers. store_scope_unverified=true means the report had no store id column and the rows could not be re-checked at all. A report_columns_ambiguous error means the report carried two columns that both read as the same one, so the scope could not be decided: it reproduces on every retry, with any date range and any store list — report it and tell the user to contact support rather than retrying. Reports generate asynchronously (up to ~5 minutes upstream). When a response has status='pending', re-invoke the SAME action with the SAME arguments after retry_after_seconds (~2 minutes): report ids are cached server-side, so an identical re-call RESUMES the same report instead of creating a new one. Changing any argument starts a fresh report.", "input_params": [ { "name": "store_ids", "type": "list", "required": false, "description": "store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores.", "example": [ 901, 902 ], "source": "upstream:action:doordash/list_stores" }, { "name": "start_date", "type": "string", "required": true, "description": "Start of the window, ISO date (YYYY-MM-DD), inclusive. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got.", "example": "2026-08-01" }, { "name": "end_date", "type": "string", "required": true, "description": "End of the window, ISO date (YYYY-MM-DD), inclusive. At most 365 days after start_date (366 days counting both ends). Use yesterday to cover 'today' (data is D-1).", "example": "2026-08-10" }, { "name": "limit", "type": "integer", "required": false, "description": "Maximum rows echoed back, 1-1000. Default 200. Values above 1000 are rejected rather than clamped. Aggregates always cover the FULL report regardless of limit.", "example": 200 } ], "output_shape": "{store_ids, start_date, end_date, data_freshness, payouts: [], total_rows, returned_rows, truncated, aggregates: {row_count, sums: {column: decimal-string}, missing_columns, unreadable_columns, parsed_values, unparsed_values}, out_of_scope_rows?, unattributed_rows?, store_scope_unverified?, scope_note?, note?} OR, while the report is still generating: {status: 'pending', report_id, retry_after_seconds, note, store_ids, start_date, end_date, data_freshness}" }, { "id": "action:doordash/list_promotion_performance", "namespace": "doordash", "action": "list_promotion_performance", "description": "List DoorDash promotion performance rows.", "mode": "read", "platforms": [ "doordash" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Promotion performance from the PROMOTION_PERFORMANCE report across the selected granted stores — orders, marketing spend and attributed sales per promotion — with aggregate sums. May be legitimately EMPTY for accounts with no such activity: an empty report returns normally with rows=[] and an explanatory note — it is not an error. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got. start_date and end_date are required ISO dates (YYYY-MM-DD), inclusive on both ends; the window may span at most 366 days counting both ends (so end_date is at most 365 days after start_date). store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores. Aggregates are computed over the FULL report before any row truncation (check `truncated` and `total_rows`). Money columns are never guessed: a column absent from the CSV is listed under aggregates.missing_columns, a column present but with NO parseable number in any row is listed under aggregates.unreadable_columns and is left OUT of aggregates.sums (a missing sum means 'could not read it', never 'it is zero' — report it as unavailable, do not tell the user it was 0), and per-column cell counts are in aggregates.parsed_values / aggregates.unparsed_values so you can see the coverage behind a partially readable column. Rows are re-checked locally against the queried stores before aggregation. If any come back that do not belong to them, the response carries out_of_scope_rows (a different store's rows) and/or unattributed_rows (no usable store id) plus a scope_note: those rows are in NEITHER the echoed rows nor the sums, so the result is a subset of the report — say so when you report the numbers. store_scope_unverified=true means the report had no store id column and the rows could not be re-checked at all. A report_columns_ambiguous error means the report carried two columns that both read as the same one, so the scope could not be decided: it reproduces on every retry, with any date range and any store list — report it and tell the user to contact support rather than retrying. Reports generate asynchronously (up to ~5 minutes upstream). When a response has status='pending', re-invoke the SAME action with the SAME arguments after retry_after_seconds (~2 minutes): report ids are cached server-side, so an identical re-call RESUMES the same report instead of creating a new one. Changing any argument starts a fresh report.", "input_params": [ { "name": "store_ids", "type": "list", "required": false, "description": "store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores.", "example": [ 901, 902 ], "source": "upstream:action:doordash/list_stores" }, { "name": "start_date", "type": "string", "required": true, "description": "Start of the window, ISO date (YYYY-MM-DD), inclusive. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got.", "example": "2026-08-01" }, { "name": "end_date", "type": "string", "required": true, "description": "End of the window, ISO date (YYYY-MM-DD), inclusive. At most 365 days after start_date (366 days counting both ends). Use yesterday to cover 'today' (data is D-1).", "example": "2026-08-10" }, { "name": "limit", "type": "integer", "required": false, "description": "Maximum rows echoed back, 1-1000. Default 200. Values above 1000 are rejected rather than clamped. Aggregates always cover the FULL report regardless of limit.", "example": 200 } ], "output_shape": "{store_ids, start_date, end_date, data_freshness, rows: [], total_rows, returned_rows, truncated, aggregates: {row_count, sums: {column: decimal-string}, missing_columns, unreadable_columns, parsed_values, unparsed_values}, out_of_scope_rows?, unattributed_rows?, store_scope_unverified?, scope_note?, note?} OR, while the report is still generating: {status: 'pending', report_id, retry_after_seconds, note, store_ids, start_date, end_date, data_freshness}" }, { "id": "action:doordash/list_sponsored_listing_performance", "namespace": "doordash", "action": "list_sponsored_listing_performance", "description": "List DoorDash Sponsored Listing (ads) performance rows.", "mode": "read", "platforms": [ "doordash" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Ads performance from the SPONSORED_LISTING_PERFORMANCE report across the selected granted stores — impressions, clicks, orders, ad spend and attributed sales — with aggregate sums. May be legitimately EMPTY for accounts with no such activity: an empty report returns normally with rows=[] and an explanatory note — it is not an error. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got. start_date and end_date are required ISO dates (YYYY-MM-DD), inclusive on both ends; the window may span at most 366 days counting both ends (so end_date is at most 365 days after start_date). store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores. Aggregates are computed over the FULL report before any row truncation (check `truncated` and `total_rows`). Money columns are never guessed: a column absent from the CSV is listed under aggregates.missing_columns, a column present but with NO parseable number in any row is listed under aggregates.unreadable_columns and is left OUT of aggregates.sums (a missing sum means 'could not read it', never 'it is zero' — report it as unavailable, do not tell the user it was 0), and per-column cell counts are in aggregates.parsed_values / aggregates.unparsed_values so you can see the coverage behind a partially readable column. Rows are re-checked locally against the queried stores before aggregation. If any come back that do not belong to them, the response carries out_of_scope_rows (a different store's rows) and/or unattributed_rows (no usable store id) plus a scope_note: those rows are in NEITHER the echoed rows nor the sums, so the result is a subset of the report — say so when you report the numbers. store_scope_unverified=true means the report had no store id column and the rows could not be re-checked at all. A report_columns_ambiguous error means the report carried two columns that both read as the same one, so the scope could not be decided: it reproduces on every retry, with any date range and any store list — report it and tell the user to contact support rather than retrying. Reports generate asynchronously (up to ~5 minutes upstream). When a response has status='pending', re-invoke the SAME action with the SAME arguments after retry_after_seconds (~2 minutes): report ids are cached server-side, so an identical re-call RESUMES the same report instead of creating a new one. Changing any argument starts a fresh report.", "input_params": [ { "name": "store_ids", "type": "list", "required": false, "description": "store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores.", "example": [ 901, 902 ], "source": "upstream:action:doordash/list_stores" }, { "name": "start_date", "type": "string", "required": true, "description": "Start of the window, ISO date (YYYY-MM-DD), inclusive. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got.", "example": "2026-08-01" }, { "name": "end_date", "type": "string", "required": true, "description": "End of the window, ISO date (YYYY-MM-DD), inclusive. At most 365 days after start_date (366 days counting both ends). Use yesterday to cover 'today' (data is D-1).", "example": "2026-08-10" }, { "name": "limit", "type": "integer", "required": false, "description": "Maximum rows echoed back, 1-1000. Default 200. Values above 1000 are rejected rather than clamped. Aggregates always cover the FULL report regardless of limit.", "example": 200 } ], "output_shape": "{store_ids, start_date, end_date, data_freshness, rows: [], total_rows, returned_rows, truncated, aggregates: {row_count, sums: {column: decimal-string}, missing_columns, unreadable_columns, parsed_values, unparsed_values}, out_of_scope_rows?, unattributed_rows?, store_scope_unverified?, scope_note?, note?} OR, while the report is still generating: {status: 'pending', report_id, retry_after_seconds, note, store_ids, start_date, end_date, data_freshness}" }, { "id": "action:doordash/list_stores", "namespace": "doordash", "action": "list_stores", "description": "List the DoorDash stores this brand can read.", "mode": "read", "platforms": [ "doordash" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns every DoorDash store granted to this brand with its store_id, name, business_id and address, read from the STORE_INFORMATION report and filtered to the brand's grants. Start here: store_id is the value the other actions' store_ids take. A granted store missing from DoorDash's snapshot is listed under missing_from_report (its DoorDash-side authorization may have been revoked). No date parameters. Reports generate asynchronously (up to ~5 minutes upstream). When a response has status='pending', re-invoke the SAME action with the SAME arguments after retry_after_seconds (~2 minutes): report ids are cached server-side, so an identical re-call RESUMES the same report instead of creating a new one. Changing any argument starts a fresh report.", "input_params": [ { "name": "store_ids", "type": "list", "required": false, "description": "Optional subset of granted store ids to describe; omit to list every granted store. An ungranted id is not_found.", "example": [ 901, 902 ] } ], "output_shape": "{store_ids, start_date: null, end_date: null, data_freshness, stores: [{store_id, name, business_id, address}], store_count, missing_from_report?, note?} OR, while the report is still generating: {status: 'pending', report_id, retry_after_seconds, note, store_ids, start_date, end_date, data_freshness}" }, { "id": "action:doordash/list_transactions", "namespace": "doordash", "action": "list_transactions", "description": "List DoorDash per-order transaction rows (fees, refunds, marketing).", "mode": "read", "platforms": [ "doordash" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Per-order financial transactions from the TRANSACTION_DETAIL report (v4) — subtotal, tax, commission, marketing fees, error charges, refunds and net total per row — with aggregate sums. This is the financial source of truth; use doordash/get_sales_summary when you only need totals. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got. start_date and end_date are required ISO dates (YYYY-MM-DD), inclusive on both ends; the window may span at most 366 days counting both ends (so end_date is at most 365 days after start_date). store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores. Aggregates are computed over the FULL report before any row truncation (check `truncated` and `total_rows`). Money columns are never guessed: a column absent from the CSV is listed under aggregates.missing_columns, a column present but with NO parseable number in any row is listed under aggregates.unreadable_columns and is left OUT of aggregates.sums (a missing sum means 'could not read it', never 'it is zero' — report it as unavailable, do not tell the user it was 0), and per-column cell counts are in aggregates.parsed_values / aggregates.unparsed_values so you can see the coverage behind a partially readable column. Rows are re-checked locally against the queried stores before aggregation. If any come back that do not belong to them, the response carries out_of_scope_rows (a different store's rows) and/or unattributed_rows (no usable store id) plus a scope_note: those rows are in NEITHER the echoed rows nor the sums, so the result is a subset of the report — say so when you report the numbers. store_scope_unverified=true means the report had no store id column and the rows could not be re-checked at all. A report_columns_ambiguous error means the report carried two columns that both read as the same one, so the scope could not be decided: it reproduces on every retry, with any date range and any store list — report it and tell the user to contact support rather than retrying. Reports generate asynchronously (up to ~5 minutes upstream). When a response has status='pending', re-invoke the SAME action with the SAME arguments after retry_after_seconds (~2 minutes): report ids are cached server-side, so an identical re-call RESUMES the same report instead of creating a new one. Changing any argument starts a fresh report.", "input_params": [ { "name": "store_ids", "type": "list", "required": false, "description": "store_ids is optional: omit it to cover ALL stores granted to this brand (up to 50 per call — more granted stores require an explicit store_ids subset). When provided, every id must be a granted store; an ungranted id is not_found. Get ids from doordash/list_stores.", "example": [ 901, 902 ], "source": "upstream:action:doordash/list_stores" }, { "name": "start_date", "type": "string", "required": true, "description": "Start of the window, ISO date (YYYY-MM-DD), inclusive. IMPORTANT: DoorDash report data is D-1 — it refreshes daily around 7pm Pacific with the PREVIOUS day's data, and today's data is never available. To cover 'today', query through YESTERDAY (end_date = yesterday). Every response echoes the actually-queried window and data_freshness so you can verify what you got.", "example": "2026-08-01" }, { "name": "end_date", "type": "string", "required": true, "description": "End of the window, ISO date (YYYY-MM-DD), inclusive. At most 365 days after start_date (366 days counting both ends). Use yesterday to cover 'today' (data is D-1).", "example": "2026-08-10" }, { "name": "limit", "type": "integer", "required": false, "description": "Maximum rows echoed back, 1-1000. Default 200. Values above 1000 are rejected rather than clamped. Aggregates always cover the FULL report regardless of limit.", "example": 200 } ], "output_shape": "{store_ids, start_date, end_date, data_freshness, transactions: [], total_rows, returned_rows, truncated, aggregates: {row_count, sums: {column: decimal-string}, missing_columns, unreadable_columns, parsed_values, unparsed_values}, out_of_scope_rows?, unattributed_rows?, store_scope_unverified?, scope_note?, note?} OR, while the report is still generating: {status: 'pending', report_id, retry_after_seconds, note, store_ids, start_date, end_date, data_freshness}" }, { "id": "action:ga4/get_conversion_overview", "namespace": "ga4", "action": "get_conversion_overview", "description": "Cached GA4 key-event summary for a property over a date range; returns total key events, revenue, and top converting traffic sources.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ga4/list_properties", "action:ga4/get_daily_trend", "action:ga4/list_traffic_sources", "action:ga4/list_events" ], "long_description": "Primary use:\nConversion summary using GA4 v5+ key_events, together with the top sources that drove those events.\n\nUse when:\n- The user asks for total conversions or key events for a period.\n- The user asks which sources drove the most conversions.\n- You need a quick conversion + revenue snapshot without picking specific events.\n\nDo not use when:\n- The user needs event-level counts beyond conversions. Use list_events.\n- The user needs a daily key-event time series. Use get_daily_trend and read rows[].key_events.\n- The user needs per-page conversion breakdown. Use list_top_pages and read rows[].key_events.\n\nWorkflow:\n1. Call list_properties if property_id is unknown.\n2. Call this action with property_id, date_start, date_end.\n\nOutput semantics:\n- overview.total_key_events sums property-level key_events for the range.\n- overview.total_revenue_micros is in micros.\n- overview.top_converting_sources has the same row shape as list_traffic_sources, sorted by key_events DESC and capped at 10.\n- Returns zeros and an empty top_converting_sources when the property has no synced data for the range.", "input_params": [ { "name": "property_id", "type": "string", "required": true, "description": "Property id from list_properties.rows[].property_id. account_id is accepted as an alias.", "example": "123456789" }, { "name": "date_start", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format.", "example": "2026-05-01" }, { "name": "date_end", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-05-18" } ], "output_shape": "{\"property\": {\"property_id\": \"123456789\", \"data_freshness\": \"ISO\"}, \"overview\": {\"total_key_events\": N, \"total_revenue_micros\": micros, \"top_converting_sources\": [{\"session_source\": \"...\", \"session_medium\": \"...\", \"session_campaign_name\": \"...\", \"key_events\": N, \"total_revenue_micros\": micros, \"sessions\": N, \"total_users\": N, ...}]}}" }, { "id": "action:ga4/get_daily_trend", "namespace": "ga4", "action": "get_daily_trend", "description": "Cached GA4 daily KPI time series for a property over a date range; returns one row per day for charting and anomaly detection.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ga4/list_properties", "action:ga4/get_property_overview", "action:ga4/list_traffic_sources" ], "long_description": "Primary use:\nPer-day GA4 KPI time series for a property.\n\nUse when:\n- The user asks for a daily chart, week-over-week or day-over-day comparison, or anomaly check.\n- You need report_date x property-level metrics ordered by day.\n\nDo not use when:\n- The user needs a single aggregated snapshot. Use get_property_overview.\n- The user needs per-page, per-source, or per-event daily breakdown. This action does not split by dimension.\n- The user wants daily ad spend. Use ads.query_single_dimension with dimension=date.\n\nWorkflow:\n1. Call list_properties if property_id is unknown.\n2. Call this action with property_id, date_start, date_end.\n\nOutput semantics:\n- rows[].report_date is ISO YYYY-MM-DD.\n- rows[].total_revenue_micros is in micros; rows[].bounce_rate is a 0-1 ratio; rows[].avg_session_duration_seconds is in seconds.\n- rows[].key_events replaces legacy 'conversions' (GA4 v5+).\n- Returns [] when the property has no synced data for the range.", "input_params": [ { "name": "property_id", "type": "string", "required": true, "description": "Property id from list_properties.rows[].property_id. account_id is accepted as an alias.", "example": "123456789" }, { "name": "date_start", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format.", "example": "2026-05-01" }, { "name": "date_end", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-05-18" } ], "output_shape": "{\"property\": {\"property_id\": \"123456789\", \"data_freshness\": \"ISO\"}, \"rows\": [{\"report_date\": \"2026-05-01\", \"total_users\": N, \"new_users\": N, \"active_users\": N, \"sessions\": N, \"screen_page_views\": N, \"avg_session_duration_seconds\": seconds, \"bounce_rate\": 0..1, \"events_count\": N, \"key_events\": N, \"total_revenue_micros\": micros, \"currency\": \"USD\"}]}" }, { "id": "action:ga4/get_metadata", "namespace": "ga4", "action": "get_metadata", "description": "Live GA4 Data API metadata discovery for one property; returns queryable dimensions and metrics, including custom definitions when GA4 exposes them.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ga4/run_report", "action:ga4/list_properties", "action:ga4/list_metrics" ], "long_description": "Primary use:\nDiscover valid GA4 Data API dimension and metric api_name values before calling ga4.run_report for fields outside the cached action set.\n\nUse when:\n- You need a GA4 dimension or metric that is not covered by fixed cached actions.\n- ga4.run_report returned ga4_invalid_query and you need valid selectable fields.\n- You need custom GA4 definitions exposed by the property metadata endpoint.\n\nDo not use when:\n- You only need Soku metric registry semantics. Use ga4.list_metrics.\n- You only need standard cached summaries, trends, pages, sources, or events.\n\nOutput semantics:\n- dimensions[].api_name and metrics[].api_name are the names accepted by run_report.\n- custom_definition=true means the field is property-specific.\n- This is live GA4 Data API metadata and requires a valid GA4 credential.", "input_params": [ { "name": "property_id", "type": "string", "required": true, "description": "Property id from list_properties.rows[].property_id. account_id is accepted as an alias.", "example": "123456789" } ], "output_shape": "{\"property\": {\"property_id\": \"123456789\", \"data_freshness\": \"ISO\"}, \"dimensions\": [{\"api_name\": \"date\", \"ui_name\": \"Date\", \"custom_definition\": false, \"category\": \"Time\"}], \"metrics\": [{\"api_name\": \"sessions\", \"ui_name\": \"Sessions\", \"type\": \"TYPE_INTEGER\", \"custom_definition\": false}]}" }, { "id": "action:ga4/get_property_overview", "namespace": "ga4", "action": "get_property_overview", "description": "Aggregate GA4 KPI snapshot for a property over a date range; returns one overview block with users, sessions, page views, key events, and revenue.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ga4/list_properties", "action:ga4/get_daily_trend", "action:ga4/list_traffic_sources", "action:ga4/list_top_pages" ], "long_description": "Primary use:\nHeadline KPIs for a GA4 property over a date range, without dimensional breakdown. Use it for a one-shot snapshot answer.\n\nUse when:\n- The user asks for a property-level summary across a window.\n- You need total/new/active users, sessions, page views, key events, or revenue across the period.\n\nDo not use when:\n- The user needs per-page, per-channel, or per-event breakdown. Use list_top_pages, list_traffic_sources, or list_events.\n- The user needs a daily time series. Use get_daily_trend.\n- The user needs CPA / ROAS or ad spend. Use ads.query_single_dimension.\n\nWorkflow:\n1. Call list_properties if property_id is unknown.\n2. Call this action with property_id, date_start, date_end.\n\nOutput semantics:\n- overview.total_revenue_micros is in micros (1 USD = 1_000_000); pair with overview.currency for display.\n- overview.bounce_rate is a 0-1 ratio.\n- overview.avg_session_duration_seconds is in seconds.\n- overview.key_events is the GA4 v5+ replacement for the legacy 'conversions' concept.\n- Returns null overview when the property has no synced data for the range.", "input_params": [ { "name": "property_id", "type": "string", "required": true, "description": "Property id from list_properties.rows[].property_id. account_id is accepted as an alias.", "example": "123456789" }, { "name": "date_start", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format.", "example": "2026-05-01" }, { "name": "date_end", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-05-18" } ], "output_shape": "{\"property\": {\"property_id\": \"123456789\", \"data_freshness\": \"ISO\"}, \"overview\": {\"total_users\": N, \"new_users\": N, \"active_users\": N, \"sessions\": N, \"screen_page_views\": N, \"avg_session_duration_seconds\": seconds, \"bounce_rate\": 0..1, \"events_count\": N, \"key_events\": N, \"total_revenue_micros\": micros, \"currency\": \"USD\"}}" }, { "id": "action:ga4/list_events", "namespace": "ga4", "action": "list_events", "description": "Cached GA4 event breakdown for a property over a date range; returns rows of event_name with event_count, event_value_micros, and per-event unique_users (deduplicated users) for funnel / conversion analysis.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ga4/list_properties", "action:ga4/get_conversion_overview", "action:ga4/list_metrics" ], "long_description": "Primary use:\nEvent-level GA4 analytics — not limited to conversion events. Includes per-event unique_users, the building block for user-level funnel and conversion-rate analysis.\n\nUse when:\n- The user asks about any GA4 event volume (scroll, session_start, add_to_cart, purchase, etc.).\n- You need per-event counts, event value sums, or unique users for a date range.\n- You are computing a funnel (people per step) or per-step conversion rate.\n\nDo not use when:\n- The user wants only conversion / key-event totals. Use get_conversion_overview.\n- The user wants per-page or per-source breakdown. Use list_top_pages or list_traffic_sources.\n\nWorkflow:\n1. Call list_properties if property_id is unknown.\n2. Call this action with property_id, date range, and optional event_names filter / limit.\n\nOutput semantics:\n- Default sort is event_count DESC.\n- rows[].event_value_micros sums GA4 event_value in micros; events without value report 0.\n- rows[].unique_users is deduplicated users for that event over the WHOLE date range (count people, not events) — the metric to use for funnels.\n- unique_users_basis qualifies every rows[].unique_users value:\n - 'exact': single-day query (cached daily dedup) or cross-day query resolved via a live GA4 deduplicated lookup.\n - 'approximate': cross-day query whose live dedup lookup was unavailable; unique_users is a daily sum. It can overcount cross-day returning users and can undercount when legacy partitions missing the column contribute no value (unique_users_note explains it). Treat as directional, not precise.\n - null: no unique_users available (e.g. legacy partitions synced before the column existed); rows[].unique_users may be null.\n- 'exact' for a single-day query assumes the requested day matches the GA4 property timezone day; for a non-UTC property a day boundary can shift the dedup window slightly.\n- A derived per-user rate (event_count / unique_users) is approximate on cross-day queries because count comes from the cached snapshot while unique_users comes from a live lookup.\n- When event_names is omitted, returns top events ranked by event_count up to limit.\n- Returns [] (and unique_users_basis null) when the property has no synced event data for the range.", "input_params": [ { "name": "property_id", "type": "string", "required": true, "description": "Property id from list_properties.rows[].property_id. account_id is accepted as an alias.", "example": "123456789" }, { "name": "date_start", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format.", "example": "2026-05-01" }, { "name": "date_end", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-05-18" }, { "name": "event_names", "type": "list", "required": false, "description": "GA4 event_name slugs to filter (string list). Omit to list all events ranked by count. Empty/falsy entries are ignored.", "example": [ "purchase", "add_to_cart" ] }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap. Default 100, hard cap 500. page_size accepted as an alias." } ], "output_shape": "{\"property\": {\"property_id\": \"123456789\", \"data_freshness\": \"ISO\"}, \"rows\": [{\"event_name\": \"page_view\", \"event_count\": N, \"event_value_micros\": micros, \"unique_users\": N|null}], \"unique_users_basis\": \"exact\"|\"approximate\"|null, \"unique_users_note\": \"...\"}" }, { "id": "action:ga4/list_metrics", "namespace": "ga4", "action": "list_metrics", "description": "Discover GA4 metric slugs, units, and formulas from the Metric Registry before reading them as field names in other ga4.* outputs.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ga4/get_property_overview", "action:ga4/get_daily_trend", "action:ga4/list_events" ], "long_description": "Primary use:\nMetric Registry discovery filtered to source=ga4. Same registry as the metrics block in ads.list_dimensions, but scoped to GA4 metrics only.\n\nUse when:\n- You are unsure which metric slug, unit, or formula a GA4 field uses.\n- You need to map a metric slug to its dependency chain or platform support.\n\nDo not use when:\n- You need a queryable dimension list. ga4 has no list_dimensions equivalent; other ga4.* actions return fixed column sets.\n- You need ads-side metric semantics. Use ads.list_dimensions.\n\nWorkflow:\n1. Call this action.\n2. Match returned slugs to columns in get_property_overview, get_daily_trend, list_top_pages, list_traffic_sources, or list_events output.\n\nOutput semantics:\n- rows[].slug is the canonical metric name (e.g. engagement_rate, total_users).\n- rows[].unit is the metric unit class (ratio, micros, seconds, count).\n- rows[].available_on lists the platforms exposing this metric; for source=ga4 it always includes 'ga4'.\n- This is metadata; no property_id or date range needed and no data is queried.", "input_params": [], "output_shape": "{\"rows\": [{\"slug\": \"engagement_rate\", \"formula\": \"engaged_sessions / sessions\", \"unit\": \"ratio\", \"depends_on\": [...], \"platform_support\": [\"ga4\"], \"available_on\": [\"ga4\"], \"source\": \"ga4\", \"description\": \"...\"}]}" }, { "id": "action:ga4/list_properties", "namespace": "ga4", "action": "list_properties", "description": "Discover org-bound GA4 properties; returns property_id values required by every other ga4.* action.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ga4/get_property_overview", "action:ga4/get_daily_trend", "action:ga4/list_metrics" ], "long_description": "Primary use:\nProperty discovery for the current brand scope. Use the returned property_id values as the only valid property_id inputs for later ga4.* analytics calls. Only properties granted to the caller's brand are returned.\n\nUse when:\n- The user did not provide a property_id.\n- The user asks which GA4 properties are available.\n- You need to choose a GA4 property before querying overviews, trends, pages, sources, or events.\n\nDo not use when:\n- You already have a property_id from this response in the same workflow.\n- You need ad accounts. Use ads.list_ad_accounts instead.\n\nWorkflow:\n1. Call this action.\n2. Pick one row's property_id.\n3. Reuse that property_id (alias account_id is accepted) in any later ga4.* call.\n\nOutput semantics:\n- rows[].property_id is the external GA4 property id expected by later calls.\n- rows[].data_freshness is the latest completed GA4 sync run finish time; null when never synced.\n\nConstraints:\n- Rows are scoped to the caller's organization and active ResourceBrandGrant.\n- A property absent from this response is not queryable by other ga4 actions.", "input_params": [ { "name": "limit", "type": "integer", "required": false, "description": "Max rows. Default 100, hard cap 500. page_size accepted as an alias." } ], "output_shape": "{\"rows\": [{\"property_id\": \"123456789\", \"property_name\": \"Main Web Property\", \"status\": \"active\", \"currency_code\": \"USD\", \"timezone\": \"America/Los_Angeles\", \"data_freshness\": \"ISO\"}]}" }, { "id": "action:ga4/list_top_pages", "namespace": "ga4", "action": "list_top_pages", "description": "Cached GA4 top-page ranking for a date range; returns rows of page_path/page_title with views, users, engagement, and key events.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ga4/list_properties", "action:ga4/get_property_overview", "action:ga4/list_traffic_sources", "action:ga4/list_events" ], "long_description": "Primary use:\nPage-level ranking over a property's synced GA4 data.\n\nUse when:\n- The user asks for most-viewed pages, top landing pages, or best-performing content.\n- You need page_path x per-page metrics for a date range.\n\nDo not use when:\n- The user needs a per-page time series. This action does not split by day.\n- The user needs traffic source breakdown. Use list_traffic_sources.\n- The user needs event-level breakdown. Use list_events.\n\nWorkflow:\n1. Call list_properties if property_id is unknown.\n2. Call this action with property_id, date range, and optional sort_by / limit.\n\nOutput semantics:\n- Default sort is screen_page_views DESC.\n- rows[].bounce_rate is a 0-1 ratio; rows[].avg_session_duration_seconds is in seconds.\n- rows[].key_events is page-level conversion count (GA4 v5+ naming).\n- Returns [] when the property has no synced page data for the range.", "input_params": [ { "name": "property_id", "type": "string", "required": true, "description": "Property id from list_properties.rows[].property_id. account_id is accepted as an alias.", "example": "123456789" }, { "name": "date_start", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format.", "example": "2026-05-01" }, { "name": "date_end", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-05-18" }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap. Default 20, hard cap 500. page_size accepted as an alias." }, { "name": "sort_by", "type": "string", "required": false, "description": "Sort metric. One of screen_page_views (default), total_users, avg_session_duration_seconds, bounce_rate, key_events. Unknown values fall back to screen_page_views.", "example": "total_users" } ], "output_shape": "{\"property\": {\"property_id\": \"123456789\", \"data_freshness\": \"ISO\"}, \"rows\": [{\"page_path\": \"/products/x\", \"page_title\": \"...\", \"screen_page_views\": N, \"total_users\": N, \"avg_session_duration_seconds\": seconds, \"bounce_rate\": 0..1, \"key_events\": N}]}" }, { "id": "action:ga4/list_traffic_sources", "namespace": "ga4", "action": "list_traffic_sources", "description": "Cached GA4 traffic source ranking for a date range; returns rows of session_source/medium/campaign with users, sessions, page views, and revenue.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:ga4/list_properties", "action:ga4/list_top_pages", "action:ga4/get_daily_trend", "action:ga4/get_conversion_overview" ], "long_description": "Primary use:\nAcquisition breakdown across session_source x session_medium x session_campaign_name.\n\nUse when:\n- The user asks where traffic comes from, top channels, organic vs paid, or campaign-level acquisition.\n- You need session-level acquisition metrics for a date range.\n\nDo not use when:\n- The user needs page-level data. Use list_top_pages and join client-side.\n- The user needs event-level data. Use list_events.\n- The user needs ad spend, CPA, or ROAS. Use ads.query_single_dimension.\n\nWorkflow:\n1. Call list_properties if property_id is unknown.\n2. Call this action with property_id, date range, and optional sort_by / limit.\n\nOutput semantics:\n- Default sort is sessions DESC.\n- rows[].total_revenue_micros is in micros; rows[].bounce_rate is a 0-1 ratio; rows[].avg_session_duration_seconds is in seconds.\n- rows[].key_events replaces legacy 'conversions' (GA4 v5+).\n- ChatGPT Ads click traffic appears under session_source 'chatgpt-ads'. The organic 'chatgpt.com' referral stays a separate row and is never merged into it — report them separately.\n- Returns [] when the property has no synced source data for the range.", "input_params": [ { "name": "property_id", "type": "string", "required": true, "description": "Property id from list_properties.rows[].property_id. account_id is accepted as an alias.", "example": "123456789" }, { "name": "date_start", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format.", "example": "2026-05-01" }, { "name": "date_end", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-05-18" }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap. Default 20, hard cap 500. page_size accepted as an alias." }, { "name": "sort_by", "type": "string", "required": false, "description": "Sort metric. One of sessions (default), total_users, new_users, screen_page_views, bounce_rate, avg_session_duration_seconds, key_events, total_revenue_micros. Unknown values fall back to sessions.", "example": "key_events" } ], "output_shape": "{\"property\": {\"property_id\": \"123456789\", \"data_freshness\": \"ISO\"}, \"rows\": [{\"session_source\": \"google\", \"session_medium\": \"organic\", \"session_campaign_name\": \"...\", \"total_users\": N, \"new_users\": N, \"sessions\": N, \"screen_page_views\": N, \"bounce_rate\": 0..1, \"avg_session_duration_seconds\": seconds, \"key_events\": N, \"total_revenue_micros\": micros}]}" }, { "id": "action:ga4/run_report", "namespace": "ga4", "action": "run_report", "description": "Live GA4 Data API RunReport for ad-hoc custom dimension x metric queries, filters, date ranges, and property-specific fields.", "mode": "read", "platforms": [ "ga4" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:ga4/get_metadata", "action:ga4/list_properties", "action:ga4/get_property_overview" ], "long_description": "Primary use:\nExecute a constrained GA4 RunReport request against one brand-granted property. Prefer cached actions for standard summaries; use this when the user asks for a GA4 breakdown, filter, or custom field outside the fixed cached shapes.\n\nUse when:\n- The requested dimensions or metrics are not available in cached actions.\n- You need custom GA4 fields discovered through ga4.get_metadata.\n- You need live GA4 results instead of the latest synced parquet snapshot.\n\nDo not use when:\n- A cached action can answer the question; cached actions are cheaper and more stable.\n- You need Google Ads spend, campaign, or keyword data. Use ads.* actions.\n\nSupported DSL:\n- property_id plus either date_ranges[] or date_start/date_end.\n- dimensions[] and metrics[]. metrics is required; dimensions may be empty.\n- dimension_filter and metric_filter support string_filter, in_list_filter, numeric_filter, between_filter, plus and_group/or_group/not_expression.\n- order_bys[] entries must reference requested dimensions or metrics.\n- limit defaults to 100 and is capped at 1000; offset defaults to 0.\n\nOutput semantics:\n- rows are row-shaped dictionaries; columns is the same data pivoted by field.\n- When multiple date_ranges are requested, rows and columns include dateRange, the zero-based GA4 date range index.\n- row_count is the GA4 total available rows, not only the returned page length.\n- truncated=true means more rows exist after offset + returned rows.", "input_params": [ { "name": "property_id", "type": "string", "required": true, "description": "Property id from list_properties.rows[].property_id. account_id is accepted as an alias.", "example": "123456789" }, { "name": "date_start", "type": "string", "required": false, "description": "Inclusive start date in YYYY-MM-DD format when date_ranges is omitted.", "example": "2026-05-01" }, { "name": "date_end", "type": "string", "required": false, "description": "Inclusive end date in YYYY-MM-DD format when date_ranges is omitted.", "example": "2026-05-18" }, { "name": "date_ranges", "type": "list", "required": false, "description": "Optional GA4 date ranges, each with start_date/end_date. Overrides date_start/date_end.", "example": [ { "start_date": "2026-05-01", "end_date": "2026-05-18" } ] }, { "name": "dimensions", "type": "list", "required": false, "description": "GA4 dimension api_name values from get_metadata.", "example": [ "date", "sessionSource" ] }, { "name": "metrics", "type": "list", "required": true, "description": "GA4 metric api_name values from get_metadata.", "example": [ "sessions", "totalUsers" ] }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap. Default 100, hard cap 1000." }, { "name": "offset", "type": "integer", "required": false, "description": "Zero-based row offset. Default 0." } ], "output_shape": "{\"property\": {\"property_id\": \"123456789\", \"data_freshness\": \"ISO\"}, \"dimensions\": [\"date\"], \"metrics\": [\"sessions\"], \"rows\": [{\"date\": \"20260501\", \"sessions\": 42}], \"columns\": {\"date\": [\"20260501\"], \"sessions\": [42]}, \"row_count\": 1, \"truncated\": false, \"limit\": 100, \"offset\": 0}" }, { "id": "action:gsc/get_daily_trend", "namespace": "gsc", "action": "get_daily_trend", "description": "Daily Google Search organic time series for a property; returns one row per day (ascending) with clicks, impressions, CTR, and position.", "mode": "read", "platforms": [ "gsc" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:gsc/list_sites", "action:gsc/query_search_analytics", "action:ga4/get_daily_trend" ], "long_description": "Primary use:\nConvenience wrapper over searchAnalytics.query with dimensions=[date] — per-day organic performance for charting and anomaly detection.\n\nUse when:\n- The user asks for an organic traffic trend, a daily chart, or week-over-week organic comparison.\n\nDo not use when:\n- You need per-query or per-page daily breakdowns: use query_search_analytics with dimensions=['date', ...].\n- You need on-site daily sessions/users: use ga4.get_daily_trend.\n\nWorkflow:\n1. Call list_sites if site_url is unknown.\n2. Call this action with site_url, start_date, end_date.\n\nOutput semantics:\n- rows[].date is ISO YYYY-MM-DD; rows are returned in ascending date order.\n- Days with zero organic activity are absent, not zero-filled.\n- The most recent ~2 days may be missing or partial (Search Console data lag).\n- site_url must match a property EXACTLY as Search Console knows it: a URL-prefix property is a full origin like 'https://example.com/' (trailing slash included), while a site verified as a DOMAIN property is addressed as 'sc-domain:example.com' — never as a URL-prefix. When a call fails with gsc_permission_denied or gsc_site_not_found, re-check the exact site_url against gsc.list_sites before retrying.", "input_params": [ { "name": "site_url", "type": "string", "required": true, "description": "Property identifier from gsc.list_sites.rows[].site_url — a URL-prefix ('https://example.com/') or a domain property ('sc-domain:example.com').", "example": "sc-domain:example.com", "source": "upstream:action:gsc/list_sites" }, { "name": "start_date", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format. Search analytics data lags ~2 days behind real time.", "example": "2026-06-01" }, { "name": "end_date", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-06-30" } ], "output_shape": "{\"site\": {\"site_url\": \"sc-domain:example.com\", \"permission_level\": \"siteOwner\"}, \"rows\": [{\"date\": \"2026-06-01\", \"clicks\": N, \"impressions\": N, \"ctr\": 0..1, \"position\": N}]}" }, { "id": "action:gsc/inspect_url", "namespace": "gsc", "action": "inspect_url", "description": "Inspect one URL's Google index status via the URL Inspection API; returns index verdict, coverage state, canonical, mobile usability, and rich results.", "mode": "read", "platforms": [ "gsc" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:gsc/list_sites", "action:gsc/list_sitemaps", "action:gsc/list_top_pages" ], "long_description": "Primary use:\nPer-URL diagnosis of how Google sees a page: indexed or not, why, which canonical Google chose, last crawl time, mobile usability, and rich result validation.\n\nUse when:\n- The user asks whether a specific page is indexed, why it is not ranking, or what Google's canonical for it is.\n\nDo not use when:\n- You need aggregate coverage across many URLs: inspect one URL at a time and mind the quota (below); for bulk health prefer list_sitemaps plus query_search_analytics with dimensions=['page'].\n- You want to request (re)indexing: this integration is read-only and the Indexing API is out of scope.\n\nQuota (hard, per Google):\n- ~2000 inspections per DAY per property and ~600 per MINUTE. Exhaustion surfaces as gsc_quota_exceeded (HTTP 429); budget calls accordingly and never loop this action over large URL lists.\n\nConstraints:\n- inspection_url must belong to the property identified by site_url (same URL-prefix scope or under the sc-domain).\n- site_url must match a property EXACTLY as Search Console knows it: a URL-prefix property is a full origin like 'https://example.com/' (trailing slash included), while a site verified as a DOMAIN property is addressed as 'sc-domain:example.com' — never as a URL-prefix. When a call fails with gsc_permission_denied or gsc_site_not_found, re-check the exact site_url against gsc.list_sites before retrying.\n\nOutput semantics:\n- inspection_result.indexStatusResult.verdict is PASS / FAIL / NEUTRAL; coverageState is the human-readable index coverage (e.g. 'Submitted and indexed').\n- googleCanonical vs userCanonical mismatch indicates a canonicalization issue.\n- inspection_result.inspectionResultLink opens the same report in the Search Console UI.", "input_params": [ { "name": "site_url", "type": "string", "required": true, "description": "Property identifier from gsc.list_sites.rows[].site_url — a URL-prefix ('https://example.com/') or a domain property ('sc-domain:example.com').", "example": "sc-domain:example.com", "source": "upstream:action:gsc/list_sites" }, { "name": "inspection_url", "type": "string", "required": true, "description": "Fully-qualified URL to inspect. Must be inside the site_url property's scope.", "example": "https://example.com/products/widget" } ], "output_shape": "{\"site\": {\"site_url\": \"sc-domain:example.com\", \"permission_level\": \"siteOwner\"}, \"inspection_url\": \"https://example.com/products/widget\", \"inspection_result\": {\"indexStatusResult\": {\"verdict\": \"PASS\", \"coverageState\": \"Submitted and indexed\", \"lastCrawlTime\": \"ISO\", \"googleCanonical\": \"...\", \"userCanonical\": \"...\", \"robotsTxtState\": \"ALLOWED\"}, \"inspectionResultLink\": \"https://search.google.com/...\"}}" }, { "id": "action:gsc/list_sitemaps", "namespace": "gsc", "action": "list_sitemaps", "description": "List sitemaps submitted for a Search Console property; returns per-sitemap status, submitted/indexed counts, warnings, and errors.", "mode": "read", "platforms": [ "gsc" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:gsc/list_sites", "action:gsc/inspect_url" ], "long_description": "Primary use:\nSitemap inventory and health for one property (sitemaps.list).\n\nUse when:\n- The user asks which sitemaps are submitted, whether a sitemap has errors/warnings, or how many URLs were submitted vs indexed.\n\nDo not use when:\n- You need per-URL index status: use inspect_url.\n- You want to submit or delete a sitemap: this integration is read-only.\n\nWorkflow:\n1. Call list_sites if site_url is unknown.\n2. Call this action with site_url.\n\nOutput semantics:\n- rows[].path is the sitemap URL; rows[].is_pending means Google has not processed it yet; rows[].contents breaks submitted/indexed counts down by content type (web, image, video, ...).\n- site_url must match a property EXACTLY as Search Console knows it: a URL-prefix property is a full origin like 'https://example.com/' (trailing slash included), while a site verified as a DOMAIN property is addressed as 'sc-domain:example.com' — never as a URL-prefix. When a call fails with gsc_permission_denied or gsc_site_not_found, re-check the exact site_url against gsc.list_sites before retrying.", "input_params": [ { "name": "site_url", "type": "string", "required": true, "description": "Property identifier from gsc.list_sites.rows[].site_url — a URL-prefix ('https://example.com/') or a domain property ('sc-domain:example.com').", "example": "sc-domain:example.com", "source": "upstream:action:gsc/list_sites" } ], "output_shape": "{\"site\": {\"site_url\": \"sc-domain:example.com\", \"permission_level\": \"siteOwner\"}, \"rows\": [{\"path\": \"https://example.com/sitemap.xml\", \"last_submitted\": \"ISO\", \"last_downloaded\": \"ISO\", \"is_pending\": false, \"is_sitemaps_index\": false, \"type\": \"sitemap\", \"warnings\": N, \"errors\": N, \"contents\": [{\"type\": \"web\", \"submitted\": N, \"indexed\": N}]}]}" }, { "id": "action:gsc/list_sites", "namespace": "gsc", "action": "list_sites", "description": "Discover brand-granted Search Console properties; returns site_url values required by every other gsc.* action.", "mode": "read", "platforms": [ "gsc" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:gsc/query_search_analytics", "action:gsc/list_top_queries", "action:gsc/get_daily_trend" ], "long_description": "Primary use:\nProperty discovery for the current brand scope. Use the returned site_url values as the only valid site_url inputs for later gsc.* calls. Only properties granted to the caller's brand are returned.\n\nUse when:\n- The user did not provide a site_url.\n- The user asks which Search Console properties are available.\n- A gsc.* call failed with gsc_site_not_found or gsc_permission_denied and you need to re-check the exact property address.\n\nDo not use when:\n- You already have a site_url from this response in the same workflow.\n- You need GA4 properties (ga4.list_properties) or ad accounts (ads.list_ad_accounts).\n\nOutput semantics:\n- rows[].site_url is the exact property identifier Search Console expects: a URL-prefix property ('https://example.com/') or a domain property ('sc-domain:example.com'). Pass it verbatim — do not normalize slashes or strip the sc-domain: prefix.\n- rows[].permission_level is the connected Google account's access level on the property (siteOwner, siteFullUser, siteRestrictedUser, or siteUnverifiedUser). siteUnverifiedUser rows will fail data reads with gsc_permission_denied.\n\nConstraints:\n- Rows are scoped to the caller's organization and active brand grants; a property absent from this response is not queryable by other gsc actions.", "input_params": [ { "name": "limit", "type": "integer", "required": false, "description": "Max rows. Default 100, hard cap 500." } ], "output_shape": "{\"rows\": [{\"site_url\": \"sc-domain:example.com\", \"permission_level\": \"siteOwner\", \"status\": \"active\"}]}" }, { "id": "action:gsc/list_top_pages", "namespace": "gsc", "action": "list_top_pages", "description": "Top pages by Google Search organic performance for a property over a date range; returns page rows with clicks, impressions, CTR, and position.", "mode": "read", "platforms": [ "gsc" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:gsc/list_sites", "action:gsc/list_top_queries", "action:gsc/query_search_analytics", "action:ga4/list_top_pages" ], "long_description": "Primary use:\nConvenience wrapper over searchAnalytics.query with dimensions=[page] — which URLs earned organic impressions and clicks.\n\nUse when:\n- The user asks which pages perform best in Google Search, top landing pages from organic, or per-URL organic metrics.\n\nDo not use when:\n- You need page x query combinations: use query_search_analytics.\n- You need on-site engagement per page (views, bounce): use ga4.list_top_pages.\n\nWorkflow:\n1. Call list_sites if site_url is unknown.\n2. Call this action with site_url, start_date, end_date, optional limit.\n\nOutput semantics:\n- rows[].page is the full canonical URL; rows are sorted by clicks descending.\n- site_url must match a property EXACTLY as Search Console knows it: a URL-prefix property is a full origin like 'https://example.com/' (trailing slash included), while a site verified as a DOMAIN property is addressed as 'sc-domain:example.com' — never as a URL-prefix. When a call fails with gsc_permission_denied or gsc_site_not_found, re-check the exact site_url against gsc.list_sites before retrying.", "input_params": [ { "name": "site_url", "type": "string", "required": true, "description": "Property identifier from gsc.list_sites.rows[].site_url — a URL-prefix ('https://example.com/') or a domain property ('sc-domain:example.com').", "example": "sc-domain:example.com", "source": "upstream:action:gsc/list_sites" }, { "name": "start_date", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format. Search analytics data lags ~2 days behind real time.", "example": "2026-06-01" }, { "name": "end_date", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-06-30" }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap. Default 100, hard cap 25000." } ], "output_shape": "{\"site\": {\"site_url\": \"sc-domain:example.com\", \"permission_level\": \"siteOwner\"}, \"rows\": [{\"page\": \"https://example.com/products\", \"clicks\": N, \"impressions\": N, \"ctr\": 0..1, \"position\": N}]}" }, { "id": "action:gsc/list_top_queries", "namespace": "gsc", "action": "list_top_queries", "description": "Top Google Search queries for a property over a date range; returns query rows with clicks, impressions, CTR, and average position.", "mode": "read", "platforms": [ "gsc" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:gsc/list_sites", "action:gsc/list_top_pages", "action:gsc/query_search_analytics" ], "long_description": "Primary use:\nConvenience wrapper over searchAnalytics.query with dimensions=[query] — which search terms drove organic impressions and clicks.\n\nUse when:\n- The user asks what people searched to find the site, top keywords, or organic query performance.\n\nDo not use when:\n- You need query x page or query x country breakdowns: use query_search_analytics with multiple dimensions.\n- You need paid keyword data: use ads.* actions.\n\nWorkflow:\n1. Call list_sites if site_url is unknown.\n2. Call this action with site_url, start_date, end_date, optional limit.\n\nOutput semantics:\n- Rows are sorted by clicks descending; ctr is a 0-1 ratio; position is the average result position (lower is better).\n- site_url must match a property EXACTLY as Search Console knows it: a URL-prefix property is a full origin like 'https://example.com/' (trailing slash included), while a site verified as a DOMAIN property is addressed as 'sc-domain:example.com' — never as a URL-prefix. When a call fails with gsc_permission_denied or gsc_site_not_found, re-check the exact site_url against gsc.list_sites before retrying.", "input_params": [ { "name": "site_url", "type": "string", "required": true, "description": "Property identifier from gsc.list_sites.rows[].site_url — a URL-prefix ('https://example.com/') or a domain property ('sc-domain:example.com').", "example": "sc-domain:example.com", "source": "upstream:action:gsc/list_sites" }, { "name": "start_date", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format. Search analytics data lags ~2 days behind real time.", "example": "2026-06-01" }, { "name": "end_date", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-06-30" }, { "name": "limit", "type": "integer", "required": false, "description": "Row cap. Default 100, hard cap 25000." } ], "output_shape": "{\"site\": {\"site_url\": \"sc-domain:example.com\", \"permission_level\": \"siteOwner\"}, \"rows\": [{\"query\": \"example shoes\", \"clicks\": N, \"impressions\": N, \"ctr\": 0..1, \"position\": N}]}" }, { "id": "action:gsc/query_search_analytics", "namespace": "gsc", "action": "query_search_analytics", "description": "Generic Search Console searchAnalytics.query for one property; returns clicks, impressions, CTR, and position grouped by any dimension combination.", "mode": "read", "platforms": [ "gsc" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:gsc/list_sites", "action:gsc/list_top_queries", "action:gsc/list_top_pages", "action:gsc/get_daily_trend" ], "long_description": "Primary use:\nFull-control Google Search organic performance query — the generic pass-through to searchAnalytics.query. Prefer the convenience actions (list_top_queries, list_top_pages, get_daily_trend) for single-dimension questions; use this one for multi-dimension grouping, filters, or non-web search types.\n\nUse when:\n- You need multiple dimensions at once (e.g. query x page, date x device).\n- You need dimension_filter_groups (e.g. only queries containing a brand term, only one country) or a non-default search_type / data_state / aggregation_type.\n\nDo not use when:\n- A convenience action answers the question with fewer knobs.\n- You need paid-search data (ads.*) or on-site analytics (ga4.*): Search Console covers Google ORGANIC search only.\n\nConstraints:\n- Valid dimensions: country, device, page, query, searchAppearance, date.\n- Valid search_type values: web, image, video, news, discover, googleNews (default web).\n- data_state 'all' includes fresh (still-consolidating) data; 'final' (default upstream) returns only finalized data.\n- row_limit defaults to 100 and is hard-capped at 25000 (the API maximum per request); page beyond that with start_row.\n- Large result sets must be paginated by the caller: row_limit caps at 25000 per call, so keep it bounded and walk pages with start_row instead of asking for everything at once.\n- site_url must match a property EXACTLY as Search Console knows it: a URL-prefix property is a full origin like 'https://example.com/' (trailing slash included), while a site verified as a DOMAIN property is addressed as 'sc-domain:example.com' — never as a URL-prefix. When a call fails with gsc_permission_denied or gsc_site_not_found, re-check the exact site_url against gsc.list_sites before retrying.\n\nOutput semantics:\n- rows[].keys aligns positionally with the requested dimensions.\n- ctr is a 0-1 ratio; position is the average result position (lower is better).\n- Rows are sorted by clicks descending (API default). Empty rows means no organic data for the range/filters, not an error.", "input_params": [ { "name": "site_url", "type": "string", "required": true, "description": "Property identifier from gsc.list_sites.rows[].site_url — a URL-prefix ('https://example.com/') or a domain property ('sc-domain:example.com').", "example": "sc-domain:example.com", "source": "upstream:action:gsc/list_sites" }, { "name": "start_date", "type": "string", "required": true, "description": "Inclusive start date in YYYY-MM-DD format. Search analytics data lags ~2 days behind real time.", "example": "2026-06-01" }, { "name": "end_date", "type": "string", "required": true, "description": "Inclusive end date in YYYY-MM-DD format.", "example": "2026-06-30" }, { "name": "dimensions", "type": "list", "required": false, "description": "Dimensions to group by, in order. Valid values: country, device, page, query, searchAppearance, date. Omit for property-level totals.", "example": [ "query", "page" ] }, { "name": "search_type", "type": "string", "required": false, "description": "Search surface to report on. Default web.", "enum": [ "web", "image", "video", "news", "discover", "googleNews" ] }, { "name": "row_limit", "type": "integer", "required": false, "description": "Rows per page. Default 100, hard cap 25000 (API maximum)." }, { "name": "start_row", "type": "integer", "required": false, "description": "Zero-based row offset for pagination. Default 0." }, { "name": "data_state", "type": "string", "required": false, "description": "'all' includes fresh (not yet finalized) data; 'final' returns only finalized data (upstream default).", "enum": [ "all", "final" ] }, { "name": "dimension_filter_groups", "type": "object|list", "required": false, "description": "searchAnalytics.query dimensionFilterGroups passed through verbatim: a group object (or list of groups) with filters of {dimension, operator, expression}. Operators: equals, notEquals, contains, notContains, includingRegex, excludingRegex.", "example": { "filters": [ { "dimension": "country", "operator": "equals", "expression": "usa" } ] } }, { "name": "aggregation_type", "type": "string", "required": false, "description": "How metrics aggregate: auto (default), byProperty, or byPage. byProperty is unavailable for page/searchAppearance dimensions.", "enum": [ "auto", "byProperty", "byPage" ] } ], "output_shape": "{\"site\": {\"site_url\": \"sc-domain:example.com\", \"permission_level\": \"siteOwner\"}, \"rows\": [{\"keys\": [\"query text\", \"https://example.com/page\"], \"clicks\": N, \"impressions\": N, \"ctr\": 0..1, \"position\": N}], \"response_aggregation_type\": \"byProperty\", \"row_limit\": 100, \"start_row\": 0}" }, { "id": "action:integration/byo_mcp_read_tool", "namespace": "integration", "action": "byo_mcp_read_tool", "description": "Execute an authorized BYO MCP read tool through Gateway.", "mode": "read", "platforms": [ "byo_mcp" ], "requires_review": false, "freshness_kind": "synced", "see_also": [], "long_description": "Execute a BYO MCP tool only when integration/list_tools marks that exact tool dispatch_action as integration/byo_mcp_read_tool. Do not use this for write, unreviewed, unknown, hidden, or drifted tools.", "input_params": [ { "name": "package_slug", "type": "string", "required": true, "description": "Published BYO MCP package slug." }, { "name": "tool_name", "type": "string", "required": true, "description": "BYO MCP tool name." }, { "name": "credential_id", "type": "integer", "required": true, "description": "Org credential id." }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments." } ], "output_shape": "BYO MCP worker result envelope with trace and snapshot binding." }, { "id": "action:integration/byo_mcp_request_change", "namespace": "integration", "action": "byo_mcp_request_change", "description": "Request HITL approval for a BYO MCP write tool.", "mode": "risk", "platforms": [ "byo_mcp" ], "requires_review": true, "freshness_kind": "synced", "see_also": [], "long_description": "Use this for BYO MCP tools that integration/list_tools marks requires_review=true. This creates a HITL approval request and binds the current package snapshot, credential/resource grant, and tool arguments.", "input_params": [ { "name": "package_slug", "type": "string", "required": true, "description": "Published BYO MCP package slug." }, { "name": "tool_name", "type": "string", "required": true, "description": "BYO MCP tool name." }, { "name": "credential_id", "type": "integer", "required": true, "description": "Org credential id." }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments." }, { "name": "chat_id", "type": "string", "required": true, "description": "Current chat id for HITL review." }, { "name": "_summary", "type": "string", "required": true, "description": "Human-readable review summary." } ], "output_shape": "BYO MCP worker result envelope after approved replay." }, { "id": "action:integration/list_tools", "namespace": "integration", "action": "list_tools", "description": "List published BYO MCP packages, tools, and credential grants for this brand.", "mode": "read", "platforms": [ "byo_mcp" ], "requires_review": false, "freshness_kind": "synced", "see_also": [ "action:integration/byo_mcp_read_tool", "action:integration/byo_mcp_request_change" ], "long_description": "DB-only BYO MCP discovery. This action does not call the upstream MCP endpoint and does not execute a tool. Use it before BYO MCP tasks to find the exact package_slug, tool_name, input_schema, dispatch_action, and credential_id/resource grant available to the current brand.", "input_params": [ { "name": "package_slug", "type": "string", "required": false, "description": "Optional published BYO MCP package slug." }, { "name": "tool_name", "type": "string", "required": false, "description": "Optional exact BYO MCP tool name." } ], "output_shape": "packages[].tools[] plus credentials[] grant bindings for BYO MCP runtime calls." }, { "id": "action:org/get_overview", "namespace": "org", "action": "get_overview", "description": "Read the current organization's cross-brand overview.", "mode": "read", "platforms": [], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:org/list_brands", "action:org/get_usage_summary" ], "long_description": "Returns the existing org-workspace overview aggregate for an inclusive UTC date window: per-brand usage and spend, health badges, unattributed/unassigned buckets, approvals, and seats.", "input_params": [ { "name": "start_date", "type": "string", "required": false, "description": "Inclusive UTC start date in YYYY-MM-DD format.", "example": "2026-07-01" }, { "name": "end_date", "type": "string", "required": false, "description": "Inclusive UTC end date in YYYY-MM-DD format.", "example": "2026-07-29" } ], "output_shape": "{start_date, end_date, brands, total_credits, total_turns, total_spend, ...}" }, { "id": "action:org/get_usage_summary", "namespace": "org", "action": "get_usage_summary", "description": "Read cross-brand usage, tokens, source split, and spend.", "mode": "read", "platforms": [], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:org/get_overview", "action:org/list_brands" ], "long_description": "Wraps the existing org-workspace usage and overview services over the same inclusive UTC date window. Returns credits and tokens, coarse source attribution, time series, and currency-grouped spend. Vendor/internal cost is never exposed.", "input_params": [ { "name": "start_date", "type": "string", "required": false, "description": "Inclusive UTC start date in YYYY-MM-DD format.", "example": "2026-07-01" }, { "name": "end_date", "type": "string", "required": false, "description": "Inclusive UTC end date in YYYY-MM-DD format.", "example": "2026-07-29" } ], "output_shape": "{start_date, end_date, total_credits, total_tokens, brands, timeseries, spend}" }, { "id": "action:org/list_activity", "namespace": "org", "action": "list_activity", "description": "List the current organization's merged activity feed.", "mode": "read", "platforms": [], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:org/list_brands" ], "long_description": "Reads the existing org activity aggregate across agent reviews, org/member audit events, and billing events. Use the opaque cursor unchanged for the next page.", "input_params": [ { "name": "kinds", "type": "list|string", "required": false, "description": "Optional subset of agent, org, member, and billing.", "enum": [ "agent", "org", "member", "billing" ] }, { "name": "brand_id", "type": "string", "required": false, "description": "Optional live brand UUID filter for agent activity." }, { "name": "start", "type": "string", "required": false, "description": "Optional inclusive ISO datetime lower bound." }, { "name": "end", "type": "string", "required": false, "description": "Optional inclusive ISO datetime upper bound." }, { "name": "cursor", "type": "string", "required": false, "description": "Opaque cursor returned by the previous page." }, { "name": "limit", "type": "integer", "required": false, "description": "Page size from 1 to 100; defaults to 50.", "example": 50 } ], "output_shape": "{items: [{kind, occurred_at, source, title, ...}], next_cursor: string|null}" }, { "id": "action:org/list_brands", "namespace": "org", "action": "list_brands", "description": "List live brands in the current organization.", "mode": "read", "platforms": [], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Use this before cross-brand analysis to discover the current organization's live brand ids, slugs, names, and active status. Organization scope always comes from the sandbox token.", "input_params": [], "output_shape": "{brands: [{id, slug, name, status}], count: integer}" }, { "id": "action:posthog/list_projects", "namespace": "posthog", "action": "list_projects", "description": "List the PostHog projects this brand can query.", "mode": "read", "platforms": [ "posthog" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the PostHog projects granted to the current brand (project_id + name). START HERE for any PostHog task: every other posthog action requires one of these project_ids. Do NOT ask the user for a project id — this action is the source of truth. (Upstream PostHog project listing is org-wide and not available.)", "input_params": [], "output_shape": "data.rows[].{project_id, name}" }, { "id": "action:posthog/list_tools", "namespace": "posthog", "action": "list_tools", "description": "List the PostHog analytics tools available for a project.", "mode": "read", "platforms": [ "posthog" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the live PostHog MCP tool catalog for one granted project, each annotated with `requires_review`. The default response is a slim catalog without input schemas. Pass `tool` to fetch one exact tool's full input schema before calling `posthog/query`. Tools with `requires_review=true` (writes, or anything not on our read-only allowlist) must go through `posthog/request_change`, not `posthog/query`.", "input_params": [ { "name": "project_id", "type": "string", "required": false, "description": "PostHog project id (from posthog/list_projects).", "example": "12345" }, { "name": "tool", "type": "string", "required": false, "description": "Exact PostHog tool name; when provided, returns that tool's full input schema.", "example": "insight-query" } ], "output_shape": "data.project_id, data.tools[].{name, description, requires_review[, input_schema when tool is provided]}" }, { "id": "action:posthog/query", "namespace": "posthog", "action": "query", "description": "Run a read-only PostHog analytics tool (trends, funnels, HogQL, etc.).", "mode": "read", "platforms": [ "posthog" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Forwards a read-only PostHog tool call for one granted project. Common tools: `insight-query` (trends / funnels / retention), `execute-sql` (HogQL — read-only, validated), `read-data-schema` (use before writing HogQL to get real field names). Get the project_id from `posthog/list_projects` (never ask the user for it). Only allowlisted read tools are accepted here; a write or unvetted tool returns `requires_review` — call `posthog/request_change` for those. The response echoes which project was queried.", "input_params": [ { "name": "project_id", "type": "string", "required": true, "description": "PostHog project id (from posthog/list_projects).", "example": "12345" }, { "name": "tool", "type": "string", "required": true, "description": "Exact PostHog tool name from posthog/list_tools.", "example": "insight-query" }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments object per the tool's input schema.", "example": { "query": { "kind": "TrendsQuery" } } } ], "output_shape": "data.{project_id, tool, is_error, content[], structured, truncated}" }, { "id": "action:posthog/request_change", "namespace": "posthog", "action": "request_change", "description": "Request a PostHog write (feature flag / experiment / survey) via human approval.", "mode": "risk", "platforms": [ "posthog" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "Submits a PostHog write or non-allowlisted tool call for human approval (HITL). Returns a pending_review_id immediately; the call is forwarded to PostHog only after a human approves. Use this for anything `posthog/query` rejected as requires_review.", "input_params": [ { "name": "project_id", "type": "string", "required": true, "description": "PostHog project id (from posthog/list_projects).", "example": "12345" }, { "name": "tool", "type": "string", "required": true, "description": "Exact PostHog tool name (e.g. a create/update tool).", "example": "feature-flag-create" }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments object per the tool's input schema.", "example": { "key": "new-flag", "active": false } } ], "output_shape": "202 -> {ok, pending_review_id}; on approval data.{project_id, tool, is_error, content[], structured}" }, { "id": "action:seo_hosting/delete_page", "namespace": "seo_hosting", "action": "delete_page", "description": "Delete a file-based SEO page's whole folder (index.html + page.json).", "mode": "write", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Removes /brand/seo-hosting/pages/{section}/{slug}/ entirely. The slug frees up for reuse. Page-local files are deleted; CDN assets uploaded via upload_asset are not auto-purged.", "input_params": [ { "name": "section", "type": "string", "required": true, "description": "Content section — must be one of the brand's catalog sections (see get_sections).", "source": "upstream:action:seo_hosting/get_sections" }, { "name": "slug", "type": "string", "required": true, "description": "Page slug (the folder name under the section).", "source": "upstream:action:seo_hosting/list_pages" } ], "output_shape": "{ section, slug, removed_objects, status: 'deleted' }" }, { "id": "action:seo_hosting/get_sections", "namespace": "seo_hosting", "action": "get_sections", "description": "Read the brand's section catalog (the content sections, i.e. the first URL path segments pages can live under). Returns whether it is established and, if not, discovered seed sections.", "mode": "read", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:seo_hosting/put_sections" ], "long_description": "Returns { established, sections: [{key,label,order}], discovered }. When not established (no catalog yet), 'discovered' lists section folders that already hold pages as a seed hint. Establish/replace the catalog with put_sections before authoring pages — put_page/publish_page reject sections not in the catalog.", "input_params": [], "output_shape": "{ established, sections: [{ key, label, order }], discovered: [str] }" }, { "id": "action:seo_hosting/get_status", "namespace": "seo_hosting", "action": "get_status", "description": "Where this brand's SEO pages will be served: connected custom domains, live state, URL contract (subdomain vs subdirectory), served sections, the asset CDN base URL, and the /brand/seo-hosting workspace dir.", "mode": "read", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Call FIRST when authoring SEO pages. Returns each domain connection (hostname/method/status/live/served_sections/public_base_url), the allowed sections (the brand's catalog), sections_established, and where to write files. If sections_established is false, call put_sections to establish the catalog before put_page/publish_page. Pages only go public once a connection is live for their section.", "input_params": [], "output_shape": "{ domains: [{ hostname, method, status, live, url_contract, served_sections, public_base_url }], allowed_sections, sections_established, asset_cdn_base_url, workspace_dir, note }" }, { "id": "action:seo_hosting/list_pages", "namespace": "seo_hosting", "action": "list_pages", "description": "List this brand's file-based SEO pages (from page.json), newest first. Use before authoring to avoid duplicates and find internal-link targets.", "mode": "read", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns page summaries (section/slug/title/status/url_path/published_at) parsed from each page's page.json under /brand/seo-hosting/pages. Filter by 'section' / 'status'.", "input_params": [ { "name": "section", "type": "string", "required": false, "description": "Content section filter (one of the brand's catalog sections).", "source": "upstream:action:seo_hosting/get_sections" }, { "name": "status", "type": "string", "required": false, "description": "Filter by lifecycle status.", "enum": [ "draft", "published", "archived" ], "source": "constant" } ], "output_shape": "{ items: [{ section, slug, title, status, url_path, ... }], count }" }, { "id": "action:seo_hosting/publish_page", "namespace": "seo_hosting", "action": "publish_page", "description": "Publish a file-based SEO page after light validation (no custom JS, page.json fields). Flips page.json to published. Returns advisory link_warnings (dead internal links, zero internal links) and content_warnings (off-brand canonical/og/link domains, canonical/og:url on a host with no connected SEO-hosting domain, no , emoji in headings/icons, missing JSON-LD or publication date) — neither blocks publishing.", "mode": "write", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:seo_hosting/get_status" ], "long_description": "Reads the index.html + page.json you wrote under /brand/seo-hosting/pages/{section}/{slug}/, validates them, and on success sets status=published (stamps publishedAt on first publish). Returns the public_url + whether it is actually served (depends on a live domain connection), plus advisory link_warnings and content_warnings. 422 with the issues if validation fails.", "input_params": [ { "name": "section", "type": "string", "required": true, "description": "Content section — must be one of the brand's catalog sections (see get_sections).", "source": "upstream:action:seo_hosting/get_sections" }, { "name": "slug", "type": "string", "required": true, "description": "Page slug (the folder name under the section).", "source": "upstream:action:seo_hosting/list_pages" } ], "output_shape": "{ section, slug, status: 'published', public_url, served, link_warnings, content_warnings, ... }" }, { "id": "action:seo_hosting/put_page", "namespace": "seo_hosting", "action": "put_page", "description": "Create or overwrite a file-based SEO page's HTML + metadata as a draft (API write path for external callers like the Soku CLI; the in-product agent writes files directly). Call publish_page after to go live.", "mode": "write", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:seo_hosting/publish_page" ], "long_description": "Writes index.html + page.json under /brand/seo-hosting/pages/{section}/{slug}/. The section must already exist in the brand's catalog (get_sections / put_sections) — otherwise this returns sections_not_established / unknown_section. Always saved as draft — publish_page is the validation gate (no custom JS) before it serves publicly. Reference assets uploaded via upload_asset by absolute URL.", "input_params": [ { "name": "section", "type": "string", "required": true, "description": "Content section — must be one of the brand's catalog sections (see get_sections).", "source": "upstream:action:seo_hosting/get_sections" }, { "name": "slug", "type": "string", "required": true, "description": "Page slug (folder name under the section).", "source": "user" }, { "name": "title", "type": "string", "required": true, "description": "Page title (used in / listings).", "source": "user" }, { "name": "html", "type": "string", "required": true, "description": "The full HTML document (no custom <script>).", "source": "user" }, { "name": "description", "type": "string", "required": false, "description": "Meta description.", "source": "user" }, { "name": "template", "type": "string", "required": false, "description": "Template name this page was built from.", "source": "user" }, { "name": "seo", "type": "object", "required": false, "description": "SEO overrides (canonical / ogImage / keywords).", "source": "user" } ], "output_shape": "{ section, slug, status: 'draft', url_path, ... }" }, { "id": "action:seo_hosting/put_sections", "namespace": "seo_hosting", "action": "put_sections", "description": "Establish or replace the brand's section catalog (the COMPLETE list of content sections). Required before authoring pages in a new section.", "mode": "write", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:seo_hosting/get_sections" ], "long_description": "Writes sections.json (the full catalog). 'sections' is the complete list of { key, label, order? } — keys are lowercase [a-z0-9-], unique, not reserved, max 20. Removing a section that still has PUBLISHED pages requires force=true (otherwise rejected to avoid orphaning live pages). There is no default catalog — a brand has no sections until this is called.", "input_params": [ { "name": "sections", "type": "array", "required": true, "description": "The complete catalog: [{ key, label, order? }]. key is the URL segment (lowercase a-z0-9-), label is the display name.", "source": "user" }, { "name": "force", "type": "boolean", "required": false, "description": "Allow removing a section that still has published pages.", "source": "user" } ], "output_shape": "{ established: true, sections: [{ key, label, order }] }" }, { "id": "action:seo_hosting/reprovision_domain", "namespace": "seo_hosting", "action": "reprovision_domain", "description": "Re-deploy a connected subdirectory (T4 worker/vercel) domain with its CURRENT sections and re-verify — recovers a broken connection or re-applies after an external change. Does NOT change which sections are mounted.", "mode": "write", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:seo_hosting/set_domain_sections" ], "long_description": "Re-uploads the Worker (or rebuilds the Vercel rules) and reconciles routes to the connection's current mounted_sections, then runs verification. Idempotent; reuses the stored provider token. Identify the domain by its hostname (from get_status). To change which sections are served, use set_domain_sections instead.", "input_params": [ { "name": "hostname", "type": "string", "required": true, "description": "The connected domain hostname (from get_status).", "source": "upstream:action:seo_hosting/get_status" } ], "output_shape": "{ hostname, method, status, live, served_sections }" }, { "id": "action:seo_hosting/set_domain_sections", "namespace": "seo_hosting", "action": "set_domain_sections", "description": "Set which sections a connected subdirectory (T4 worker/vercel) domain serves, then re-provision + re-verify so the live worker/routes match. Use after adding a catalog section that an already-connected T4 domain should start serving (T3/CNAME serves the whole catalog automatically).", "mode": "write", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:seo_hosting/get_status", "action:seo_hosting/reprovision_domain" ], "long_description": "Updates the connection's mounted_sections to the given set and re-deploys (worker: re-upload script + add/prune routes; vercel: rebuild rules), then runs verification. Sections must be a subset of the brand's catalog (see get_sections). Reuses the domain's stored provider token — no token needed. Identify the domain by its hostname (from get_status). CNAME subdomains have no per-section mounts. To re-deploy without changing sections, use reprovision_domain.", "input_params": [ { "name": "hostname", "type": "string", "required": true, "description": "The connected domain hostname (from get_status).", "source": "upstream:action:seo_hosting/get_status" }, { "name": "sections", "type": "array", "required": true, "description": "The complete set of catalog sections this domain should serve.", "source": "upstream:action:seo_hosting/get_sections" } ], "output_shape": "{ hostname, method, status, live, served_sections }" }, { "id": "action:seo_hosting/unpublish_page", "namespace": "seo_hosting", "action": "unpublish_page", "description": "Unpublish a file-based SEO page (back to draft; stops serving).", "mode": "write", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Sets the page's page.json status back to 'draft'. The page stops being served publicly but its files remain for further editing. Reversible via publish_page.", "input_params": [ { "name": "section", "type": "string", "required": true, "description": "Content section — must be one of the brand's catalog sections (see get_sections).", "source": "upstream:action:seo_hosting/get_sections" }, { "name": "slug", "type": "string", "required": true, "description": "Page slug (the folder name under the section).", "source": "upstream:action:seo_hosting/list_pages" } ], "output_shape": "{ section, slug, status: 'draft', ... }" }, { "id": "action:seo_hosting/upload_asset", "namespace": "seo_hosting", "action": "upload_asset", "description": "Upload an image / theme.css / font to the public CDN and get a stable absolute URL to reference from page HTML (HTML+CSS only; no JS assets).", "mode": "write", "platforms": [ "seo_hosting" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [ "action:seo_hosting/publish_page" ], "long_description": "Writes asset bytes to the public asset CDN under the brand's namespace and returns { url }. Provide the bytes via EITHER 'source_path' (a file you already wrote under your /brand/seo-hosting/ mount, e.g. 'pages/blog/best-crm/hero.png' — preferred in-sandbox, no base64) OR 'content_base64' (inline base64, for callers without the mount). 'path' is the relative CDN path like 'blog/best-crm/hero.png' (page asset) or '_templates/article/theme.css' (shared template asset). Allowed types: images / css / fonts. Reference the returned URL absolutely in your HTML. Content-Type is derived from the file extension. Assets are not auto-deleted with pages. For .css uploads the response 'warnings' lists fonts declared via font-family but never loaded (no @font-face / @import) — advisory; add an @font-face (upload the woff2 first) or use a system font stack.", "input_params": [ { "name": "path", "type": "string", "required": true, "description": "Relative CDN asset path, e.g. 'blog/{slug}/hero.png'.", "source": "user" }, { "name": "source_path", "type": "string", "required": false, "description": "Path to a file already written under your /brand/seo-hosting/ mount (e.g. 'pages/blog/x/hero.png'). Avoids base64. Provide this OR 'content_base64'.", "source": "user" }, { "name": "content_base64", "type": "string", "required": false, "description": "Base64-encoded file bytes. Provide this OR 'source_path'.", "source": "user" } ], "output_shape": "{ url, path, size_bytes, warnings: [str] (css only; [] otherwise) }" }, { "id": "action:shopify/graphql_query", "namespace": "shopify", "action": "graphql_query", "description": "Run a read-only Shopify Admin GraphQL query (products, orders, customers, ...).", "mode": "read", "platforms": [ "shopify" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Executes a query document against the connected store's Shopify Admin GraphQL API (version 2026-07). One GraphQL endpoint covers the whole Admin API read surface — products, orders, customers, inventory, collections, and shop metadata — so there are no per-entity wrapper actions; consult the official Shopify Admin GraphQL API reference for query shapes. The Shopify integration is READ-ONLY in this version: no write action exists, and a document containing a mutation or subscription is rejected before any request is sent. Known scope limit: read_orders only covers roughly the last 60 days of orders by default (full history needs the Shopify-approved read_all_orders scope, which is not enabled). When exactly one store is connected, store_id may be omitted and is auto-resolved. The raw GraphQL response body (data / errors / extensions.cost throttle status) is returned verbatim.", "input_params": [ { "name": "store_id", "type": "string", "required": false, "description": "Shopify store id (the shop's gid). Optional when the brand has exactly one connected store; required to disambiguate when several are connected (the error lists the choices).", "example": "gid://shopify/Shop/548380009" }, { "name": "query", "type": "string", "required": true, "description": "GraphQL query document (reads only — mutations and subscriptions are rejected).", "example": "{ products(first: 10) { edges { node { id title } } } }" }, { "name": "variables", "type": "object", "required": false, "description": "GraphQL variables object for the query document.", "example": { "first": 10 } } ], "output_shape": "data.{store_id, shop_domain, status, data} — inner `data` is the raw Shopify GraphQL response body ({data, errors[], extensions.cost})" }, { "id": "action:shoplazza/get_order", "namespace": "shoplazza", "action": "get_order", "description": "Read a single Shoplazza order by id (read-only).", "mode": "read", "platforms": [ "shoplazza" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Fetches one order from the connected store via the Shoplazza OpenAPI (version 2022-01, GET /orders/{order_id}). The Shoplazza integration is READ-ONLY in this version: no write action exists and every request is a GET against a fixed path. This is a single-object read — there is no pagination; use list_orders to page through orders. order_id is the vendor order id shaped like {store_id}-{number} (e.g. 633130-00000007), as returned by list_orders. When exactly one store is connected, store_id may be omitted and is auto-resolved. The raw OpenAPI response body ({order: {...}}) is returned verbatim inside the envelope.", "input_params": [ { "name": "store_id", "type": "string", "required": false, "description": "Shoplazza store id. Optional when the brand has exactly one connected store; required to disambiguate when several are connected (the error lists the choices).", "example": "633130" }, { "name": "order_id", "type": "string", "required": true, "description": "Order id shaped like {store_id}-{number} (letters, digits, hyphens).", "example": "633130-00000007" } ], "output_shape": "data.{store_id, shop_domain, status, data} — inner `data` is the raw Shoplazza OpenAPI response body ({order: {...}})" }, { "id": "action:shoplazza/get_product", "namespace": "shoplazza", "action": "get_product", "description": "Read a single Shoplazza product by its UUID (read-only).", "mode": "read", "platforms": [ "shoplazza" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Fetches one product from the connected store via the Shoplazza OpenAPI (version 2022-01, GET /products/{product_id}). The Shoplazza integration is READ-ONLY in this version: no write action exists and every request is a GET against a fixed path. This is a single-object read — there is no pagination; use list_products to page through the catalog. product_id must be the product UUID as returned by list_products. When exactly one store is connected, store_id may be omitted and is auto-resolved. The raw OpenAPI response body ({product: {...}}) is returned verbatim inside the envelope.", "input_params": [ { "name": "store_id", "type": "string", "required": false, "description": "Shoplazza store id. Optional when the brand has exactly one connected store; required to disambiguate when several are connected (the error lists the choices).", "example": "633130" }, { "name": "product_id", "type": "string", "required": true, "description": "Product UUID (as returned by list_products).", "example": "0f3e2b1a-1234-5678-9abc-def012345678" } ], "output_shape": "data.{store_id, shop_domain, status, data} — inner `data` is the raw Shoplazza OpenAPI response body ({product: {...}})" }, { "id": "action:shoplazza/get_shop", "namespace": "shoplazza", "action": "get_shop", "description": "Read a Shoplazza store's shop metadata (name, domain, currency, ...).", "mode": "read", "platforms": [ "shoplazza" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Fetches the connected store's shop object from the Shoplazza OpenAPI (version 2022-01, GET /shop). The Shoplazza integration is READ-ONLY in this version: no write action exists and every request is a GET against a fixed path. This is a single-object read — there is no pagination. When exactly one store is connected, store_id may be omitted and is auto-resolved. The raw OpenAPI response body ({shop: {...}}) is returned verbatim inside the envelope.", "input_params": [ { "name": "store_id", "type": "string", "required": false, "description": "Shoplazza store id. Optional when the brand has exactly one connected store; required to disambiguate when several are connected (the error lists the choices).", "example": "633130" } ], "output_shape": "data.{store_id, shop_domain, status, data} — inner `data` is the raw Shoplazza OpenAPI response body ({shop: {...}})" }, { "id": "action:shoplazza/list_orders", "namespace": "shoplazza", "action": "list_orders", "description": "List orders from a Shoplazza store (page/limit or page_token, read-only).", "mode": "read", "platforms": [ "shoplazza" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Lists orders from the connected store via the Shoplazza OpenAPI (version 2022-01, GET /orders). The Shoplazza integration is READ-ONLY in this version: no write action exists and every request is a GET against a fixed path. Pagination: either page (1-based) + limit (default 50, max 250), or page_token from a previous response's next_token (page_token takes precedence upstream). Optional filters: status, financial_status, fulfillment_status, created_at_min, created_at_max, and keyword. When exactly one store is connected, store_id may be omitted and is auto-resolved. The raw OpenAPI response body ({orders: [...], count, next_token}) is returned verbatim inside the envelope.", "input_params": [ { "name": "store_id", "type": "string", "required": false, "description": "Shoplazza store id. Optional when the brand has exactly one connected store; required to disambiguate when several are connected (the error lists the choices).", "example": "633130" }, { "name": "page", "type": "integer", "required": false, "description": "1-based page number (ignored upstream when page_token is set).", "example": 1 }, { "name": "limit", "type": "integer", "required": false, "description": "Page size (default 50, clamped to 1-250).", "example": 50 }, { "name": "page_token", "type": "string", "required": false, "description": "Pagination token from a previous response's next_token.", "example": "eyJwYWdlIjoy..." }, { "name": "status", "type": "string", "required": false, "description": "Filter by order status (opened / placed / finished / cancelled).", "example": "opened" }, { "name": "financial_status", "type": "string", "required": false, "description": "Filter by financial status (waiting / paying / paid / cancelled / failed / refunding / refund_failed / refunded / partially_refunded).", "example": "paid" }, { "name": "fulfillment_status", "type": "string", "required": false, "description": "Filter by fulfillment status (initialled / waiting / partially_shipped / shipped / partially_finished / finished / cancelled / returning / returned / partially_returned).", "example": "shipped" }, { "name": "created_at_min", "type": "string", "required": false, "description": "Only orders created at or after this timestamp (ISO 8601).", "example": "2026-01-01T00:00:00Z" }, { "name": "created_at_max", "type": "string", "required": false, "description": "Only orders created at or before this timestamp (ISO 8601).", "example": "2026-07-01T00:00:00Z" }, { "name": "keyword", "type": "string", "required": false, "description": "Keyword search over orders (upstream matching semantics).", "example": "alice@example.com" } ], "output_shape": "data.{store_id, shop_domain, status, data} — inner `data` is the raw Shoplazza OpenAPI response body ({orders: [...], count, next_token})" }, { "id": "action:shoplazza/list_products", "namespace": "shoplazza", "action": "list_products", "description": "List products from a Shoplazza store (cursor-paginated, read-only).", "mode": "read", "platforms": [ "shoplazza" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Lists products from the connected store via the Shoplazza OpenAPI (version 2022-01, GET /products). The Shoplazza integration is READ-ONLY in this version: no write action exists and every request is a GET against a fixed path. Pagination is cursor-based: pass the `cursor` value from the previous response to fetch the next page (`pre_cursor` walks back); per_page controls the page size (default 50, max 250). Optional filters: ids (comma-separated product UUIDs), title, and published_status. When exactly one store is connected, store_id may be omitted and is auto-resolved. The raw OpenAPI response body ({products: [...], cursor, pre_cursor}) is returned verbatim inside the envelope.", "input_params": [ { "name": "store_id", "type": "string", "required": false, "description": "Shoplazza store id. Optional when the brand has exactly one connected store; required to disambiguate when several are connected (the error lists the choices).", "example": "633130" }, { "name": "cursor", "type": "string", "required": false, "description": "Pagination cursor from a previous list_products response.", "example": "eyJsYXN0X2lkIjoi..." }, { "name": "pre_cursor", "type": "string", "required": false, "description": "Pagination cursor walking back to the previous page.", "example": "eyJmaXJzdF9pZCI6Ii..." }, { "name": "per_page", "type": "integer", "required": false, "description": "Page size (default 50, clamped to 1-250).", "example": 50 }, { "name": "ids", "type": "string", "required": false, "description": "Comma-separated product UUIDs to restrict the listing to.", "example": "0f3e2b1a-....,9c8d7e6f-..." }, { "name": "title", "type": "string", "required": false, "description": "Filter products by title (upstream matching semantics).", "example": "T-shirt" }, { "name": "published_status", "type": "string", "required": false, "description": "Filter by published status (e.g. published / unpublished).", "example": "published" } ], "output_shape": "data.{store_id, shop_domain, status, data} — inner `data` is the raw Shoplazza OpenAPI response body ({products: [...], cursor, pre_cursor})" }, { "id": "action:sprout/list_profiles", "namespace": "sprout", "action": "list_profiles", "description": "List a Sprout Social customer's social profiles and tags (LinkedIn, Facebook, Instagram, X, ...).", "mode": "read", "platforms": [ "sprout" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the connected customer's owned social profiles (customer_profile_id, network_type, name, native_name, link) and the customer's tags. START HERE for any Sprout Social task: the analytics actions filter by the customer_profile_id values this action returns. When exactly one customer is connected, customer_id may be omitted and is auto-resolved. Read live from the Sprout Social Public API (read-only; costs 2 upstream requests against the shared per-token quota).", "input_params": [ { "name": "customer_id", "type": "string", "required": false, "description": "Sprout Social customer id. Optional when the brand has exactly one connected customer; required to disambiguate when several are connected (the error lists the choices).", "example": "2881331" } ], "output_shape": "data.{customer_id, profiles[].{customer_profile_id, network_type, name, native_name, link, native_id, groups[]}, tags[].{tag_id, text, type, active}}" }, { "id": "action:sprout/post_analytics", "namespace": "sprout", "action": "post_analytics", "description": "Per-post analytics (impressions, reactions, clicks per published post) for a time range.", "mode": "read", "platforms": [ "sprout" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Queries Sprout Social post analytics: one row per published post created in the selected time range, with lifetime metrics and descriptive fields (text, perma_link, created_time). start/end accept YYYY-MM-DD or an ISO datetime such as 2026-07-01T00:00:00 (created_time filtering supports datetime precision). Results are paged — the response `paging` object carries current_page/total_pages; pass `page` to fetch more. The handler constructs all Sprout filter strings from these structured parameters — do not pass raw filter syntax. Post metric keys are `lifetime.`-prefixed. Commonly valid across LinkedIn / Facebook / Instagram / X: lifetime.impressions, lifetime.reactions, lifetime.likes, lifetime.comments_count, lifetime.shares_count, lifetime.post_content_clicks, lifetime.video_views; Facebook additionally lifetime.impressions_unique and lifetime.post_link_clicks. IMPORTANT: unknown metrics are silently dropped upstream — Sprout returns no error for a wrong metric key, the key is just absent from every row. Always check `missing_metrics` in the response; a non-empty value means those requested keys came back in no row (wrong key, or a metric this network does not support). When the data array is empty, missing_metrics is [] — an empty result cannot distinguish a bad metric key from a range with no posts.", "input_params": [ { "name": "customer_id", "type": "string", "required": false, "description": "Sprout Social customer id. Optional when the brand has exactly one connected customer; required to disambiguate when several are connected (the error lists the choices).", "example": "2881331" }, { "name": "profile_ids", "type": "list", "required": false, "description": "customer_profile_id values to query (from sprout/list_profiles). Omitted = all of the customer's profiles (auto-enumerated, which costs one extra upstream request against the shared quota). At most 100 profiles per request (Sprout's limit): for a customer with more, pass profile_ids explicitly and query them in batches of 100 or fewer — omitting it would exceed the cap and be rejected.", "example": [ 7662736 ], "source": "upstream:action:sprout/list_profiles" }, { "name": "start", "type": "string", "required": true, "description": "Range start, inclusive — YYYY-MM-DD or ISO datetime (post_analytics supports datetime precision).", "example": "2026-07-01" }, { "name": "end", "type": "string", "required": true, "description": "Range end, inclusive — YYYY-MM-DD or ISO datetime (post_analytics supports datetime precision).", "example": "2026-08-01" }, { "name": "metrics", "type": "list", "required": true, "description": "Post metric keys to return (`lifetime.`-prefixed; unknown keys are silently dropped upstream).", "example": [ "lifetime.impressions", "lifetime.reactions" ] }, { "name": "fields", "type": "list", "required": false, "description": "Descriptive post fields to include alongside metrics (e.g. text, perma_link, created_time, internal.tags.id).", "example": [ "text", "perma_link", "created_time" ] }, { "name": "page", "type": "integer", "required": false, "description": "Result page to fetch (1-based; see response paging).", "example": 1 } ], "output_shape": "data.{customer_id, profile_ids[], start, end, data[].{<requested fields>, metrics.{...}}, paging.{current_page, total_pages}, missing_metrics[]}" }, { "id": "action:sprout/profile_analytics", "namespace": "sprout", "action": "profile_analytics", "description": "Daily owned-profile analytics (impressions, engagement, follower growth) for a date range.", "mode": "read", "platforms": [ "sprout" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Queries Sprout Social profile analytics for the selected profiles over an inclusive date range, grouped daily (the response dimension key is literally `reporting_period.by(day)`). Dates are YYYY-MM-DD. The handler constructs all Sprout filter strings from these structured parameters — do not pass raw filter syntax. At most 100 profiles per request: for a customer with more, pass profile_ids in batches of 100 or fewer (an over-large set, including an auto-enumerated one, is rejected — never silently truncated). Commonly valid profile metrics by network — LinkedIn (linkedin_company): impressions, reactions, comments_count, shares_count, post_content_clicks, net_follower_growth, lifetime_snapshot.followers_count; Facebook: impressions, impressions_unique, post_content_clicks, reactions, comments_count, shares_count, net_follower_growth, lifetime_snapshot.followers_count, video_views; Instagram: impressions, engagements_other, comments_count, likes, saves, net_follower_growth, lifetime_snapshot.followers_count, video_views; X (Twitter): impressions, likes, comments_count, shares_count, post_content_clicks, net_follower_growth, lifetime_snapshot.followers_count. Note the followers count key is `lifetime_snapshot.followers_count` — a bare `followers_count` is silently dropped. IMPORTANT: unknown metrics are silently dropped upstream — Sprout returns no error for a wrong metric key, the key is just absent from every row. Always check `missing_metrics` in the response; a non-empty value means those requested keys came back in no row (wrong key, or a metric this network does not support). When the data array is empty, missing_metrics is [] — an empty result cannot distinguish a bad metric key from a range with no data.", "input_params": [ { "name": "customer_id", "type": "string", "required": false, "description": "Sprout Social customer id. Optional when the brand has exactly one connected customer; required to disambiguate when several are connected (the error lists the choices).", "example": "2881331" }, { "name": "profile_ids", "type": "list", "required": false, "description": "customer_profile_id values to query (from sprout/list_profiles). Omitted = all of the customer's profiles (auto-enumerated, which costs one extra upstream request against the shared quota). At most 100 profiles per request (Sprout's limit): for a customer with more, pass profile_ids explicitly and query them in batches of 100 or fewer — omitting it would exceed the cap and be rejected.", "example": [ 7662736 ], "source": "upstream:action:sprout/list_profiles" }, { "name": "start_date", "type": "string", "required": true, "description": "Range start, inclusive, format YYYY-MM-DD.", "example": "2026-07-01" }, { "name": "end_date", "type": "string", "required": true, "description": "Range end, inclusive, format YYYY-MM-DD.", "example": "2026-07-31" }, { "name": "metrics", "type": "list", "required": true, "description": "Profile metric keys to return (see the network lists in the action description; unknown keys are silently dropped upstream).", "example": [ "impressions", "lifetime_snapshot.followers_count" ] } ], "output_shape": "data.{customer_id, profile_ids[], start_date, end_date, data[].{dimensions.{customer_profile_id, 'reporting_period.by(day)'}, metrics.{...}}, missing_metrics[]}" }, { "id": "action:stripe/list_accounts", "namespace": "stripe", "action": "list_accounts", "description": "List the Stripe accounts this brand can query.", "mode": "read", "platforms": [ "stripe" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the Stripe accounts granted to the current brand (account_id + name). START HERE for any Stripe task: every other stripe action requires one of these account_ids. Do NOT ask the user for an account id — this action is the source of truth.", "input_params": [], "output_shape": "data.rows[].{account_id, name}" }, { "id": "action:stripe/list_tools", "namespace": "stripe", "action": "list_tools", "description": "List the Stripe payment/billing tools available for an account.", "mode": "read", "platforms": [ "stripe" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns the live Stripe MCP tool catalog for one granted account, each annotated with `requires_review`. The default response is a slim catalog without input schemas. Pass `tool` to fetch one exact tool's full input schema before calling `stripe/query`. Tools with `requires_review=true` (writes like refunds or payment links, or anything not on our read-only allowlist) must go through `stripe/request_change`, not `stripe/query`. Data is live from Stripe (realtime, no cached pipeline).", "input_params": [ { "name": "project_id", "type": "string", "required": false, "description": "Stripe account id (from stripe/list_accounts).", "example": "acct_1ABC2DEF3GHI4JKL" }, { "name": "tool", "type": "string", "required": false, "description": "Exact Stripe tool name; when provided, returns that tool's full input schema.", "example": "stripe_api_read" } ], "output_shape": "data.project_id, data.tools[].{name, description, requires_review[, input_schema when tool is provided]}" }, { "id": "action:stripe/query", "namespace": "stripe", "action": "query", "description": "Run a read-only Stripe tool (balance, customers, invoices, subscriptions, etc.).", "mode": "read", "platforms": [ "stripe" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Forwards a read-only Stripe tool call for one granted account and returns live data (realtime — there is no cached sync pipeline). Current hosted MCP exposes generic API reads through `stripe_api_read` (for example GET `/v1/balance`, `/v1/customers`, `/v1/invoices`, `/v1/subscriptions`, `/v1/payment_intents`) plus `get_stripe_account_info` and `search_stripe_resources`. Get the account_id from `stripe/list_accounts` (never ask the user for it). Only allowlisted read tools run immediately; a write or unvetted tool returns `requires_review` — call `stripe/request_change` for those. The response echoes which account was queried.", "input_params": [ { "name": "project_id", "type": "string", "required": true, "description": "Stripe account id (from stripe/list_accounts).", "example": "acct_1ABC2DEF3GHI4JKL" }, { "name": "tool", "type": "string", "required": true, "description": "Exact Stripe tool name from stripe/list_tools.", "example": "stripe_api_read" }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments object per the tool's input schema.", "example": { "path": "/v1/customers", "params": { "limit": 10 } } } ], "output_shape": "data.{project_id, tool, is_error, content[], structured, truncated}" }, { "id": "action:stripe/request_change", "namespace": "stripe", "action": "request_change", "description": "Request a Stripe write (refund / payment link / coupon) via human approval.", "mode": "risk", "platforms": [ "stripe" ], "requires_review": true, "freshness_kind": "realtime", "see_also": [], "long_description": "Submits a Stripe write or non-allowlisted tool call for human approval (HITL). Returns a pending_review_id immediately; the call is forwarded to Stripe only after a human approves. Use this for anything `stripe/query` rejected as requires_review (e.g. `create_refund`, `create_payment_link`).", "input_params": [ { "name": "project_id", "type": "string", "required": true, "description": "Stripe account id (from stripe/list_accounts).", "example": "acct_1ABC2DEF3GHI4JKL" }, { "name": "tool", "type": "string", "required": true, "description": "Exact Stripe tool name (e.g. a create/update tool).", "example": "create_refund" }, { "name": "arguments", "type": "object", "required": false, "description": "Tool arguments object per the tool's input schema.", "example": { "payment_intent": "pi_123", "amount": 500 } } ], "output_shape": "202 -> {ok, pending_review_id}; on approval data.{project_id, tool, is_error, content[], structured}" }, { "id": "action:toast/get_order", "namespace": "toast", "action": "get_order", "description": "Get one Toast order in full detail.", "mode": "read", "platforms": [ "toast" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "The complete Order object: every check with its line items (selections), applied discounts, service charges, and payments. Use after toast/list_orders or toast/list_checks when you need what was actually ordered rather than totals.", "input_params": [ { "name": "restaurant_guid", "type": "string", "required": false, "description": "Toast restaurant GUID, from toast/list_restaurants. Optional when the brand has exactly one granted restaurant (it is resolved automatically); required to disambiguate when several are granted (the error lists the choices). Only granted restaurants are addressable.", "source": "prior_action:toast/list_restaurants" }, { "name": "order_guid", "type": "string", "required": true, "description": "Toast order GUID, from toast/list_orders or toast/list_checks. Letters, digits and hyphens only.", "source": "prior_action:toast/list_orders" } ], "output_shape": "{restaurant_guid, order: <full Toast Order object>}" }, { "id": "action:toast/list_checks", "namespace": "toast", "action": "list_checks", "description": "List Toast checks (bills) across a business day or time range.", "mode": "read", "platforms": [ "toast" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Checks flattened across the selected orders, each carrying its order context. Toast has no standalone checks endpoint — this reads the same orders as toast/list_orders and projects them differently, at the same upstream cost. Use it for split-check analysis, per-check totals, tax and tip breakdowns. IMPORTANT: business dates are the RESTAURANT'S LOCAL business day, whose boundary is set by the location's closeoutHour (a late-night venue's business day crosses midnight). Do NOT pass today's UTC date. To resolve 'today' or 'yesterday', call toast/list_restaurants first and convert using the returned time_zone and closeout_hour. A wrong business date returns a DIFFERENT day's data with no error — every row echoes its business_date so you can verify you got the day you meant. has_more is a HEURISTIC: Toast returns a bare array with no total, so it is true whenever the page came back full. A final page that is exactly full reports has_more once too often — stop when the next page is empty.", "input_params": [ { "name": "restaurant_guid", "type": "string", "required": false, "description": "Toast restaurant GUID, from toast/list_restaurants. Optional when the brand has exactly one granted restaurant (it is resolved automatically); required to disambiguate when several are granted (the error lists the choices). Only granted restaurants are addressable.", "source": "prior_action:toast/list_restaurants" }, { "name": "business_date", "type": "string", "required": false, "description": "Business day in YYYYMMDD form. Use this OR the start_date/end_date pair, not both. IMPORTANT: business dates are the RESTAURANT'S LOCAL business day, whose boundary is set by the location's closeoutHour (a late-night venue's business day crosses midnight). Do NOT pass today's UTC date. To resolve 'today' or 'yesterday', call toast/list_restaurants first and convert using the returned time_zone and closeout_hour. A wrong business date returns a DIFFERENT day's data with no error — every row echoes its business_date so you can verify you got the day you meant.", "example": "20260809", "one_of_group": "order_window" }, { "name": "start_date", "type": "string", "required": false, "description": "ISO-8601 instant, INCLUSIVE, and it MUST carry a UTC offset. Filters by order modification time, not business day. Requires end_date. Use this pair for a sub-day window.", "example": "2026-08-09T14:00:00+00:00", "one_of_group": "order_window" }, { "name": "end_date", "type": "string", "required": false, "description": "ISO-8601 instant, EXCLUSIVE, with a UTC offset. Requires start_date. Keep ranges to a month or less — Toast rate-limits wide historical scans.", "example": "2026-08-10T00:00:00+00:00", "one_of_group": "order_window" }, { "name": "page", "type": "integer", "required": false, "description": "1-based page number. Default 1.", "example": 1 }, { "name": "page_size", "type": "integer", "required": false, "description": "Orders per page, 1-100. Default 100 (Toast's maximum).", "example": 100 } ], "output_shape": "{restaurant_guid, business_date?|start_date+end_date, checks: [{check_guid, order_guid, business_date, opened_date, display_number, amount, tax_amount, tip_amount, total_amount, voided, customer_guid, payment_count, payment_guids}], order_count, page, page_size, has_more}" }, { "id": "action:toast/list_orders", "namespace": "toast", "action": "list_orders", "description": "List Toast order summaries for a business day or time range.", "mode": "read", "platforms": [ "toast" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Order-level summaries: identifiers, open/paid/closed timestamps, business date, dining option, void flag, check count and check GUIDs, and the summed check total. Use toast/get_order for a single order's line items, and toast/list_checks when you want check-level rows across many orders. IMPORTANT: business dates are the RESTAURANT'S LOCAL business day, whose boundary is set by the location's closeoutHour (a late-night venue's business day crosses midnight). Do NOT pass today's UTC date. To resolve 'today' or 'yesterday', call toast/list_restaurants first and convert using the returned time_zone and closeout_hour. A wrong business date returns a DIFFERENT day's data with no error — every row echoes its business_date so you can verify you got the day you meant. has_more is a HEURISTIC: Toast returns a bare array with no total, so it is true whenever the page came back full. A final page that is exactly full reports has_more once too often — stop when the next page is empty.", "input_params": [ { "name": "restaurant_guid", "type": "string", "required": false, "description": "Toast restaurant GUID, from toast/list_restaurants. Optional when the brand has exactly one granted restaurant (it is resolved automatically); required to disambiguate when several are granted (the error lists the choices). Only granted restaurants are addressable.", "source": "prior_action:toast/list_restaurants" }, { "name": "business_date", "type": "string", "required": false, "description": "Business day in YYYYMMDD form. Use this OR the start_date/end_date pair, not both. IMPORTANT: business dates are the RESTAURANT'S LOCAL business day, whose boundary is set by the location's closeoutHour (a late-night venue's business day crosses midnight). Do NOT pass today's UTC date. To resolve 'today' or 'yesterday', call toast/list_restaurants first and convert using the returned time_zone and closeout_hour. A wrong business date returns a DIFFERENT day's data with no error — every row echoes its business_date so you can verify you got the day you meant.", "example": "20260809", "one_of_group": "order_window" }, { "name": "start_date", "type": "string", "required": false, "description": "ISO-8601 instant, INCLUSIVE, and it MUST carry a UTC offset. Filters by order modification time, not business day. Requires end_date. Use this pair for a sub-day window.", "example": "2026-08-09T14:00:00+00:00", "one_of_group": "order_window" }, { "name": "end_date", "type": "string", "required": false, "description": "ISO-8601 instant, EXCLUSIVE, with a UTC offset. Requires start_date. Keep ranges to a month or less — Toast rate-limits wide historical scans.", "example": "2026-08-10T00:00:00+00:00", "one_of_group": "order_window" }, { "name": "page", "type": "integer", "required": false, "description": "1-based page number. Default 1.", "example": 1 }, { "name": "page_size", "type": "integer", "required": false, "description": "Orders per page, 1-100. Default 100 (Toast's maximum).", "example": 100 } ], "output_shape": "{restaurant_guid, business_date?|start_date+end_date, orders: [{order_guid, display_number, opened_date, paid_date, closed_date, business_date, voided, source, dining_option_guid, check_count, check_guids, total_amount}], page, page_size, has_more}" }, { "id": "action:toast/list_payments", "namespace": "toast", "action": "list_payments", "description": "List Toast payment details for one paid business day.", "mode": "read", "platforms": [ "toast" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Payments taken on the given business day, with type, amount, tip, card type, last four digits, status, and refund status. Toast's payments endpoint returns identifiers only, so each payment costs one extra upstream request — bounded by `limit`. Check `truncated` before treating the list as complete. IMPORTANT: business dates are the RESTAURANT'S LOCAL business day, whose boundary is set by the location's closeoutHour (a late-night venue's business day crosses midnight). Do NOT pass today's UTC date. To resolve 'today' or 'yesterday', call toast/list_restaurants first and convert using the returned time_zone and closeout_hour. A wrong business date returns a DIFFERENT day's data with no error — every row echoes its business_date so you can verify you got the day you meant.", "input_params": [ { "name": "restaurant_guid", "type": "string", "required": false, "description": "Toast restaurant GUID, from toast/list_restaurants. Optional when the brand has exactly one granted restaurant (it is resolved automatically); required to disambiguate when several are granted (the error lists the choices). Only granted restaurants are addressable.", "source": "prior_action:toast/list_restaurants" }, { "name": "paid_business_date", "type": "string", "required": true, "description": "Business day the payments were taken, YYYYMMDD. IMPORTANT: business dates are the RESTAURANT'S LOCAL business day, whose boundary is set by the location's closeoutHour (a late-night venue's business day crosses midnight). Do NOT pass today's UTC date. To resolve 'today' or 'yesterday', call toast/list_restaurants first and convert using the returned time_zone and closeout_hour. A wrong business date returns a DIFFERENT day's data with no error — every row echoes its business_date so you can verify you got the day you meant.", "example": "20260809" }, { "name": "limit", "type": "integer", "required": false, "description": "Maximum payment details to fetch, 1-500. Default 200. Values above 500 are rejected rather than clamped, so a truncated result is never mistaken for a complete one.", "example": 200 } ], "output_shape": "{restaurant_guid, paid_business_date, payments: [{payment_guid, type, amount, tip_amount, card_type, last4, paid_date, paid_business_date, payment_status, refund_status, refund: {refund_amount, tip_refund_amount, refund_date, refund_business_date}|null}], payment_ids_found, details_fetched, truncated, quota_limited?, note?}" }, { "id": "action:toast/list_refunds", "namespace": "toast", "action": "list_refunds", "description": "List Toast refunds issued on one business day.", "mode": "read", "platforms": [ "toast" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Refunded payments for the given refund business day. In Toast a refund is not its own record — it is a field on the payment — so each row is the original payment plus its refund amounts and dates, and payments with refund_status NONE are filtered out. Note the date selector is the REFUND business day, which is often not the day the payment was taken (paid_business_date is returned on every row so you can see both). Same identifier-then-detail cost and `limit` bound as toast/list_payments. IMPORTANT: business dates are the RESTAURANT'S LOCAL business day, whose boundary is set by the location's closeoutHour (a late-night venue's business day crosses midnight). Do NOT pass today's UTC date. To resolve 'today' or 'yesterday', call toast/list_restaurants first and convert using the returned time_zone and closeout_hour. A wrong business date returns a DIFFERENT day's data with no error — every row echoes its business_date so you can verify you got the day you meant.", "input_params": [ { "name": "restaurant_guid", "type": "string", "required": false, "description": "Toast restaurant GUID, from toast/list_restaurants. Optional when the brand has exactly one granted restaurant (it is resolved automatically); required to disambiguate when several are granted (the error lists the choices). Only granted restaurants are addressable.", "source": "prior_action:toast/list_restaurants" }, { "name": "refund_business_date", "type": "string", "required": true, "description": "Business day the refunds were issued, YYYYMMDD. IMPORTANT: business dates are the RESTAURANT'S LOCAL business day, whose boundary is set by the location's closeoutHour (a late-night venue's business day crosses midnight). Do NOT pass today's UTC date. To resolve 'today' or 'yesterday', call toast/list_restaurants first and convert using the returned time_zone and closeout_hour. A wrong business date returns a DIFFERENT day's data with no error — every row echoes its business_date so you can verify you got the day you meant.", "example": "20260809" }, { "name": "limit", "type": "integer", "required": false, "description": "Maximum payment details to fetch, 1-500. Default 200. Counts payments examined, not refunds found.", "example": 200 } ], "output_shape": "{restaurant_guid, refund_business_date, refunds: [{payment_guid, type, amount, original_amount, tip_amount, card_type, last4, paid_date, paid_business_date, payment_status, refund_status, refund_amount, tip_refund_amount, refund_date, refund_business_date}], payment_ids_found, details_fetched, truncated, quota_limited?, note?}" }, { "id": "action:toast/list_restaurants", "namespace": "toast", "action": "list_restaurants", "description": "List the Toast restaurant locations this brand can read.", "mode": "read", "platforms": [ "toast" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns every Toast location granted to this brand with its name, address, time zone, and closeout hour. Start here: the time zone and closeout hour are what turn 'today' into the correct business_date for the other actions, and the restaurant_guid is the value they take. Costs one upstream request per location.", "input_params": [ { "name": "offset", "type": "integer", "required": false, "description": "0-based offset into this brand's granted restaurants. Default 0. At most 20 are returned per call, so a brand with more than 20 grants needs offset=20, 40, … — read has_more / total_granted to know when to stop.", "example": 0 } ], "output_shape": "{restaurants: [{restaurant_guid, name, location_name, time_zone, closeout_hour, management_group_guid, archived, address: {line1, city, state, zip, country}, phone}], offset, returned, total_granted, has_more}" }, { "id": "action:ubereats/get_order", "namespace": "ubereats", "action": "get_order", "description": "Get one Uber Eats order in full detail.", "mode": "read", "platforms": [ "ubereats" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "The complete order object: current_state (CREATED / ACCEPTED / DENIED / FINISHED / CANCELED / UNKNOWN), placed_at, the owning store, and the charges money breakdown. Works for any order id belonging to a store granted to this brand — including orders that have left the CREATED feed. An order belonging to an ungranted store returns not_found with no data. Costs one upstream request. Every response carries data_source: 'live' means the numbers came from Uber Eats, 'mock' means they are FIXTURES from a non-production demo mode and are invented. Never present mock figures as the customer's real sales, payouts or orders, and never copy them into a deliverable.", "input_params": [ { "name": "order_id", "type": "string", "required": true, "description": "Uber Eats order UUID, from ubereats/list_active_orders or a report. Letters, digits, hyphens and underscores only.", "source": "prior_action:ubereats/list_active_orders" } ], "output_shape": "{store_id, order: <full Uber Eats order object>, data_source: 'live'|'mock'}" }, { "id": "action:ubereats/get_report", "namespace": "ubereats", "action": "get_report", "description": "Poll a requested Uber Eats report and read its rows once ready.", "mode": "read", "platforms": [ "ubereats" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Checks the report's generation status (status names are matched case-insensitively). While it is Preparing, only the status is returned — wait and poll again. Any OTHER non-Available status is terminal: the report cannot be read, the error names the status value verbatim, and since Uber documents no such status the right move is to relay it to the user rather than to keep polling or auto-start another report. Once Available, the CSV is streamed and parsed by HEADER NAME (column sets vary by report_type and unknown columns are tolerated); the returned window is offset..offset+max_rows, with truncated=true plus next_offset when more rows follow and total_rows always carrying the full count. With summarize=true, numeric columns are summed per (store, date) group over ALL rows (not just the returned page): money formatting (1,234.00 / $800.00 / (12.50) for a negative) is understood, and any column that still cannot be summed is listed in summary.skipped_columns with the reason rather than dropped silently. When the store or date column cannot be identified the summary comes back grouped=false with a reason — it never falls back to a global sum. Report figures for the most recent 72 hours are PROVISIONAL — Uber settles report data within 72 hours, so recent numbers may still change. Re-request the report later for final figures. Uber documents a 60 requests/minute limit on the report surface, and the budget may be shared across the whole platform app rather than per merchant. On a rate_limited error with source=upstream, back off (honoring retry_after when present) instead of retrying immediately. report_ref tokens are brand-bound and expire 24 hours after request_report: another brand's ref, a tampered ref, an expired ref, or a ref whose stores are no longer granted all return not_found. Every response carries data_source: 'live' means the numbers came from Uber Eats, 'mock' means they are FIXTURES from a non-production demo mode and are invented. Never present mock figures as the customer's real sales, payouts or orders, and never copy them into a deliverable.", "input_params": [ { "name": "report_ref", "type": "string", "required": true, "description": "The opaque report reference returned by ubereats/request_report.", "source": "prior_action:ubereats/request_report" }, { "name": "max_rows", "type": "integer", "required": false, "description": "Maximum CSV rows to return, 1-2000. Default 200. Values above 2000 are rejected rather than clamped. When the report is bigger, rows are truncated (truncated=true, total_rows carries the full count) — page on with offset, or use summarize=true for whole-report aggregates instead of paging.", "example": 200 }, { "name": "offset", "type": "integer", "required": false, "description": "Row index the returned window starts at, 0-1000000. Default 0. Pass the previous response's next_offset to read the following page; the report is re-read upstream on each call, so page only when the rows themselves are needed.", "example": 200 }, { "name": "summarize", "type": "boolean", "required": false, "description": "When true, adds per-(store, date) sums of the numeric columns computed over the WHOLE report. Default false. Accepts true/false (also as the strings 'true'/'false').", "example": true } ], "output_shape": "{report_ref, status: 'Preparing'|'Available', report_type, data_source: 'live'|'mock', columns?, duplicate_columns?, rows?, returned?, offset?, total_rows?, truncated?, next_offset?, note?, summary?: {grouped, store_column?, date_column?, value_columns?, skipped_columns?: [{column, reason}], groups?: [{store, date, row_count, totals}], reason?}}" }, { "id": "action:ubereats/get_store", "namespace": "ubereats", "action": "get_store", "description": "Get one Uber Eats store's live details.", "mode": "read", "platforms": [ "ubereats" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Reads the store live from Uber Eats: name, location, timezone, current status (e.g. ONLINE / PAUSED), merchant_store_id and POS wiring. Use this — not ubereats/list_stores — whenever the store's CURRENT state matters (the listing is an import-time snapshot). Costs one upstream request. Every response carries data_source: 'live' means the numbers came from Uber Eats, 'mock' means they are FIXTURES from a non-production demo mode and are invented. Never present mock figures as the customer's real sales, payouts or orders, and never copy them into a deliverable.", "input_params": [ { "name": "store_id", "type": "string", "required": false, "description": "Uber Eats store UUID, from ubereats/list_stores. Optional when the brand has exactly one granted store (it is resolved automatically); required to disambiguate when several are granted (the error lists the choices). Only granted stores are addressable.", "source": "prior_action:ubereats/list_stores" } ], "output_shape": "{store_id, store: <live Uber Eats store object>, data_source: 'live'|'mock'}" }, { "id": "action:ubereats/list_active_orders", "namespace": "ubereats", "action": "list_active_orders", "description": "List a store's incoming Uber Eats orders (CREATED state only).", "mode": "read", "platforms": [ "ubereats" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "The store's order-intake feed, oldest first: orders placed by customers that have not yet been accepted or rejected by the restaurant. Each order carries its id, placement time and charge breakdown. IMPORTANT: Uber Eats exposes NO synchronous order-history endpoint. ubereats/list_active_orders is the order-INTAKE feed and returns only orders currently in the CREATED state (placed, not yet accepted). Historical orders, sales, payments and financials are only reachable through the asynchronous report surface: ubereats/request_report then ubereats/get_report. This integration is read-only: it can see the feed but can never accept, deny or cancel an order. Costs one upstream request. Every response carries data_source: 'live' means the numbers came from Uber Eats, 'mock' means they are FIXTURES from a non-production demo mode and are invented. Never present mock figures as the customer's real sales, payouts or orders, and never copy them into a deliverable.", "input_params": [ { "name": "store_id", "type": "string", "required": false, "description": "Uber Eats store UUID, from ubereats/list_stores. Optional when the brand has exactly one granted store (it is resolved automatically); required to disambiguate when several are granted (the error lists the choices). Only granted stores are addressable.", "source": "prior_action:ubereats/list_stores" }, { "name": "limit", "type": "integer", "required": false, "description": "Maximum orders to return, 1-100. Default 50. Values above 100 are rejected rather than clamped, so a truncated result is never mistaken for a complete one.", "example": 50 } ], "output_shape": "{store_id, orders: [{id, display_id, current_state, placed_at, store: {id, name}, charges: {total, sub_total, tax, total_fee, delivery_fee, small_order_fee, tip, cash_amount_due}}], returned, feed: 'created_only', data_source: 'live'|'mock', note}" }, { "id": "action:ubereats/list_stores", "namespace": "ubereats", "action": "list_stores", "description": "List the Uber Eats stores this brand can read (import-time snapshot).", "mode": "read", "platforms": [ "ubereats" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Returns every Uber Eats store granted to this brand with its import-time snapshot fields: store_id, name, timezone, merchant_store_id (the merchant's own store code) and status. Start here — the store_id is the value the other actions take. Costs ZERO upstream requests. Store fields are an IMPORT-TIME SNAPSHOT captured when the store was connected — name, timezone, merchant_store_id and status may be stale. Use ubereats/get_store for the store's live state. Every response carries data_source: 'live' means the numbers came from Uber Eats, 'mock' means they are FIXTURES from a non-production demo mode and are invented. Never present mock figures as the customer's real sales, payouts or orders, and never copy them into a deliverable.", "input_params": [], "output_shape": "{stores: [{store_id, name, timezone, merchant_store_id, status}], total_granted, source: 'import_snapshot', data_source: 'live'|'mock', note}" }, { "id": "action:ubereats/request_report", "namespace": "ubereats", "action": "request_report", "description": "Request an async Uber Eats CSV report (sales, payments, financials, history).", "mode": "read", "platforms": [ "ubereats" ], "requires_review": false, "freshness_kind": "realtime", "see_also": [], "long_description": "Starts generation of one of Uber's 10 read-only merchant reports across one or more granted stores and a date range, and returns a report_ref to poll with ubereats/get_report. This is the ONLY way to read historical orders, sales, payments and financial data. Reports are ASYNCHRONOUS: ubereats/request_report returns a report_ref immediately, and the CSV is generated in the background. Poll ubereats/get_report with the report_ref; while status is Preparing, wait and poll again (typically within minutes). start_date and end_date are YYYY-MM-DD calendar DAYS (day granularity, both inclusive) — there is no intraday report window. Report figures for the most recent 72 hours are PROVISIONAL — Uber settles report data within 72 hours, so recent numbers may still change. Re-request the report later for final figures. Uber documents a 60 requests/minute limit on the report surface, and the budget may be shared across the whole platform app rather than per merchant. On a rate_limited error with source=upstream, back off (honoring retry_after when present) instead of retrying immediately. A single report accepts at most 50 stores and a window of at most 366 days. The returned report_ref expires 24 hours after it is issued; after that it returns not_found and a fresh request_report is needed. Every response carries data_source: 'live' means the numbers came from Uber Eats, 'mock' means they are FIXTURES from a non-production demo mode and are invented. Never present mock figures as the customer's real sales, payouts or orders, and never copy them into a deliverable.", "input_params": [ { "name": "report_type", "type": "string", "required": true, "description": "Which report to generate. PAYMENT_DETAILS_REPORT = per-payment payout detail; ORDER_HISTORY_REPORT = historical orders; ORDERS_AND_ITEMS_REPORT = orders with line items; FINANCE_SUMMARY_REPORT = daily financial summary; BILLING_DETAILS_REPORT = billing/fee detail; DOWNTIME_REPORT = store downtime; the two ORDER_ERRORS_* reports = order error adjustments; the two *_FEEDBACK_* reports = customer feedback.", "example": "FINANCE_SUMMARY_REPORT", "enum": [ "PAYMENT_DETAILS_REPORT", "ORDER_ERRORS_MENU_ITEM_REPORT", "ORDER_ERRORS_TRANSACTION_REPORT", "ORDER_HISTORY_REPORT", "DOWNTIME_REPORT", "CUSTOMER_AND_DELIVERY_FEEDBACK_REPORT", "MENU_ITEM_FEEDBACK_REPORT", "BILLING_DETAILS_REPORT", "ORDERS_AND_ITEMS_REPORT", "FINANCE_SUMMARY_REPORT" ], "source": "constant" }, { "name": "store_ids", "type": "list", "required": false, "description": "Store UUIDs to include, each from ubereats/list_stores. Defaults to ALL stores granted to this brand (when the brand has 50 or fewer). At most 50 per report — with more grants, pass an explicit subset.", "source": "prior_action:ubereats/list_stores" }, { "name": "start_date", "type": "string", "required": true, "description": "First day of the report window, YYYY-MM-DD (inclusive). start_date and end_date are YYYY-MM-DD calendar DAYS (day granularity, both inclusive) — there is no intraday report window.", "example": "2026-08-01" }, { "name": "end_date", "type": "string", "required": true, "description": "Last day of the report window, YYYY-MM-DD (inclusive). Must not precede start_date; the window is capped at 366 days counting BOTH ends.", "example": "2026-08-10" } ], "output_shape": "{report_ref, report_type, store_ids, start_date, end_date, status: 'Requested', data_source: 'live'|'mock', note}" } ] }