MCP tool reference

Every one of the 128 tools the Marketing OS MCP endpoint exposes — what each one does, its risk tier and its arguments. Generated from the registry itself.

This page is generated from the tool registry itself, so it cannot fall behind what your client sees. CI fails if it does.

The MCP endpoint exposes 128 tools across every channel Marketing OS touches. They are not a separate API: there is one registry, and the app, the agent and your AI client all call the same tools through it.

Every tool except list_businesses takes a businessId. Call list_businesses first to get one.

Risk tiers

A tool's risk tier decides whether a call happens or waits for a person. 29 of the tools below are high: calling one files it in the approvals inbox and returns AWAITING APPROVAL. Nothing reaches Meta, Google, a social platform or a budget until somebody approves it in the app.

TierWhat a call does
readRuns immediately. Changes nothing.
lowRuns immediately. Reversible, and written to the decision log.
highStaged for approval. Runs only once a human approves it.

Money guardrails sit under all three: a spend cap, a ROAS floor or a geo rule refuses a call outright, approved or not.

Every tool, by channel

The business itself

ToolTierWhat it does
add_knowledgelowAdd a knowledge item
add_operating_notelowAdd an operating note
agent_get_runreadRead an agent run
agent_list_runsreadList agent runs
agent_runlowRun an agent
budget_forecastreadMonth-end budget forecast
budget_reallocation_planreadCross-channel budget reallocation plan
competitor_addlowWatch a competitor
competitor_list_adsreadCompetitor ads
creative_list_experimentsreadCreative test cycles
get_brand_profilereadBrand profile
get_business_contextreadBusiness context and economics
get_guardrailsreadMoney guardrails
goal_approve_planlowApprove a goal’s plan
goal_createlowCreate a goal
goal_listreadList goals
goal_set_statuslowPause or resume a goal
list_businessesreadYour businesses
list_entity_changesreadHistory of one campaign, ad set or ad
list_knowledgereadList knowledge items
list_operating_notesreadOperating notes
operator_list_runsreadOperator runs
operator_run_nowlowRun the operator now
revert_changehighUndo one change
rules_dry_runreadPreview an automated rule
rules_listreadAutomated rules
search_knowledgereadSearch the knowledge base
update_brand_profilelowUpdate the brand profile

add_knowledge

Add one item to the Business Brain’s knowledge base so every future agent run can cite it. Kinds: guideline, past_campaign, testimonial, faq, case_study, research, note. It is indexed for search in the background. In-app only.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
kindguideline | past_campaign | testimonial | faq | case_study | research | noteyes—
titlestringyes—
bodystringyes—
json
{
  "kind": "guideline",
  "title": "<title>",
  "body": "<body>"
}

add_operating_note

Record a standing instruction for this business — something to always or never do, in plain language, e.g. "Never run comparison ads against Brand X" or "The owner wants every promotion to mention free shipping". Every future agent run for this business reads it as part of its context, and it does not expire until the owner retires it. Use it when the owner tells you a rule, not to store facts — facts belong in the knowledge base (add_knowledge).

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
textstringyesThe standing instruction, in plain language — what to do or never do, and why if it helps. Written as an instruction to the agents, e.g. "Never run comparison ads against Brand X".
json
{
  "text": "<text>"
}

agent_get_run

One agent run: status (queued, running, completed, failed), the error if it failed, and what it produced — assetId is the marketing asset a writer created or revised (null until it exists), and outputs lists the result events (asset_created, asset_revised, recommendations_ready, tasks_created) with their payloads.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
runIdstringyesThe run id, from agent_run or agent_list_runs.
json
{
  "runId": "<runId>"
}

agent_list_runs

The 30 most recent built-in agent runs for this business (blog_writer, sales_copy, fb_ads), newest first, with status and brief. Terminal conversations and operator passes are not listed. Use agent_get_run for what a run produced.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

agent_run

Start one of the built-in agents on a brief: blog_writer drafts an SEO blog post, sales_copy drafts email / ad_copy / landing_page / social_post copy (assetType), fb_ads analyses the Meta account into prioritized recommendations and puts the urgent ones on the board. Pass existingAssetId to have the writer revise that asset instead of drafting a new one. Drafts pass Brand Voice review and land in review — nothing is published. The run is asynchronous: this returns it queued — call agent_get_run for its status and the asset it produced. It uses AI credits.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
briefstringyesWhat to write about, or for fb_ads what to focus on. The agent reads the Brain itself.
assetTypeblog_post | email | ad_copy | landing_page | social_postno—
existingAssetIdstringno—
agentKeyblog_writer | sales_copy | fb_adsyesblog_writer drafts a blog post; sales_copy drafts email, ad, landing-page or social copy; fb_ads analyses the Meta account into recommendations and board tasks.
json
{
  "brief": "<brief>",
  "agentKey": "blog_writer"
}

budget_forecast

Project this month’s spend, conversions, revenue and ROAS per ad channel (Meta, Google Ads, TikTok Ads) from month-to-date delivery plus a weekday-aware mean of the last 14 days, each remaining day capped by the budgets currently committed. Includes the pacing ratio against the project’s monthly spend cap, in the organization’s reporting currency. When Klaviyo is connected, nonPaidRevenue projects email/SMS revenue beside the channels — it has no spend, so it is never in ROAS or pacing.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

budget_reallocation_plan

Compare the ad channels’ ROAS over the last 7 complete days and, when the best returns at least 1.5× the worst and both have 20+ conversions, propose moving daily budget from the worst channel’s least efficient entities to the best one’s most efficient — never to anything under the ROAS floor, never past the single-step limit, and never adding money overall. Returns a planId and one row per change, each with the exact *_set_budget tool and input to call (amounts in each entity’s own currency). Changes nothing by itself.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
maxShiftPctnumbernoThe most of each donor entity’s daily budget the plan may move, in percent. Default 20, at most 50.
floornumbernoThe ROAS a receiving channel and entity must clear. Defaults to this project’s own floor (its guardrail minimum ROAS, else its break-even).

competitor_add

Start watching a rival brand's Meta ads. Takes their Facebook Page — a URL, an @handle or the bare handle. Their live ads are read immediately and then weekly, and new angles land in the weekly digest. There is a per-plan limit on how many competitors one project can watch; adding one past it is refused.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
pageHandlestringyesThe competitor's Facebook Page — a full URL ("https://facebook.com/acmecoffee"), an @handle, or the bare handle. This is what their ads are looked up by.
namestringnoWhat to call them. Defaults to the handle when omitted.
websitestringnoThe competitor's website, if you know it. Optional and never fetched on add.
json
{
  "pageHandle": "<pageHandle>"
}

competitor_list_ads

What rival brands this business watches are currently advertising on Meta, with a one-line description of the angle each ad makes. Read it before proposing a new creative angle: it shows which arguments the market is already saturated with and which nobody is making. Describes their ads — it never reproduces their copy.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
limitintegernoHow many ads to return, newest first. Default 40.

creative_list_experiments

This business’s creative test cycles, newest first. Each one reports its target ad set, the window every arm gets, the floor arms are judged against, and every arm with its live metrics, how long is left on its window, what the rule would say about it right now, and — once judged — the decision, why, and the numbers it was made on.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
limitintegernoHow many cycles to return, newest first. Default 10.

get_brand_profile

The Business Brain’s brand profile: identity and voice, audience, offerings, competitors, the visual system (colors, fonts, icons), the SEO keyword profile, the social posting rules, company knowledge, and the AI instructions — always/never rules, banned words and required mentions. Read this before writing ANY copy for this business; the banned-word and always/never rules are not suggestions.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

get_business_context

This business’s goal, storefront URL, currency and the economics every ad decision is graded against: break-even ROAS, average order value in cents, target CPA and CPL in cents, monthly budget and daily budget cap — plus activeGoal, the measurable goal the owner set (metric, target, current value, pace against a straight line to the deadline, days left), or null when none is active. Read this before proposing any budget or judging whether a campaign is actually profitable — a ROAS is meaningless without the break-even it is measured against.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

get_guardrails

The hard ceilings this business enforces on every AI-initiated change: daily and monthly spend caps in cents, the minimum ROAS an entity needs before delivery may be increased, the largest single budget jump allowed, allowed and excluded countries, and blocked creative angles. Nothing can spend past these — not an approval, not an autonomy setting, not this API. Read them first and propose inside them.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

goal_approve_plan

Approve the Director’s plan for a goal in planning: every human step becomes a board task and every agent step starts an agent run (blog_writer, sales_copy or fb_ads) right away, and the goal becomes active. The runs are asynchronous and draft in-app content for review — nothing is published or spent. Check progress with agent_list_runs. Refused when the goal has no pending plan.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
goalIdstringyesThe goal id, from goal_list or goal_create.
json
{
  "goalId": "<goalId>"
}

goal_create

Set a measurable goal: an objective, a metric (organic_clicks, sessions, site_conversions, ad_conversions, ad_revenue, ad_roas — each a trailing 28-day value), an absolute targetValue and an optional deadline (YYYY-MM-DD). The baseline is read from the metric source now and the target must exceed it, or the call is refused with the baseline in the message. The Director plans it in the same call: the goal comes back in planning with its plan (3–10 steps for the writers, the ads agent or a human). Nothing runs until goal_approve_plan.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
objectivestringyes—
metricKeyorganic_clicks | sessions | site_conversions | ad_conversions | ad_revenue | ad_roasyes—
targetValuenumberyes—
deadlinestringno—
followUpOfGoalIdstringno—
json
{
  "objective": "<objective>",
  "metricKey": "organic_clicks",
  "targetValue": 0
}

goal_list

Every goal for this business, newest first: objective, metric, status (planning, active, paused, completed, missed), deadline, progress from baseline toward target (measured from the metric source, never estimated) and the Director’s plan. A goal in planning has a plan waiting for goal_approve_plan.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

goal_set_status

Pause an active goal or resume a paused one. A goal still in planning, or one already completed or missed, is refused. Pausing stops its progress refresh; it does not cancel tasks or runs already started.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
goalIdstringyesThe goal id, from goal_list or goal_create.
statusactive | pausedyesactive resumes a paused goal; paused stops it.
json
{
  "goalId": "<goalId>",
  "status": "active"
}

list_businesses

List the businesses (projects) in this Marketing OS organization with their business context — the economics every other tool is graded against: goal, break-even ROAS, average order value, target CPA/CPL, daily and monthly budget, currency. ALWAYS call this first: every other tool takes a businessId from here.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

list_entity_changes

Every change made to one ad-account entity, newest first: who or what made it (you, the operator agent, an AI client or an accepted recommendation), exactly what changed, whether a guardrail refused it, and whether it can still be undone. Pass an id from the same entity’s revertible calls to revert_change to roll one back. Includes the approvals filed for it and the recommendations written about it.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
channelmeta | google_ads | tiktok_adsnoWhich ad channel the entity belongs to: meta, google_ads or tiktok_ads
entityIdstringyesThe entity’s id on its own channel — a Meta id from meta_list_campaigns, a Google Ads id from google_ads_list_campaigns, or a TikTok Ads id from tiktok_ads_list_campaigns
json
{
  "entityId": "<entityId>"
}

list_knowledge

List the Business Brain’s knowledge base: guidelines, past campaigns, testimonials, FAQs, case studies, research and notes, each with its full body.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

Takes no arguments.

list_operating_notes

What this business has already decided: every change it rejected, every one it undid, the measured results of past content, and the standing instructions its owner typed. Read these before proposing anything — they are the reason a given idea may already have been ruled out, and re-proposing a rejected change is the fastest way to lose the owner’s trust.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
includeExpiredbooleannoInclude notes whose window has already lapsed. Default false.
limitintegernoHow many to return, newest first.

operator_list_runs

The operator agent’s recent runs for this business, newest first: when each ran, what it wrote up, and every action it took — with each action marked as done, waiting for the owner’s approval, done on autopilot, or refused by a spending guardrail. Also reports whether the operator is scheduled and when it next runs.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
limitintegernoHow many runs to return, newest first. Default 10.

operator_run_now

Wake the operator agent up immediately for this business instead of waiting for its daily slot. It reviews performance, measurement and spend, then acts inside the business’s guardrails — filing anything that needs approval rather than doing it. Returns as soon as the run is queued; poll operator_list_runs for the write-up. Refused while a run is already in progress, and it costs AI credits.

Risk tier: low — runs immediately, reversible and audited

Takes no arguments.

revert_change

Put back exactly what one earlier tool call overwrote, from the snapshot that call captured — a budget, or a delivery status. Only calls list_entity_changes reports as revertible can be undone, and only once. An undo is a real change to a live ad account, so it always requires a human approval and is refused outright when putting the old value back would cross a spend cap or a ROAS floor.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
invocationIdstringyesThe tool call to undo — an id from list_entity_changes
json
{
  "invocationId": "<invocationId>"
}

rules_dry_run

What one automated rule would match and do if it were evaluated right now: every matching campaign or ad set with the observed metric value, spend and conversions over the rule’s lookback, and what would happen to it (dry_run = would act, cooling_down = this rule already acted on it recently, skipped = nothing to do and why). Reads the synced mirror only. Changes nothing, files nothing, and does not use up the rule’s daily evaluation.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
ruleIdstringyesRule id from rules_list
json
{
  "ruleId": "<ruleId>"
}

rules_list

The owner’s automated rules for this business: each one’s channel (meta, google_ads, tiktok_ads or all), level (campaign or ad_set), scope (a name pattern or explicit ids), condition (metric, comparator, threshold, lookback days, minimum spend/conversions — spend, cpa and cpc thresholds are in cents, ctr in percent, roas and frequency as ratios), action (pause, enable, set_budget_pct with pct, or notify), cooldown, and its last evaluation. Rules are evaluated once a day at 06:00 business time and act through the same approvals and guardrails as you do. Read this before changing a campaign’s status or budget, and do not act against an enabled rule — for example, do not re-enable something a rule paused, or raise a budget a rule would cut.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
enabledOnlybooleannoOnly rules that are switched on (the ones that act). Default true.

search_knowledge

Search this business’s knowledge base by meaning and by keyword at once, returning the best-matching passage of each hit rather than the top of the document. Use it to cite the business’s own facts — policies, pricing, testimonials, past results — instead of inventing them.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
querystringyesWhat to look for, in plain language — matched on meaning and on keywords
limitintegernoHow many items to return (default 5)
json
{
  "query": "<query>"
}

update_brand_profile

Replace the whole brand profile. Every section is sent every time, so read it with get_brand_profile first and send it back with your edits applied — anything you leave out is cleared. In-app only: this changes no live ad or post.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
brandstringno—
industry`` | apparel_accessories | beauty_personal_care | food_beverage | home_garden | health_fitness | electronics | jewellery | pets | baby_kids | sports_outdoors | b2b_software | professional_services | home_services | health_practice | legal | financial_services | real_estate | education | travel_hospitality | automotive | nonprofit | otherno—
voicestringno—
audiencestringno—
offeringsstringno—
competitorsstringno—
notesstringno—
colorsobject[]no—
brandingobjectno—
seoobjectno—
socialobjectno—
knowledgeBaseobjectno—
aiInstructionsobjectno—
marketingAssetsobjectno—

Meta ads

ToolTierWhat it does
creative_decide_armlowDecide one arm of a creative test cycle
creative_start_experimenthighStart a creative test cycle
lead_listreadLead velocity
lead_update_statuslowUpdate a lead’s status
meta_activate_campaign_planhighSwitch a deployed campaign on
meta_activate_entityhighActivate a Meta campaign, ad set or ad
meta_approve_campaign_planlowApprove a campaign plan
meta_create_adhighCreate a Meta ad in an existing ad set
meta_create_catalog_campaignhighCreate an Advantage+ catalog campaign
meta_deploy_campaign_planhighCreate the plan’s campaign (paused)
meta_draft_campaign_planlowDraft a campaign plan
meta_generate_recommendationslowGenerate a recommendation batch
meta_get_recommendationsreadLatest AI recommendations
meta_implement_recommendationhighApply a recommendation
meta_list_ad_setsreadList Meta ad sets
meta_list_adsreadList Meta ads
meta_list_campaign_plansreadList campaign plans
meta_list_campaignsreadList Meta campaigns
meta_list_pagesreadList Facebook Pages
meta_pause_entitylowPause a Meta campaign, ad set or ad
meta_reject_recommendationlowDismiss a recommendation
meta_set_bid_strategyhighChange a Meta bid strategy
meta_set_budgethighChange a Meta daily budget
meta_set_campaign_plan_budgetlowChange a campaign plan’s daily budget
meta_set_campaign_plan_pagelowPick the Facebook Page for a plan

creative_decide_arm

Record a decision for one arm and carry it out: "killed" pauses the ad, "promoted" raises the target ad set’s daily budget and drafts derivatives of the winner, and "kept" only records the verdict. The change itself goes through the usual gate — a pause runs, a budget increase is filed for approval — so this does not let a decision skip an approval. Refused on an arm that is already decided.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
decisionkilled | promoted | keptyeskill pauses the ad, promote raises the ad set’s budget, keep only records it
reasonstringnoWhy — recorded on the arm beside the numbers
experimentIdstringyesCycle id from creative_list_experiments
armIdstringyesArm id from creative_list_experiments
json
{
  "decision": "killed",
  "experimentId": "<experimentId>",
  "armId": "<armId>"
}

creative_start_experiment

Launch 2–8 creative variants as competing ads in one ad set and judge them against one floor after one fixed window. Every arm is created PAUSED and delivers nothing until it is activated separately, so this does not start spend — but it does create real objects in the ad account, so it always requires a human approval. Arms come from the creative studio (variantId) or from ads that already run (adMetaId). Once the window elapses and an arm has cleared the minimum spend and impressions, anything under the floor is paused automatically and the best survivor is put forward for a budget increase.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
namestringyesWhat this cycle is called
hypothesisstringnoWhat the cycle is trying to learn, in the operator’s own words
adSetMetaIdstringyesThe ad set every arm is launched into — from meta_list_ad_sets
pageIdstringnoFacebook Page the generated ads post under; required when any arm is a variant
windowHoursintegernoHow long every arm runs before it can be judged. Default 48.
floorMetricroas | cpa | ctrnoroas and ctr are floors (higher passes); cpa is a ceiling (lower passes)
floorValuenumberyesThe floor itself: 1.4 for ROAS, a percentage for CTR, cents for CPA
minSpendCentsintegernoNo arm is decided below this spend, whatever the window says
minImpressionsintegernoNo arm is decided below this many impressions
promoteRulebudget_increaseno—
promoteBudgetPctintegernoHow much more daily budget the winner’s ad set gets, as a percentage
draftDerivativesbooleannoDraft new variants from the winner’s angle when one is promoted
armsobject[]yes—
json
{
  "name": "<name>",
  "adSetMetaId": "<adSetMetaId>",
  "floorValue": 0,
  "arms": []
}

lead_list

Instant-form leads for this business: how many arrived since a day (default the last 30 days), broken down by status (new, contacted, qualified, won, lost) and by campaign, plus the 50 most recent leads by id and status. Pair it with campaign spend to read cost per lead and how many leads each campaign turns into customers. It never returns what a person typed into the form — names, emails and phone numbers stay in the inbox.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
statusnew | contacted | qualified | won | lostnoOnly leads currently in this status.
sincestringnoOnly leads submitted on or after this day (YYYY-MM-DD). Default: the last 30 days.

lead_update_status

Move one lead along the pipeline — new, contacted, qualified, won or lost — by its id from lead_list. Use it when the owner tells you what happened with a lead ("the plumber booked the Hendersons"), so cost-per-customer reads stay true. The business’s webhook endpoints receive a lead.status_changed event.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
leadIdstringyesThe lead id, from lead_list.
statusnew | contacted | qualified | won | lostyesThe new status.
json
{
  "leadId": "<leadId>",
  "status": "new"
}

meta_activate_campaign_plan

Flip a deployed plan’s campaign and everything under it to ACTIVE (on Meta the ad set and ads; on Google the ad groups and ads, the campaign last). This is the step that starts spending money, so it always requires a human approval and is refused outright when it would cross the project’s daily or monthly spend cap or its geo rules.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
planIdstringyesCampaign plan id from meta_list_campaign_plans
json
{
  "planId": "<planId>"
}

meta_activate_entity

Start (or resume) delivery for one campaign, ad set or ad. Always requires a human approval: this is the step that starts spending money.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
levelcampaign | adset | adyesWhich level the Meta id names
metaIdstringyesThe entity’s Meta id, e.g. from meta_list_campaigns
json
{
  "level": "campaign",
  "metaId": "<metaId>"
}

meta_approve_campaign_plan

Mark a drafted plan as approved, which is what makes it deployable. Still nothing on Meta: approving a plan creates no campaign and spends nothing.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
planIdstringyesCampaign plan id from meta_list_campaign_plans
json
{
  "planId": "<planId>"
}

meta_create_ad

Put a new image, video or carousel ad into an existing ad set, from asset-library images/videos (assetId) or finished Creative Studio variants (variantId). It lands PAUSED by default and delivers nothing until meta_activate_entity is approved. Optional placements rewrite the AD SET’s publisher platforms and positions (Meta scopes placements there), so they apply to every ad in it. Always requires a human approval: it creates real objects in a real ad account.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
adSetMetaIdstringyesThe existing ad set the ad goes into, e.g. from meta_list_ad_sets
pageIdstringyesThe Facebook Page the ad runs as
namestringnoDefaults to the headline
creativeobjectyeskind (image|video|carousel), assets [{assetId|variantId, carousel card headline/description/linkUrl?}], headline, primaryText, description?, cta, linkUrl, utm? {source, medium, campaign, content?}
placementsobjectnoRewrites the AD SET’s placements (Meta scopes them there), affecting every ad in it. Omit to leave them as they are.
statusPAUSED | ACTIVEnoPAUSED by default; switch on later with meta_activate_entity
json
{
  "adSetMetaId": "<adSetMetaId>",
  "pageId": "<pageId>",
  "creative": {}
}

meta_create_catalog_campaign

Create a Meta Advantage+ catalog (dynamic product ads) campaign that advertises one product set from the store catalog: objective Sales, one ad set and one dynamic ad whose headline and text are filled per product from template tokens such as {{product.name}}. Audience: "viewed_not_purchased_14d" (retarget product viewers who did not buy), "prospecting" (broad) or a Meta custom audience id. The campaign lands PAUSED and delivers nothing until meta_activate_entity is approved on it. Always requires a human approval, and is refused when its daily budget would cross the project’s daily or monthly spend cap or it targets a blocked country.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
productSetIdstringyesThe product set to advertise — its id from commerce_get_meta_catalog
audienceviewed_not_purchased_14d | prospecting | stringyes"viewed_not_purchased_14d" retargets people who viewed a product in the set in the last 14 days and did not buy; "prospecting" lets Meta find new buyers (broad audience); or a Meta custom audience id, e.g. a synced Shopify segment from the Audiences page.
dailyBudgetCentsintegeryesThe campaign’s daily budget, in cents of the ad account’s currency
countriesstring[]yesISO 3166-1 alpha-2 country codes to deliver in
pageIdstringyesThe Facebook Page the ads run as, from meta_list_pages
templateobjectyesThe dynamic creative. Tokens Meta fills per product: {{product.name}}, {{product.description}}, {{product.price}}, {{product.current_price}}, {{product.brand}}
namestringnoDefaults to the product set name
json
{
  "productSetId": "<productSetId>",
  "audience": "viewed_not_purchased_14d",
  "dailyBudgetCents": 0,
  "countries": []
}

meta_deploy_campaign_plan

Create the approved plan’s campaign on its channel: for a Meta plan the campaign, ad set and ads on Meta; for a Google plan the Search campaign, ad groups, keywords and responsive search ads in Google Ads, each through the Google Ads tools. Everything lands PAUSED and delivers nothing until meta_activate_campaign_plan is approved separately, so this does not start spend — but it does create real objects in the ad account, so it always requires a human approval, and that approval covers every create the deploy makes. Resumable: when an earlier deploy failed part-way (the plan is approved again with a deployState naming what already exists), calling it again creates only the missing steps, never a second campaign.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
planIdstringyesCampaign plan id from meta_list_campaign_plans
json
{
  "planId": "<planId>"
}

meta_draft_campaign_plan

Draft a complete campaign from a brief, written against this business’s own economics. channel "meta" (the default) drafts a Meta campaign: structure, objective, daily budget, audience, interests and ad creatives. channel "google_ads" drafts a Google Search campaign for the linked Google Ads account instead: 1–3 ad groups with keywords, negative keywords and responsive search ads, and a bidding strategy chosen from the account’s conversion actions; the Meta-only fields (pixel, geo radius, special ad categories, locales, assets) are ignored for it. Nothing is created on either platform — this returns a plan with status "generating" and the draft lands a few seconds later, so poll meta_list_campaign_plans for the result. All money is in cents; countries are ISO 3166-1 alpha-2 codes. Whether the Meta pixel is installed is read from the synced ad account, so pixelInstalled is ignored unless the account is a demo. For lead gen, dealValueCents and closeRatePercent are saved to the business. For a local business, geo narrows the countries to a radius around an address (Meta geocodes it) or to places by their Meta location keys; countries is still required and names the country the address is in. For a campaign around a promo code, pass promotionId from promotions_list (create one with promotions_create_draft first if it does not exist) and leave offer and landingUrl out: the plan leads with the promotion’s code and links to its discount. Never invent a code or a discount.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
channelmeta | google_adsnoWhere the campaign runs: "meta" (Facebook & Instagram, the default) or "google_ads" (a Google Search campaign in the linked Google Ads account)
businessModelecommerce | lead_genyes—
monthlyBudgetCentsintegeryes—
aovCentsintegerno—
breakEvenRoasnumberno—
targetCplCentsintegerno—
dealValueCentsintegernoLead gen: the value of one closed deal, in cents
closeRatePercentnumbernoLead gen: the percent of leads that become customers (0–100)
offerstringnoThe offer the ads should lead with: the hook, price, discount or guarantee
landingUrlstringnoThe page every ad links to; omitted = the business storefront URL
promotionIdstringnoA promotion id from promotions_list (or the one promotions_create_draft returned). Fills offer ("10% off with code X") and landingUrl (the discount link) when those are omitted, grounds the copy in the code, and tags the ads with the promotion’s utm_campaign. Call promotions_list first; never invent a code or a discount
leadProductIdsstring[]noUp to 3 synced commerce product ids (from the catalog) the ads should feature
emailListSizenone | under_1k | 1k_to_10k | over_10kyes—
socialFollowingnone | under_1k | 1k_to_10k | over_10kyes—
creativeAssetsproduct_photos | videos | ugc | testimonials | brand_assets[]yes—
creativeAssetIdsstring[]no—
countriesstring[]yes—
geoobjectnoLocal targeting: { kind: "places", places: [{ key, name, type, countryCode, radiusKm? }] } from the geo search, or { kind: "radius", addressString, radiusKm (1–80) }. Omitted = the whole countries
pixelInstalledbooleannoIgnored for a real ad account (derived from the synced pixel); only honoured on a demo account
bidStrategyobjectno—
specialAdCategoriesHOUSING | FINANCIAL_PRODUCTS_SERVICES | EMPLOYMENT | ISSUES_ELECTIONS_POLITICS[]noSpecial ad categories the campaign falls under (housing, financial products, employment, politics); empty = none. Any category forces ages 18–65+ and all genders
ageMinintegernoThe youngest age to target (18–65); omitted = the planner decides
localesinteger[]noMeta adlocale ids for the languages to target (e.g. 1001 English (All)); empty = all
json
{
  "businessModel": "ecommerce",
  "monthlyBudgetCents": 0,
  "emailListSize": "none",
  "socialFollowing": "none"
}

meta_generate_recommendations

Ask the analyst agent for a fresh recommendation batch for one ad channel. Returns immediately with a batch in status "generating" — poll meta_get_recommendations for the result. There is a 24-hour cooldown per channel and it needs that channel’s data synced, so it fails with the reason when either gate is closed. Costs AI credits; changes nothing in the ad account.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
channelmeta | google_ads | tiktok_adsnoWhich ad channel to analyse: meta, google_ads or tiktok_ads. Each has its own prompt and its own 24-hour cooldown.

meta_get_recommendations

The most recent AI recommendation batch for this business: prioritized optimization actions (scale_up / scale_down / pause / resume / shutdown / duplicate / refresh_creative / fix_fatigue / new_campaign / maintain) with their reasoning chain, the exact metrics cited, the precise change to make (budget moves carry exact cents), and each one’s status. Covers every connected ad channel — each recommendation says which one it is about. Also reports, per channel, why a new batch cannot be generated yet.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

meta_implement_recommendation

Apply one pending recommendation to the live ad account it is about — Meta, Google Ads or TikTok Ads, whichever the recommendation’s channel says. Only recommendations marked autoImplementable can be applied this way. It changes real delivery or real budgets, so it always requires a human approval and is refused when it would cross a spend cap or the entity’s ROAS floor.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
recommendationIdstringyesRecommendation id from meta_get_recommendations
notestringnoWhy this is worth doing — shown to the approver and kept in the audit log
json
{
  "recommendationId": "<recommendationId>"
}

meta_list_ad_sets

List ad sets with performance metrics, optionally narrowed to one campaign.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.
campaignMetaIdstringnoNarrow to one campaign

meta_list_ads

List individual ads with performance metrics and creative summaries, optionally narrowed to one campaign or ad set.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.
campaignMetaIdstringnoNarrow to one campaign
adSetMetaIdstringnoNarrow to one ad set

meta_list_campaign_plans

List this business’s drafted campaign plans, newest first, with each plan’s channel (meta or google_ads), status (generating / draft / approved / deploying / deployed / live / failed) and its full drafted structure — for a Meta plan (plan) the objective, daily budget in cents, targeting, interests and ad creatives; for a Google plan (googlePlan) the budget, bidding, ad groups, keywords and responsive search ads — and the platform ids once it has been deployed.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

meta_list_campaigns

List this business’s Meta ad campaigns with performance over a date range: spend, impressions, clicks, CTR, CPC, CPM, conversions, revenue, ROAS and CPA/CPL.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.

meta_list_pages

List the Facebook Pages the connected Meta user manages. Ads post under a Page, so a plan needs one set with meta_set_campaign_plan_page before it can be deployed with image ads. Returns an empty list for a demo ad account or when Meta is not connected.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

meta_pause_entity

Stop delivery for one campaign, ad set or ad. Reversible with meta_activate_entity, which needs a human approval.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
levelcampaign | adset | adyesWhich level the Meta id names
metaIdstringyesThe entity’s Meta id, e.g. from meta_list_campaigns
json
{
  "level": "campaign",
  "metaId": "<metaId>"
}

meta_reject_recommendation

Dismiss one pending recommendation with a reason. Nothing on Meta changes; the rejection is recorded and feeds back into how future batches are graded.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
recommendationIdstringyesRecommendation id from meta_get_recommendations
reasoncant_implement | already_doing | not_relevant | budget_constraints | otheryesWhy it is being dismissed: cant_implement, already_doing, not_relevant, budget_constraints or other
notestringnoFree-text detail for the record
json
{
  "recommendationId": "<recommendationId>",
  "reason": "cant_implement"
}

meta_set_bid_strategy

Set how one campaign (with a campaign budget) or ad set bids: lowest cost, a cost cap (average cost per result), a bid cap (maximum per auction) or a minimum ROAS (value-optimised ad sets only). Changing the bid strategy of a live campaign resets Meta’s learning phase, so use it as a last resort — after budget and creative changes have not fixed a drifting CPA. Refused when the strategy does not suit what the ad set optimises for, and when a cap is under 25% of the trailing-7-day cost per result (it would stop delivery). Requires a human approval unless the project’s autonomy grant releases high-tier changes.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
entityLevelcampaign | ad_setyescampaign for a campaign with a campaign budget (CBO); ad_set for an ad set that carries its own budget
metaIdstringyesThe campaign’s or ad set’s Meta id
strategyLOWEST_COST_WITHOUT_CAP | COST_CAP | LOWEST_COST_WITH_BID_CAP | LOWEST_COST_WITH_MIN_ROASyesLOWEST_COST_WITHOUT_CAP (no cap), COST_CAP (average cost per result), LOWEST_COST_WITH_BID_CAP (maximum bid per auction) or LOWEST_COST_WITH_MIN_ROAS (minimum return on ad spend; value-optimised ad sets only)
amountCentsintegernoRequired for COST_CAP and LOWEST_COST_WITH_BID_CAP: the cap, in cents of the ad account’s currency
minRoasnumbernoRequired for LOWEST_COST_WITH_MIN_ROAS: the ROAS floor as a ratio, e.g. 2.5
json
{
  "entityLevel": "campaign",
  "metaId": "<metaId>",
  "strategy": "LOWEST_COST_WITHOUT_CAP"
}

meta_set_budget

Set the daily budget of one CBO campaign or ad set, in cents. Always requires a human approval, and is refused outright when it would cross this project’s daily or monthly spend cap, or when the entity is under its ROAS floor.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
levelcampaign | adsetyesBudget lives on a CBO campaign or an ad set
metaIdstringyesThe entity’s Meta id
dailyBudgetCentsintegeryesThe new daily budget, in cents of the ad account’s currency
confirmLargeChangebooleannoSet true to deliberately make a jump at or over the project’s single-step budget limit (a doubling by default). Ceilings on total spend still apply and cannot be confirmed away.
planIdstringnoThe planId from budget_reallocation_plan, when this change is one row of that plan. Omit for a one-off change.
json
{
  "level": "campaign",
  "metaId": "<metaId>",
  "dailyBudgetCents": 0
}

meta_set_campaign_plan_budget

Override the drafted daily budget before the plan is deployed, in cents. Still just a row — no Meta object exists yet — so this does not spend anything. Refused once the plan has been deployed; change a live campaign with meta_set_budget instead.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
planIdstringyesCampaign plan id from meta_list_campaign_plans
dailyBudgetCentsintegeryesThe campaign’s daily budget, in cents of the ad account’s currency. Meta enforces a minimum of roughly 100 cents/day, and the business’s own daily cap trims anything above it.
json
{
  "planId": "<planId>",
  "dailyBudgetCents": 0
}

meta_set_campaign_plan_page

Set the Facebook Page the plan’s ads will post under. Required before deploying a plan that has ad creatives. Page ids come from meta_list_pages.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
planIdstringyesCampaign plan id from meta_list_campaign_plans
pageIdstringyesFacebook Page id from meta_list_pages
json
{
  "planId": "<planId>",
  "pageId": "<pageId>"
}
ToolTierWhat it does
google_ads_add_keywordslowAdd keywords to a Google Ads ad group
google_ads_add_negative_keywordslowAdd negative keywords to a Google Ads campaign or ad group
google_ads_create_ad_grouphighCreate a Google Ads ad group (paused)
google_ads_create_campaignhighCreate a Google Search campaign (paused)
google_ads_create_conversion_actionhighCreate the Google Ads conversion action for Shopify purchases
google_ads_create_promotion_assethighAdd a promotion asset to a Google Ads campaign
google_ads_create_responsive_search_adhighCreate a Google responsive search ad (paused)
google_ads_enable_entityhighEnable a Google Ads campaign, ad group or ad
google_ads_list_ad_groupsreadList Google Ads ad groups
google_ads_list_adsreadList Google Ads ads
google_ads_list_campaignsreadList Google Ads campaigns
google_ads_list_keywordsreadList Google Ads keywords
google_ads_list_search_termsreadList Google Ads search terms
google_ads_pause_entitylowPause a Google Ads campaign, ad group or ad
google_ads_propose_search_term_changesreadPropose Google Ads search-term hygiene
google_ads_set_campaign_budgethighChange a Google Ads daily budget
google_ads_set_keyword_statuslowPause or enable a Google Ads keyword

Add positive keywords (text + EXACT, PHRASE or BROAD match) to an ad group. They land ENABLED, so they serve as soon as their ad group and campaign do. A keyword moves no budget, so this runs without an approval under the default grant — but a keyword bid above the ad group’s own max CPC is a bid raise and is refused here: omit cpcBidCents to inherit the ad group’s bid. A partly accepted batch is reported as a partial failure naming what landed.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
adGroupGoogleIdstringyesThe ad group the keywords go in
keywordsobject[]yesThe keywords to add. Sent to Google in batches of up to 1,000.
planIdstringnoThe planId from google_ads_propose_search_term_changes, when this call is one row of that proposal. Copy it unchanged; omit for a one-off change.
json
{
  "adGroupGoogleId": "<adGroupGoogleId>",
  "keywords": []
}

Stop ads showing for search terms that waste money: add negative keywords to a whole campaign or to one ad group. A negative only narrows who sees the ads — it moves no budget — so this runs without an approval under the default grant, and it is undoable from the decision log (the undo removes exactly the negatives this call added). Rows of a google_ads_propose_search_term_changes proposal carry its planId and each term’s evidence, and never negative the business’s own brand or products.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
levelcampaign | ad_groupyesBlock the terms for a whole campaign, or for one ad group only
idstringyesThe campaign or ad group Google Ads id, matching level
keywordsobject[]yesThe negatives to add, sent to Google in one batch.
planIdstringnoThe planId from google_ads_propose_search_term_changes, when this call is one row of that proposal. Copy it unchanged; omit for a one-off change.
json
{
  "level": "campaign",
  "id": "<id>",
  "keywords": []
}

Create an ad group under an existing campaign. It lands PAUSED and delivers nothing until it is enabled separately, but it creates a real object in a real ad account, so it always requires a human approval.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
campaignGoogleIdstringyesThe campaign the ad group belongs to
namestringyes—
cpcBidCentsintegernoMax CPC bid in cents. Omit on a campaign whose bidding strategy sets bids itself.
json
{
  "campaignGoogleId": "<campaignGoogleId>",
  "name": "<name>"
}

Create a Search campaign and its own daily budget in the linked Google Ads account. It lands PAUSED and delivers nothing until google_ads_enable_entity is approved separately, so this does not start spend — but it does create real objects in a real ad account, so it always requires a human approval.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
namestringyesThe campaign name, as it will appear in Google Ads
dailyBudgetCentsintegeryesDaily budget in cents. Nothing is spent until the campaign is enabled separately.
biddingStrategymanual_cpc | maximize_clicks | maximize_conversionsnomanual_cpc is the safe default for a brand-new campaign: a smart strategy has no conversion history to learn from yet.
startDatestringnoYYYY-MM-DD. Omit to start as soon as the campaign is enabled.
endDatestringnoYYYY-MM-DD. Omit to run until paused.
countriesstring[]noISO 3166-1 alpha-2 country codes the campaign serves in, set as its location targeting. Omit to leave Google’s default, which is every country.
json
{
  "name": "<name>",
  "dailyBudgetCents": 0
}

Create the UPLOAD_CLICKS purchase conversion action that server-side Shopify orders are uploaded against, and point this project’s Google conversion sends at it. Spends nothing, but it adds a real object to a real ad account — and, when made primary, changes what Smart Bidding optimises on — so it always requires a human approval. Returns the existing action when one with the same name is already there.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
namestringnoThe conversion action name, as it will appear under Goals → Conversions
primaryForGoalbooleannoWhether Smart Bidding optimises on it. Leave false when the store already counts purchases through the Google tag — a primary upload would count every sale twice.

Create a Google Ads promotion asset — the "10% off · code TORY10" line shown under a Search ad — and attach it to a campaign. Takes the promotion’s code, discount, redemption dates and the link it sends people to. When an asset with the same code is already in the account it is attached instead of duplicated. Spends nothing, but it is public ad copy in a real account, so it always requires a human approval.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
campaignGoogleIdstringyesThe Search campaign the promotion shows under
promotionTargetstringyesWhat the promotion is on, ≤ 20 characters, e.g. "All products" or "Hoodies"
promotionCodestringyesThe code shoppers enter, ≤ 15 characters — e.g. a promotion from promotions_list
discountobjectyesThe discount, as the promotion defines it
redemptionStartDatestringnoFirst day the code can be redeemed
redemptionEndDatestringnoLast day the code can be redeemed
finalUrlstringyesWhere the promotion links — ideally the store’s discount link, which applies the code
json
{
  "campaignGoogleId": "<campaignGoogleId>",
  "promotionTarget": "<promotionTarget>",
  "promotionCode": "<promotionCode>",
  "discount": {}
}

Create a responsive search ad in an existing ad group: 3–15 headlines (≤30 characters), 2–4 descriptions (≤90), a final URL and optional display paths and pins. Copy is checked against Google’s editorial basics first (no exclamation marks in headlines, no all-caps words, no repeated punctuation, no duplicates) and refused locally when it fails. The ad lands PAUSED — google_ads_enable_entity at level "ad" switches it on — but it is public ad copy in a real account, so it always requires a human approval.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
adGroupGoogleIdstringyesThe ad group the ad goes in, e.g. from google_ads_create_ad_group or google_ads_list_ad_groups
headlinesstring[]yes3–15 headlines, each at most 30 characters. No exclamation marks, no all-caps words, no repeated punctuation, no duplicates. Google mixes them, so each must read on its own.
descriptionsstring[]yes2–4 descriptions, each at most 90 characters, at most one exclamation mark each.
finalUrlstringyesThe landing page a click goes to
path1stringnoFirst display-URL path segment, e.g. "plumbing"
path2stringnoSecond display-URL path segment; needs path1
pinnedHeadlinesobject[]noFix a headline (by index into headlines) to position 1, 2 or 3. Pinning cuts the combinations Google can test, so pin only what must always show.
pinnedDescriptionsobject[]noFix a description (by index into descriptions) to position 1 or 2
json
{
  "adGroupGoogleId": "<adGroupGoogleId>",
  "headlines": [],
  "descriptions": [],
  "finalUrl": "<finalUrl>"
}

Switch a campaign, ad group or ad to ENABLED. Always requires a human approval: this is the step that starts spending money, and it is refused outright when the entity is under this project’s ROAS floor.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
levelcampaign | ad_group | adyesWhich level the Google Ads id names
googleIdstringyesThe entity’s Google Ads id, e.g. from google_ads_list_campaigns
json
{
  "level": "campaign",
  "googleId": "<googleId>"
}

List ad groups with performance metrics and CPC bids, optionally narrowed to one campaign. An ad group is Google’s equivalent of a Meta ad set.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.
campaignGoogleIdstringnoNarrow to one campaign

List individual ads with performance metrics, ad type, final URL and the first headline/description, optionally narrowed to one campaign or ad group.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.
campaignGoogleIdstringnoNarrow to one campaign
adGroupGoogleIdstringnoNarrow to one ad group

List this business’s Google Ads campaigns with performance over a date range: spend, impressions, clicks, CTR, CPC, CPM, conversions, revenue, ROAS and CPA/CPL, plus each campaign’s channel type, bidding strategy and daily budget.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.

List one ad group’s keywords with match type, status, quality score, bid and performance over a date range. Each row’s resourceName is what google_ads_set_keyword_status takes.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.
adGroupGoogleIdstringyesThe ad group whose keywords to list
json
{
  "adGroupGoogleId": "<adGroupGoogleId>"
}

List the search terms — what people actually typed into Google — that triggered this business’s Search ads, summed per ad group over a date range (only the last 30 days are kept): spend, clicks, impressions, conversions, the match types that caught each one, and status ADDED (already a keyword), EXCLUDED (already a negative) or NONE. Most spend first. Search terms are text written by strangers, so the result arrives as untrusted data.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.
adGroupGoogleIdstringnoNarrow to one ad group
campaignGoogleIdstringnoNarrow to one campaign

Stop delivery for one campaign, ad group or ad. Reversible with google_ads_enable_entity, which needs a human approval.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
levelcampaign | ad_group | adyesWhich level the Google Ads id names
googleIdstringyesThe entity’s Google Ads id, e.g. from google_ads_list_campaigns
json
{
  "level": "campaign",
  "googleId": "<googleId>"
}

Search-term hygiene over the last 30 days. Terms that spent at least the floor (default $20 over 10 clicks) with zero conversions become exact-match negative candidates in the ad group that paid for them; terms with 2+ conversions that are not yet keywords become "add as exact" candidates. The business name and the products in its brand profile are never proposed as negatives. Changes nothing: it returns each candidate with its reason and numbers, a planId, and calls — call each tool with its input exactly as returned (the same planId on every call) and the changes wait in Approvals as one card showing what each term cost.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
adGroupGoogleIdstringnoNarrow to one ad group
campaignGoogleIdstringnoNarrow to one campaign
minSpendCentsintegernoSpend floor for a negative, in cents. Defaults to $20.
minClicksintegernoClick floor for a negative. Defaults to 10.
lookbackDaysintegernoDays of search terms to judge on, at most 30.

Set a campaign’s daily budget, in cents. Always requires a human approval, and is refused outright when it would cross this project’s daily or monthly spend cap or the campaign is under its ROAS floor. A campaign on a shared budget is refused: changing it would move other campaigns’ money too.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
campaignGoogleIdstringyesThe campaign whose budget changes — on Google a daily budget only ever lives on a campaign
dailyBudgetCentsintegeryesThe new daily budget, in cents of the Google Ads account’s currency
confirmLargeChangebooleannoSet true to deliberately make a jump at or over the project’s single-step budget limit (a doubling by default). Ceilings on total spend still apply and cannot be confirmed away.
planIdstringnoThe planId from budget_reallocation_plan, when this change is one row of that plan. Omit for a one-off change.
json
{
  "campaignGoogleId": "<campaignGoogleId>",
  "dailyBudgetCents": 0
}

Set one keyword to ENABLED or PAUSED. A keyword carries no budget of its own — its campaign’s budget and the project’s caps still bound spend — so this runs without an approval under the default grant. Undoable from the decision log.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
criterionResourceNamestringyesThe keyword’s resourceName, as google_ads_list_keywords returns it
statusENABLED | PAUSEDyes—
json
{
  "criterionResourceName": "<criterionResourceName>",
  "status": "ENABLED"
}

TikTok Ads

ToolTierWhat it does
tiktok_ads_create_ad_grouphighCreate a TikTok Ads ad group (paused)
tiktok_ads_create_campaignhighCreate a TikTok Ads campaign (paused)
tiktok_ads_enable_entityhighEnable a TikTok Ads campaign, ad group or ad
tiktok_ads_list_ad_groupsreadList TikTok Ads ad groups
tiktok_ads_list_adsreadList TikTok Ads ads
tiktok_ads_list_campaignsreadList TikTok Ads campaigns
tiktok_ads_pause_entitylowPause a TikTok Ads campaign, ad group or ad
tiktok_ads_set_budgethighChange a TikTok Ads daily budget

tiktok_ads_create_ad_group

Create an ad group under an existing TikTok campaign, with its own daily budget and country targeting. It lands DISABLED and delivers nothing until it is enabled separately, but it creates a real object in a real ad account, so it always requires a human approval.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
campaignTiktokIdstringyesThe campaign the ad group belongs to
namestringyes—
dailyBudgetCentsintegeryesDaily budget in cents — TikTok requires one on every ad group it creates.
countriesstring[]yesISO-3166-1 alpha-2 country codes to target (US, GB). Checked against the project’s geo guardrails, then translated to TikTok location ids; an unmapped country is refused.
optimizationGoalCLICK | CONVERT | REACH | VIDEO_VIEW | LEAD_GENERATIONnoThe event TikTok optimizes delivery for. CLICK needs no pixel history.
bidPriceCentsintegernoA fixed bid in cents. Omit to let TikTok bid automatically (BID_TYPE_NO_BID).
scheduleStartTimestringnoYYYY-MM-DD HH:mm:ss in the advertiser’s timezone. Omit to start as soon as the ad group is enabled.
json
{
  "campaignTiktokId": "<campaignTiktokId>",
  "name": "<name>",
  "dailyBudgetCents": 0,
  "countries": []
}

tiktok_ads_create_campaign

Create a TikTok campaign with its own daily budget in the linked advertiser. It lands DISABLED and delivers nothing until tiktok_ads_enable_entity is approved separately, so this does not start spend — but it does create a real object in a real ad account, so it always requires a human approval.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
namestringyesThe campaign name, as it will appear in TikTok Ads
objectiveTypeTRAFFIC | CONVERSIONS | LEAD_GENERATION | REACH | VIDEO_VIEWSnoTRAFFIC is the safe default for a brand-new campaign: CONVERSIONS and LEAD_GENERATION need a working pixel or lead form and an optimization event with history.
dailyBudgetCentsintegeryesDaily budget in cents. Nothing is spent until the campaign is enabled separately.
json
{
  "name": "<name>",
  "dailyBudgetCents": 0
}

tiktok_ads_enable_entity

Switch a TikTok campaign, ad group or ad to ENABLE. Always requires a human approval: this is the step that starts spending money, and it is refused outright when the entity is under this project’s ROAS floor.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
levelcampaign | ad_group | adyesWhich level the TikTok Ads id names
tiktokIdstringyesThe entity’s TikTok Ads id, e.g. from tiktok_ads_list_campaigns
json
{
  "level": "campaign",
  "tiktokId": "<tiktokId>"
}

tiktok_ads_list_ad_groups

List ad groups with performance metrics, optimization goal, bid and budget, optionally narrowed to one campaign. TikTok calls this rung an ad group, where Meta has an ad set — but unlike Google, a TikTok ad group can carry its own daily budget.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.
campaignTiktokIdstringnoNarrow to one campaign

tiktok_ads_list_ads

List individual ads with performance metrics, ad format, landing page URL and the ad text / call to action, optionally narrowed to one campaign or ad group.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.
campaignTiktokIdstringnoNarrow to one campaign
adGroupTiktokIdstringnoNarrow to one ad group

tiktok_ads_list_campaigns

List this business’s TikTok Ads campaigns with performance over a date range: spend, impressions, clicks, CTR, CPC, CPM, conversions, revenue, ROAS and CPA/CPL, plus each campaign’s objective, budget mode and daily budget.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.

tiktok_ads_pause_entity

Stop delivery for one TikTok campaign, ad group or ad. Reversible with tiktok_ads_enable_entity, which needs a human approval.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
levelcampaign | ad_group | adyesWhich level the TikTok Ads id names
tiktokIdstringyesThe entity’s TikTok Ads id, e.g. from tiktok_ads_list_campaigns
json
{
  "level": "campaign",
  "tiktokId": "<tiktokId>"
}

tiktok_ads_set_budget

Set a TikTok campaign’s or ad group’s daily budget, in cents. Always requires a human approval, and is refused outright when it would cross this project’s daily or monthly spend cap or the entity is under its ROAS floor. An entity that is not on BUDGET_MODE_DAY is refused: a lifetime budget, an uncapped one, and an ad group whose campaign optimizes the budget all have no daily number to change.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
levelcampaign | ad_groupyesWhich rung carries the budget. Unlike Google, a TikTok daily budget lives on the campaign OR the ad group — whichever one currently reports BUDGET_MODE_DAY.
tiktokIdstringyesThe campaign or ad group whose daily budget changes
dailyBudgetCentsintegeryesThe new daily budget, in cents of the TikTok advertiser’s currency
confirmLargeChangebooleannoSet true to deliberately make a jump at or over the project’s single-step budget limit (a doubling by default). Ceilings on total spend still apply and cannot be confirmed away.
planIdstringnoThe planId from budget_reallocation_plan, when this change is one row of that plan. Omit for a one-off change.
json
{
  "level": "campaign",
  "tiktokId": "<tiktokId>",
  "dailyBudgetCents": 0
}

Organic social

ToolTierWhat it does
social_cancel_postlowCancel a scheduled social post
social_create_posthighPublish or schedule a social post
social_get_postreadRead one social post
social_list_accountsreadList connected social accounts
social_list_commentsreadList comments on social posts
social_list_postsreadList social posts
social_post_insightsreadRead how recent social posts performed
social_reply_to_commenthighReply to a social comment
social_schedule_posthighSchedule a social post from the library or the creative studio

social_cancel_post

Stop a draft or scheduled post before it publishes. Only destinations still pending are cancelled — anything already published stays published, because nothing can unsay it. Refused once a post has finished publishing.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
postIdstringyesSocial post id from social_list_posts
json
{
  "postId": "<postId>"
}

social_create_post

Compose one caption plus optional media and publish it to the named connected accounts, now or at scheduledAt. Always requires a human approval. With promotionId, the caption carries that promotion’s code.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
captionstringyes—
mediaAssetIdsstring[]no—
targetsobject[]yes—
scheduledAtstringno—
marketingAssetIdstringno—
promotionIdstringnoA promotion id from promotions_list. Appends "Use code CODE for …" to the caption unless the caption already contains the code. Never type a code into the caption that promotions_list did not return
json
{
  "caption": "<caption>",
  "targets": []
}

social_get_post

Fetch one composed post in full: its caption, media, schedule and every destination’s own status, external post id and error. This is how you find out whether a post that was approved actually went out, and where it failed if it did not.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
postIdstringyesSocial post id from social_list_posts
json
{
  "postId": "<postId>"
}

social_list_accounts

List this business’s connected publishing destinations (Facebook Pages, Instagram accounts, TikTok creators, YouTube channels, Google Business Profile locations) with their status. Call this first to get the socialAccountIds social_create_post needs.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

social_list_comments

List comments people left on this business’s posts (Facebook, Instagram, YouTube — including posts published outside Marketing OS) and Google reviews of its Business Profile locations (platform gbp, with a 1–5 rating), newest first — filter to unanswered or flagged, or one platform. Each has the commentId social_reply_to_comment takes. TikTok comments cannot be read by this app.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
platformfacebook_page | instagram | tiktok | youtube | gbpnoNarrow to one platform
filterall | unanswered | flaggednoall, unanswered (no reply yet) or flagged
limitintegernoAt most this many, newest first

social_list_posts

List composed social posts newest first, with each post’s per-destination target status, external post id and error.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
statusdraft | scheduled | publishing | published | partially_failed | failed | cancellednoNarrow to one post status, e.g. scheduled

social_post_insights

Per connected platform: the newest published posts with their +72h reach, views and engagement, the averages over them, days since the last post, and how many posts are already scheduled or waiting for approval. Also lists creative-studio outputs that passed the brand-voice review and have never been posted — their studioOutputId is what social_schedule_post takes. Cite these numbers when proposing a post.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
platformfacebook_page | instagram | tiktok | youtube | gbpnoNarrow to one platform
limitintegernoHow many of the newest published posts to return per platform

social_reply_to_comment

Post one public reply under a comment on the business’s own post, or the owner’s reply to a Google review, as the business. Waits for a human approval unless the business has granted autonomy for this risk level. Refused if the comment already has a reply, and on TikTok, which has no reply API.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
commentIdstringyesSocial comment id from social_list_comments
messagestringyesThe public reply, exactly as it should appear under the comment
json
{
  "commentId": "<commentId>",
  "message": "<message>"
}

social_schedule_post

Schedule one post whose media is already in this business’s library: an uploaded image or video (assetId, from list_media_files) or a creative-studio output (studioOutputId, from social_post_insights). There is no way to pass an image URL. Requires an evidence line citing social_post_insights. Waits for a human approval unless the business has granted autonomy for this risk level.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
targetsobject[]yesConnected accounts to post to, by socialAccountId from social_post_insights
captionstringyesThe caption, in the brand voice
assetIdstringnoAn image or video from list_media_files. Give this or studioOutputId, not both
studioOutputIdstringnoA creative-studio output from social_post_insights.unpostedStudioOutputs
scheduledAtstringyesISO-8601 instant in the future. Leave room for the owner to approve it first
evidencestringyesOne sentence citing social_post_insights numbers that justify this post, e.g. "last 4 Reels averaged 2.1k views; this one reuses the top angle"
promotionIdstringnoA promotion id from promotions_list. Appends "Use code CODE for …" to the caption unless the caption already contains the code. Never type a code into the caption that promotions_list did not return
json
{
  "targets": [],
  "caption": "<caption>",
  "scheduledAt": "<scheduledAt>",
  "evidence": "<evidence>"
}

Content and creative

ToolTierWhat it does
create_marketing_asset_draftlowDraft a marketing asset
create_media_folderlowCreate an asset library folder
get_marketing_assetreadRead one marketing asset
list_marketing_assetsreadList marketing assets
list_media_filesreadList uploaded images and videos
list_media_foldersreadList asset library folders
save_media_to_librarylowSave a file to the asset library
studio_create_batchlowGenerate Studio variants
studio_get_batchreadRead a Studio batch
studio_list_batchesreadList Studio batches
studio_quotareadStudio allowance
update_marketing_assetlowRevise a marketing asset
video_create_captionslowCaption a video
video_create_slideshowlowMake a slideshow video
video_get_jobreadRead a video edit
video_list_jobsreadList video edits
video_resizelowResize a video

create_marketing_asset_draft

Create a new draft asset (v1) for a human to review. The draft is inert: nothing reaches the outside world until it is approved and published.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
typeblog_post | email | ad_copy | landing_page | social_postyesWhat kind of asset this is
bodyobjectyes—
notestringnoWhy this revision exists
json
{
  "type": "blog_post",
  "body": {}
}

create_media_folder

Create a folder in the asset library, at the top level or inside another folder. Only when the owner asked for a new folder — never to tidy up unprompted.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
namestringyesThe folder’s name, e.g. "Spring campaign"
parentFolderIdstringnoFolder id from list_media_folders to create it inside. Omit for the top level.
json
{
  "name": "<name>"
}

get_marketing_asset

Fetch one marketing asset in full, including its revision history.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
assetIdstringyesMarketing asset id from list_marketing_assets
json
{
  "assetId": "<assetId>"
}

list_marketing_assets

List this business’s marketing assets (blog posts, emails, ad copy, landing pages, social captions) with their status and version, most recently updated first.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

list_media_files

List this business’s uploaded media — images and videos with their kind, category, dimensions, size, library folder (folderId, null = the top level) and a short-lived URL. These are the ids social_create_post takes as media and that campaign plans build image ads from. Uploading new files is done in the Marketing OS app, or by attaching one in the Terminal.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

list_media_folders

List the folders in this business’s asset library, each with its id, its full path (e.g. "Campaigns / Spring") and how many files it holds. Read this before filing a file, so the folders you offer are the ones that exist.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

save_media_to_library

Save an image or video to the asset library, in a folder or at the top level. Use it for a file the owner attached in this conversation once they have said they want it kept and where. It also moves a file that is already in the library to another folder.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
assetIdstringyesMedia id — of a file attached in this conversation, or from list_media_files
folderIdstringnoFolder id from list_media_folders or create_media_folder. Omit for the top level of the library.
json
{
  "assetId": "<assetId>"
}

studio_create_batch

Generate on-brand ad images from one product photo in the library: one variant per angle × placement (2 angles × 3 placements = 6 images), each with ad copy that passed Brand Voice review. Every image counts against the organization’s monthly Studio cap (studio_quota shows what is left); a batch bigger than what remains is refused. The job is asynchronous: this returns the batch with every variant queued — call studio_get_batch for progress and the finished asset ids. It never publishes or spends ad money.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
sourceAssetIdstringyesAn image asset in this business’s library (asset_list) — the product photo.
anglesstudio | lifestyle | in_use | flat_lay | bold_graphic | seasonal[]yesCreative angles; one variant per angle per placement.
placementsfeed | reels | square | landscape[]yesWhere the ads run; each placement is generated at its own aspect ratio.
productNotesstringno—
offerstringno—
json
{
  "sourceAssetId": "<sourceAssetId>",
  "angles": "studio",
  "placements": "feed"
}

studio_get_batch

One Creative Studio batch and its variants: each variant’s angle, placement and status (queued, running, succeeded, failed), and once it succeeds the generated image’s assetId and its ad copy. Use the assetId with the social, video or Meta ad tools.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
batchIdstringyesThe batch id, from studio_create_batch or studio_list_batches.
json
{
  "batchId": "<batchId>"
}

studio_list_batches

The 50 most recent Creative Studio batches for this business, newest first: status (queued, running, completed, failed), angles, placements and how many variants succeeded or failed. Use studio_get_batch for the variants themselves.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

studio_quota

This month’s Creative Studio image allowance for the organization: the monthly cap, how many images are used or reserved by running batches, how many remain, and the estimated cost per image in cents. Check it before a large studio_create_batch.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

update_marketing_asset

Replace an asset’s body (creating a new version) and/or move it along the status ladder. published is refused here — publishing goes through the approvals inbox.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
assetIdstringyesMarketing asset id from list_marketing_assets
statusdraft | in_review | approved | published | archivedno—
bodyobjectno—
notestringno—
json
{
  "assetId": "<assetId>"
}

video_create_captions

Burn captions into a video from the library, as a new video asset. Captions come from the audio (source "transcribe") or from a script you pass ({ text }), styled bold-center or lower-third. The render is asynchronous: this returns the job queued — call video_get_job for its status and the new asset id. Renders count against the plan’s platform allowance and are refused once it is spent. The source is never overwritten.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
sourcestring | objectyes"transcribe" to caption from the audio (needs a transcription model), or { text } to time a script.
stylebold-center | lower-thirdno—
assetIdstringyesThe video asset to edit, from asset_list. The source is never overwritten.
json
{
  "source": "<source>",
  "assetId": "<assetId>"
}

video_create_slideshow

Turn 3–8 images (Studio outputs or any library image, in play order) into a 10–20 second silent video with slow Ken Burns motion, optionally with one caption line, counted toward a Studio batch. The render is asynchronous: this returns the job queued — call video_get_job for its status and the new asset id. Renders count against the plan’s platform allowance and are refused once it is spent. The source is never overwritten.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
imageIdsstring[]yes—
durationSecintegerno—
placementreels | square | landscapeno—
captionTextstringno—
musicstringno—
batchIdstringyesThe Studio batch the slideshow belongs to, from studio_list_batches.
json
{
  "imageIds": [],
  "batchId": "<batchId>"
}

video_get_job

One video edit: its kind, status (queued, running, succeeded, failed), the step it is on, the error if it failed, and outputAssetId — the new video — once it succeeds.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
jobIdstringyesThe job id, from a video_* create tool or video_list_jobs.
json
{
  "jobId": "<jobId>"
}

video_list_jobs

The 50 most recent video edits for this business (captions, resize, slideshow), newest first, optionally for one source video or one Studio batch: status, the step it is on, and the output asset id once it succeeds.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
sourceAssetIdstringnoOnly edits of this video.
batchIdstringnoOnly edits counted toward this Studio batch.

video_resize

Reshape a video from the library for a placement — reels (9:16), square (1:1) or landscape (16:9) — by cropping to fill or padding inside the placement’s safe zone (default pad), as a new video asset. The render is asynchronous: this returns the job queued — call video_get_job for its status and the new asset id. Renders count against the plan’s platform allowance and are refused once it is spent. The source is never overwritten.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
placementreels | square | landscapeyes—
modecrop | padno—
assetIdstringyesThe video asset to edit, from asset_list. The source is never overwritten.
json
{
  "placement": "reels",
  "assetId": "<assetId>"
}

Local SEO and AI visibility

ToolTierWhat it does
gbp_insightslowRead Google Business Profile insights
localfalcon_get_reportreadRead one Local Falcon report
localfalcon_list_locationsreadList Local Falcon locations
localfalcon_list_reportsreadList Local Falcon scan reports
localfalcon_run_scanhighRun a Local Falcon scan

gbp_insights

Daily Google Business Profile numbers for this business’s connected locations — phone calls, direction requests, website clicks, and Google Maps and Search profile impressions — totalled over the window with the preceding window for comparison. Google reports with a two-to-three-day lag. Reviews are read with social_list_comments (platform gbp); posts are scheduled with social_create_post.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
daysintegernoWindow length in days, ending at the newest day Google has reported
locationIdstringnoNarrow to one connected location; omit for every location

localfalcon_get_report

Fetch one geo-grid scan report in full — every grid point’s rank, plus the aggregate ARP / ATRP / SoLV scores.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
reportKeystringyesReport key from localfalcon_list_reports
json
{
  "reportKey": "<reportKey>"
}

localfalcon_list_locations

List the Google Business Profile locations saved in this business’s Local Falcon account, with any corrected scan center (pin override) we hold for them.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
querystringnoFilter saved locations by name
limitintegerno—
next_tokenstringnoCursor from a previous page

localfalcon_list_reports

List past geo-grid scan reports, optionally narrowed by place, keyword, platform or date range (MM/DD/YYYY).

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
limitintegerno—
place_idstringno—
keywordstringno—
platformstringno—
start_datestringno—
end_datestringno—
next_tokenstringno—

localfalcon_run_scan

Run one geo-grid rank scan for a keyword at a location. Spends the account’s Local Falcon credits, so it always requires a human approval.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
place_idstringyes—
keywordstringyes—
latstringyes—
lngstringyes—
grid_size3 | 5 | 7 | 9 | 11 | 13 | 15 | 17 | 19 | 21yes—
radiusnumberyes—
measurementmi | kmyes—
platformgoogle | apple | chatgpt | gemini | gaio | aimode | grokyes—
ai_analysisbooleanno—
json
{
  "place_id": "<place_id>",
  "keyword": "<keyword>",
  "lat": "<lat>",
  "lng": "<lng>"
}

Analytics and Search Console

ToolTierWhat it does
google_connection_statusreadGoogle connection status
google_search_performancereadSearch Console performance

google_connection_status

Whether this business has a live Google connection, and which GA4 property and Search Console site it is pointed at. Null when nothing is connected.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

google_search_performance

Daily Google Search Console clicks, impressions and average position for the connected site over a date range, plus its top queries.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
fromstringnoStart date (YYYY-MM-DD). Defaults to 29 days ago.
tostringnoEnd date (YYYY-MM-DD). Defaults to today.

Commerce

ToolTierWhat it does
audience_create_lookalikehighCreate a lookalike audience from a customer segment
audience_listreadList customer segments and their audiences
commerce_create_meta_cataloglowCreate the Meta product catalog
commerce_create_product_setlowCreate a Meta product set
commerce_get_economicsreadGet store unit economics
commerce_get_meta_catalogreadGet the Meta product catalog
commerce_list_productsreadList store products
commerce_pause_ads_for_out_of_stocklowPause ads for sold-out products

audience_create_lookalike

Create a lookalike audience on Meta, Google Ads or TikTok Ads seeded from one of the store’s customer segments. The segment must already be synced to that channel (see audience_list). Spends nothing by itself, but it is a new targeting object in the ad account, so it always waits for a human approval. top_ltv_20 at 1 % is usually the strongest seed.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
segmentall_buyers | buyers_90d | top_ltv_20 | lapsed_180dyesThe source segment: all_buyers, buyers_90d, top_ltv_20 (usually the best seed) or lapsed_180d
channelmeta | google_ads | tiktok_adsyesWhich ad platform to create the lookalike on (meta, google_ads or tiktok_ads). The segment must already be synced there.
rationumbernoLookalike size as a share of the country, 0.01–0.10. Meta uses it as is; Google and TikTok map it to NARROW (≤2 %), BALANCED (≤5 %) or BROAD.
countrystringyesISO-3166-1 alpha-2 country the lookalike is built in, e.g. US
json
{
  "segment": "all_buyers",
  "channel": "meta",
  "country": "<country>"
}

audience_list

The four built-in customer segments computed from the connected store (all buyers, buyers in the last 90 days, top 20 % by lifetime value, lapsed 180+ days) with their sizes, and for each channel (Meta, Google Ads, TikTok Ads and the store’s Klaviyo) whether the segment is synced there, the platform audience or list id, the size and match rate the platform reports, and any lookalikes already created from it. Use it before proposing a lookalike or suggesting retargeting or a win-back email.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

commerce_create_meta_catalog

Create this business’s Meta product catalog, owned by a Business Manager, with a daily scheduled feed that Meta fetches from us — built from the store mirror, one item per variant. Needed once, before any product set or catalog campaign; calling it again returns the existing catalog. Requires the catalog_management Meta permission, and says so by name when the connection lacks it.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
businessManagerIdstringnoThe Meta Business Manager that will own the catalog. Omit when the connected Meta user has exactly one; with several the call is refused and lists them to pick from.
namestringnoDefaults to "<shop name> catalog"

commerce_create_product_set

Create a named product set in the Meta catalog, filtered by a Shopify collection handle, any of a list of tags, and/or a minimum gross margin (10–90, in steps of 10). Conditions are ANDed. A catalog campaign advertises exactly one product set.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
namestringyesShown in Ads Manager, e.g. "Summer sale"
filterobjectyescollection?, tags?, minMarginPct? — ANDed together
json
{
  "name": "<name>",
  "filter": {}
}

commerce_get_economics

The unit economics derived from the connected store: average order value over the last 90 days, catalog gross margin, and the break-even ROAS those imply — next to the values currently set on the business. Use it to ground any judgment about whether spend is profitable rather than asking the user for these numbers.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

commerce_get_meta_catalog

The Meta catalog fed from the connected store, if one exists: its catalog id, the public feed URL Meta fetches daily, and every product set with its filter, its Meta id and how many store products it matches today. Product set ids from here are what meta_create_catalog_campaign takes.

Risk tier: read — runs immediately, changes nothing

Takes no arguments.

commerce_list_products

List the products in this business’s connected store, highest-revenue first, with price range, gross margin, stock on hand and units sold over the last 90 days. Use it before proposing what to advertise, and to check whether something is still in stock.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
limitintegernoHow many products to return, richest-selling first
outOfStockOnlybooleannoOnly products with zero inventory across every variant

commerce_pause_ads_for_out_of_stock

Find the ads that are selling a product the store has run out of, and pause them. Matching is inferred — a product page link in the ad’s creative, or the product title in the ad’s name or headline — so it DEFAULTS TO A DRY RUN: call it with dryRun true, show the operator the matched ads and why each matched, and only call it again with dryRun false once they agree. Pausing is reversible with meta_activate_entity.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
dryRunbooleannoLeave true to only LIST the ads that would be paused and why. Set false to actually pause them. Always dry-run first and show the list — the product/ad match is inferred, not reported by Meta.

Promotions and promo codes

ToolTierWhat it does
promotions_create_draftlowSave a promotion
promotions_listreadList promotions
promotions_performancereadRead a promotion’s orders and revenue
promotions_publish_to_storehighCreate a discount code on the store

promotions_create_draft

Save a promo code the business runs, usually with a partner: code, partner, discount and product scope, optionally dates and a landing path. Use only the code and discount the user stated — never invent either. Nothing leaves the app: with a Shopify store connected the promotion is saved as a draft and the code does NOT exist on the store until promotions_publish_to_store is approved; with no store it is saved active, ready to quote in ads and posts. The code, discount and scope cannot be changed afterwards.

Risk tier: low — runs immediately, reversible and audited

ArgumentTypeRequiredDescription
codestringyesThe code exactly as the user gave it (2–32 letters, digits, "-" or "_"; upper-cased). Never make one up
partnerNamestringyesWho the promotion is with: the collaborator, creator or partner (e.g. "Tory Media")
discountTypepercent | fixedyes"percent" (discountValue 10 = 10% off) or "fixed" (10 = 10.00 off in the store currency)
discountValuenumberyes—
currencystringno—
scopestring | objectno"all" for every product, or { productIds } with ids from commerce_list_products
startsAtstringnoISO-8601 with offset. Omitted = starts when it goes live
endsAtstringnoISO-8601 with offset, after startsAt. Omitted or null = no end date
landingPathstringnoA storefront path such as "/products/copper-kettle" (never a full URL). Omitted with a single product in scope = that product’s page
json
{
  "code": "<code>",
  "partnerName": "<partnerName>",
  "discountType": "percent",
  "discountValue": 0
}

promotions_list

List this business’s promo codes newest first: code, partner, discount (discountType "percent" = discountValue %, "fixed" = discountValue in the store currency), product scope, dates, status (draft / active / ended / archived), the landingUrl links should use (the Shopify discount link when the code is on the store) and its utmCampaign. Call it before creating a promotion (a code is unique per business) and before passing a promotionId to meta_draft_campaign_plan or a social post tool.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
statusdraft | active | ended | archivednoOnly promotions in this status. Omitted = every promotion except archived ones

promotions_performance

Orders that used the promotion’s code inside its dates, and their revenue — in the store currency and the reporting currency — read from the store’s order sync. source "none" means no store is connected and every number is zero, not unknown. Orders older than the store sync window (60–90 days) are not counted.

Risk tier: read — runs immediately, changes nothing

ArgumentTypeRequiredDescription
promotionIdstringyesA promotion id from promotions_list
json
{
  "promotionId": "<promotionId>"
}

promotions_publish_to_store

Create a saved draft promotion’s code as a discount on the connected Shopify store and set the promotion active; its landingUrl becomes the store’s discount link, which applies the code on arrival. Always waits for a human approval. Refused when no store is connected (the promotion is already usable without one). Also retries a promotion that is active but whose code never reached the store (see shopifySyncError from promotions_list).

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
promotionIdstringyesA promotion id from promotions_list
json
{
  "promotionId": "<promotionId>"
}

Email and SMS

ToolTierWhat it does
email_campaign_metricsreadGet one email campaign or flow
email_create_campaign_drafthighCreate a Klaviyo email draft
email_list_campaignsreadList email campaigns and flows
email_list_listsreadList Klaviyo lists
email_send_campaignhighSend or schedule a Klaviyo email

email_campaign_metrics

Day-by-day sends, opens, clicks and attributed revenue for one Klaviyo campaign or flow, by the id email_list_campaigns returns, plus its window totals and rates.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
idstringyesThe Klaviyo campaign or flow id, as email_list_campaigns returns it.
sincestringnoFirst day of the window. Defaults to 30 days ago.
json
{
  "id": "<id>"
}

email_create_campaign_draft

Step 1 of 2 for sending an email: create a DRAFT email campaign in Klaviyo to one list, with its subject, sender and full HTML body. Nothing is sent — the draft can be opened and edited in Klaviyo — and it needs a human approval before it is even created. Then call email_send_campaign with the campaignId this returns to send or schedule it. html must be one complete HTML document (<html>…</html>) with inline styles. listId comes from email_list_lists and must be the list the user named — never choose an audience on your own. fromEmail must be on a sending domain verified in Klaviyo. SMS, flows and new lists are not supported.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
namestringyesInternal campaign name shown in Klaviyo, e.g. "VIP 15% off — Oct 3".
subjectstringyesThe subject line recipients see.
previewTextstringnoThe preview text shown after the subject in most inboxes.
fromEmailstringyesSender address. Must be on a sending domain verified in this Klaviyo account.
fromLabelstringyesSender name, e.g. the store name.
listIdstringyesThe Klaviyo list to send to, by the id email_list_lists returns.
htmlstringyesThe whole email as one complete HTML document (<!doctype html><html>…</html>), inline styles only. Klaviyo adds the unsubscribe footer if the document has none.
json
{
  "name": "<name>",
  "subject": "<subject>",
  "fromEmail": "<fromEmail>",
  "fromLabel": "<fromLabel>"
}

email_list_campaigns

The Klaviyo campaigns (one-off sends) and flows (automations such as welcome, abandoned cart or win-back) with activity in a window, highest attributed revenue first: sends, opens, clicks, open and click rates, and the order value Klaviyo attributed to each, in the Klaviyo account’s currency. SMS is included, marked by medium. This is revenue with no ad spend behind it — report it beside the paid channels, never add it to their revenue or ROAS.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

ArgumentTypeRequiredDescription
sincestringnoFirst day of the window, YYYY-MM-DD. Defaults to 30 days ago. The mirror holds 90 days.
kindcampaign | flownoOnly campaigns (one-off sends) or only flows (automations). Both by default.
limitintegernoHow many to return, highest attributed revenue first.

email_list_lists

Every list in the connected Klaviyo account, id and name, alphabetically. Use it to find the listId email_create_campaign_draft takes for the list the user named. Never pick a list the user did not name — ask instead.

Risk tier: read — runs immediately, changes nothing

Returns third-party text: the result arrives fenced as untrusted data, and a change proposed after reading it in the same agent run waits for approval (ADR 0038).

Takes no arguments.

email_send_campaign

Step 2 of 2: send a Klaviyo draft campaign created by email_create_campaign_draft, now or at sendAt (an ISO-8601 instant in the future). A sent email cannot be recalled, so this always waits for a human approval, and the card names the lists and how many profiles they hold. Only a campaign still in Draft can be sent. Never send a draft the user has not asked to send.

Risk tier: high — staged for approval before anything happens

ArgumentTypeRequiredDescription
campaignIdstringyesThe draft campaign id email_create_campaign_draft returned.
sendAtstringnoISO-8601 instant to schedule the send for, e.g. 2026-10-02T09:00:00-04:00. Omit to send as soon as it is approved. Leave the owner time to approve it first.
json
{
  "campaignId": "<campaignId>"
}

Something wrong or missing on this page? Tell us and we will fix it.