道具Reference

Tools reference

203 tools for Google Ads, Meta Ads, LinkedIn Ads and ChatGPT Ads. 15 are callable directly; the rest go through a platform router, reads through one and changes through its _write partner. Read tools run immediately; write tools return a proposal.

Each description below is the standard operating procedure the model reads: when to use the tool, when not to, what it returns and what to do when it is unsure. The risk badge and the cost line are added by the registry.

A flat list of 203 tools costs more tokens than most clients will spend on a tool list, so Adako splits the surface. The tools under Callable directly appear in the client’s tool list and are called by name. Everything else is reached through its platform’s routers, with action="execute" and the tool name: reads through the read router (google_ads), changes through the write router (google_ads_write), which the assistant asks about each time. Each entry carries the exact line to use.

search_tools finds a tool from a plain description of the job (“stop wasting money on bad search terms”) and returns that same call line; get_tool_schema returns a tool’s arguments. Both are free and never touch an ad platform, so an assistant can look before it calls.

A platform’s read router also takes accounts: a list of account ids, or "all_active". It then runs one read tool across those accounts, one after another, and returns a single consolidated answer with a section per account. Reads are free, so this costs nothing. Writes stay one account at a time — every change gets its own preview and its own approval.

Cost is in tasks. 0 means the call is never billed.

Callable directly15

These appear in the client’s tool list and are called by name.

  • approve_proposaldestructive writefree

    Approve and execute a pending proposal

    Executes a pending proposal exactly once and reads the object back. Only call after the user has explicitly said yes to the specific proposal (quote its summary). Use when: the user's write policy is "inbox" and they approve in chat, or a client deferred a write. If the proposal expired or was already decided, returns an error — do not re-create it silently; tell the user.

    How to call: approve_proposal({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "proposal_id": {
          "type": "string",
          "minLength": 8,
          "description": "Id from list_pending_proposals or the proposal_pending error"
        }
      },
      "required": [
        "proposal_id"
      ]
    }
  • check_mediaread-onlyfree

    Check an image or video before it becomes an ad

    Checks a creative file before any ad is built from it, and answers where a file with no link yet has to go. Use when: the user offers an image or a video in any form — a link, a file on their computer, "the one from our site" — and always before a create tool that takes image_url or video_url. What it does: repairs the share links that do not serve files (Drive, Dropbox, OneDrive, GitHub, Imgur have a direct form), fetches only the head of the file to read its type, size, dimensions and an MP4's duration, and reports slot by slot which platforms accept it and what would be cropped. Call it with no arguments when the user has a file but no link: it returns the three ways to get one, cheapest first, starting with reusing what is already in their ad account. Adako hosts nothing and never receives the file. The platform downloads the URL itself, from its own servers and often hours later, which is why a local path, a login-gated page or a link that expires cannot work however it is passed. Free, instant, touches no ad account and creates nothing, so run it as often as needed. If a file is refused, say exactly which rule it broke and what to change — never retry a create with the same file.

    How to call: check_media({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "urls": {
          "description": "The file links to check, as the user gave them — share pages and http links are repaired rather than rejected. Leave this out to get the \"where do I put my file\" answer instead.",
          "minItems": 1,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048
          }
        },
        "platforms": {
          "description": "Only judge these platforms. Defaults to all five, which is what to use when the user has not chosen yet.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "google_ads",
              "meta_ads",
              "chatgpt_ads",
              "tiktok_ads",
              "linkedin_ads"
            ]
          }
        },
        "kind": {
          "description": "Only judge image slots or video slots. Defaults to what the file turns out to be.",
          "type": "string",
          "enum": [
            "image",
            "video"
          ]
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • get_connections_statusread-onlyfree

    Connection health per platform

    Shows every platform login the user has connected, its token health (active / needs re-authorisation / revoked), and the active + primary ad accounts under it. Use when: a platform tool failed with not_connected or needs_reauth, or before the first platform call in a conversation. Returns links the user can open to fix a broken connection. Reads Adako's database only — no platform calls.

    How to call: get_connections_status({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {}
    }
  • get_tool_schemaread-onlyfree

    Get the exact arguments a tool takes

    Returns the live JSON schema of one or more tools: required fields, enums, defaults, example prompts and the exact line to call it with. Free and instant — it never touches an ad platform. Use when: search_tools or a router pointed you at a tool and you need its arguments before calling it; or a call failed validation and you want the real field names. When not to use: the tool is already in your tool list with its schema attached — read that instead. Returns: one block per tool with risk, cost, the "How to call" line and the schema. Pass verbose=false for a compact field/type list when you only need the names. If a name is unknown, you get near matches; pick one or call search_tools. Never invent field names.

    How to call: get_tool_schema({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "tool_names": {
          "minItems": 1,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64
          },
          "description": "Exact tool names, 1–10 of them. Use the names search_tools or a router returned."
        },
        "verbose": {
          "description": "Full JSON schema (default true). false returns just field names and types.",
          "type": "boolean"
        }
      },
      "required": [
        "tool_names"
      ]
    }
  • get_usage_statusread-onlyfree

    Plan and task usage

    Returns the user's plan, tasks used vs limit for the current period, when it resets, ad accounts used this billing period vs the plan limit, and a link to the plans. Use when: a tool returned quota_exceeded or account_limit, or the user asks how many tasks or accounts they have left. Free; reads Adako's database only. A "task" is one billable tool call. System tools and resolvers are free. An ad account used this period keeps its place on the plan after it is switched off.

    How to call: get_usage_status({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {}
    }
  • list_connected_accountsread-onlyfree

    List ad accounts (active and inactive)

    Lists every ad account Adako knows for this user, with platform id, name, currency, timezone, and whether it is active and primary. Tools can only use active accounts. The plan covers a number of ad accounts per billing period across all platforms, and an account used this period keeps its place after it is switched off. Each platform has one primary account, used when a tool call names no account. Use when: the user mentions an account by name and you need its id, or a tool returned account_ambiguous or not_connected. Optional platform filter. Reads Adako's database only.

    How to call: list_connected_accounts({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "platform": {
          "description": "Filter by platform",
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads"
          ]
        }
      }
    }
  • list_pending_proposalsread-onlyfree

    List proposals waiting for approval

    Lists writes that were proposed but not yet approved, with their preview and expiry (48 h). Use when: the user asks "what is waiting for my approval", or after a tool returned proposal_pending. Free. Pass proposal_ids to also get those proposals as they stand now, whatever their status (approved and applied with what exists now, rejected, failed or expired): use it when the user asks whether a change went through.

    How to call: list_pending_proposals({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "proposal_ids": {
          "description": "Proposals to return as they stand now, whatever their status. Omit to list only what is pending.",
          "minItems": 1,
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 8
          }
        }
      }
    }
  • reject_proposalwritefree

    Reject a pending proposal

    Marks a pending proposal as rejected so it will never execute. Store the user's reason when they give one (it improves future proposals). Use when: the user says no to a proposed change. Free.

    How to call: reject_proposal({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "proposal_id": {
          "type": "string",
          "minLength": 8
        },
        "reason": {
          "type": "string",
          "maxLength": 500
        }
      },
      "required": [
        "proposal_id"
      ]
    }
  • search_toolsread-onlyfree

    Find the right Adako tool for a job

    Finds the tools that do a job, ranked, from the user's own wording. Free and instant. Use when: you are not sure which tool to call, the user asked for something you have not seen a tool for, or a tool name you guessed came back unknown. Searching costs nothing — guessing costs a failed call. When not to use: you already know the tool name (call it, or call get_tool_schema for its arguments). Returns: up to top_k rows with the tool name, what it does, its risk and cost, its arguments (required ones marked) and the exact "How to call" line — most tools are reached through their platform router, so follow that line as written. For flat arguments that is enough to make the call; get_tool_schema adds descriptions and nested fields. A request to delete campaigns, ad groups, ad sets or ads also gets a note first: no tool does that, and what to offer instead. Searches every platform unless you pass platform. If nothing matches, say so and ask the user what they want to achieve; do not invent a tool name.

    How to call: search_tools({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "query": {
          "type": "string",
          "minLength": 2,
          "maxLength": 300,
          "description": "What the user wants to do, in their own words (\"stop wasting money on bad search terms\")."
        },
        "platform": {
          "description": "Restrict to one platform. Leave empty to search all of them.",
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads",
            "system"
          ]
        },
        "top_k": {
          "description": "How many tools to return. Default 5.",
          "type": "integer",
          "minimum": 1,
          "maximum": 20
        }
      },
      "required": [
        "query"
      ]
    }
  • start_hereread-onlyfree

    Start here — personalised next step

    Call this first in a new conversation, or whenever you are unsure what the user can do. Returns the real state of this user's Adako account: which ad platforms are connected, which accounts are active and which is primary, how many tasks remain, and three example prompts with their task cost. When not to use: you already know the connection state from an earlier call in this conversation. If nothing is connected, tell the user to open the connections link; do not attempt platform tools.

    How to call: start_here({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {}
    }
  • switch_primary_accountwritefree

    Switch the primary ad account for a platform

    Makes one account the primary account for its platform, the one later tools use when no account id is passed, and switches it on if it was inactive. Other active accounts stay active. When switching it on would go over the ad accounts the plan covers this billing period, the platform's previous primary is switched off to make room if that frees a place (it stays connected); otherwise the call fails with account_limit. Changes Adako settings only; nothing on the ad platform changes, so no proposal is created. Use when: the user says "use account X from now on", or a tool returned account_ambiguous or not_connected and the user confirmed which account they mean. Not for: manager accounts, which hold no campaigns. If you are unsure which account the user means, ask before switching. The account must already be known to Adako (see list_connected_accounts).

    How to call: switch_primary_account({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "platform": {
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads"
          ]
        },
        "account_id": {
          "type": "string",
          "minLength": 1,
          "description": "Platform account id as shown by list_connected_accounts"
        }
      },
      "required": [
        "platform",
        "account_id"
      ]
    }
  • google_get_campaign_performanceread-onlyfree

    Google Ads campaign performance with period-over-period change

    Reports spend, impressions, clicks, CTR, conversions, conversion value, CPA and ROAS per campaign for a period, plus the change against the immediately preceding period of the same length. Use when: the user asks how campaigns performed, whether results improved, or what a campaign costs per conversion. Do not use it for a brief or a report to keep or forward: that is generate_report_now (monitoring router), which covers every connected account and saves a page. Do not use when: the user wants to know what exists rather than how it performed (google_list_campaigns), or wants search-query detail (google_analyze_search_terms). Pass either date_range (a preset) or start_date + end_date, never both. Periods are calendar days in the account's own timezone. Money is a decimal in the account currency. CPA is blank when there were no conversions and ROAS is blank when there was no spend — say so rather than printing zero. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_get_campaign_performance({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "campaign_ids": {
          "description": "Limit the report to these campaign ids.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          }
        },
        "limit": {
          "description": "Maximum campaigns to return (default 50).",
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        }
      }
    }
  • meta_get_campaign_performanceread-onlyfree

    Meta campaign performance

    Campaign-level results for a window, next to the same-length window before it, with the KPI each campaign's objective is actually judged on: purchases and purchase ROAS for sales, leads and cost per lead for lead generation, link clicks and CPC for traffic, reach and CPM for awareness, engagements for engagement, installs for app promotion. Use when: the user asks how Meta is doing, which campaigns are worth more budget, or what changed week over week. Do not use it for a brief or a report to keep or forward: that is generate_report_now (monitoring router), which covers every connected account and saves a page. Do not use to inspect settings (meta_list_campaigns) or to find waste inside an ad set (meta_analyze_wasted_spend). Returns "not applicable" rather than a blank or a zero for ROAS on objectives that record no revenue — never present a missing ROAS as a bad ROAS. If the user's date wording is vague ("recently", "lately"), ask which window they mean instead of picking one.

    How to call: meta_get_campaign_performance({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Report on a single campaign."
        },
        "limit": {
          "description": "Maximum campaigns in the table (default 25, ordered by spend).",
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • chatgpt_get_performanceread-onlyfree

    Read ChatGPT Ads performance

    Reports impressions, clicks, spend, conversions and the ratios derived from them (CTR, CPC, CPM, CPA, ROAS) for a window, broken down by campaign, ad group or ad — and compares every number with the equally long period immediately before it. Use when: the user asks how anything is doing, whether something is working, or what changed. Scope it with campaign_id, ad_group_id or ad_id; without any of them it reports the whole account. Do not use it for a brief or a report to keep or forward: that is generate_report_now (monitoring router), which covers every connected account and saves a page. Do not use it to find out what exists: that is chatgpt_list_campaigns. Daily numbers are noisy at small budgets, so prefer last_7_days or longer before recommending a change. The "last N days" windows end yesterday, so a partial day is never compared with a whole one. Returns spend as a decimal in the account currency and a percentage change per metric against the previous period. A dash in the change column means the previous period was zero, not that nothing changed. ROAS is only meaningful where the account reports attributed revenue; where it does not, it comes back empty rather than as zero. If the numbers are all zero, check chatgpt_list_ads for a review verdict and chatgpt_list_campaigns for a serving issue before concluding the ads are simply performing badly.

    How to call: chatgpt_get_performance({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "level": {
          "description": "How to break the numbers down: account (one total), campaign, ad_group or ad. Defaults to campaign.",
          "type": "string",
          "enum": [
            "account",
            "campaign",
            "ad_group",
            "ad"
          ]
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "Only this campaign."
        },
        "ad_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "Only this ad group."
        },
        "ad_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "Only this ad."
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "compare_previous": {
          "description": "Compare with the equally long period before the window. On by default, and still one task.",
          "type": "boolean"
        },
        "limit": {
          "description": "Maximum rows in the breakdown (default 25, by spend).",
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • linkedin_get_campaign_performanceread-onlyfree

    LinkedIn campaign (ad set) performance

    Reports spend, impressions, clicks, CTR, CPC, CPM, leads, cost per lead and website conversions for a window, per campaign (an ad set in Campaign Manager) and in total, next to the equally long window before it so every number has a baseline. Use when: the user asks how LinkedIn is doing, before recommending any budget or bid change, and to decide which campaign is worth more money. Start here, not with a list tool. Do not use it for a brief or a report to keep or forward: that is generate_report_now (monitoring router), which covers every connected account and saves a page. Do not judge a LinkedIn campaign on a day or two: at typical B2B volumes a single day is noise, and LinkedIn's cost per lead is an order of magnitude above other channels by design. Use last_7_days at minimum, last_30_days for a decision about money. Do not read a rise in CPC as failure on its own — on LinkedIn it usually means the auction got busier, and the number that matters is cost per lead. Returns account totals with period-over-period change, plus a per-campaign table sorted by spend. LinkedIn ad accounts carry no reporting timezone, so every window here is UTC days. If the user has not said which window, use last_30_days and say so. If a campaign shows spend but zero leads, check linkedin_list_conversions before concluding the ads are bad — an unattached conversion rule reports nothing.

    How to call: linkedin_get_campaign_performance({...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_ids": {
          "description": "Limit the report to these campaigns. Omit for the whole account.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "campaign_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "Limit the report to one campaign group. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "compare_previous": {
          "description": "Include the previous equally long period for comparison (default true).",
          "type": "boolean"
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

Google Ads69

Reads run through google_ads, changes through google_ads_write. Call google_ads(action="list_tools") for the same list at runtime — it is free.

audiences5

  • google_add_audience_signalwrite1 task

    Add an audience signal to an asset group

    Adds an audience as a signal on a Performance Max asset group. A signal tells Google who to start with; it is not targeting and Google will serve well outside it. Use when: the account has a genuinely useful audience — a customer list, a converter list, a close in-market segment — and the user picked it from google_search_audiences. Do not use when: the user expects the campaign to stay inside that audience. It will not. If they want strict targeting, Performance Max is the wrong campaign type and a Search or Display campaign is the honest answer. Pass an audience id from google_search_audiences. Adding a second identical signal is refused. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_add_audience_signal", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "asset_group_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Asset group id as a string of digits."
        },
        "audience_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Audience id from google_search_audiences — only an entry from its `audiences` list. The `interest_segments` and `user_lists` it also returns are different kinds of id and Google refuses them here with INVALID_COMBINED_AUDIENCE_ID (seen live 2026-09-18). An account with an empty `audiences` list has no audience to signal with; say so rather than passing an interest id."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "asset_group_id",
        "audience_id"
      ]
    }
  • google_add_search_themeswrite1 task

    Add search themes to an asset group

    Adds search themes to a Performance Max asset group. A search theme is a phrase telling Google what customers look for; it is a hint, not a keyword, and Google may serve on plenty of queries you never listed. Use when: the campaign is new and has nothing to learn from, or the user knows terms the ads should cover. Do not use when: the user expects keyword-level control. Performance Max has none, and search themes do not add any. Say so rather than implying otherwise. An asset group holds at most 25 themes; the tool refuses if the addition would go over, and themes that are already there are skipped rather than duplicated. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_add_search_themes", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "asset_group_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Asset group id as a string of digits."
        },
        "search_themes": {
          "minItems": 1,
          "maxItems": 25,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "description": "Short phrases describing what customers search for. At most 25 in total per group."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "asset_group_id",
        "search_themes"
      ]
    }
  • google_get_search_themesread-onlyfree

    Read the signals on an asset group

    Lists the signals on a Performance Max asset group: the search themes and the audience signals, with Google's approval status for each. Use when: before adding or removing themes, or when the user asks what Performance Max has been told to look for. Do not use when: the user expects keywords. Search themes are hints, not keywords: Google is free to ignore them and will also serve on queries you never listed. An asset group may hold at most 25 search themes. A theme with a non-approved status is not being used at all. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_search_themes", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "asset_group_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Asset group id as a string of digits."
        }
      },
      "required": [
        "asset_group_id"
      ]
    }
  • google_remove_search_themesdestructive write1 task

    Remove search themes from an asset group

    Removes search themes from a Performance Max asset group. Removal is permanent: a theme can be added again, but its history is gone. Use when: a theme is bringing the wrong traffic and the user has confirmed which ones to drop, by name. Do not use when: the user wants to "pause" a theme — Google has no paused state for signals, so removal is the only option and it is not reversible in the reporting sense. Say that first. Requires confirm_delete: true. Pass the exact theme texts; anything that is not on the asset group is reported back rather than silently ignored. Removing every theme does not stop the campaign: Performance Max keeps serving from the assets and the landing page. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_remove_search_themes", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "asset_group_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Asset group id as a string of digits."
        },
        "search_themes": {
          "minItems": 1,
          "maxItems": 25,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "description": "The exact theme texts to remove, as google_get_search_themes shows them."
        },
        "confirm_delete": {
          "description": "Must be true to remove anything. Ask the user first.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "asset_group_id",
        "search_themes"
      ]
    }
  • google_search_audiencesread-onlyfree

    Find audiences, user lists and interest segments

    Finds audiences usable in this account: the account's own audiences and remarketing/customer lists, plus Google's in-market and affinity interest segments when you pass a search term. Use when: you need an audience id for google_add_audience_signal, or the user asks what audiences exist. Do not use when: the user means keywords or locations — those are google_research_keywords and google_resolve_locations. Remarketing lists need a minimum size before they can be used (roughly 1,000 for Search), and the reported size is an estimate. A list that is too small is returned with its size so you can tell the user why it will not work, rather than it silently doing nothing. This is a resolver: never invent an audience id, always pass one this tool returned. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_search_audiences", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "search": {
          "description": "Term to match against audience and list names, and against Google in-market/affinity segment names.",
          "type": "string",
          "minLength": 2,
          "maxLength": 60
        },
        "include_interest_segments": {
          "description": "Include Google's in-market and affinity segments. Requires a search term; defaults to true when one is given.",
          "type": "boolean"
        },
        "limit": {
          "description": "Maximum rows per group (default 25).",
          "type": "integer",
          "minimum": 1,
          "maximum": 50
        }
      }
    }

assets10

  • google_add_call_assetwrite1 task

    Add a call extension

    Creates a call asset — a phone number shown next to the ad — and links it to the account, a campaign or an ad group. Use when: the user gave you the number and the country it belongs to, and someone actually answers it during the hours the ads run. Do not use when: the number is not staffed, or the user has not confirmed it. Never guess a phone number. The number must be in the country given by country_code and pass Google's verification; numbers that fail verification stop serving silently. Calls are charged like clicks. An identical number already linked at the same level is skipped. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_add_call_asset", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Attach at campaign level. Leave out to attach to the whole account.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "ad_group_id": {
          "description": "Attach at ad group level. Narrower than campaign level.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "phone_number": {
          "type": "string",
          "minLength": 5,
          "maxLength": 30,
          "description": "The phone number as a user would dial it locally, e.g. \"020 7946 0958\"."
        },
        "country_code": {
          "type": "string",
          "minLength": 2,
          "maxLength": 2,
          "description": "Two-letter country code the number belongs to, e.g. \"GB\"."
        },
        "call_conversion_reporting": {
          "description": "Use the account-level call conversion action. Defaults to the account setting; only set it when the user asked.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "phone_number",
        "country_code"
      ]
    }
  • google_add_calloutswrite1 task

    Add callout extensions

    Creates callout assets — short, non-clickable phrases such as "Free shipping" or "Cancel anytime" — and links them to the account, a campaign or an ad group. Use when: the user has given the phrases, or approved ones you proposed. Do not use when: the phrase is a claim the business cannot back up, contains a phone number or a URL, or repeats a headline — Google disapproves those. Each callout is 25 characters or fewer. Google shows at least two at a time, so four or more is the useful minimum. Identical callouts already linked at the same level are skipped. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_add_callouts", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Attach at campaign level. Leave out to attach to the whole account.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "ad_group_id": {
          "description": "Attach at ad group level. Narrower than campaign level.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "callouts": {
          "minItems": 1,
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 25
          },
          "description": "Callout phrases, 25 characters or fewer each (12 in Chinese, Japanese or Korean, whose characters count as 2)."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "callouts"
      ]
    }
  • google_add_image_assetswrite1 task

    Upload image assets from URLs

    Downloads images from https URLs, checks them against Google's rules (PNG/JPEG/static GIF, at most 5120 KB, the aspect ratio and minimum size of the slot they are for) and uploads the ones that pass as image assets. Use when: the user gave you image URLs and you are about to build a Performance Max asset group or add image extensions. Do not use when: you have not seen the images pass google_validate_and_prepare_assets and the user has not confirmed which slot each one is for — an image in the wrong slot is rejected by Google with an unhelpful error. Nothing is uploaded unless every image passes: a half-uploaded set is harder to clean up than a refusal. Google silently drops a new name for an image whose bytes already exist, so re-uploading the same file is safe but keeps the original name. The upload creates assets in the account library; it does not link them to anything. Link them with google_add_sitelinks-style tools or by building an asset group. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_add_image_assets", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "images": {
          "minItems": 1,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "format": "uri",
                "description": "The image file (PNG, JPEG or static GIF). Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet."
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128,
                "description": "A name for the asset library, e.g. \"Spring sale — square\"."
              },
              "field_type": {
                "type": "string",
                "enum": [
                  "MARKETING_IMAGE",
                  "SQUARE_MARKETING_IMAGE",
                  "PORTRAIT_MARKETING_IMAGE",
                  "LOGO",
                  "LANDSCAPE_LOGO"
                ],
                "description": "Which slot the image fills. Each slot has its own aspect ratio; the tool checks it before uploading."
              }
            },
            "required": [
              "url",
              "name",
              "field_type"
            ]
          },
          "description": "The images to upload."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "images"
      ]
    }
  • google_add_sitelinkswrite1 task

    Add sitelink extensions

    Creates sitelink assets and links them to the account, a campaign or an ad group. A sitelink is a short label plus a URL that appears under the ad. Use when: the user has given the link texts and the URLs they point to. Do not use when: you would be inventing the destinations — a sitelink pointing at a page that does not exist is a bad ad and a disapproval risk. Ask for the URLs. Google wants at least four sitelinks before it shows them, and both description lines together or neither. Identical sitelinks already linked at the same level are skipped, not duplicated. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_add_sitelinks", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Attach at campaign level. Leave out to attach to the whole account.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "ad_group_id": {
          "description": "Attach at ad group level. Narrower than campaign level.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "sitelinks": {
          "minItems": 1,
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "link_text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 25,
                "description": "The clickable text, 25 characters or fewer (12 in Chinese, Japanese or Korean, whose characters count as 2)."
              },
              "final_url": {
                "type": "string",
                "format": "uri",
                "description": "Where the sitelink goes. Must be an https URL."
              },
              "description1": {
                "description": "First description line, 35 characters. Give both lines or neither.",
                "type": "string",
                "maxLength": 35
              },
              "description2": {
                "description": "Second description line, 35 characters.",
                "type": "string",
                "maxLength": 35
              }
            },
            "required": [
              "link_text",
              "final_url"
            ]
          },
          "description": "The sitelinks to add."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "sitelinks"
      ]
    }
  • google_add_structured_snippetswrite1 task

    Add structured snippet extensions

    Creates a structured snippet asset — a fixed header such as "Types" or "Brands" followed by 3–10 values — and links it to the account, a campaign or an ad group. Use when: the user has a list that genuinely fits one of Google's headers. Do not use when: no header fits. Google only accepts its own header list (Amenities, Brands, Courses, Degree programs, Destinations, Featured hotels, Insurance coverage, Models, Neighborhoods, Service catalog, Shows, Styles, Types); inventing one fails. Callouts are the right tool for free-form selling points. The header must be Google's exact translation for the language of the ads. Pass header in English and, when the ads are not in English, language as Google's locale code (for example "de", "fr", "ja", "pt-BR", "zh-TW"): the tool sends Google's translated header, and refuses a header Google has not translated for that locale. When language is left out the header is sent in English and the preview warns if the campaign targets other languages. If you do not know the language of the ads, ask. Each value is 25 characters or fewer (12 in Chinese, Japanese or Korean) and at least three values are required. An identical header+values snippet already linked at the same level is skipped. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_add_structured_snippets", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Attach at campaign level. Leave out to attach to the whole account.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "ad_group_id": {
          "description": "Attach at ad group level. Narrower than campaign level.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "header": {
          "type": "string",
          "enum": [
            "Amenities",
            "Brands",
            "Courses",
            "Degree programs",
            "Destinations",
            "Featured hotels",
            "Insurance coverage",
            "Models",
            "Neighborhoods",
            "Service catalog",
            "Shows",
            "Styles",
            "Types"
          ],
          "description": "One of Google's predefined snippet headers, in English; see language."
        },
        "language": {
          "description": "Google locale code of the ad text, e.g. \"de\", \"es-419\", \"ja\". The header is sent as Google's exact translation for it. Omit for English.",
          "type": "string",
          "enum": [
            "en",
            "ar",
            "bg",
            "ca",
            "zh-HK",
            "zh-CN",
            "zh-TW",
            "hr",
            "cs",
            "da",
            "nl",
            "en-AU",
            "en-GB",
            "et",
            "fil",
            "fi",
            "fr",
            "de",
            "el",
            "iw",
            "hi",
            "hu",
            "id",
            "it",
            "ja",
            "ko",
            "lv",
            "lt",
            "ms",
            "no",
            "pl",
            "pt-BR",
            "pt-PT",
            "ro",
            "ru",
            "sr",
            "sk",
            "sl",
            "es-419",
            "es",
            "sv",
            "th",
            "tr",
            "uk",
            "vi"
          ]
        },
        "values": {
          "minItems": 3,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 25
          },
          "description": "3–10 values, 25 characters or fewer each."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "header",
        "values"
      ]
    }
  • google_get_asset_performanceread-onlyfree

    Per-asset performance labels (BEST / GOOD / LOW)

    Returns Google's per-asset performance labels for responsive search ads — BEST, GOOD, LOW, LEARNING or PENDING — for each headline and description, with the impressions behind them. Use when: the user wants to improve an existing ad and needs to know which parts to replace. Do not use when: the ad is new. Assets stay LEARNING until Google has served them enough, and replacing a LEARNING asset restarts that clock for nothing. The labels are relative to the other assets in the same ad, not absolute quality: in a strong ad, some asset still has to be LOW. Replace LOW assets with genuinely different ideas rather than reworded versions of the BEST one, or the next report says the same thing. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_asset_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "ad_group_id": {
          "description": "Limit to one ad group.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "Maximum assets (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        }
      }
    }
  • google_list_assetsread-onlyfree

    List Google Ads assets and where they are used

    Lists the assets (extensions) that exist in the account and which campaigns or ad groups they are linked to. Assets live once in an account library and are then linked in one or more places, so the same sitelink can serve on several campaigns. Use when: you need asset ids before linking, removing or building a Performance Max asset group, or the user asks what extensions are running. Do not use when: the user wants ad copy (google_get_ad_creative) or per-asset performance labels (google_get_asset_performance). Returns one row per asset with its type, content and the campaigns/ad groups it is linked to. Paging is by asset id: pass the returned next_after_asset_id as after_asset_id to get the next page. Business names are stored as plain text assets, so asking for BUSINESS_NAME lists text assets; the link field type is what makes one a business name. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_list_assets", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "asset_types": {
          "description": "Asset types to include. Defaults to every type listed here.",
          "minItems": 1,
          "maxItems": 6,
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "SITELINK",
              "CALLOUT",
              "STRUCTURED_SNIPPET",
              "CALL",
              "BUSINESS_NAME",
              "IMAGE"
            ]
          }
        },
        "campaign_id": {
          "description": "Only show assets linked to this campaign (and account-level assets it inherits).",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "limit": {
          "description": "Maximum assets per page (default 50).",
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        },
        "after_asset_id": {
          "description": "Page cursor: the next_after_asset_id from the previous call.",
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      }
    }
  • google_remove_asset_linksdestructive write1 task

    Unlink assets from a campaign or ad group

    Removes the link between an asset and a campaign, ad group or the account, so the extension stops serving there. The asset itself stays in the account library and can be linked again later. Use when: the user has confirmed which extensions should stop showing, by name. Do not use when: the user said "pause" or "turn off for now" and you have not explained that Google has no pause for asset links — removing and re-linking is the only path, and the link's history is lost. Requires confirm_delete: true. Pass the link resource names from google_list_assets (each asset's links[].link_resource_name), not asset ids: an asset id alone does not say which of its links you mean, and an asset with an empty links list is not linked anywhere, so there is nothing to remove. Removing a link is immediate. Nothing else about the campaign changes. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_remove_asset_links", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "link_resource_names": {
          "minItems": 1,
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 10,
            "maxLength": 200
          },
          "description": "Link resource names from google_list_assets, e.g. \"customers/123/campaignAssets/456~SITELINK\"."
        },
        "confirm_delete": {
          "description": "Must be true to remove anything. Ask the user first.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "link_resource_names"
      ]
    }
  • google_set_business_namewrite1 task

    Set the business name asset

    Creates a text asset holding the business name and links it as the BUSINESS_NAME asset of a campaign (or of the account). Use when: you are building a Performance Max campaign, or a campaign is missing its business name. Do not use when: the campaign already has one — Google allows a single business name per campaign and a second link is rejected. Check google_list_assets first. A business name is linked to a campaign or to the whole account; Google has no ad-group-level business name, so passing ad_group_id is refused with that explanation instead of failing at Google. Google requires advertiser verification on the account before a business name asset can be used; without it the call comes back CUSTOMER_NOT_VERIFIED. That is the account's state, not the arguments: tell the user to finish verification in Google Ads and do not retry with different text. The name is 25 characters or fewer and should be the business as customers know it, not a slogan. An identical name already linked at the same level is skipped. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_set_business_name", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Attach at campaign level. Leave out to attach to the whole account.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "ad_group_id": {
          "description": "Not usable for a business name: Google links one only to a campaign or to the whole account. Leave it out.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "business_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 25,
          "description": "The business name as customers know it, 25 characters or fewer (Chinese, Japanese and Korean characters count as 2)."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "business_name"
      ]
    }
  • google_validate_and_prepare_assetsread-onlyfree

    Check assets against Google rules before uploading

    Checks assets against Google's published rules without writing anything: image URLs are downloaded and measured (format, file size, dimensions, aspect ratio for the slot), text is length-checked, and every URL is checked for being a valid https address. Use when: before google_add_image_assets or google_create_pmax_campaign, so the user learns about a wrong crop before a half-built campaign does. Do not use when: you want per-asset performance (google_get_asset_performance) or the account's existing assets (google_list_assets). This call is free, needs no connection to a campaign and changes nothing. It reports each asset as ok or with the exact reason it would be rejected. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_validate_and_prepare_assets", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "images": {
          "description": "The image files to download and measure. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet.",
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "field_type": {
                "type": "string",
                "enum": [
                  "MARKETING_IMAGE",
                  "SQUARE_MARKETING_IMAGE",
                  "PORTRAIT_MARKETING_IMAGE",
                  "LOGO",
                  "LANDSCAPE_LOGO"
                ],
                "description": "Which slot you intend to use the image for."
              }
            },
            "required": [
              "url",
              "field_type"
            ]
          }
        },
        "headlines": {
          "description": "Headlines to length-check (30; each Chinese, Japanese or Korean character counts as 2).",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "long_headlines": {
          "description": "Performance Max long headlines to length-check (90).",
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "descriptions": {
          "description": "Descriptions to length-check (90; the first one 60).",
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "business_name": {
          "description": "Business name to length-check (25).",
          "type": "string"
        },
        "final_url": {
          "description": "Landing page URL to check.",
          "type": "string"
        }
      }
    }

keywords7

  • google_add_keywordswrite1 task

    Add keywords to a Google Ads ad group

    Adds keywords to one existing ad group, with an optional max CPC per keyword. Keywords that already exist in that ad group with the same match type are skipped rather than duplicated, and the result says which ones were skipped. Use when: the user has agreed on specific keywords, usually after google_research_keywords or google_analyze_search_terms. Do not use when: you would be guessing the terms, or when the campaign only accepts broad match — some newer campaign types reject exact and phrase keywords outright, and Google will refuse the whole batch with a broad-match-only error. If that happens, tell the user rather than retrying with different match types. Write keyword text plainly: no quotation marks for phrase and no square brackets for exact — the match type is a separate field. Bids are decimals in the account currency and are ignored by campaigns using automated bidding. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_add_keywords", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Ad group id as returned by google_get_campaign_structure."
        },
        "keywords": {
          "minItems": 1,
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80,
                "description": "Keyword text without match-type punctuation (no quotes or brackets)."
              },
              "match_type": {
                "type": "string",
                "enum": [
                  "EXACT",
                  "PHRASE",
                  "BROAD"
                ]
              },
              "cpc_bid": {
                "description": "Optional max CPC, decimal in the account currency.",
                "type": "number",
                "exclusiveMinimum": 0
              }
            },
            "required": [
              "text",
              "match_type"
            ]
          },
          "description": "Keywords to add to this ad group."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "ad_group_id",
        "keywords"
      ]
    }
  • google_add_negative_keywordswrite1 task

    Add campaign-level negative keywords

    Blocks queries from triggering ads anywhere in one campaign. Negatives that already exist are skipped, and the result lists them. Use when: google_analyze_search_terms found wasted queries and the user has approved the list. Do not use when: the term also appears in queries that converted — blocking it costs real conversions. Show the user the list and get an explicit yes first. Match types work differently for negatives than for normal keywords: a broad negative blocks queries containing all of its words in any order, a phrase negative blocks queries containing the words in that order, and an exact negative blocks only that precise query. Negatives never expand to synonyms or close variants, so plurals and misspellings need their own entries. Adding a negative is reversible with google_remove_negative_keywords, but removal is permanent, so prefer adding fewer, well-understood negatives. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_add_negative_keywords", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "keywords": {
          "minItems": 1,
          "maxItems": 200,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "match_type": {
                "type": "string",
                "enum": [
                  "EXACT",
                  "PHRASE",
                  "BROAD"
                ]
              }
            },
            "required": [
              "text",
              "match_type"
            ]
          },
          "description": "Negative keywords to add at campaign level."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id",
        "keywords"
      ]
    }
  • google_bulk_update_keyword_bidsdestructive write1 task

    Change many keyword bids at once

    Sets the maximum CPC on up to 300 keywords in one call. Bids are decimals in the account currency; Adako converts them for Google. Use when: the campaign uses manual bidding and the user has given the new amounts, or approved amounts you proposed from google_get_keyword_performance. Do not use when: the campaign runs Smart Bidding (maximise conversions, target CPA, target ROAS). Keyword bids are ignored there, so setting them looks like an action but changes nothing — check the strategy with google_list_campaigns and say so instead. Each id is resolved first: the preview shows the keyword text and its current bid next to the new one, and keywords that already have the requested bid are skipped. Ids that are not keywords in this account are reported back. Raising bids raises cost per click immediately, so the effect on the daily budget is immediate too. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_bulk_update_keyword_bids", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "bids": {
          "minItems": 1,
          "maxItems": 300,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "keyword_id": {
                "type": "string",
                "pattern": "^[0-9]+$",
                "description": "Keyword criterion id."
              },
              "cpc_bid": {
                "type": "number",
                "exclusiveMinimum": 0,
                "description": "New maximum CPC as a decimal in the account currency. Never micros. Rounded to the smallest amount Google bills in that currency."
              }
            },
            "required": [
              "keyword_id",
              "cpc_bid"
            ]
          },
          "description": "One entry per keyword, at most 300 per call."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "bids"
      ]
    }
  • google_bulk_update_keyword_statusdestructive write1 task

    Pause or enable many keywords at once

    Pauses or enables up to 300 keywords in one call. Each id is looked up first, so the preview shows the actual keyword text, match type and current state — never a bare count. Use when: you have a concrete list of keyword ids from an analysis tool (google_analyze_wasted_spend, google_get_keyword_performance) and the user has seen and agreed to the list. Do not use when: the list came from your own guesswork, or when the user asked to *remove* keywords — pausing is not removing, and Adako does not delete keywords. Ids that are not keywords in this account are reported back rather than silently ignored. Keywords already in the requested state are skipped. If Google rejects individual rows, the rest still apply and every rejected row is named. Pausing a whole ad group's worth of keywords stops that ad group serving; check what is left before you do it. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_bulk_update_keyword_status", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "keyword_ids": {
          "minItems": 1,
          "maxItems": 300,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "description": "Keyword criterion ids from google_get_keyword_performance. At most 300 per call."
        },
        "status": {
          "type": "string",
          "enum": [
            "ENABLED",
            "PAUSED"
          ],
          "description": "The state to put every listed keyword into."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "keyword_ids",
        "status"
      ]
    }
  • google_get_keyword_performanceread-onlyfree

    Google Ads keyword performance (top spenders)

    Reports the highest-spending keywords for a period with match type, state, quality score, clicks, conversions, CPA and ROAS. Use when: the user asks which keywords work, which are expensive, or you need keyword ids and current bids before changing them. Do not use when: the user asks about the actual search queries people typed — those are search terms, not keywords (google_analyze_search_terms). Returns the top N by spend, so a keyword missing from the list simply spent less than the ones shown. Pass either date_range or start_date + end_date, never both. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_keyword_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "ad_group_id": {
          "description": "Limit to one ad group.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "How many keywords by spend (default 25).",
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        }
      }
    }
  • google_remove_negative_keywordsdestructive write1 task

    Remove campaign negative keywords (permanent)

    Deletes negative keywords from a campaign so those queries can trigger ads again. This is the only tool in Adako that removes anything, and the removal is permanent: a removed criterion cannot be restored, only re-added as a new one. Use when: the user has confirmed that a specific negative is blocking traffic they want, and you have shown them the exact list by name. Do not use when: the user asked to "turn off", "stop" or "pause" something — that is never a removal. Negatives cannot be paused; if the user is unsure, leave them in place. Requires confirm_delete: true. Ask the user first, in plain words, naming every negative that will be deleted. Ids that are not negatives on this campaign are reported back rather than silently ignored. Removing a negative widens matching immediately, which can increase spend. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_remove_negative_keywords", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "keyword_ids": {
          "minItems": 1,
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "description": "Criterion ids of the campaign negatives to remove, from google_get_campaign_targeting."
        },
        "confirm_delete": {
          "description": "Must be true to remove anything. Ask the user first.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id",
        "keyword_ids"
      ]
    }
  • google_update_keyworddestructive write1 task

    Change a keyword bid or state

    Pauses, re-enables or re-bids one keyword. Use when: a specific keyword is over- or under-spending and the user has agreed what to do about it. Do not use when: you want to change the keyword text or its match type — neither can be edited after creation. Add a new keyword with google_add_keywords and pause the old one instead, and say that is what you are doing. Bids are decimals in the account currency. A max CPC is ignored while the campaign uses an automated bidding strategy, so check the strategy with google_list_campaigns before promising the bid will take effect. Pausing a keyword stops it matching; it is not deleted and can be re-enabled at any time. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_update_keyword", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Ad group id as returned by google_get_campaign_structure."
        },
        "keyword_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Criterion id of the keyword, from google_get_campaign_structure or google_get_keyword_performance."
        },
        "status": {
          "description": "Turn the keyword on or off.",
          "type": "string",
          "enum": [
            "ENABLED",
            "PAUSED"
          ]
        },
        "cpc_bid": {
          "description": "New max CPC as a decimal in the account currency, rounded to the smallest amount Google bills in that currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "ad_group_id",
        "keyword_id"
      ]
    }

analysis7

  • google_analyze_search_termsread-onlyfree

    Analyse Google Ads search terms and suggest negatives

    Reads the actual queries people typed before an ad showed, then sorts them into three buckets: converting (at least one conversion), wasted (spend at or above the threshold with no conversions — by default the cost of 10 average clicks in this account, or wasted_threshold in the account currency) and new (not yet added as a keyword or a negative). It also proposes negative keywords with a match type and says why each was chosen. Use when: the user asks where their budget is going, wants negative keyword ideas, or wants to find keywords worth adding. Do not use when: the user means the keywords they bid on rather than the queries that matched them (google_get_keyword_performance). Suggestions are proposals only — nothing is added. To apply them call google_add_negative_keywords, and show the user the list first. Search term data lags roughly a day and Google hides low-volume queries, so the numbers here will not add up exactly to campaign spend. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_analyze_search_terms", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "wasted_threshold": {
          "description": "Minimum spend (decimal, in the account currency) before a zero-conversion term counts as wasted. Default: the cost of 10 average clicks in this account over the period, so it means the same in every currency.",
          "type": "number",
          "minimum": 0
        },
        "limit": {
          "description": "How many terms to pull, by spend (default 200).",
          "type": "integer",
          "minimum": 10,
          "maximum": 500
        }
      }
    }
  • google_analyze_wasted_spendread-onlyfree

    Find Google Ads spend with nothing to show for it

    Finds keywords, and the campaigns they sit in, that spent real money over the period and recorded zero conversions. The rule is deliberately conservative so it never flags something that simply has not had a chance yet: a keyword must have spent at least the threshold — never less than the cost of 20 average clicks in this account, so the floor means the same in every currency — must have zero conversions, and its campaign must have been running for more than 14 days. Anything newer is in its learning period and is excluded and counted separately. Use when: the user asks where money is being wasted, wants to cut costs, or asks for a spend audit. Do not use when: conversion tracking may be missing entirely — check google_list_conversion_actions first, because zero conversions everywhere usually means broken tracking rather than bad keywords. Returns candidates only. Pausing anything is a separate, explicit step the user must approve. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_analyze_wasted_spend", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "min_spend": {
          "description": "Minimum spend (decimal, in the account currency) for a keyword to count. Raised to the floor — the cost of 20 average clicks in this account over the period — if lower. Default: that floor.",
          "type": "number",
          "minimum": 0
        },
        "limit": {
          "description": "How many keyword rows to scan, by spend (default 300).",
          "type": "integer",
          "minimum": 10,
          "maximum": 500
        }
      }
    }
  • google_explain_performance_anomalyread-onlyfree

    Explain why a metric moved

    Decomposes a period-over-period change in one metric across campaigns, devices, networks, countries and search terms, and ranks the segments by how much of the total change each one accounts for. Use when: the user says something dropped, spiked or "looks wrong" and wants to know why. This is the right first tool for that question. Do not use when: the user just wants the numbers (google_get_campaign_performance) or a single dimension they already suspect (google_get_device_performance, google_get_geo_performance). Returns, per dimension, the segments that moved most, with their share of the total change. Read the top one or two lines and say what they mean — a change concentrated in one campaign is an account change, one spread evenly across every dimension is usually seasonality or an auction-wide shift you did not cause. Two cautions worth repeating to the user: a short window makes everything look dramatic, and conversions keep arriving for days after the click, so the most recent days always look worse than they will turn out to be. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_explain_performance_anomaly", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "metric": {
          "description": "Which metric moved. Defaults to cost.",
          "type": "string",
          "enum": [
            "cost",
            "clicks",
            "impressions",
            "conversions",
            "conversion_value",
            "cpa",
            "roas"
          ]
        },
        "campaign_id": {
          "description": "Limit the whole analysis to one campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "include_search_terms": {
          "description": "Include the search-term breakdown (default true). It is the slowest dimension.",
          "type": "boolean"
        }
      }
    }
  • google_get_benchmark_contextread-onlyfree

    Indicative industry benchmarks for Google Search

    Returns indicative Google Search medians — click-through rate, cost per click and landing-page conversion rate — for an industry. The table is static, so this call is free, needs no connection and touches no API. Use when: the user asks whether a number is normal, or wants a sanity check before setting expectations. Do not use when: the user wants a target. These are medians across thousands of accounts with different offers, prices, landing pages and countries, gathered from public industry surveys and denominated in US dollars. They cannot tell anyone what their CPC *should* be, and quoting them as a goal is misleading. The honest benchmark for an account is its own recent history: compare against google_get_campaign_performance over the previous period first, and use this table only to say whether something is in the right order of magnitude. Pass a vertical, or call it with nothing to see the whole table. If the user's industry is not listed, say so rather than picking the nearest row and presenting it as theirs. CPC medians are US dollars. A CPC in another currency is only compared once it has been converted to US dollars and passed with compare_cpc_currency "USD"; otherwise the tool shows it next to the median without a reading. Never present a euro or yen CPC as above or below a dollar median. If you are unsure which industry the user is in, ask them — do not guess.

    How to call: google_ads(action="execute", tool_name="google_get_benchmark_context", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "vertical": {
          "description": "Industry to look up, e.g. \"legal\", \"e-commerce\", \"home improvement\".",
          "type": "string",
          "minLength": 2,
          "maxLength": 60
        },
        "compare_ctr": {
          "description": "The account's own CTR as a fraction (0.042 for 4.2%), to put next to the median.",
          "type": "number",
          "minimum": 0,
          "maximum": 1
        },
        "compare_cpc": {
          "description": "The account's own average CPC, for the same comparison. The medians are in US dollars: pass compare_cpc_currency when this CPC is in another currency.",
          "type": "number",
          "minimum": 0
        },
        "compare_cpc_currency": {
          "description": "ISO code of compare_cpc, e.g. \"EUR\" — the account currency list_connected_accounts shows. Default USD. A CPC in any other currency is shown but not compared, because the medians are US dollars and this tool does no currency conversion.",
          "type": "string",
          "pattern": "^[A-Za-z]{3}$"
        },
        "compare_conversion_rate": {
          "description": "The account's own conversion rate as a fraction.",
          "type": "number",
          "minimum": 0,
          "maximum": 1
        }
      }
    }
  • google_get_geo_performanceread-onlyfree

    Performance by country

    Splits spend, clicks, conversions, CPA and ROAS by country for the period, using Google's geographic report. Use when: the user asks where their traffic or conversions come from, or before changing location targeting. Do not use when: the user wants city or region detail — this tool reports countries, and claiming finer granularity than it returns would be wrong. Spend appearing in a country the campaign does not target usually means the campaign targets "presence or interest" rather than "presence"; that is a campaign setting, not a bug. A country with very little spend is not evidence of anything. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_geo_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "Maximum countries (default 25).",
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        }
      }
    }
  • google_get_hourly_performanceread-onlyfree

    Performance by hour and day of week

    Splits performance by hour of day and by day of week, in the account timezone. This is the evidence a schedule decision needs. Use when: the user is thinking about dayparting, or asks when their ads perform best. Do not use when: there is little data. Splitting a month across 168 hour-and-day buckets leaves most of them empty, and acting on an empty bucket is how campaigns lose their best hours. Returns the hour-of-day table and the day-of-week table separately, because the full 168-cell grid is almost never readable or reliable. Hours are in the account timezone, which may not be the user's. Before recommending a schedule, check the bidding strategy: Smart Bidding already varies bids by time of day, so a schedule mostly removes volume rather than improving efficiency. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_hourly_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        }
      }
    }
  • google_optimize_budget_allocationread-onlyfree

    Propose a budget reallocation (writes nothing)

    Produces a **plan** for moving budget between campaigns, based on what each one currently returns and whether it is actually budget-limited. **It changes nothing.** Applying any line of the plan means calling google_update_campaign_budget for that campaign, which the user approves separately. Use when: the user asks where their money should go and several campaigns have enough data to compare. Do not use when: campaigns have very different objectives (brand versus prospecting), or when a campaign has fewer than about 15 conversions in the window — the comparison is then noise dressed up as arithmetic. Say that instead of producing a plan. The heuristic: rank campaigns by cost per conversion (or by ROAS when you ask for value), raise the efficient ones that are spending their budget, lower the inefficient ones, cap every move at max_change_percentage, never go below min_daily_budget, and keep the account total the same unless you ask otherwise. Proposed budgets are whole multiples of the smallest amount Google bills in the account currency (a whole yen or won, a cent for most others), so any line can be applied exactly as shown. A campaign that is not spending its budget will not spend more just because the budget goes up: the plan says so rather than proposing a rise. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_optimize_budget_allocation", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "objective": {
          "description": "Optimise for cost per conversion (default) or for return on ad spend.",
          "type": "string",
          "enum": [
            "conversions",
            "value"
          ]
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "max_change_percentage": {
          "description": "Largest move for any one campaign, as a percentage. Default 20.",
          "type": "number",
          "minimum": 5,
          "maximum": 100
        },
        "min_daily_budget": {
          "description": "Floor no campaign goes below, as a decimal in the account currency.",
          "type": "number",
          "minimum": 0
        }
      }
    }

management16

  • google_apply_labelwrite1 task

    Apply a label to campaigns or ad groups

    Applies an existing label to campaigns and/or ad groups. Use when: the user wants to group things for reporting and the label already exists (google_list_labels, or create it with google_create_label). Do not use when: the user actually wants to change a campaign name or state — a label is only a tag. Applying a label changes nothing about delivery, bids or spend. Things that already carry the label are skipped rather than linked twice. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_apply_label", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "label_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Label id from google_list_labels."
        },
        "campaign_ids": {
          "description": "Campaigns to label.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$",
            "description": "Campaign id as returned by google_list_campaigns."
          }
        },
        "ad_group_ids": {
          "description": "Ad groups to label.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$",
            "description": "Ad group id as returned by google_get_campaign_structure."
          }
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "label_id"
      ]
    }
  • google_create_labelwrite1 task

    Create a label

    Creates a label in the account. A label is a name (and optionally a colour and a note) that can then be applied to campaigns and ad groups. Use when: the user wants a new way to group things and no existing label already means the same. Do not use when: a label with that name already exists — check google_list_labels first, because two labels with the same name are indistinguishable in reports. Creating a label changes nothing about delivery, bids or spend. Apply it afterwards with google_apply_label. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_create_label", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80,
          "description": "Label name as the user would read it."
        },
        "description": {
          "description": "Optional note about what the label means.",
          "type": "string",
          "maxLength": 200
        },
        "background_color": {
          "description": "Optional hex colour shown in the Google Ads UI, e.g. \"#2C7BE5\".",
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "name"
      ]
    }
  • google_list_bidding_strategiesread-onlyfree

    List portfolio bidding strategies

    Lists the portfolio (shared) bidding strategies in the account, their type, their target and how many campaigns use each one. An id from here goes into google_update_bid_strategy (attach_portfolio_strategy_id) to put a campaign on one, or into google_create_search_campaign (portfolio_strategy_id) to create it on one. Use when: google_update_bid_strategy has refused because a campaign is on a portfolio, or the user asks why several campaigns move together. Do not use when: you want a single campaign's bidding — google_list_campaigns already shows the strategy type per campaign. A portfolio strategy is one set of bids shared by several campaigns: changing its target changes every campaign on it. Campaigns with their own standard strategy never appear here. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_list_bidding_strategies", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        }
      }
    }
  • google_list_labelsread-onlyfree

    List labels and what they are applied to

    Lists the labels defined in the account and the campaigns and ad groups each one is applied to. Use when: the user organises work by label, or you need a label id before applying or removing one. Do not use when: the user means campaign names or campaign types — labels are a separate, optional layer. Labels change nothing about delivery, bids or spend. They exist so a person can group things; Google reports can then be filtered by them. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_list_labels", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "label_id": {
          "description": "Only show what this one label is applied to.",
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      }
    }
  • google_pause_addestructive write1 task

    Pause one ad

    Stops one ad from serving, leaving the rest of the ad group running. Use when: the ad is clearly the worst performer over a meaningful number of impressions, and the user has agreed. Do not use when: it is the only enabled ad in the ad group — the ad group then shows nothing and the keywords stop getting traffic. Check google_get_ad_performance first and say what will be left. Nothing is deleted; google_resume_ad puts it back. Responsive search ads need a few weeks to be judged fairly, so pausing after a couple of days usually just resets learning. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_pause_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "ad_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Ad id from google_get_ad_performance or google_get_ad_creative."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "ad_id"
      ]
    }
  • google_pause_ad_groupdestructive write1 task

    Pause a Google Ads ad group

    Stops one ad group inside a campaign from serving, leaving the rest of the campaign running. Use when: one ad group is clearly the problem and the user wants only that part stopped. Do not use when: the whole campaign should stop (google_pause_campaign) or when only some keywords are at fault (google_update_keyword). Nothing is deleted; the ad group can be resumed at any time. Its budget belongs to the campaign, so pausing an ad group frees that spend for the other ad groups rather than saving it. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_pause_ad_group", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Ad group id as returned by google_get_campaign_structure."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "ad_group_id"
      ]
    }
  • google_pause_campaigndestructive write1 task

    Pause a Google Ads campaign

    Stops a live campaign from serving. Spend stops as soon as Google applies the change; nothing is deleted and google_resume_campaign puts it back exactly as it was. Use when: the user has clearly said to stop a specific campaign, and you have shown them what it is currently spending. Do not use when: the user only wants to reduce spend (change the budget instead), or when the campaign is already paused — check google_list_campaigns first. Pausing is never a way to delete something. Smart Bidding strategies lose learning while paused, so a long pause can mean a slower ramp when it is resumed; mention this for conversion-based campaigns. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_pause_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • google_remove_labeldestructive write1 task

    Take a label off campaigns or ad groups

    Removes the link between a label and the campaigns or ad groups you list. The label itself is **not** deleted — it stays in the account and can be applied again. Use when: the user has said which campaigns or ad groups should stop carrying the label. Do not use when: the user wants the label gone from the account entirely. Adako does not delete labels: that has to be done in the Google Ads UI, because a deleted label disappears from every saved report that filtered on it. Requires confirm_delete: true. Items that do not carry the label are reported back rather than silently ignored. Removing a label changes nothing about delivery, bids or spend. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_remove_label", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "label_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Label id from google_list_labels."
        },
        "campaign_ids": {
          "description": "Campaigns to label.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$",
            "description": "Campaign id as returned by google_list_campaigns."
          }
        },
        "ad_group_ids": {
          "description": "Ad groups to label.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$",
            "description": "Ad group id as returned by google_get_campaign_structure."
          }
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "confirm_delete": {
          "description": "Must be true to remove anything. Ask the user first.",
          "type": "boolean"
        }
      },
      "required": [
        "label_id"
      ]
    }
  • google_resume_adwrite1 task

    Resume a paused ad

    Turns a paused ad back on. It serves again alongside the other enabled ads in its ad group. Use when: the user asked for a specific ad to run again. Do not use when: the ad was paused for a policy reason that still stands, or its landing page is still broken — check google_get_ad_creative for the approval status first. The ad group's traffic is split between all enabled ads, so resuming one takes impressions from the others. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_resume_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "ad_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Ad id from google_get_ad_performance or google_get_ad_creative."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "ad_id"
      ]
    }
  • google_resume_ad_groupwrite1 task

    Resume a paused Google Ads ad group

    Turns a paused ad group back on. Use when: the user asked to restart a specific ad group. Do not use when: its campaign is paused — the ad group will still show nothing, and the user probably means google_resume_campaign. Check the campaign state and say so. The ad group resumes with the keywords, bids and ads it already had; nothing is recreated. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_resume_ad_group", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Ad group id as returned by google_get_campaign_structure."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "ad_group_id"
      ]
    }
  • google_resume_campaignwrite1 task

    Resume a paused Google Ads campaign

    Turns a paused campaign back on. It resumes against its existing daily budget and bidding strategy. With include_paused_children: true it also turns on every paused ad group, ad and asset group inside it in the same approval. Use it for a campaign whose ad groups or ads were paused on their own (campaigns Adako created before 2026-10 start that way): a campaign shows nothing while its ad groups, ads or asset groups are paused. A campaign Adako creates now needs only the campaign turned on. Use when: the user has asked to restart a specific campaign, or said "enable all" after a create. Do not use when: the campaign was paused because of a problem that is still unresolved (broken landing page, disapproved ads, exhausted budget) — check first and say what you found. Money starts moving again immediately, so confirm the daily budget with the user before resuming. Automated bidding may need a few days to re-learn. A campaign whose end date has already passed is refused rather than resumed: Google would accept the change and serve nothing. Give it a new end date with google_update_campaign first. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_resume_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "include_paused_children": {
          "description": "Also turn on every paused ad group, ad and asset group (Performance Max) in the campaign, in the same approval. Use it when the user asked to \"enable all\" on a campaign whose ad groups or ads are paused on their own. Default false: only the campaign changes. Not for a campaign where someone paused parts on purpose.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • google_set_device_bid_modifiersdestructive write1 task

    Set device bid modifiers

    Sets the bid multiplier for phones, computers and tablets on one campaign. A multiplier of 1 leaves bids alone; 0.8 bids 20% less; 1.2 bids 20% more. Google allows 0.1 (−90%) to 10 (+900%). Use when: google_get_device_performance shows a clear, sustained difference in cost per conversion between devices and there is enough data behind it. Do not use when: the campaign uses target CPA or target ROAS. Those strategies set their own device bids and ignore these modifiers except as a hint — the change looks like an action and does nothing. Say that instead. Do not use it to switch a device off either: 0.1 is the lowest multiplier and still buys traffic. Excluding a device entirely is a different change Adako does not make. The preview shows the current and new multiplier per device. Devices you do not pass are left alone. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_set_device_bid_modifiers", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "mobile": {
          "description": "Bid multiplier for phones.",
          "type": "number",
          "minimum": 0.1,
          "maximum": 10
        },
        "desktop": {
          "description": "Bid multiplier for computers.",
          "type": "number",
          "minimum": 0.1,
          "maximum": 10
        },
        "tablet": {
          "description": "Bid multiplier for tablets.",
          "type": "number",
          "minimum": 0.1,
          "maximum": 10
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • google_update_bid_strategydestructive write1 task

    Change a campaign bidding strategy

    Changes how a campaign bids: maximise conversions (optionally with a target CPA), maximise conversion value (optionally with a target ROAS), maximise clicks, or manual CPC. Use when: the user has said what they want to optimise for and, for a target, the number they want. Do not use when: the campaign is less than two weeks old or has fewer than ~15 conversions in the last 30 days — Smart Bidding needs data, and switching resets the learning period for about a week, during which cost per conversion usually gets worse before it gets better. Say that first. Before changing anything the tool checks two things. If the campaign is driven by a *portfolio* strategy it refuses, names the portfolio and says how many campaigns share it; changing what that portfolio targets is then possible only with portfolio_strategy_id passed explicitly, because it moves every campaign on it. If you ask for conversion-based bidding it checks that the account actually has a working conversion action, and refuses with an explanation if it does not. Moving one campaign between strategies is separate and safe for the others: attach_portfolio_strategy_id puts this campaign on an existing portfolio (from google_list_bidding_strategies), and detach_from_portfolio takes it off and gives it the strategy you pass. Neither touches the portfolio or the other campaigns on it. The preview shows the before and after. Read-back confirms what Google stored. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_update_bid_strategy", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "strategy": {
          "description": "MAXIMIZE_CONVERSIONS (optionally with a target CPA), MAXIMIZE_CONVERSION_VALUE (optionally with a target ROAS), TARGET_SPEND (shown as \"Maximize clicks\" in Google Ads) or MANUAL_CPC. Required unless attach_portfolio_strategy_id moves the campaign onto a portfolio, which decides the bidding itself.",
          "type": "string",
          "enum": [
            "MAXIMIZE_CONVERSIONS",
            "MAXIMIZE_CONVERSION_VALUE",
            "TARGET_SPEND",
            "MANUAL_CPC"
          ]
        },
        "target_cpa": {
          "description": "Target cost per conversion as a decimal in the account currency. Only with MAXIMIZE_CONVERSIONS. Rounded to the smallest amount Google bills in that currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "target_roas": {
          "description": "Target return on ad spend as a multiplier (4 means 400%). Only with MAXIMIZE_CONVERSION_VALUE.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "enhanced_cpc": {
          "description": "Retired by Google and refused by the API: passing true returns an error explaining what to use instead. Kept so older callers get that explanation rather than a raw platform error. Leave it unset.",
          "type": "boolean"
        },
        "portfolio_strategy_id": {
          "description": "Required when the campaign is driven by a portfolio strategy and you are changing that strategy (which changes every campaign on it). Pass the id the refusal message gave you, after telling the user how many campaigns share it. To move this campaign onto a portfolio, or off one, use attach_portfolio_strategy_id or detach_from_portfolio instead.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "attach_portfolio_strategy_id": {
          "description": "Move this one campaign onto an existing portfolio (shared) bidding strategy, from google_list_bidding_strategies. The portfolio then decides its bidding, and nothing about the other campaigns on it changes. Its own strategy and targets are replaced, so pass no strategy with it.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "detach_from_portfolio": {
          "description": "Take this one campaign off the portfolio strategy that drives it and give it the strategy in `strategy` instead. The portfolio and every other campaign on it are untouched.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • google_update_campaigndestructive write1 task

    Update Google Ads campaign settings

    Changes a campaign's name, on/off state, end date or Search-partner setting. Only the fields you pass are touched; everything else is left alone. Use when: the user asked for one of these specific changes and named the campaign. Do not use when: the user only wants to pause or resume — google_pause_campaign and google_resume_campaign say exactly that and are easier to audit. Do not use it to change budgets (google_update_campaign_budget). Search partners default to off for a reason: that inventory sits on other search sites, cannot be excluded site by site, and usually performs differently from Google Search. Turning it on is a real change in where ads appear, so say so. Setting an end date stops delivery at the end of that day (23:59:59) in the account time zone. An end date before today, or before the campaign's start date, is refused. The preview shows the exact before/after for every field. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_update_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "name": {
          "description": "New campaign name.",
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        },
        "status": {
          "description": "Turn the campaign on or off.",
          "type": "string",
          "enum": [
            "ENABLED",
            "PAUSED"
          ]
        },
        "end_date": {
          "description": "Last day the campaign may run (YYYY-MM-DD) in the account time zone; it stops at the end of that day. Today or later.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "remove_end_date": {
          "description": "Set true to let the campaign run indefinitely.",
          "type": "boolean"
        },
        "include_search_partners": {
          "description": "Show on Google Search partner sites. Off is the safer default: partner traffic cannot be excluded per site and often converts worse.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • google_update_campaign_budgetdestructive write1 task

    Change a Google Ads campaign daily budget

    Changes the average daily budget attached to one campaign. The amount is a decimal in the account currency; Adako converts it for Google. Before changing anything the tool reads the budget and detects whether it is a *shared* budget. A shared budget is used by several campaigns at once, so raising or lowering it changes all of them. When that is the case the call is refused until you have listed the affected campaigns to the user and they agree — then call again with confirm_shared_budget: true. Use when: the user has stated a new daily amount. Do not use when: the campaign uses a portfolio bidding strategy that manages spend centrally, or the user only wants a temporary change — say what will happen instead. Google may spend up to twice the daily budget on a single day and balances it across the month, so the monthly figure is roughly the daily amount times 30.4. The amount is rounded to the smallest unit Google bills in the account currency (a whole yen or won, a cent for most others) and the preview shows the rounded figure. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_update_campaign_budget", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "daily_budget": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "New average daily budget as a decimal in the account currency (for example 75 for $75/day). Never micros. Rounded to the smallest amount Google bills in that currency."
        },
        "confirm_shared_budget": {
          "description": "Set true only after telling the user which other campaigns share this budget and getting their agreement.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id",
        "daily_budget"
      ]
    }
  • google_update_campaign_networksdestructive write1 task

    Turn search partners and display expansion on or off

    Turns a Search campaign's two extra networks on or off: Google search partners (other search sites) and display expansion (the Display Network with leftover budget). Use when: the user has asked about where their ads appear, or an audit shows partner or display traffic performing badly. Do not use when: you have not looked at what those networks are actually doing — google_get_campaign_performance segmented by network (google_explain_performance_anomaly reports it) shows whether they are worth keeping. Both default to off for good reason. Search-partner inventory cannot be excluded site by site. Display expansion buys cheap impressions with very different intent and often flatters CTR while quietly raising CPA. Turning either on is a real change in where money goes; say so before doing it. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_update_campaign_networks", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "search_partners": {
          "description": "Show on Google Search partner sites. Off is the safer default.",
          "type": "boolean"
        },
        "display_expansion": {
          "description": "Let a Search campaign also show on the Display Network with leftover budget. Off is the safer default.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id"
      ]
    }

conversions4

  • google_create_conversion_actionwrite1 task

    Create a website conversion action

    Creates a website (WEBPAGE) conversion action so the account can record a goal. Use when: the user has decided what they want to count and has a way to fire the tag on their site. Do not use when: a conversion action for the same thing already exists — duplicates double-count and make every CPA in the account wrong. Check google_list_conversion_actions first, always. **This tool creates the action ENABLED, not paused.** Every other create in Adako is paused, but a paused conversion action records nothing at all, so creating one paused would be the same as not creating it. Creating it changes no spend and no bids by itself; it only starts collecting. Creating the action is only half the job: the user still has to put the tag or the Google tag event on their site (Adako cannot do that part). Until the tag fires, the action records zero and any campaign bidding on it is bidding on nothing. Say that. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_create_conversion_action", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100,
          "description": "A name the user will recognise, e.g. \"Demo request\"."
        },
        "category": {
          "type": "string",
          "enum": [
            "DEFAULT",
            "PURCHASE",
            "LEAD",
            "SUBMIT_LEAD_FORM",
            "BOOK_APPOINTMENT",
            "REQUEST_QUOTE",
            "SIGNUP",
            "CONTACT",
            "PHONE_CALL_LEAD",
            "ADD_TO_CART",
            "BEGIN_CHECKOUT",
            "SUBSCRIBE_PAID",
            "DOWNLOAD",
            "PAGE_VIEW",
            "ENGAGEMENT",
            "OUTBOUND_CLICK",
            "QUALIFIED_LEAD"
          ],
          "description": "What kind of conversion this is."
        },
        "counting_type": {
          "description": "Defaults to ONE_PER_CLICK, which is right for leads. Use MANY_PER_CLICK for sales where a repeat purchase should count again.",
          "type": "string",
          "enum": [
            "ONE_PER_CLICK",
            "MANY_PER_CLICK"
          ]
        },
        "default_value": {
          "description": "Value per conversion as a decimal in the account currency (set as the action's default currency). Leave out when the tag sends its own value.",
          "type": "number",
          "minimum": 0
        },
        "click_lookback_days": {
          "description": "Click-through window in days; Google defaults to 30.",
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "counts_towards_conversions": {
          "description": "Primary (true, the default) means Smart Bidding optimises towards it. False makes it observation only.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "name",
        "category"
      ]
    }
  • google_get_conversion_action_performanceread-onlyfree

    Conversion action performance

    Reports, per conversion action, how many conversions it recorded in the period, their value, and whether it counts towards the "Conversions" column that Smart Bidding optimises against. Use when: the user asks whether tracking works, which goals fire, or why a campaign's conversion number looks wrong. Do not use when: you want conversions per campaign (google_get_campaign_performance) or the account's configuration only (google_list_conversion_actions, which is cheaper). A conversion action recording zero over a long window is the single most common cause of "Smart Bidding stopped working": it means the tag is broken or the page changed. Flag it plainly rather than reporting a zero without comment. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_conversion_action_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "Maximum actions (default 50).",
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        }
      }
    }
  • google_list_conversion_actionsread-onlyfree

    Google Ads conversion actions

    Lists the conversion actions set up on the account: name, state, category, how each one counts (every conversion or one per click), whether it is included in the "Conversions" column, and the click-through lookback window. Use when: conversion numbers look wrong or absent, before recommending a conversion-based bidding strategy, or when the user asks what is being tracked. Do not use when: the user wants conversion counts per campaign (use google_get_campaign_performance). Read this before blaming keywords for zero conversions: if no action is enabled and primary for a goal, the account cannot report conversions at all, and Smart Bidding on conversions will not work. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_list_conversion_actions", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        }
      }
    }
  • google_update_conversion_actiondestructive write1 task

    Change a conversion action

    Changes one conversion action: its status, category, counting type, default value, click-through window, or whether it counts towards the Conversions column. Use when: the user knows what the setting should be and why. Marking an action secondary, for example, is the correct fix when a low-value goal is dragging Smart Bidding around. Do not use when: you are not sure what the action currently tracks. Read google_get_conversion_action_performance first and show the user the current settings. Every conversion-based campaign in the account bids against the primary actions, so a change here reaches far beyond one campaign. Counting-type and category changes are not retroactive: historical conversions keep their old shape, which makes before/after comparisons misleading for the following weeks. The preview shows each field before and after; read-back confirms what Google stored. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_update_conversion_action", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "conversion_action_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "From google_list_conversion_actions."
        },
        "status": {
          "description": "Pausing a conversion action stops it recording anything new.",
          "type": "string",
          "enum": [
            "ENABLED",
            "PAUSED"
          ]
        },
        "category": {
          "description": "What kind of conversion this is.",
          "type": "string",
          "enum": [
            "DEFAULT",
            "PURCHASE",
            "LEAD",
            "SUBMIT_LEAD_FORM",
            "BOOK_APPOINTMENT",
            "REQUEST_QUOTE",
            "SIGNUP",
            "CONTACT",
            "PHONE_CALL_LEAD",
            "ADD_TO_CART",
            "BEGIN_CHECKOUT",
            "SUBSCRIBE_PAID",
            "DOWNLOAD",
            "PAGE_VIEW",
            "ENGAGEMENT",
            "OUTBOUND_CLICK",
            "QUALIFIED_LEAD"
          ]
        },
        "counting_type": {
          "description": "ONE_PER_CLICK counts one conversion per ad click (leads); MANY_PER_CLICK counts every one (sales).",
          "type": "string",
          "enum": [
            "ONE_PER_CLICK",
            "MANY_PER_CLICK"
          ]
        },
        "default_value": {
          "description": "Default value per conversion, a decimal in the conversion action's own default currency (google_get_conversion_action_performance shows it; usually the account currency).",
          "type": "number",
          "minimum": 0
        },
        "always_use_default_value": {
          "description": "Use the default value even when the tag sends its own value.",
          "type": "boolean"
        },
        "click_lookback_days": {
          "description": "Click-through conversion window in days (1–90).",
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "counts_towards_conversions": {
          "description": "Whether this action feeds the Conversions column Smart Bidding optimises against (primary) or is observation only (secondary).",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "conversion_action_id"
      ]
    }

creation3

  • google_create_pmax_campaigndestructive write1 task

    Create a Performance Max campaign (paused)

    Creates a whole Performance Max campaign in one atomic change: budget, campaign, location targeting, asset group, every text and image asset, and optional search themes and audience signal. The campaign is created PAUSED and its asset group enabled inside it, so nothing serves and nothing is spent until the campaign is turned on, and turning it on launches everything. Report that with the two links the result gives (Google Ads and the campaign's page in Adako), and offer "enable all": google_resume_campaign turns the campaign on in one approval. Use when: the user has given a budget, a landing page, the copy, the images and the countries — and understands what Performance Max is: one campaign that spends across Search, Shopping, YouTube, Display, Discover, Gmail and Maps with very little per-channel control or reporting. Do not use when: anything is missing or invented. Google needs at least 3 headlines, 1 long headline, 2 descriptions, a business name, a landscape image, a square image and a square logo; the tool refuses with a plain list of what is missing rather than sending a request that fails halfway. Do not use it when a Search campaign is what the user actually needs — Performance Max cannot be steered by keyword and will happily cannibalise an existing brand campaign. Images may be asset ids (from google_list_assets) or https URLs, which are downloaded, measured against Google's aspect-ratio rules and uploaded in the same change. Locations must be ids from google_resolve_locations; they reach people in or regularly in those places (Presence) unless location_targeting is PRESENCE_OR_INTEREST. Final-URL expansion is switched off, so traffic goes to the page you named. Text limits count each Chinese, Japanese or Korean character as 2, and the daily budget is rounded to the smallest amount Google bills in the account currency. If anything is rejected, nothing at all is created: there is no half-built campaign to clean up. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_create_pmax_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120,
          "description": "Campaign name the user will recognise."
        },
        "daily_budget": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Average daily budget as a decimal in the account currency. Never micros."
        },
        "final_url": {
          "type": "string",
          "format": "uri",
          "description": "The landing page the asset group points at (https)."
        },
        "business_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 25,
          "description": "Business name, 25 characters or fewer (Chinese, Japanese and Korean count as 2)."
        },
        "headlines": {
          "minItems": 3,
          "maxItems": 15,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 30
          },
          "description": "At least 3 headlines, 30 characters or fewer each (Chinese, Japanese and Korean characters count as 2). Five is the practical minimum."
        },
        "long_headlines": {
          "minItems": 1,
          "maxItems": 5,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 90
          },
          "description": "At least 1 long headline, 90 characters or fewer."
        },
        "descriptions": {
          "minItems": 2,
          "maxItems": 5,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 90
          },
          "description": "At least 2 descriptions; the first must be 60 characters or fewer."
        },
        "marketing_images": {
          "minItems": 1,
          "maxItems": 15,
          "type": "array",
          "items": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^[0-9]+$",
                "description": "An existing image asset id from google_list_assets."
              },
              {
                "type": "string",
                "format": "uri",
                "description": "An https URL; the image is downloaded, checked and uploaded."
              }
            ]
          },
          "description": "Landscape 1.91:1 images — asset ids or https URLs."
        },
        "square_images": {
          "minItems": 1,
          "maxItems": 15,
          "type": "array",
          "items": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^[0-9]+$",
                "description": "An existing image asset id from google_list_assets."
              },
              {
                "type": "string",
                "format": "uri",
                "description": "An https URL; the image is downloaded, checked and uploaded."
              }
            ]
          },
          "description": "Square 1:1 images — asset ids or https URLs."
        },
        "logos": {
          "minItems": 1,
          "maxItems": 5,
          "type": "array",
          "items": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^[0-9]+$",
                "description": "An existing image asset id from google_list_assets."
              },
              {
                "type": "string",
                "format": "uri",
                "description": "An https URL; the image is downloaded, checked and uploaded."
              }
            ]
          },
          "description": "Square 1:1 logos — asset ids or https URLs."
        },
        "location_ids": {
          "minItems": 1,
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "description": "Geo target constant ids from google_resolve_locations. Free text is never accepted."
        },
        "location_targeting": {
          "description": "Who the locations reach. PRESENCE (the default here): people in or regularly in those places. PRESENCE_OR_INTEREST (Google’s own default): also people elsewhere who show interest in them, e.g. someone abroad planning a trip. Choose it only when the user wants that wider reach.",
          "type": "string",
          "enum": [
            "PRESENCE",
            "PRESENCE_OR_INTEREST"
          ]
        },
        "target_roas": {
          "description": "Optional target ROAS (4 means 400%). With it the campaign maximises conversion value; without it, conversions.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "search_themes": {
          "description": "Optional search themes to seed the asset group (at most 25).",
          "maxItems": 25,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          }
        },
        "audience_id": {
          "description": "Optional audience signal. Audience id from google_search_audiences — only an entry from its `audiences` list. The `interest_segments` and `user_lists` it also returns are different kinds of id and Google refuses them here with INVALID_COMBINED_AUDIENCE_ID (seen live 2026-09-18). An account with an empty `audiences` list has no audience to signal with; say so rather than passing an interest id.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_name",
        "daily_budget",
        "final_url",
        "business_name",
        "headlines",
        "long_headlines",
        "descriptions",
        "marketing_images",
        "square_images",
        "logos",
        "location_ids"
      ]
    }
  • google_create_responsive_search_adwrite1 task

    Create a responsive search ad (paused)

    Creates one responsive search ad inside an existing ad group. The ad is created **PAUSED**: it costs nothing and serves nothing until the user enables it with google_resume_ad, so a mistake is never live. Use when: the ad group exists, the user has approved the copy, and the landing page is the one they named. Do not use when: you would be inventing claims, prices or offers — everything in an ad must be true and must appear on the landing page. Ask rather than guess. Do not use it to "edit" an ad either: Google has no ad edit, and creating a near-duplicate splits the data for no gain. Every headline and description is checked against the same rules as google_validate_ad_copy before anything is sent: length, shouting, repeated punctuation, duplicates. A failing ad is refused with the exact reason. Google needs at least three headlines and two descriptions; eight to ten distinct headlines is what earns a good ad strength. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_create_responsive_search_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "The ad group the new ad goes into."
        },
        "headlines": {
          "minItems": 3,
          "maxItems": 15,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 30
          },
          "description": "3–15 headlines, 30 characters or fewer each (each Chinese, Japanese or Korean character counts as 2). Aim for 8–10 genuinely different ones."
        },
        "descriptions": {
          "minItems": 2,
          "maxItems": 4,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 90
          },
          "description": "2–4 descriptions, 90 characters or fewer each."
        },
        "final_url": {
          "type": "string",
          "format": "uri",
          "description": "The landing page, an https URL."
        },
        "path1": {
          "description": "First display path segment, 15 characters.",
          "type": "string",
          "maxLength": 15
        },
        "path2": {
          "description": "Second display path segment, 15 characters.",
          "type": "string",
          "maxLength": 15
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "ad_group_id",
        "headlines",
        "descriptions",
        "final_url"
      ]
    }
  • google_create_search_campaignwrite1 task

    Create a Google Ads Search campaign (paused)

    Creates a complete Search campaign in one atomic operation: daily budget, campaign, location and language targeting, ad groups, keywords, one responsive search ad per ad group and optional campaign-level negatives. Either everything is created or nothing is. The campaign is created PAUSED and its ad groups and ads are created enabled inside it, so no money can be spent until the campaign is turned on, and turning it on launches everything. Say this explicitly when you report the result, with the two links the result gives (Google Ads and the campaign's page in Adako), and offer "enable all": google_resume_campaign turns the campaign on in one approval. Use when: the user has agreed on a structure (budget, locations, ad groups, keywords, ad copy) and wants it built. Do not use when: anything is still undecided. Resolve place names with google_resolve_locations first (free text is rejected), take language ids from google_list_languages, check ad copy with google_validate_ad_copy, and confirm the daily budget in the account currency. Call this once per campaign: on an error, read what exists with google_list_campaigns rather than retrying. The preview runs Google's own dry run (validateOnly), so policy and structural rejections surface before anything is created. Search partners are off unless include_search_partners is true. Locations reach people in or regularly in those places (Presence) unless location_targeting is PRESENCE_OR_INTEREST, which also reaches people elsewhere who show interest in them. Leaving language_ids out sets no language criterion, so language does not restrict the campaign. Google Ads Help says that from September 2026 Search ads are matched on the language of the ad text and the campaign language setting is being removed for Search, so write the ads in the language the audience reads. Dates are whole days in the account time zone: a start date before today is refused, and an end date stops the campaign at the end of that day. Budgets and bids are decimals in the account currency, never micros, rounded to the smallest amount Google bills in that currency (whole units for JPY, KRW and a few others); the preview shows the rounded amounts. Ids are strings. Political advertising is not supported: every campaign is declared as containing no EU political advertising. If the user wants to run political ads, tell them to create the campaign in the Google Ads interface. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_create_search_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255,
          "description": "Campaign name. Must not already exist in the account."
        },
        "daily_budget": {
          "description": "Average daily budget as a decimal in the account currency (for example 50 for $50/day). Never micros. Give this or budget_id, never both.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "budget_id": {
          "description": "Join a budget that already exists instead of creating one: the `budget_id` of any campaign in google_list_campaigns. Use it for a shared budget, where several campaigns draw on one amount — the campaigns then compete for it, and changing the amount later moves all of them at once.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "bidding_strategy": {
          "description": "How Google should bid. TARGET_CPA needs target_cpa; TARGET_ROAS needs target_roas. Give this or portfolio_strategy_id, never both.",
          "type": "string",
          "enum": [
            "MAXIMIZE_CLICKS",
            "MAXIMIZE_CONVERSIONS",
            "TARGET_CPA",
            "TARGET_ROAS",
            "MANUAL_CPC"
          ]
        },
        "portfolio_strategy_id": {
          "description": "Run the campaign on a portfolio (shared) bidding strategy that already exists, from google_list_bidding_strategies. The strategy then decides the bidding for every campaign on it, so target_cpa and target_roas belong to it and are not set here.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "target_cpa": {
          "description": "Target cost per conversion, decimal in the account currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "target_roas": {
          "description": "Target return on ad spend as a ratio, e.g. 4 for 400%.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "location_ids": {
          "minItems": 1,
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "description": "Geo target constant ids from google_resolve_locations. Place names are rejected."
        },
        "location_targeting": {
          "description": "Who the locations reach. PRESENCE (the default here): people in or regularly in those places. PRESENCE_OR_INTEREST (Google’s own default): also people elsewhere who show interest in them, e.g. someone abroad planning a trip. Choose it only when the user wants that wider reach.",
          "type": "string",
          "enum": [
            "PRESENCE",
            "PRESENCE_OR_INTEREST"
          ]
        },
        "language_ids": {
          "description": "Language constant ids from google_list_languages, e.g. [\"1000\"] for English. Omit to set no language criterion, which leaves the campaign unrestricted by language.",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          }
        },
        "include_search_partners": {
          "description": "Show on Google Search partner sites too. Default false: partner traffic converts differently and is hard to segment.",
          "type": "boolean"
        },
        "start_date": {
          "description": "First day the campaign may run (YYYY-MM-DD), in the account time zone. Today or later; omit to start whenever it is enabled.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day the campaign may run (YYYY-MM-DD), in the account time zone; it stops at the end of that day.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "ad_groups": {
          "minItems": 1,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 255,
                "description": "Ad group name, unique within the campaign."
              },
              "default_bid": {
                "description": "Default max CPC for the ad group, decimal in the account currency.",
                "type": "number",
                "exclusiveMinimum": 0
              },
              "keywords": {
                "minItems": 1,
                "maxItems": 200,
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "text": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 80,
                      "description": "Keyword text, without match-type punctuation."
                    },
                    "match_type": {
                      "type": "string",
                      "enum": [
                        "EXACT",
                        "PHRASE",
                        "BROAD"
                      ],
                      "description": "EXACT, PHRASE or BROAD."
                    },
                    "cpc_bid": {
                      "description": "Optional max CPC for this keyword, decimal in the account currency.",
                      "type": "number",
                      "exclusiveMinimum": 0
                    }
                  },
                  "required": [
                    "text",
                    "match_type"
                  ]
                },
                "description": "Keywords for this ad group."
              },
              "headlines": {
                "minItems": 3,
                "maxItems": 15,
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1
                },
                "description": "Between 3 and 15 headlines, each at most 30 characters."
              },
              "descriptions": {
                "minItems": 2,
                "maxItems": 4,
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1
                },
                "description": "Between 2 and 4 descriptions, each at most 90 characters."
              },
              "path1": {
                "description": "First display path segment.",
                "type": "string",
                "maxLength": 15
              },
              "path2": {
                "description": "Second display path segment; needs path1.",
                "type": "string",
                "maxLength": 15
              },
              "final_url": {
                "type": "string",
                "format": "uri",
                "description": "Landing page for this ad group, including https://."
              }
            },
            "required": [
              "name",
              "keywords",
              "headlines",
              "descriptions",
              "final_url"
            ]
          },
          "description": "One to ten ad groups, each with keywords and one responsive search ad."
        },
        "negative_keywords": {
          "description": "Campaign-level negative keywords applied to every ad group.",
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "match_type": {
                "type": "string",
                "enum": [
                  "EXACT",
                  "PHRASE",
                  "BROAD"
                ]
              }
            },
            "required": [
              "text",
              "match_type"
            ]
          }
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_name",
        "location_ids",
        "ad_groups"
      ]
    }

structure4

  • google_get_ad_creativeread-onlyfree

    Read the copy behind an ad

    Returns the actual copy of responsive search ads: every headline and description, the display paths, the final URLs, the ad strength and Google's approval status. Use when: the user wants to see, review or rewrite what is running, or before creating a replacement ad so the new one is genuinely different. Do not use when: you want numbers per ad (google_get_ad_performance) or per headline (google_get_asset_performance). Pass ad_id for one ad, or ad_group_id for every ad in an ad group. An ad's copy cannot be edited in place on Google: changing an ad means creating a new one and pausing the old. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_ad_creative", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "ad_id": {
          "description": "One specific ad.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "ad_group_id": {
          "description": "Every ad in this ad group.",
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      },
      "description": "Pass ad_id or ad_group_id."
    }
  • google_get_campaign_structureread-onlyfree

    Google Ads campaign contents (ad groups, keywords, ads)

    Returns the inside of one campaign: its ad groups, the keywords in each (text, match type, state, quality score) and the responsive search ads (headlines, descriptions, display paths, final URL). Use when: you need ad group or keyword ids before changing anything, or the user asks what a campaign is actually targeting or saying. Do not use when: the user asks about performance numbers (use the performance tools) or about which search queries triggered ads (use google_analyze_search_terms). Paginated by ad group. When has_more is true, call again with page_token set to the returned value and merge the pages before answering; never present a partial structure as complete. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_campaign_structure", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "page_size": {
          "description": "Ad groups per page (default 5, max 20).",
          "type": "integer",
          "minimum": 1,
          "maximum": 20
        },
        "page_token": {
          "description": "Cursor from the previous call. Omit for the first page.",
          "type": "string"
        },
        "include": {
          "description": "Trim the response: summary lists ad groups only.",
          "type": "string",
          "enum": [
            "all",
            "keywords",
            "ads",
            "summary"
          ]
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • google_list_asset_groupsread-onlyfree

    List Performance Max asset groups

    Lists the asset groups in the account or in one Performance Max campaign, with their status, final URL and the ad strength Google assigns. Use when: you need an asset group id for search themes, audience signals or performance, or the user asks what a Performance Max campaign contains. Do not use when: the campaign is a Search campaign — those have ad groups (google_get_campaign_structure), not asset groups. Ad strength is Google's own judgement of asset variety, not of results. "Excellent" does not mean the campaign works; it means the asset group is well filled in. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_list_asset_groups", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one Performance Max campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      }
    }
  • google_list_campaignsread-onlyfree

    List Google Ads campaigns

    Lists the campaigns in a Google Ads account with their state, campaign type, bidding strategy, daily budget (shared or not) and last-30-day spend, clicks and conversions. Use when: you need campaign ids for any other tool, or the user asks what campaigns exist, what they cost or which are running. Do not use when: the user wants performance over a specific period (use google_get_campaign_performance) or the contents of one campaign (use google_get_campaign_structure). Returns one row per campaign, most expensive first. Budgets are decimals in the account currency; a "shared" budget is used by more than one campaign, so changing it affects all of them. A budget_id from here can be given to google_create_search_campaign as budget_id, which puts the new campaign on that same budget instead of creating another. Google does not report when a campaign was created. The nearest signal is the Started column: a campaign starts on the day it was made unless someone set a later date. Campaign ids are not creation order — never rank them by id to find the newest, and if the user asks for "the latest one", say what you are going by and let them confirm. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_list_campaigns", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "status": {
          "description": "Campaign state to include. Defaults to everything except removed campaigns.",
          "type": "string",
          "enum": [
            "ENABLED",
            "PAUSED",
            "REMOVED",
            "ALL"
          ]
        },
        "campaign_type": {
          "description": "Campaign type to include. Defaults to all types.",
          "type": "string",
          "enum": [
            "SEARCH",
            "PERFORMANCE_MAX",
            "DISPLAY",
            "SHOPPING",
            "VIDEO",
            "DEMAND_GEN",
            "ALL"
          ]
        },
        "limit": {
          "description": "Maximum campaigns to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        }
      }
    }

performance4

  • google_get_ad_group_performanceread-onlyfree

    Google Ads ad group performance

    Reports spend, clicks, CTR, conversions, CPA and ROAS per ad group for a period, optionally inside one campaign, ordered by spend. Use when: a campaign looks off and you need to see which ad group is responsible, or the user asks about a specific ad group. Do not use when: the question is about individual keywords (google_get_keyword_performance) or about the whole campaign (google_get_campaign_performance). Pass either date_range or start_date + end_date, never both. Money is a decimal in the account currency; ad group ids are strings. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_ad_group_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one campaign. Omit to see every ad group in the account.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "Maximum ad groups (default 50, by spend).",
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        }
      }
    }
  • google_get_ad_performanceread-onlyfree

    Ad performance with a period comparison

    Reports spend, clicks, CTR, conversions, CPA and ROAS for each ad, next to the immediately preceding period of the same length so the direction is visible. Use when: the user asks which ad or which creative is working, or before pausing an ad. Do not use when: the question is about keywords (google_get_keyword_performance), ad groups (google_get_ad_group_performance) or the copy itself (google_get_ad_creative). Returns one row per ad, most expensive first, with the ad strength Google assigns. Judge ads on conversions and CPA, not on CTR alone; and an ad with very few impressions has not been tested yet — say so rather than declaring a winner. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_ad_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "ad_group_id": {
          "description": "Limit to one ad group.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "Maximum ads (default 50).",
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        }
      }
    }
  • google_get_asset_group_performanceread-onlyfree

    Performance Max asset group performance

    Reports spend, clicks, conversions, CPA and ROAS per Performance Max asset group for the period. Use when: the user asks how Performance Max is doing at a level finer than the campaign. Do not use when: the user wants a channel breakdown — Google does not report Performance Max spend by channel at all, and pretending otherwise is worse than saying so. Asset groups inside one campaign share a budget, so a group with little spend is usually one Google has decided against, not one that is being starved by a setting you can change. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_asset_group_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        }
      }
    }
  • google_get_device_performanceread-onlyfree

    Performance by device

    Splits spend, clicks, conversions, CPA and ROAS by device (mobile, desktop, tablet) for the period, next to the previous period of the same length. Use when: the user asks about mobile versus desktop, or before setting device bid modifiers. Do not use when: the campaign uses Smart Bidding and the user wants to act on the split — Smart Bidding already bids per device, so the split is information, not an instruction. A device with very little spend is not evidence. Mobile usually shows a worse conversion rate on considered purchases and a better one on calls and local intent; look at the landing page before blaming the device. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_device_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "description": "Limit to one campaign.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day of the period (YYYY-MM-DD). Use with end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of the period (YYYY-MM-DD). Use with start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        }
      }
    }

targeting6

  • google_get_ad_scheduleread-onlyfree

    Read a campaign ad schedule

    Returns the campaign's ad schedule: which days and hours it may serve, and the bid modifier on each block. Use when: the user asks when ads run, or before changing a schedule so you can show what is there now. Do not use when: you want hour-of-day performance — that is google_get_hourly_performance, which is what tells you whether a schedule is a good idea. An empty schedule means the campaign runs all day, every day: that is Google's default and is not a problem by itself. Hours are in the account timezone, and end_hour 24 means midnight. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_ad_schedule", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • google_get_campaign_targetingread-onlyfree

    Google Ads campaign targeting

    Shows who and where a campaign is targeting: locations (with the resolved place names and any exclusions), languages, ad schedule, device bid adjustments and attached audiences. Use when: the user asks who is being targeted, why an ad shows in the wrong place, or before changing targeting so you can describe what exists today. Do not use when: the user asks about keywords — those are targeting too but live in google_get_campaign_structure. Location and language ids returned here are the same constant ids the write tools accept, so you can quote them directly. Nothing is changed by this call. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_get_campaign_targeting", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • google_list_languagesread-onlyfree

    Language ids for campaign targeting

    Returns Google's language constant ids, which google_update_campaign_languages needs. The table is the same in every account, so this call is free, needs no connection and touches no API. Use when: you are about to set language targeting and need the numeric ids. Do not use when: the user is asking about locations — that is google_resolve_locations. Language targeting matches the language of the user's Google interface and of the pages they browse, not the language of your keywords. Targeting only one language when your ads are in that language is usually right; adding English on top is a common accident that brings the wrong traffic. Pass a search term to filter the table. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_list_languages", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "search": {
          "description": "Filter by language name or code, e.g. \"german\" or \"de\".",
          "type": "string",
          "minLength": 1,
          "maxLength": 40
        }
      }
    }
  • google_set_ad_scheduledestructive write1 task

    Set a campaign ad schedule

    Replaces a campaign's whole ad schedule with the blocks you pass. Google has no way to edit one block, so the old schedule is removed and the new one created in the same atomic change. Use when: the user wants the campaign to run only at certain times, or to bid differently at certain times, and you have looked at google_get_hourly_performance so the decision is based on something. Do not use when: the campaign has little data. Turning ads off for two-thirds of the week on a hunch is the fastest way to make a campaign stop learning, and a Smart Bidding campaign already handles time of day itself — a schedule then mostly removes volume. **Any day you do not list becomes a day the campaign does not serve.** The preview says exactly which days go dark. Hours are in the account timezone. Blocks may not overlap and may not cross midnight; a 22:00–02:00 window is two blocks on two days. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_set_ad_schedule", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "schedule": {
          "minItems": 1,
          "maxItems": 84,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "day": {
                "type": "string",
                "enum": [
                  "MONDAY",
                  "TUESDAY",
                  "WEDNESDAY",
                  "THURSDAY",
                  "FRIDAY",
                  "SATURDAY",
                  "SUNDAY"
                ],
                "description": "Day of the week."
              },
              "start_hour": {
                "type": "integer",
                "minimum": 0,
                "maximum": 23,
                "description": "First hour the ads may serve (0–23)."
              },
              "end_hour": {
                "type": "integer",
                "minimum": 1,
                "maximum": 24,
                "description": "Hour the block ends (1–24). 24 means midnight."
              },
              "start_minute": {
                "description": "Minute within start_hour. Defaults to ZERO.",
                "type": "string",
                "enum": [
                  "ZERO",
                  "FIFTEEN",
                  "THIRTY",
                  "FORTY_FIVE"
                ]
              },
              "end_minute": {
                "description": "Minute within end_hour. Defaults to ZERO.",
                "type": "string",
                "enum": [
                  "ZERO",
                  "FIFTEEN",
                  "THIRTY",
                  "FORTY_FIVE"
                ]
              },
              "bid_modifier": {
                "description": "Optional bid multiplier for this block.",
                "type": "number",
                "minimum": 0.1,
                "maximum": 10
              }
            },
            "required": [
              "day",
              "start_hour",
              "end_hour"
            ]
          },
          "description": "The complete weekly schedule. This replaces whatever is there now: any day not listed becomes a day the campaign does not serve."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id",
        "schedule"
      ]
    }
  • google_update_campaign_languagesdestructive write1 task

    Set the languages a campaign targets

    Replaces the set of languages a campaign targets. Pass the complete list you want, not just the change: anything missing from the list is removed. Use when: the user has said which languages the campaign should reach and you have ids from google_list_languages. Do not use when: the user means the language of the ad copy. Google's language targeting matches the user's interface language and browsing, not your keywords; an English-language campaign targeting every language mostly buys traffic that cannot read the ad. The preview names the languages being added and removed. Leaving a campaign with no language criterion at all is not possible through this tool — pass at least one. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_update_campaign_languages", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "language_ids": {
          "minItems": 1,
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          },
          "description": "The complete set of languages the campaign should target, from google_list_languages. This replaces the current set."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id",
        "language_ids"
      ]
    }
  • google_update_campaign_locationsdestructive write1 task

    Change where a campaign runs

    Adds or removes the locations a campaign targets. Ids must come from google_resolve_locations; the tool verifies every id against Google and refuses anything it cannot find, so a wrong number never becomes wrong targeting. Use when: the user has named the places and you have resolved them to ids. Do not use when: the user wants to *exclude* a place rather than stop targeting it — an exclusion is a different criterion, and removing a targeted location is not the same as excluding it. The preview lists the place names, not the ids. Removing every location a campaign targets would stop it serving anywhere, so the tool refuses that outright. Location changes take effect within minutes and can move spend sharply: a new country starts competing against the same daily budget. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads_write(action="execute", tool_name="google_update_campaign_locations", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "pattern": "^[0-9]+$",
          "description": "Campaign id as returned by google_list_campaigns."
        },
        "add_location_ids": {
          "description": "Geo target constant ids to add. They must come from google_resolve_locations.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          }
        },
        "remove_location_ids": {
          "description": "Geo target constant ids to stop targeting.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          }
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        }
      },
      "required": [
        "campaign_id"
      ]
    }

research3

  • google_research_keywordsread-onlyfree

    Google Ads keyword ideas with volume and bid ranges

    Asks Google Keyword Planner for keyword ideas from seed terms or a landing page, and returns average monthly searches, competition and the top-of-page bid range for each. Use when: planning a new campaign, expanding an existing one, or checking whether a term has any real search volume. Do not use when: the user wants the performance of keywords they already run (google_get_keyword_performance) or the queries that triggered their ads (google_analyze_search_terms). Volumes are broad averages Google rounds heavily, and bid ranges are estimates, not what you will pay. Pass location_ids (from google_resolve_locations) and language_id (from google_list_languages) for the market the campaign will target: without locations Google reports worldwide volumes, and without a language it includes keywords in every language, which overstates what one country can deliver. The answer states which market the numbers cover. Keyword Planner needs at least Basic API access; on a lower access level this returns access_level_insufficient — say so plainly and suggest planning from search-term data instead. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_research_keywords", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "seed_keywords": {
          "description": "Starting terms. Give either these, a page_url, or both.",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          }
        },
        "page_url": {
          "description": "A landing page Google should read for ideas.",
          "type": "string",
          "format": "uri"
        },
        "location_ids": {
          "description": "Geo target constant ids from google_resolve_locations. Volumes are for these places; omit for worldwide volumes.",
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string",
            "pattern": "^[0-9]+$"
          }
        },
        "language_id": {
          "description": "Language constant id from google_list_languages, e.g. \"1000\" for English. Omit to include keywords in every language.",
          "type": "string",
          "pattern": "^[0-9]+$"
        },
        "include_search_partners": {
          "description": "Include the Search Network partners in the volume estimate. Default false.",
          "type": "boolean"
        },
        "limit": {
          "description": "Maximum ideas to return (default 50).",
          "type": "integer",
          "minimum": 5,
          "maximum": 200
        }
      }
    }
  • google_resolve_locationsread-onlyfree

    Turn place names into Google location ids

    Converts place names ("Berlin", "California", "United Kingdom") into the numeric geo target constant ids the write tools require. Free text never reaches a campaign: every location a campaign targets must come from this tool. Use when: the user names a place and you are about to create or change a campaign's targeting. Do not use when: you already have numeric ids from google_get_campaign_targeting — pass those straight through. A name is resolved only when exactly one place carries that exact name (within country_code when given). If several places share the name, or only places whose names merely contain it exist ("Paris" finding only "Paris 15e Arrondissement"), the tool refuses to choose and returns the candidates with their canonical names and country codes; show them to the user and ask which one they mean. A name that matches nothing is reported as unresolved, never silently dropped. This call is free and changes nothing. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_resolve_locations", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "customer_id": {
          "description": "Google Ads customer id (10 digits, dashes optional). Omit to use the primary account; list_connected_accounts shows the valid ids.",
          "type": "string"
        },
        "locations": {
          "minItems": 1,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": "Place names or numeric geo target constant ids. Ids are verified rather than searched."
        },
        "country_code": {
          "description": "Two-letter country code to disambiguate names that exist in several countries, e.g. \"GB\".",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        }
      },
      "required": [
        "locations"
      ]
    }
  • google_validate_ad_copyread-onlyfree

    Check responsive search ad copy against the RSA limits

    Checks headlines, descriptions and display paths against the responsive search ad rules before anything is sent to Google: at most 15 headlines of 30 characters, at most 4 descriptions of 90, display paths of 15, at least 3 headlines and 2 descriptions, no duplicates, no shouting in capitals and no repeated or excessive punctuation. Use when: you have drafted ad copy, or the user pasted copy and wants to know whether it will be accepted. Do not use when: the question is about policy topics (medical claims, trademarks) — this tool only checks format and style, not eligibility. Runs entirely inside Adako: no Google call, no cost, no connection required. Fix every error before calling google_create_search_campaign, which applies exactly the same rules and will refuse the whole campaign otherwise. Warnings are advice, not blockers. If you are unsure which account, campaign or period the user means, ask them — do not guess ids or dates.

    How to call: google_ads(action="execute", tool_name="google_validate_ad_copy", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "headlines": {
          "maxItems": 30,
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Headline texts, in the order you would submit them."
        },
        "descriptions": {
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Description texts."
        },
        "path1": {
          "description": "First display path segment shown after the domain.",
          "type": "string"
        },
        "path2": {
          "description": "Second display path segment. Only valid with path1.",
          "type": "string"
        },
        "final_url": {
          "description": "Landing page URL, including https://.",
          "type": "string"
        }
      },
      "required": [
        "headlines",
        "descriptions"
      ]
    }

Meta Ads43

Reads run through meta_ads, changes through meta_ads_write. Call meta_ads(action="list_tools") for the same list at runtime — it is free.

creation7

  • meta_add_adwrite2 tasks

    Add a paused ad to a Meta ad set

    Creates one ad, PAUSED, inside an ad set that already exists. The creative is either one that already exists (creative_id) or a new single-image creative built from image_url plus copy. Use when: the user wants another ad in an ad set — a new creative to test, or the same creative reused in a second audience. Do not use to create a campaign or an ad set; meta_create_image_campaign and meta_add_ad_set do that. Do not pass both creative_id and image_url: reusing a creative and making one are different intentions and the tool will not guess. Returns the new ad id (and the creative id when one was created) and reads the ad back so the paused status is confirmed. If the user has not said which ad set, ask. Adding an ad to the wrong ad set puts it in front of the wrong audience.

    How to call: meta_ads_write(action="execute", tool_name="meta_add_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The ad set the new ad goes into, from meta_list_ad_sets."
        },
        "ad_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Name for the new ad."
        },
        "creative_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Reuse an existing creative, from meta_get_ad_creatives. Pass exactly one of creative_id, image_url or image_hash."
        },
        "image_hash": {
          "type": "string",
          "minLength": 8,
          "maxLength": 200,
          "description": "An image already in the account library, from meta_list_media (its `image_hash`). Pass this instead of image_url when the file is already in the account — nothing is downloaded or uploaded, so it works for a file that has no public link at all."
        },
        "image_url": {
          "description": "The image for a brand-new single-image creative. Requires primary_text and landing_page_url. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet.",
          "type": "string",
          "format": "uri"
        },
        "primary_text": {
          "description": "Main copy for the new creative. Required with image_url.",
          "type": "string",
          "minLength": 1,
          "maxLength": 2200
        },
        "headline": {
          "description": "Headline for the new creative.",
          "type": "string",
          "maxLength": 255
        },
        "description": {
          "description": "Description line for the new creative.",
          "type": "string",
          "maxLength": 255
        },
        "landing_page_url": {
          "description": "Where the new creative sends people. Required with image_url.",
          "type": "string",
          "format": "uri"
        },
        "call_to_action": {
          "description": "Button label. Defaults to LEARN_MORE.",
          "type": "string",
          "enum": [
            "LEARN_MORE",
            "SHOP_NOW",
            "SIGN_UP",
            "SUBSCRIBE",
            "DOWNLOAD",
            "BOOK_NOW",
            "GET_QUOTE",
            "CONTACT_US",
            "APPLY_NOW",
            "GET_OFFER",
            "ORDER_NOW",
            "SEE_MORE",
            "GET_STARTED",
            "BUY_NOW",
            "DONATE_NOW",
            "NO_BUTTON"
          ]
        },
        "facebook_page_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Page the new creative runs from, from meta_list_pages."
        },
        "instagram_account_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Instagram account to run the new creative from."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_set_id",
        "ad_name"
      ]
    }
  • meta_add_ad_setwrite2 tasks

    Add a paused ad set to a Meta campaign

    Creates one new ad set, PAUSED, inside a campaign that already exists: its own audience, optimisation goal, budget and schedule. It does not create any ads — add one afterwards with meta_add_ad. Use when: the user wants to test another audience, another geography or another optimisation goal inside the same campaign. Do not use when the campaign holds the budget (campaign budget optimisation): the new ad set must not carry its own budget, and Meta rejects it. The preview says so before anything is sent. Do not pass free text for locations or interests; use meta_search_targeting first. Returns the new ad set id and reads it back so the paused status and budget are confirmed. The preview refuses what Meta would reject: unknown location keys, EU/EEA targeting without dsa_beneficiary and dsa_payor, a budget below Meta's minimum, past dates, and targeting the campaign's special ad category forbids. If the user has not said what the new ad set should target differently, ask — a duplicate of an existing ad set just splits the same budget.

    How to call: meta_ads_write(action="execute", tool_name="meta_add_ad_set", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The campaign the new ad set belongs to, from meta_list_campaigns."
        },
        "ad_set_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Name for the new ad set."
        },
        "daily_budget": {
          "description": "Daily budget as a decimal in the account currency (25 means 25 dollars, not 2500 cents). One of daily_budget or lifetime_budget is required.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "lifetime_budget": {
          "description": "Total budget for the whole flight, decimal in the account currency. Requires end_time.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "budget_level": {
          "description": "Which level holds the budget. \"ad_set\" (the default) gives this ad set its own budget, so every ad set is funded separately. \"campaign\" is Meta's campaign budget optimisation (Advantage campaign budget): the campaign holds one budget and Meta decides how much each ad set gets — ad sets added later must then carry no budget of their own. Only \"campaign\" can be edited with meta_update_campaign_budget.",
          "type": "string",
          "enum": [
            "ad_set",
            "campaign"
          ]
        },
        "start_time": {
          "description": "When delivery may start: YYYY-MM-DD (00:00 in the ad account's time zone) or an ISO timestamp with a UTC offset, e.g. 2026-10-01T09:00:00+02:00. Not in the past; today or omitted means as soon as the user activates it.",
          "type": "string"
        },
        "end_time": {
          "description": "When delivery must stop: YYYY-MM-DD (runs to 23:59:59 in the ad account's time zone) or an ISO timestamp with a UTC offset. Required with lifetime_budget.",
          "type": "string"
        },
        "optimization_goal": {
          "description": "Override what the ad set optimises for. Leave empty to use the objective default. LEAD_GENERATION (instant forms) is not supported; PAGE_LIKES promotes the Page.",
          "type": "string"
        },
        "billing_event": {
          "description": "What Meta charges for. Leave empty to use IMPRESSIONS.",
          "type": "string"
        },
        "countries": {
          "description": "ISO 3166-1 two-letter country codes from meta_search_targeting: GB, not UK; GR, not EL.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "region_keys": {
          "description": "Region keys from meta_search_targeting (never region names). Checked with Meta first.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "city_keys": {
          "description": "City keys from meta_search_targeting (never city names, postcodes or media markets). Each city is targeted with a 25 km radius.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "age_min": {
          "description": "Youngest age to target. Minimum 18.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "age_max": {
          "description": "Oldest age to target. 65 means 65 and over.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "genders": {
          "description": "Restrict by gender. Defaults to all — narrow only when the user asks.",
          "type": "string",
          "enum": [
            "all",
            "men",
            "women"
          ]
        },
        "interest_ids": {
          "description": "Interest ids from meta_search_targeting or meta_browse_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "behavior_ids": {
          "description": "Behaviour ids from meta_search_targeting or meta_browse_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "custom_audience_ids": {
          "description": "Custom audience ids from meta_list_custom_audiences.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "excluded_custom_audience_ids": {
          "description": "Custom audiences to exclude, e.g. existing customers.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "advantage_audience": {
          "description": "Let Meta deliver beyond the listed interests. Defaults to off so the targeting the user asked for is the targeting they get.",
          "type": "boolean"
        },
        "dsa_beneficiary": {
          "description": "Who benefits from the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one, including territories such as Réunion.",
          "type": "string",
          "maxLength": 512
        },
        "dsa_payor": {
          "description": "Who pays for the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one.",
          "type": "string",
          "maxLength": 512
        },
        "objective": {
          "description": "Only needed when the campaign uses a legacy objective Meta no longer accepts on create. Normally leave empty: the campaign decides.",
          "type": "string",
          "enum": [
            "OUTCOME_AWARENESS",
            "OUTCOME_TRAFFIC",
            "OUTCOME_ENGAGEMENT",
            "OUTCOME_LEADS",
            "OUTCOME_SALES",
            "OUTCOME_APP_PROMOTION"
          ]
        },
        "pixel_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Pixel for a conversion-optimised ad set; from meta_list_pixels."
        },
        "facebook_page_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The Page a PAGE_LIKES ad set promotes, from meta_list_pages. Only used with optimization_goal PAGE_LIKES; required when the account has several Pages."
        },
        "dynamic_creative": {
          "description": "Make this a dynamic creative ad set, which Meta requires before meta_create_flexible_ad can put an asset-feed ad in it. Meta allows exactly one ad in such an ad set, so leave it off unless the plan is a flexible ad.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id",
        "ad_set_name"
      ]
    }
  • meta_create_app_install_campaignwrite3 tasks

    Create a paused Meta app-install campaign

    Builds a complete app-install campaign: campaign, one ad set pointed at the right operating system, the image upload, the creative and one ad. The campaign is created PAUSED and its ad set and ad ACTIVE inside it, so nothing spends until the campaign is turned on, and turning it on launches the ad. Say that the campaign is paused when you report the result, with the two links the result gives (Ads Manager and the campaign's page in Adako), and offer "enable all": one meta_resume_entity call on the campaign, listed in the result as go_live.enable_all, one approval. Use when: the user wants installs for an app that is already registered to their business — get the application_id and store URL from meta_list_promotable_apps first. Do not use for a website or a lead form; that is meta_create_image_campaign. One campaign promotes one app on one operating system, because the store URL decides the OS. Do not pass free text for locations or interests; use meta_search_targeting. Returns the new ids and reads the statuses back so you can confirm the campaign is paused (the ad set and ad read ACTIVE, held by the campaign). iOS install reporting is limited by Apple's tracking rules unless the app uses the Meta SDK with SKAdNetwork — say so when reporting iOS results rather than treating low numbers as poor performance.

    How to call: meta_ads_write(action="execute", tool_name="meta_create_app_install_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Name shown in Ads Manager."
        },
        "application_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The app to promote, from meta_list_promotable_apps."
        },
        "object_store_url": {
          "type": "string",
          "format": "uri",
          "description": "The store listing URL. apps.apple.com means an iOS campaign, play.google.com an Android one — the tool targets the matching operating system automatically."
        },
        "daily_budget": {
          "description": "Daily budget as a decimal in the account currency (25 means 25 dollars, not 2500 cents). One of daily_budget or lifetime_budget is required.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "lifetime_budget": {
          "description": "Total budget for the whole flight, decimal in the account currency. Requires end_time.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "budget_level": {
          "description": "Which level holds the budget. \"ad_set\" (the default) gives this ad set its own budget, so every ad set is funded separately. \"campaign\" is Meta's campaign budget optimisation (Advantage campaign budget): the campaign holds one budget and Meta decides how much each ad set gets — ad sets added later must then carry no budget of their own. Only \"campaign\" can be edited with meta_update_campaign_budget.",
          "type": "string",
          "enum": [
            "ad_set",
            "campaign"
          ]
        },
        "start_time": {
          "description": "When delivery may start: YYYY-MM-DD (00:00 in the ad account's time zone) or an ISO timestamp with a UTC offset, e.g. 2026-10-01T09:00:00+02:00. Not in the past; today or omitted means as soon as the user activates it.",
          "type": "string"
        },
        "end_time": {
          "description": "When delivery must stop: YYYY-MM-DD (runs to 23:59:59 in the ad account's time zone) or an ISO timestamp with a UTC offset. Required with lifetime_budget.",
          "type": "string"
        },
        "optimization_goal": {
          "description": "Override what the ad set optimises for. Leave empty to use the objective default. LEAD_GENERATION (instant forms) is not supported; PAGE_LIKES promotes the Page.",
          "type": "string"
        },
        "billing_event": {
          "description": "What Meta charges for. Leave empty to use IMPRESSIONS.",
          "type": "string"
        },
        "countries": {
          "description": "ISO 3166-1 two-letter country codes from meta_search_targeting: GB, not UK; GR, not EL.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "region_keys": {
          "description": "Region keys from meta_search_targeting (never region names). Checked with Meta first.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "city_keys": {
          "description": "City keys from meta_search_targeting (never city names, postcodes or media markets). Each city is targeted with a 25 km radius.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "age_min": {
          "description": "Youngest age to target. Minimum 18.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "age_max": {
          "description": "Oldest age to target. 65 means 65 and over.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "genders": {
          "description": "Restrict by gender. Defaults to all — narrow only when the user asks.",
          "type": "string",
          "enum": [
            "all",
            "men",
            "women"
          ]
        },
        "interest_ids": {
          "description": "Interest ids from meta_search_targeting or meta_browse_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "behavior_ids": {
          "description": "Behaviour ids from meta_search_targeting or meta_browse_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "custom_audience_ids": {
          "description": "Custom audience ids from meta_list_custom_audiences.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "excluded_custom_audience_ids": {
          "description": "Custom audiences to exclude, e.g. existing customers.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "advantage_audience": {
          "description": "Let Meta deliver beyond the listed interests. Defaults to off so the targeting the user asked for is the targeting they get.",
          "type": "boolean"
        },
        "dsa_beneficiary": {
          "description": "Who benefits from the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one, including territories such as Réunion.",
          "type": "string",
          "maxLength": 512
        },
        "dsa_payor": {
          "description": "Who pays for the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one.",
          "type": "string",
          "maxLength": 512
        },
        "ad_set_name": {
          "description": "Ad set name. Defaults to the campaign name plus a suffix.",
          "type": "string",
          "maxLength": 200
        },
        "ad_name": {
          "description": "Ad name. Defaults to the campaign name plus a suffix.",
          "type": "string",
          "maxLength": 200
        },
        "facebook_page_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Page the ad runs from, from meta_list_pages."
        },
        "instagram_account_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Instagram account to run the ad from."
        },
        "image_url": {
          "type": "string",
          "format": "uri",
          "description": "The image the app-install ad will show. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet."
        },
        "primary_text": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2200,
          "description": "Main ad copy."
        },
        "headline": {
          "description": "Headline under the image.",
          "type": "string",
          "maxLength": 255
        },
        "description": {
          "description": "Description line.",
          "type": "string",
          "maxLength": 255
        },
        "call_to_action": {
          "description": "Button label. Defaults to INSTALL_MOBILE_APP.",
          "type": "string",
          "enum": [
            "INSTALL_MOBILE_APP",
            "USE_APP",
            "PLAY_GAME",
            "SHOP_NOW",
            "BOOK_NOW",
            "DOWNLOAD"
          ]
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_name",
        "application_id",
        "object_store_url",
        "image_url",
        "primary_text"
      ]
    }
  • meta_create_carousel_campaignwrite3 tasks

    Create a paused Meta carousel campaign

    Builds a complete carousel campaign: campaign, one ad set, one image upload per card, the creative and one ad. The campaign is created PAUSED and its ad set and ad ACTIVE inside it, so nothing spends until the campaign is turned on, and turning it on launches the ad. Say that the campaign is paused when you report the result, with the two links the result gives (Ads Manager and the campaign's page in Adako), and offer "enable all": one meta_resume_entity call on the campaign, listed in the result as go_live.enable_all, one approval. Use when: the user has two to ten distinct images (products, features, steps) with their own headlines and, optionally, their own links. Do not use for a single image — that is meta_create_image_campaign — and do not use it to add a carousel to an existing campaign, which is meta_add_ad. Do not invent card headlines or duplicate an image to reach a card count: ask the user for the real ones. Returns every new id, including one image hash per card, and reads the statuses back so you can confirm the campaign is paused (the ad set and ad read ACTIVE, held by the campaign). Card order matters unless optimize_card_order is on; say which you used when you report back.

    How to call: meta_ads_write(action="execute", tool_name="meta_create_carousel_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Name shown in Ads Manager."
        },
        "objective": {
          "type": "string",
          "enum": [
            "OUTCOME_AWARENESS",
            "OUTCOME_TRAFFIC",
            "OUTCOME_ENGAGEMENT",
            "OUTCOME_LEADS",
            "OUTCOME_SALES",
            "OUTCOME_APP_PROMOTION"
          ],
          "description": "What Meta optimises for. OUTCOME_SALES and OUTCOME_LEADS need a pixel. OUTCOME_APP_PROMOTION is refused here: use meta_create_app_install_campaign."
        },
        "daily_budget": {
          "description": "Daily budget as a decimal in the account currency (25 means 25 dollars, not 2500 cents). One of daily_budget or lifetime_budget is required.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "lifetime_budget": {
          "description": "Total budget for the whole flight, decimal in the account currency. Requires end_time.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "budget_level": {
          "description": "Which level holds the budget. \"ad_set\" (the default) gives this ad set its own budget, so every ad set is funded separately. \"campaign\" is Meta's campaign budget optimisation (Advantage campaign budget): the campaign holds one budget and Meta decides how much each ad set gets — ad sets added later must then carry no budget of their own. Only \"campaign\" can be edited with meta_update_campaign_budget.",
          "type": "string",
          "enum": [
            "ad_set",
            "campaign"
          ]
        },
        "start_time": {
          "description": "When delivery may start: YYYY-MM-DD (00:00 in the ad account's time zone) or an ISO timestamp with a UTC offset, e.g. 2026-10-01T09:00:00+02:00. Not in the past; today or omitted means as soon as the user activates it.",
          "type": "string"
        },
        "end_time": {
          "description": "When delivery must stop: YYYY-MM-DD (runs to 23:59:59 in the ad account's time zone) or an ISO timestamp with a UTC offset. Required with lifetime_budget.",
          "type": "string"
        },
        "optimization_goal": {
          "description": "Override what the ad set optimises for. Leave empty to use the objective default. LEAD_GENERATION (instant forms) is not supported; PAGE_LIKES promotes the Page.",
          "type": "string"
        },
        "billing_event": {
          "description": "What Meta charges for. Leave empty to use IMPRESSIONS.",
          "type": "string"
        },
        "countries": {
          "description": "ISO 3166-1 two-letter country codes from meta_search_targeting: GB, not UK; GR, not EL.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "region_keys": {
          "description": "Region keys from meta_search_targeting (never region names). Checked with Meta first.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "city_keys": {
          "description": "City keys from meta_search_targeting (never city names, postcodes or media markets). Each city is targeted with a 25 km radius.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "age_min": {
          "description": "Youngest age to target. Minimum 18.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "age_max": {
          "description": "Oldest age to target. 65 means 65 and over.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "genders": {
          "description": "Restrict by gender. Defaults to all — narrow only when the user asks.",
          "type": "string",
          "enum": [
            "all",
            "men",
            "women"
          ]
        },
        "interest_ids": {
          "description": "Interest ids from meta_search_targeting or meta_browse_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "behavior_ids": {
          "description": "Behaviour ids from meta_search_targeting or meta_browse_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "custom_audience_ids": {
          "description": "Custom audience ids from meta_list_custom_audiences.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "excluded_custom_audience_ids": {
          "description": "Custom audiences to exclude, e.g. existing customers.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "advantage_audience": {
          "description": "Let Meta deliver beyond the listed interests. Defaults to off so the targeting the user asked for is the targeting they get.",
          "type": "boolean"
        },
        "dsa_beneficiary": {
          "description": "Who benefits from the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one, including territories such as Réunion.",
          "type": "string",
          "maxLength": 512
        },
        "dsa_payor": {
          "description": "Who pays for the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one.",
          "type": "string",
          "maxLength": 512
        },
        "ad_set_name": {
          "description": "Ad set name. Defaults to the campaign name plus a suffix.",
          "type": "string",
          "maxLength": 200
        },
        "ad_name": {
          "description": "Ad name. Defaults to the campaign name plus a suffix.",
          "type": "string",
          "maxLength": 200
        },
        "pixel_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Pixel that records the conversion; from meta_list_pixels."
        },
        "conversion_event": {
          "description": "The pixel event to optimise for. Defaults to PURCHASE for sales, LEAD for leads.",
          "type": "string",
          "enum": [
            "PURCHASE",
            "LEAD",
            "COMPLETE_REGISTRATION",
            "ADD_TO_CART",
            "INITIATED_CHECKOUT",
            "ADD_PAYMENT_INFO",
            "SUBSCRIBE",
            "START_TRIAL",
            "SUBMIT_APPLICATION",
            "CONTACT",
            "SCHEDULE",
            "SEARCH",
            "CONTENT_VIEW",
            "DONATE",
            "FIND_LOCATION"
          ]
        },
        "special_ad_categories": {
          "description": "Declare a restricted category. Leave empty unless the ad genuinely falls into one. CREDIT is sent as FINANCIAL_PRODUCTS_SERVICES, which replaced it. Housing, employment and financial products keep the audience at ages 18–65+, all genders, no behaviours and no lookalikes. Political ads (ISSUES_ELECTIONS_POLITICS) cannot be created here.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "EMPLOYMENT",
              "HOUSING",
              "CREDIT",
              "ISSUES_ELECTIONS_POLITICS",
              "ONLINE_GAMBLING_AND_GAMING",
              "FINANCIAL_PRODUCTS_SERVICES"
            ]
          }
        },
        "special_ad_category_country": {
          "description": "Countries the special ad category ads run in (ISO two-letter codes). Meta requires it with any special_ad_categories; omit it to use the targeted countries.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "facebook_page_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Page the ad runs from, from meta_list_pages."
        },
        "instagram_account_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Instagram account to run the ad from, from meta_list_instagram_accounts."
        },
        "cards": {
          "minItems": 2,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "image_url": {
                "type": "string",
                "format": "uri",
                "description": "The image for this card. Square (1080×1080) works on every placement. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet."
              },
              "headline": {
                "description": "Card headline. About 32 characters show under the image.",
                "type": "string",
                "maxLength": 255
              },
              "description": {
                "description": "Card description, shown under the headline.",
                "type": "string",
                "maxLength": 255
              },
              "link": {
                "description": "Where this card sends people. Defaults to landing_page_url.",
                "type": "string",
                "format": "uri"
              }
            },
            "required": [
              "image_url"
            ]
          },
          "description": "Between 2 and 10 cards, in the order people should swipe through them. Each card needs its own image; repeating one is rejected."
        },
        "primary_text": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2200,
          "description": "Copy above the carousel. The same text is shown for every card."
        },
        "landing_page_url": {
          "type": "string",
          "format": "uri",
          "description": "Default destination, used for any card without its own link."
        },
        "call_to_action": {
          "description": "Button label on every card. Defaults to LEARN_MORE.",
          "type": "string",
          "enum": [
            "LEARN_MORE",
            "SHOP_NOW",
            "SIGN_UP",
            "SUBSCRIBE",
            "DOWNLOAD",
            "BOOK_NOW",
            "GET_QUOTE",
            "CONTACT_US",
            "APPLY_NOW",
            "GET_OFFER",
            "ORDER_NOW",
            "SEE_MORE",
            "GET_STARTED",
            "BUY_NOW",
            "DONATE_NOW",
            "NO_BUTTON"
          ]
        },
        "optimize_card_order": {
          "description": "Let Meta reorder the cards by performance. Turn it off when the cards tell a story in sequence.",
          "type": "boolean"
        },
        "show_end_card": {
          "description": "Show the Page profile card at the end. Defaults to on.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_name",
        "objective",
        "cards",
        "primary_text",
        "landing_page_url"
      ]
    }
  • meta_create_flexible_adwrite2 tasks

    Create a paused flexible (dynamic creative) Meta ad

    Creates one ad that carries several primary texts, headlines, descriptions and images; Meta assembles the combinations and learns which one works per person. The ad is created PAUSED inside an ad set that already exists. Use when: the user wants to test creative variants inside one audience, and the ad set is already set up. Get the ad set id from meta_list_ad_sets. Do not use to create a campaign — meta_create_image_campaign, meta_create_video_campaign and meta_create_carousel_campaign do that. Do not use it to run a clean A/B test where each variant needs its own reporting: Meta reports the ad, not the combination, so per-variant numbers are limited. Do not pad the lists with near-identical wording. Fewer, genuinely different assets learn faster. Returns the new ad and creative ids, the uploaded image hashes and how many combinations Meta can build. If the user has not said which ad set, ask — this tool never creates one.

    How to call: meta_ads_write(action="execute", tool_name="meta_create_flexible_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The existing ad set the flexible ad goes into, from meta_list_ad_sets."
        },
        "ad_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Name for the new ad, shown in Ads Manager."
        },
        "primary_texts": {
          "minItems": 1,
          "maxItems": 5,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2200
          },
          "description": "Up to 5 genuinely different versions of the main copy. Meta picks per person; near-duplicates waste the test."
        },
        "headlines": {
          "minItems": 1,
          "maxItems": 5,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          },
          "description": "Up to 5 headlines. About 40 characters show."
        },
        "descriptions": {
          "description": "Optional description lines, shown on some placements only.",
          "maxItems": 5,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255
          }
        },
        "image_urls": {
          "minItems": 1,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string",
            "format": "uri"
          },
          "description": "Up to 10 images for Meta to combine with the texts. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet."
        },
        "landing_page_url": {
          "type": "string",
          "format": "uri",
          "description": "Where every combination sends people."
        },
        "call_to_action": {
          "description": "Button label used by every combination. Defaults to LEARN_MORE.",
          "type": "string",
          "enum": [
            "LEARN_MORE",
            "SHOP_NOW",
            "SIGN_UP",
            "SUBSCRIBE",
            "DOWNLOAD",
            "BOOK_NOW",
            "GET_QUOTE",
            "CONTACT_US",
            "APPLY_NOW",
            "GET_OFFER",
            "ORDER_NOW",
            "SEE_MORE",
            "GET_STARTED",
            "BUY_NOW",
            "DONATE_NOW",
            "NO_BUTTON"
          ]
        },
        "url_tags": {
          "description": "Tracking parameters appended to the destination, e.g. utm_source=facebook&utm_medium=paid.",
          "type": "string",
          "maxLength": 1000
        },
        "facebook_page_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Page the ad runs from, from meta_list_pages."
        },
        "instagram_account_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Instagram account to run the ad from."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_set_id",
        "ad_name",
        "primary_texts",
        "headlines",
        "image_urls",
        "landing_page_url"
      ]
    }
  • meta_create_image_campaignwrite3 tasks

    Create a paused Meta image campaign

    Builds a complete single-image campaign: campaign, one ad set, the image upload, the creative and one ad. The campaign is created PAUSED and its ad set and ad ACTIVE inside it, so nothing spends until the campaign is turned on, and turning it on launches the ad. Say that the campaign is paused when you report the result, with the two links the result gives (Ads Manager and the campaign's page in Adako), and offer "enable all": one meta_resume_entity call on the campaign, listed in the result as go_live.enable_all, one approval. Use when: the user has agreed on objective, budget, audience, image and copy. Gather those first — resolve locations and interests with meta_search_targeting, the Page with meta_list_pages, the pixel with meta_list_pixels, and check the image with meta_validate_creative_url. Do not use to add another ad or audience to something that already exists; this always creates a new campaign, and calling it twice for the same brief creates two campaigns that both spend. Do not pass free text for locations or interests — only ids from the resolver tools are accepted. Returns the four new ids and reads the statuses back so you can confirm the campaign is paused (the ad set and ad read ACTIVE, held by the campaign). Refuses to start when: the account has no promotable Page; the account has several Pages and none was chosen; a sales or leads objective has no pixel; a country code is not ISO or a region or city key is unknown to Meta; the targeting reaches the EU/EEA without dsa_beneficiary and dsa_payor; a date is in the past or a timestamp has no UTC offset; the budget is below Meta's minimum for this account; the objective is app promotion or the goal is an instant form; a special ad category is combined with targeting Meta forbids for it; or image_url does not answer with a JPEG, PNG or GIF file under 30 MB. Ask the user for the missing piece rather than substituting a value.

    How to call: meta_ads_write(action="execute", tool_name="meta_create_image_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Name shown in Ads Manager. Say what it sells and to whom."
        },
        "objective": {
          "type": "string",
          "enum": [
            "OUTCOME_AWARENESS",
            "OUTCOME_TRAFFIC",
            "OUTCOME_ENGAGEMENT",
            "OUTCOME_LEADS",
            "OUTCOME_SALES",
            "OUTCOME_APP_PROMOTION"
          ],
          "description": "What Meta should optimise the campaign for. OUTCOME_SALES and OUTCOME_LEADS need a pixel; OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT and OUTCOME_AWARENESS do not. OUTCOME_APP_PROMOTION is refused here: use meta_create_app_install_campaign."
        },
        "daily_budget": {
          "description": "Daily budget as a decimal in the account currency (25 means 25 dollars, not 2500 cents). One of daily_budget or lifetime_budget is required.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "lifetime_budget": {
          "description": "Total budget for the whole flight, decimal in the account currency. Requires end_time.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "budget_level": {
          "description": "Which level holds the budget. \"ad_set\" (the default) gives this ad set its own budget, so every ad set is funded separately. \"campaign\" is Meta's campaign budget optimisation (Advantage campaign budget): the campaign holds one budget and Meta decides how much each ad set gets — ad sets added later must then carry no budget of their own. Only \"campaign\" can be edited with meta_update_campaign_budget.",
          "type": "string",
          "enum": [
            "ad_set",
            "campaign"
          ]
        },
        "start_time": {
          "description": "When delivery may start: YYYY-MM-DD (00:00 in the ad account's time zone) or an ISO timestamp with a UTC offset, e.g. 2026-10-01T09:00:00+02:00. Not in the past; today or omitted means as soon as the user activates it.",
          "type": "string"
        },
        "end_time": {
          "description": "When delivery must stop: YYYY-MM-DD (runs to 23:59:59 in the ad account's time zone) or an ISO timestamp with a UTC offset. Required with lifetime_budget.",
          "type": "string"
        },
        "optimization_goal": {
          "description": "Override what the ad set optimises for. Leave empty to use the objective default. LEAD_GENERATION (instant forms) is not supported; PAGE_LIKES promotes the Page.",
          "type": "string"
        },
        "billing_event": {
          "description": "What Meta charges for. Leave empty to use IMPRESSIONS.",
          "type": "string"
        },
        "pixel_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Pixel that records the conversion. Required for sales and leads objectives; get it from meta_list_pixels."
        },
        "conversion_event": {
          "description": "The pixel event to optimise for. Defaults to PURCHASE for sales, LEAD for leads.",
          "type": "string",
          "enum": [
            "PURCHASE",
            "LEAD",
            "COMPLETE_REGISTRATION",
            "ADD_TO_CART",
            "INITIATED_CHECKOUT",
            "ADD_PAYMENT_INFO",
            "SUBSCRIBE",
            "START_TRIAL",
            "SUBMIT_APPLICATION",
            "CONTACT",
            "SCHEDULE",
            "SEARCH",
            "CONTENT_VIEW",
            "DONATE",
            "FIND_LOCATION"
          ]
        },
        "countries": {
          "description": "ISO 3166-1 two-letter country codes from meta_search_targeting: GB, not UK; GR, not EL.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "region_keys": {
          "description": "Region keys from meta_search_targeting (never region names). Checked with Meta first.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "city_keys": {
          "description": "City keys from meta_search_targeting (never city names, postcodes or media markets). Each city is targeted with a 25 km radius.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "age_min": {
          "description": "Youngest age to target. Minimum 18.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "age_max": {
          "description": "Oldest age to target. 65 means 65 and over.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "genders": {
          "description": "Restrict by gender. Defaults to all — narrow only when the user asks.",
          "type": "string",
          "enum": [
            "all",
            "men",
            "women"
          ]
        },
        "interest_ids": {
          "description": "Interest ids from meta_search_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "behavior_ids": {
          "description": "Behaviour ids from meta_search_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "custom_audience_ids": {
          "description": "Custom audience ids from meta_list_custom_audiences.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "excluded_custom_audience_ids": {
          "description": "Custom audiences to exclude, e.g. existing customers.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "advantage_audience": {
          "description": "Let Meta deliver beyond the listed interests. Defaults to off so the targeting the user asked for is the targeting they get.",
          "type": "boolean"
        },
        "dsa_beneficiary": {
          "description": "Who benefits from the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one, including territories such as Réunion.",
          "type": "string",
          "maxLength": 512
        },
        "dsa_payor": {
          "description": "Who pays for the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one.",
          "type": "string",
          "maxLength": 512
        },
        "special_ad_categories": {
          "description": "Declare a restricted category (housing, employment, credit, politics, gambling, financial products). Leave empty unless the ad genuinely falls into one — a wrong declaration limits delivery, a missing one breaks Meta policy. CREDIT is sent as FINANCIAL_PRODUCTS_SERVICES, which replaced it. Housing, employment and financial products keep the audience at ages 18–65+, all genders, no behaviours and no lookalikes. Political ads (ISSUES_ELECTIONS_POLITICS) cannot be created here.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "EMPLOYMENT",
              "HOUSING",
              "CREDIT",
              "ISSUES_ELECTIONS_POLITICS",
              "ONLINE_GAMBLING_AND_GAMING",
              "FINANCIAL_PRODUCTS_SERVICES"
            ]
          }
        },
        "special_ad_category_country": {
          "description": "Countries the special ad category ads run in (ISO two-letter codes). Meta requires it with any special_ad_categories; omit it to use the targeted countries.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "facebook_page_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Page the ad runs from, from meta_list_pages. Required when the account has more than one Page."
        },
        "instagram_account_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Instagram account to run the ad from, from meta_list_pages."
        },
        "image_url": {
          "description": "The image the ad will show, as a link to the file itself. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet.",
          "type": "string",
          "format": "uri"
        },
        "image_hash": {
          "type": "string",
          "minLength": 8,
          "maxLength": 200,
          "description": "An image already in the account library, from meta_list_media (its `image_hash`). Pass this instead of image_url when the file is already in the account — nothing is downloaded or uploaded, so it works for a file that has no public link at all."
        },
        "primary_text": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2200,
          "description": "Main ad copy. About 125 characters show before \"See more\"."
        },
        "headline": {
          "description": "Headline under the image. About 40 characters show.",
          "type": "string",
          "maxLength": 255
        },
        "description": {
          "description": "Description line. About 30 characters show, and only on some placements.",
          "type": "string",
          "maxLength": 255
        },
        "landing_page_url": {
          "type": "string",
          "format": "uri",
          "description": "Where the ad sends people. Must be HTTPS."
        },
        "call_to_action": {
          "description": "Button label. Defaults to LEARN_MORE.",
          "type": "string",
          "enum": [
            "LEARN_MORE",
            "SHOP_NOW",
            "SIGN_UP",
            "SUBSCRIBE",
            "DOWNLOAD",
            "BOOK_NOW",
            "GET_QUOTE",
            "CONTACT_US",
            "APPLY_NOW",
            "GET_OFFER",
            "ORDER_NOW",
            "SEE_MORE",
            "GET_STARTED",
            "BUY_NOW",
            "DONATE_NOW",
            "NO_BUTTON"
          ]
        },
        "ad_set_name": {
          "description": "Ad set name. Defaults to the campaign name plus a suffix.",
          "type": "string",
          "maxLength": 200
        },
        "ad_name": {
          "description": "Ad name. Defaults to the campaign name plus a suffix.",
          "type": "string",
          "maxLength": 200
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_name",
        "objective",
        "primary_text",
        "landing_page_url"
      ]
    }
  • meta_create_video_campaignwrite3 tasks

    Create a paused Meta video campaign

    Builds a complete single-video campaign: campaign, one ad set, the video upload, the creative and one ad. The campaign is created PAUSED and its ad set and ad ACTIVE inside it, so nothing spends until the campaign is turned on, and turning it on launches the ad. Say that the campaign is paused when you report the result, with the two links the result gives (Ads Manager and the campaign's page in Adako), and offer "enable all": one meta_resume_entity call on the campaign, listed in the result as go_live.enable_all, one approval. Use when: the user has a video file at a public URL and has agreed on objective, budget, audience and copy. Resolve locations and interests with meta_search_targeting, the Page with meta_list_pages and the pixel with meta_list_pixels first. Do not use to add a video ad to a campaign that already exists — that is meta_add_ad. Calling this twice for the same brief creates two campaigns that both spend. Do not pass free text for locations or interests; only ids from the resolver tools are accepted. Returns the campaign, ad set, video, creative and ad ids, and reads the statuses back so you can confirm the campaign is paused (the ad set and ad read ACTIVE, held by the campaign). Meta transcodes the video before it can be used, which takes up to about half a minute. If the upload is still processing when the wait runs out the tool stops and tells you what exists — do not call it again, tell the user. If unsure which objective fits a video, ask: awareness and engagement optimise for views, sales and leads optimise for conversions and need a pixel.

    How to call: meta_ads_write(action="execute", tool_name="meta_create_video_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Name shown in Ads Manager. Say what it sells and to whom."
        },
        "objective": {
          "type": "string",
          "enum": [
            "OUTCOME_AWARENESS",
            "OUTCOME_TRAFFIC",
            "OUTCOME_ENGAGEMENT",
            "OUTCOME_LEADS",
            "OUTCOME_SALES",
            "OUTCOME_APP_PROMOTION"
          ],
          "description": "What Meta should optimise the campaign for. OUTCOME_SALES and OUTCOME_LEADS need a pixel; OUTCOME_AWARENESS and OUTCOME_ENGAGEMENT suit view-through goals such as THRUPLAY. OUTCOME_APP_PROMOTION is refused here: use meta_create_app_install_campaign."
        },
        "daily_budget": {
          "description": "Daily budget as a decimal in the account currency (25 means 25 dollars, not 2500 cents). One of daily_budget or lifetime_budget is required.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "lifetime_budget": {
          "description": "Total budget for the whole flight, decimal in the account currency. Requires end_time.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "budget_level": {
          "description": "Which level holds the budget. \"ad_set\" (the default) gives this ad set its own budget, so every ad set is funded separately. \"campaign\" is Meta's campaign budget optimisation (Advantage campaign budget): the campaign holds one budget and Meta decides how much each ad set gets — ad sets added later must then carry no budget of their own. Only \"campaign\" can be edited with meta_update_campaign_budget.",
          "type": "string",
          "enum": [
            "ad_set",
            "campaign"
          ]
        },
        "start_time": {
          "description": "When delivery may start: YYYY-MM-DD (00:00 in the ad account's time zone) or an ISO timestamp with a UTC offset, e.g. 2026-10-01T09:00:00+02:00. Not in the past; today or omitted means as soon as the user activates it.",
          "type": "string"
        },
        "end_time": {
          "description": "When delivery must stop: YYYY-MM-DD (runs to 23:59:59 in the ad account's time zone) or an ISO timestamp with a UTC offset. Required with lifetime_budget.",
          "type": "string"
        },
        "optimization_goal": {
          "description": "Override what the ad set optimises for. Leave empty to use the objective default. LEAD_GENERATION (instant forms) is not supported; PAGE_LIKES promotes the Page.",
          "type": "string"
        },
        "billing_event": {
          "description": "What Meta charges for. Leave empty to use IMPRESSIONS.",
          "type": "string"
        },
        "countries": {
          "description": "ISO 3166-1 two-letter country codes from meta_search_targeting: GB, not UK; GR, not EL.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "region_keys": {
          "description": "Region keys from meta_search_targeting (never region names). Checked with Meta first.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "city_keys": {
          "description": "City keys from meta_search_targeting (never city names, postcodes or media markets). Each city is targeted with a 25 km radius.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "age_min": {
          "description": "Youngest age to target. Minimum 18.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "age_max": {
          "description": "Oldest age to target. 65 means 65 and over.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "genders": {
          "description": "Restrict by gender. Defaults to all — narrow only when the user asks.",
          "type": "string",
          "enum": [
            "all",
            "men",
            "women"
          ]
        },
        "interest_ids": {
          "description": "Interest ids from meta_search_targeting or meta_browse_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "behavior_ids": {
          "description": "Behaviour ids from meta_search_targeting or meta_browse_targeting. Never free text.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "custom_audience_ids": {
          "description": "Custom audience ids from meta_list_custom_audiences.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "excluded_custom_audience_ids": {
          "description": "Custom audiences to exclude, e.g. existing customers.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "advantage_audience": {
          "description": "Let Meta deliver beyond the listed interests. Defaults to off so the targeting the user asked for is the targeting they get.",
          "type": "boolean"
        },
        "dsa_beneficiary": {
          "description": "Who benefits from the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one, including territories such as Réunion.",
          "type": "string",
          "maxLength": 512
        },
        "dsa_payor": {
          "description": "Who pays for the ad. Required whenever the targeting reaches the EU or EEA — a country, or a region or city inside one.",
          "type": "string",
          "maxLength": 512
        },
        "ad_set_name": {
          "description": "Ad set name. Defaults to the campaign name plus a suffix.",
          "type": "string",
          "maxLength": 200
        },
        "ad_name": {
          "description": "Ad name. Defaults to the campaign name plus a suffix.",
          "type": "string",
          "maxLength": 200
        },
        "pixel_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Pixel that records the conversion. Required for sales and leads objectives; get it from meta_list_pixels."
        },
        "conversion_event": {
          "description": "The pixel event to optimise for. Defaults to PURCHASE for sales, LEAD for leads.",
          "type": "string",
          "enum": [
            "PURCHASE",
            "LEAD",
            "COMPLETE_REGISTRATION",
            "ADD_TO_CART",
            "INITIATED_CHECKOUT",
            "ADD_PAYMENT_INFO",
            "SUBSCRIBE",
            "START_TRIAL",
            "SUBMIT_APPLICATION",
            "CONTACT",
            "SCHEDULE",
            "SEARCH",
            "CONTENT_VIEW",
            "DONATE",
            "FIND_LOCATION"
          ]
        },
        "special_ad_categories": {
          "description": "Declare a restricted category (housing, employment, credit, politics, gambling, financial products). Leave empty unless the ad genuinely falls into one. CREDIT is sent as FINANCIAL_PRODUCTS_SERVICES, which replaced it. Housing, employment and financial products keep the audience at ages 18–65+, all genders, no behaviours and no lookalikes. Political ads (ISSUES_ELECTIONS_POLITICS) cannot be created here.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "EMPLOYMENT",
              "HOUSING",
              "CREDIT",
              "ISSUES_ELECTIONS_POLITICS",
              "ONLINE_GAMBLING_AND_GAMING",
              "FINANCIAL_PRODUCTS_SERVICES"
            ]
          }
        },
        "special_ad_category_country": {
          "description": "Countries the special ad category ads run in (ISO two-letter codes). Meta requires it with any special_ad_categories; omit it to use the targeted countries.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "facebook_page_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Page the ad runs from, from meta_list_pages. Required when the account has more than one Page."
        },
        "instagram_account_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Instagram account to run the ad from, from meta_list_instagram_accounts."
        },
        "video_url": {
          "description": "The video file for the ad: MP4 or MOV, H.264, under 4 GB. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet.",
          "type": "string",
          "format": "uri"
        },
        "video_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "pattern": "^[0-9]+$",
          "description": "A video already in the account library, from meta_list_media (its `video_id`). Pass this instead of video_url when the file is already in the account — nothing is downloaded, uploaded or transcoded."
        },
        "thumbnail_url": {
          "description": "Optional still image for the thumbnail. Leave it out to use the frame Meta picks itself. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet.",
          "type": "string",
          "format": "uri"
        },
        "primary_text": {
          "type": "string",
          "minLength": 1,
          "maxLength": 2200,
          "description": "Main ad copy. About 125 characters show before \"See more\"."
        },
        "headline": {
          "description": "Headline under the video.",
          "type": "string",
          "maxLength": 255
        },
        "description": {
          "description": "Description line, shown on some placements only.",
          "type": "string",
          "maxLength": 255
        },
        "landing_page_url": {
          "type": "string",
          "format": "uri",
          "description": "Where the ad sends people. Must be HTTPS."
        },
        "call_to_action": {
          "description": "Button label. Defaults to LEARN_MORE.",
          "type": "string",
          "enum": [
            "LEARN_MORE",
            "SHOP_NOW",
            "SIGN_UP",
            "SUBSCRIBE",
            "DOWNLOAD",
            "BOOK_NOW",
            "GET_QUOTE",
            "CONTACT_US",
            "APPLY_NOW",
            "GET_OFFER",
            "ORDER_NOW",
            "SEE_MORE",
            "GET_STARTED",
            "BUY_NOW",
            "DONATE_NOW",
            "NO_BUTTON"
          ]
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_name",
        "objective",
        "primary_text",
        "landing_page_url"
      ]
    }

analysis6

  • meta_analyze_audiencesread-onlyfree

    Rank Meta audience segments and suggest where budget should go

    Ranks the segments of one audience dimension against the account average on the metric that matches the objective: ROAS where revenue exists, otherwise cost per result. Says which segments are worth more budget, which are dragging, and which are simply too small to judge. Use when: the user wants a decision, not a table — "who should we target", "what should we cut". Do not use it as authority to exclude a demographic without the user's agreement: special ad categories forbid some exclusions outright, and a narrow exclusion often raises costs everywhere else. Do not act on a segment with almost no spend; those keep a neutral verdict on purpose. Returns the ranking with each segment's share of spend, how far it is from the average, and a one-line recommendation. It writes nothing: applying a change is meta_update_ad_set. Rows Meta labels "unknown" are excluded from the ranking and reported separately.

    How to call: meta_ads(action="execute", tool_name="meta_analyze_audiences", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Report on one campaign. Omit both ids to report the whole account."
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Report on one ad set."
        },
        "breakdown": {
          "type": "string",
          "enum": [
            "age",
            "gender",
            "age_gender",
            "country",
            "region",
            "publisher_platform",
            "device_platform",
            "platform_position"
          ],
          "description": "Which dimension to rank."
        },
        "metric": {
          "description": "What to rank on. \"auto\" picks ROAS when revenue exists, otherwise cost per result.",
          "type": "string",
          "enum": [
            "auto",
            "cost_per_result",
            "roas",
            "ctr",
            "cpm"
          ]
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "breakdown"
      ]
    }
  • meta_analyze_wasted_spendread-onlyfree

    Find Meta spend that bought nothing

    Finds ad sets that spent money and produced no results in the window, and ad sets whose cost per result is far above the account's own average. Everything still in the learning phase is listed separately and explicitly excluded from the "pause it" recommendation. Use when: the user asks where their Meta budget is leaking, or before any budget reallocation. Do not use as a pause list without reading the rule it prints — an ad set with no results may simply be new, or may be on an objective that records no conversions at all. Returns three groups: wasted (spend above the threshold, zero results), expensive (cost per result more than the multiplier above the account average), and learning (spending but not yet out of the learning phase — leave these alone). The rule is stated in the output so the user can disagree with it. If nothing crosses the threshold, say the account looks clean rather than inventing a finding.

    How to call: meta_ads(action="execute", tool_name="meta_analyze_wasted_spend", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Restrict the analysis to one campaign."
        },
        "min_spend": {
          "description": "Minimum spend (decimal, account currency) before an ad set counts as wasteful. Default: 1% of the window total, floor 10.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "cost_multiplier": {
          "description": "How many times the account average cost per result counts as \"expensive\". Default 2.",
          "type": "number",
          "minimum": 1.1,
          "maximum": 10
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_detect_creative_fatigueread-onlyfree

    Detect Meta creative fatigue

    Compares the first days of a window with the last days, per ad, and flags creative fatigue: click-through rate falling while frequency and CPM rise. Use when: performance has drifted down without an obvious change, the user asks whether to refresh creative, or before scaling an ad that has been running a while. Do not use it on an ad that has barely delivered — the tool says "insufficient data" rather than reading noise, and that answer is the truth, not a failure. Do not read a single flagged signal as proof: one signal earns "watch", agreeing signals earn "fatigued". Report which signals fired. Returns per-ad baseline and recent numbers (CTR, CPM, frequency, cost per result), the signals that fired and a plain-language verdict. The default window is 14 days, which is the shortest span where a three-day baseline and a three-day recent period are both meaningful.

    How to call: meta_ads(action="execute", tool_name="meta_detect_creative_fatigue", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Limit the check to one campaign."
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Limit the check to one ad set."
        },
        "days": {
          "description": "How many days to look at, ending yesterday. Default 14.",
          "type": "integer",
          "minimum": 7,
          "maximum": 30
        },
        "min_spend": {
          "description": "Ignore ads that spent less than this over the window. Default 0 (report everything).",
          "type": "number",
          "minimum": 0
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_get_audience_insightsread-onlyfree

    Break Meta performance down by audience

    Splits spend and results by one audience dimension: age, gender, age and gender together, country, region, platform, device or placement position. Use when: the user asks who their ads reached, or wants to see whether one demographic or geography carries the account. Do not use to decide where to move budget on its own — meta_analyze_audiences ranks the same rows and says which segments are actually above or below average. Do not read a small segment as a finding: rows with a few impressions move wildly. The table reports spend share so the weight of each row is visible. Returns one row per segment with spend, impressions, CTR, results and cost per result, in the account currency. Meta reports some rows as "unknown" when it cannot attribute them; they are shown, labelled, and never presented as a real audience.

    How to call: meta_ads(action="execute", tool_name="meta_get_audience_insights", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Report on one campaign. Omit both ids to report the whole account."
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Report on one ad set."
        },
        "breakdown": {
          "type": "string",
          "enum": [
            "age",
            "gender",
            "age_gender",
            "country",
            "region",
            "publisher_platform",
            "device_platform",
            "platform_position"
          ],
          "description": "Which dimension to split by. age_gender crosses the two; platform_position splits feed, reels, stories and so on."
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "breakdown"
      ]
    }
  • meta_optimize_budgetread-onlyfree

    Plan a Meta budget reallocation

    Compares the ad sets in a campaign (or the whole account) on cost per result — or ROAS where revenue exists — and produces a reallocation plan: which budgets to raise, which to lower, by how much, and why. Use when: the user asks where to put more money, or wants a defensible weekly rebalance. Do not expect it to write anything. It returns a plan; each move is then a separate meta_update_adset_budget call the user approves. Do not apply moves to an ad set still in the learning phase — the plan leaves those out and says so, because a budget change restarts learning. Do not exceed the step size without saying what it costs: moves are capped at max_change_percentage (20% by default) because Meta re-enters learning on large jumps. Returns the plan with before and after budgets, the reason per move, and the ad sets deliberately left alone. If every ad set performs close to the average, the honest plan is to change nothing.

    How to call: meta_ads(action="execute", tool_name="meta_optimize_budget", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Limit the plan to the ad sets of one campaign."
        },
        "max_change_percentage": {
          "description": "Biggest step any single budget may move, as a fraction. Default 0.2 (20%).",
          "type": "number",
          "minimum": 0.05,
          "maximum": 1
        },
        "min_daily_budget": {
          "description": "Floor no budget may fall below, decimal in the account currency. Default: Meta's own minimum daily budget for what each ad set is charged for, in this account's currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "keep_total": {
          "description": "Fund every increase from the decreases so the total daily budget stays the same.",
          "type": "boolean"
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_optimize_placementsread-onlyfree

    Find Meta placements that are not earning their spend

    Breaks spend and results down by placement (platform and position) and says which placements cost far more per result than the account average. Use when: the user suspects one surface is wasting money, or before deciding whether to leave Advantage+ placements on. Do not treat a recommendation as a decision. Restricting placements shrinks the auction Meta can buy in, so costs on the remaining placements usually rise — say that whenever you report an exclusion. Do not exclude a placement on a few days of data or on a placement with a small share of spend; the verdicts already stay neutral there. Returns the placement table, a verdict per row, and the exact publisher_platforms list to pass to meta_update_ad_set if the user agrees. It writes nothing itself. If everything is close to average, the right answer is to change nothing.

    How to call: meta_ads(action="execute", tool_name="meta_optimize_placements", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Limit the analysis to one campaign."
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Limit the analysis to one ad set."
        },
        "detail": {
          "description": "platform = Facebook vs Instagram vs Audience Network (default). position also splits feed, reels, stories and so on.",
          "type": "string",
          "enum": [
            "platform",
            "position"
          ]
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

targeting4

  • meta_browse_targetingread-onlyfree

    Browse Meta’s targeting catalogue

    Walks Meta's targeting catalogue by category — interests, behaviours, demographics, life events, industries — instead of searching for a word. Returns the ids a write tool needs. Use when: the user described a kind of person rather than naming a topic ("people who just moved", "small business owners") and a keyword search found nothing useful. Do not use when the user named a thing: meta_search_targeting finds "yoga" or "Berlin" faster and more precisely. Do not pick a category for the user when several fit. Show the options with their sizes and let them choose — the wrong interest silently changes who sees the ads. Returns id, name, the category path and the approximate audience size for each entry. Free: this call does not cost a task. The ids go straight into interest_ids or behavior_ids on the creation and update tools.

    How to call: meta_ads(action="execute", tool_name="meta_browse_targeting", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "category": {
          "type": "string",
          "enum": [
            "interests",
            "behaviors",
            "demographics",
            "life_events",
            "industries"
          ],
          "description": "Which part of the catalogue to browse. behaviors covers purchase and device behaviour; life_events covers moves, jobs and relationships."
        },
        "contains": {
          "description": "Filter the returned entries to those whose name or path contains this text.",
          "type": "string",
          "maxLength": 60
        },
        "limit": {
          "description": "Maximum entries (default 40).",
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "category"
      ]
    }
  • meta_get_ad_set_delivery_estimateread-onlyfree

    Estimate the reach of a Meta ad set

    Asks Meta how many people an ad set's current targeting could reach in a month, at the ad set's own optimisation goal. Use when: the user worries an audience is too small, or before approving a targeting change that narrows it. Do not treat the numbers as precise. Meta returns a wide band and refuses an estimate entirely for very small or very new audiences; say so rather than reporting a fake number. Returns the monthly reach band. Meta no longer gives a daily reach figure on this endpoint, so there is none to report. If Meta reports the estimate is not ready, the honest answer is "Meta will not estimate this yet", not a guess.

    How to call: meta_ads(action="execute", tool_name="meta_get_ad_set_delivery_estimate", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The ad set whose targeting should be estimated."
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_set_id"
      ]
    }
  • meta_get_ad_set_targetingread-onlyfree

    Show a Meta ad set’s targeting

    Renders one ad set's targeting exactly as Meta holds it: locations, age, gender, detailed targeting, custom audiences, placements, devices and the Advantage+ audience setting. Use when: the user asks who an ad set reaches, or before meta_update_ad_set so the merge starts from a shared understanding. Do not use for performance — meta_get_adset_performance answers how it is doing. Returns both a readable table and the raw spec, so the ids needed for a later edit are already at hand. Free: this call does not cost a task.

    How to call: meta_ads(action="execute", tool_name="meta_get_ad_set_targeting", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The ad set to describe, from meta_list_ad_sets."
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_set_id"
      ]
    }
  • meta_search_targetingread-onlyfree

    Look up Meta targeting ids

    Turns a plain-language audience description into the ids Meta's write endpoints require: interest ids, behaviour ids, and location keys for countries, regions, cities, postcodes and media markets. Use when: the user describes an audience in words ("women in Berlin interested in yoga") and before any campaign creation. meta_create_image_campaign only accepts ids, never free text. Do not use to check how an existing audience is performing (meta_get_adset_performance) or to list saved audiences (meta_list_custom_audiences). Returns the id or location key plus the estimated audience size where Meta provides one. Search one concept at a time; broad queries return loosely related suggestions. If several results look plausible, show the user the options with their sizes and let them choose. Do not pick one silently.

    How to call: meta_ads(action="execute", tool_name="meta_search_targeting", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "search_type": {
          "type": "string",
          "enum": [
            "interest",
            "behavior",
            "location"
          ],
          "description": "interest = hobbies, topics, brands; behavior = purchase and device behaviours; location = places."
        },
        "query": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100,
          "description": "What to search for, e.g. \"yoga\", \"frequent travellers\", \"Berlin\"."
        },
        "location_types": {
          "description": "For search_type=location: which kinds of place to return. Defaults to country, region and city.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "country",
              "region",
              "city",
              "zip",
              "geo_market"
            ]
          }
        },
        "limit": {
          "description": "Maximum results (default 15).",
          "type": "integer",
          "minimum": 1,
          "maximum": 50
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "search_type",
        "query"
      ]
    }

management9

  • meta_duplicate_campaignwrite3 tasks

    Duplicate a Meta campaign (paused)

    Copies one campaign with Meta's own duplication, always PAUSED and always renamed with a suffix so the two are never confused. A deep copy brings the ad sets and ads with it; a shallow copy takes the campaign row only. Use when: the user wants a second version of something that already works — a new audience, a new market, a new flight — without rebuilding it by hand. Do not use to make a small change to the original: editing is meta_update_campaign, meta_update_ad_set or meta_update_ad. Two nearly identical live campaigns bid against each other. Do not deep-copy a very large campaign casually: Meta runs it in the background and the copy can take minutes; the tool reports the job rather than inventing ids. Returns the new campaign id, the copied child ids where Meta returned them, and reads the copy back to confirm it is paused. The copy never inherits ACTIVE status, whatever the original was.

    How to call: meta_ads_write(action="execute", tool_name="meta_duplicate_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The campaign to copy, from meta_list_campaigns."
        },
        "name_suffix": {
          "description": "Appended to the copied names. Defaults to \" — copy\".",
          "type": "string",
          "minLength": 1,
          "maxLength": 60
        },
        "deep_copy": {
          "description": "Copy the ad sets and ads too. Defaults to true — a campaign with no ad sets cannot be tested.",
          "type": "boolean"
        },
        "start_time": {
          "description": "Start time for the copied ad sets: YYYY-MM-DD (00:00 in the ad account's time zone) or an ISO timestamp with a UTC offset. Not in the past.",
          "type": "string"
        },
        "end_time": {
          "description": "End time for the copied ad sets: YYYY-MM-DD (23:59:59 in the ad account's time zone) or an ISO timestamp with a UTC offset.",
          "type": "string"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • meta_pause_entitydestructive write1 task

    Pause a Meta campaign, ad set or ad

    Stops delivery on one campaign, ad set or ad by setting its status to PAUSED. Spend stops; nothing is deleted, and the object can be resumed later with meta_resume_entity. Use when: the user has explicitly asked to stop something, and you have shown them what it is currently spending. Do not pause an ad set that is still in the learning phase on the strength of a few days of data — say so and let the user decide. Do not use this as a way to delete: pausing keeps the object, its history and its learning. Returns the status and effective status read back from Meta, which is how you confirm the pause actually applied — a campaign can report PAUSED while its parent is what really stopped delivery.

    How to call: meta_ads_write(action="execute", tool_name="meta_pause_entity", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "entity_type": {
          "type": "string",
          "enum": [
            "campaign",
            "adset",
            "ad"
          ],
          "description": "What the id refers to: campaign, adset or ad."
        },
        "entity_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Id of the campaign, ad set or ad."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "entity_type",
        "entity_id"
      ]
    }
  • meta_resume_entitywrite1 task

    Resume a Meta campaign, ad set or ad

    Sets one campaign, ad set or ad back to ACTIVE so it can deliver again. Real money starts being spent as soon as Meta approves delivery. Use when: the user has reviewed something paused — often a campaign this server created — and wants it live. Do not resume an object without telling the user its budget first: resuming a campaign resumes its whole structure and the daily budget applies from that moment. Returns both status and effective_status. A resumed ad set whose campaign is still paused reports ACTIVE but will not deliver — check the effective status before telling the user it is live.

    How to call: meta_ads_write(action="execute", tool_name="meta_resume_entity", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "entity_type": {
          "type": "string",
          "enum": [
            "campaign",
            "adset",
            "ad"
          ],
          "description": "What the id refers to: campaign, adset or ad."
        },
        "entity_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Id of the campaign, ad set or ad."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "entity_type",
        "entity_id"
      ]
    }
  • meta_set_frequency_capwrite1 task

    Set a frequency cap on a Meta reach ad set

    Limits how often one person may see the ads in a reach-optimised ad set: at most max_frequency impressions every interval_days. Use when: the ad set optimises for reach (or ad recall) and frequency is climbing — the usual first response to creative fatigue on a brand campaign. Do not use on a conversion, traffic or engagement ad set. Meta only honours a frequency cap on reach buying, and setting it elsewhere either fails or does nothing; the preview refuses rather than pretending. Do not set an aggressive cap on a small audience: Meta simply stops delivering once the cap binds, and the budget goes unspent. Returns the ad set read back from Meta with the cap that is actually in force. If the user wants "less repetition" on a conversion campaign, the honest answer is to refresh the creative or widen the audience, not to cap frequency.

    How to call: meta_ads_write(action="execute", tool_name="meta_set_frequency_cap", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The reach-optimised ad set to cap, from meta_list_ad_sets."
        },
        "max_frequency": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90,
          "description": "Maximum impressions one person may receive inside the interval."
        },
        "interval_days": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90,
          "description": "Length of the window in days, e.g. 7 for \"twice a week\" with max_frequency 2."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_set_id",
        "max_frequency",
        "interval_days"
      ]
    }
  • meta_update_adwrite1 task

    Edit a Meta ad

    Changes one ad's name, status or creative. Use when: the naming needs fixing, or the ad should point at a different creative. Do not use to pause or resume — meta_pause_entity and meta_resume_entity state the risk of that properly. Do not swap the creative on an ad that is performing well without saying what it will cost: Meta treats it as a new creative and delivery re-enters learning. Returns the ad read back from Meta. At least one of name, status or creative_id must be given. url_tags cannot be changed here: Meta keeps tracking parameters on the creative and will not re-tag one, so the tool refuses and explains what to do instead.

    How to call: meta_ads_write(action="execute", tool_name="meta_update_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The ad to edit, from meta_list_ads."
        },
        "name": {
          "description": "New ad name.",
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "status": {
          "description": "New status. Prefer meta_pause_entity / meta_resume_entity.",
          "type": "string",
          "enum": [
            "ACTIVE",
            "PAUSED"
          ]
        },
        "creative_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Creative to switch this ad to, from meta_get_ad_creatives."
        },
        "url_tags": {
          "description": "Not supported by Meta on an existing ad: tracking parameters live on the creative and cannot be changed after it is made. Passing this is refused with the way to do it instead.",
          "type": "string",
          "maxLength": 1000
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_id"
      ]
    }
  • meta_update_ad_setdestructive write1 task

    Edit a Meta ad set (targeting is merged)

    Changes one ad set's targeting, optimisation goal, schedule, DSA fields or name. Targeting is **merged**: every add and remove is applied to the spec Meta currently holds, so nothing you did not name is lost. The preview shows the merged result field by field before anything is sent. Use when: the user wants to widen, narrow or correct an existing audience, or shift its schedule. Do not use to change the budget (meta_update_adset_budget) or to pause it (meta_pause_entity). Do not change targeting on an ad set that is still learning unless the user accepts that learning restarts and delivery gets worse first — the preview warns when that applies. Do not pass free text: interests, behaviours and locations must be ids from meta_search_targeting or meta_browse_targeting. Returns the ad set read back from Meta, so the merged targeting is confirmed rather than assumed. If a removal would leave the ad set with no location or no detailed targeting at all, the tool refuses or warns — ask the user what they intended instead of guessing. It also refuses EU/EEA locations added to an ad set without dsa_beneficiary and dsa_payor (ask the user for both), unknown location keys, past dates, placement combinations Meta rejects, and targeting the campaign's special ad category forbids.

    How to call: meta_ads_write(action="execute", tool_name="meta_update_ad_set", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The ad set to change, from meta_list_ad_sets."
        },
        "name": {
          "description": "New ad set name.",
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "add_countries": {
          "description": "ISO 3166-1 two-letter country codes to add to the existing locations (GB, not UK).",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "remove_countries": {
          "description": "Two-letter country codes to remove. The ad set must keep at least one location.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          }
        },
        "add_region_keys": {
          "description": "Region keys to add, from meta_search_targeting. Checked with Meta first.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "remove_region_keys": {
          "description": "Region keys to remove.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "add_city_keys": {
          "description": "City keys to add, from meta_search_targeting (not postcodes or media markets). Each gets a 25 km radius.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "remove_city_keys": {
          "description": "City keys to remove.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "add_interest_ids": {
          "description": "Interest ids to add, from meta_search_targeting or meta_browse_targeting.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "remove_interest_ids": {
          "description": "Interest ids to remove.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "add_behavior_ids": {
          "description": "Behaviour ids to add.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "remove_behavior_ids": {
          "description": "Behaviour ids to remove.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "add_custom_audience_ids": {
          "description": "Custom audience ids to include, from meta_list_custom_audiences.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "remove_custom_audience_ids": {
          "description": "Custom audience ids to stop including.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "add_excluded_custom_audience_ids": {
          "description": "Custom audience ids to exclude, e.g. existing customers.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "remove_excluded_custom_audience_ids": {
          "description": "Custom audience ids to stop excluding.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "age_min": {
          "description": "New minimum age.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "age_max": {
          "description": "New maximum age.",
          "type": "integer",
          "minimum": 18,
          "maximum": 65
        },
        "genders": {
          "description": "New gender targeting.",
          "type": "string",
          "enum": [
            "all",
            "men",
            "women"
          ]
        },
        "publisher_platforms": {
          "description": "Replace the placement platforms with exactly this list. Omit to leave placements alone (Advantage+ placements). audience_network cannot be the only one, and threads needs instagram.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "facebook",
              "instagram",
              "audience_network",
              "messenger",
              "threads"
            ]
          }
        },
        "device_platforms": {
          "description": "Replace the device targeting with exactly this list.",
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "mobile",
              "desktop"
            ]
          }
        },
        "advantage_audience": {
          "description": "Turn Advantage+ audience on or off for this ad set.",
          "type": "boolean"
        },
        "optimization_goal": {
          "description": "New optimisation goal. Must be valid for the campaign objective; delivery relearns.",
          "type": "string"
        },
        "start_time": {
          "description": "New start: YYYY-MM-DD (00:00 in the ad account's time zone) or an ISO timestamp with a UTC offset. Not in the past.",
          "type": "string"
        },
        "end_time": {
          "description": "New end: YYYY-MM-DD (23:59:59 in the ad account's time zone) or an ISO timestamp with a UTC offset.",
          "type": "string"
        },
        "dsa_beneficiary": {
          "description": "Who benefits from the ad. Required when this change adds EU/EEA locations to an ad set that has no beneficiary yet.",
          "type": "string",
          "maxLength": 512
        },
        "dsa_payor": {
          "description": "Who pays for the ad. Required when this change adds EU/EEA locations to an ad set that has no payor yet.",
          "type": "string",
          "maxLength": 512
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_set_id"
      ]
    }
  • meta_update_adset_budgetdestructive write1 task

    Change a Meta ad set budget

    Changes the daily or lifetime budget of one ad set. The preview shows the current and new amounts as decimals in the account currency and the percentage change, so the user approves a real number rather than a raw platform value. Use when: the user has decided to scale an ad set up or down and named the ad set and the amount. Do not use on an ad set whose campaign holds the budget — campaign budget optimisation means Meta manages ad set budgets and this call will be rejected. Change the campaign instead. Do not change the budget of an ad set that is still learning unless the user accepts that learning restarts: a large change resets what Meta has learned and delivery gets worse before it gets better. Returns the ad set read back from Meta after the change so the new budget is confirmed, not assumed. Amounts are decimals in the account currency. For currencies Meta treats as whole units (yen, won, forint and similar) the conversion is handled here — never send minor units.

    How to call: meta_ads_write(action="execute", tool_name="meta_update_adset_budget", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The ad set to re-budget, from meta_list_ad_sets."
        },
        "daily_budget": {
          "description": "New daily budget, decimal in the account currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "lifetime_budget": {
          "description": "New lifetime budget, decimal in the account currency. The ad set must already have an end date.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_set_id"
      ]
    }
  • meta_update_campaignwrite1 task

    Edit a Meta campaign

    Changes a campaign's name, status or lifetime spend cap. The spend cap is a hard ceiling on everything the campaign will ever spend, expressed as a decimal in the account currency. Use when: renaming for clarity, or putting a hard ceiling on a campaign the user wants to bound. Do not use to pause or resume — meta_pause_entity and meta_resume_entity exist so the risk of the change is stated correctly to the user. Do not set a spend cap below what the campaign has already spent: Meta stops delivery immediately and the campaign cannot be revived by lowering it further. Returns the campaign read back from Meta. At least one of name, status or spend_cap must be given.

    How to call: meta_ads_write(action="execute", tool_name="meta_update_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The campaign to edit."
        },
        "name": {
          "description": "New campaign name.",
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "status": {
          "description": "New status. Prefer meta_pause_entity / meta_resume_entity, which explain the risk properly.",
          "type": "string",
          "enum": [
            "ACTIVE",
            "PAUSED"
          ]
        },
        "spend_cap": {
          "description": "Lifetime spend ceiling as a decimal in the account currency. Meta enforces a minimum of 100 USD or the approximate equivalent in the account currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • meta_update_campaign_budgetdestructive write1 task

    Change a Meta campaign budget (CBO)

    Changes the daily or lifetime budget held at campaign level, which is how campaign budget optimisation (CBO) campaigns are funded: Meta distributes one budget across the ad sets. Use when: the campaign itself holds the budget. Check with meta_list_campaigns — a campaign with a budget shows one there. Do not use on a campaign whose ad sets hold their own budgets; Meta rejects it and the right tool is meta_update_adset_budget. The preview refuses before anything is sent. A campaign only holds a budget if it was created that way: the launch tools take budget_level: "campaign". It cannot be moved afterwards from here. Do not make large jumps casually: Meta re-enters the learning phase on big budget moves, and delivery gets worse before it gets better. Steps of 20% or less are safer. Returns the campaign read back from Meta so the new budget is confirmed rather than assumed. Amounts are decimals in the account currency; whole-unit currencies such as yen are converted here.

    How to call: meta_ads_write(action="execute", tool_name="meta_update_campaign_budget", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The campaign to re-budget, from meta_list_campaigns."
        },
        "daily_budget": {
          "description": "New campaign daily budget, decimal in the account currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "lifetime_budget": {
          "description": "New campaign lifetime budget, decimal in the account currency. Needs an end date.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }

diagnostics2

  • meta_explain_anomalyread-onlyfree

    Explain a change in Meta performance

    Compares a window with the equally long window before it and attributes the change to the ad sets, placements, countries or age-and-gender groups that caused it. Use when: a number moved and the user wants to know why, before changing anything. Do not present the biggest row as the cause on its own — check its share of the move, which the table reports. A change spread thinly across everything usually means an auction or seasonality shift, not something inside the account. Do not use it to compare two arbitrary periods of different lengths; the comparison window is always the same number of days, immediately before. Returns the total before and after, the rows that account for most of the move, and each row's contribution. If nothing explains more than about a sixth of the change, the tool says so instead of inventing a cause.

    How to call: meta_ads(action="execute", tool_name="meta_explain_anomaly", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Limit the analysis to one campaign."
        },
        "metric": {
          "description": "What moved. Default spend. \"results\" uses the objective’s own KPI.",
          "type": "string",
          "enum": [
            "spend",
            "results",
            "impressions",
            "clicks",
            "revenue"
          ]
        },
        "dimension": {
          "description": "How to split the change. Default ad_set, which is usually where a real change lives.",
          "type": "string",
          "enum": [
            "ad_set",
            "placement",
            "age_gender",
            "country"
          ]
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_validate_creative_urlread-onlyfree

    Check an image URL and ad copy before building an ad

    Fetches a public image URL and reports whether it is reachable, what type and size it is, and its pixel dimensions, then checks any ad copy against Meta's length guidance. Use when: the user supplies an image URL or ad text, always before meta_create_image_campaign. The create tools refuse a link that is not a reachable image in their preview, before anything exists; checking here first saves a round trip. Do not use for landing page quality or policy review — this checks that the file exists and fits the format, nothing about its content. Returns dimensions when the file is a PNG or JPEG, plus warnings for anything Meta is likely to reject or crop: too small, wrong aspect ratio, unsupported type, or copy that will be truncated in the feed. Costs nothing and calls no ad platform, so run it as often as needed.

    How to call: meta_ads(action="execute", tool_name="meta_validate_creative_url", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "image_url": {
          "description": "The image the ad will use. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet.",
          "type": "string",
          "format": "uri"
        },
        "primary_text": {
          "description": "The main ad copy, checked against the 125-character guidance.",
          "type": "string"
        },
        "headline": {
          "description": "Headline, checked against the 40-character guidance.",
          "type": "string"
        },
        "description": {
          "description": "Description line, checked against the 30-character guidance.",
          "type": "string"
        },
        "landing_page_url": {
          "description": "Destination URL, checked for reachability and HTTPS.",
          "type": "string",
          "format": "uri"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

assets5

  • meta_get_ad_creativesread-onlyfree

    Show the creatives behind Meta ads

    Lists the creatives used by one ad, or by every ad in an ad set: headline, primary text, destination link, format and thumbnail. Use when: the user asks what an ad actually says, before rewriting copy, or to find a creative_id to reuse with meta_add_ad. Do not use for performance numbers — meta_get_ad_performance answers which creative works. Returns one row per creative with its id, format and text, plus the raw spec in the data for follow-up questions. Pass either ad_id or ad_set_id. Carousel creatives report their card count; flexible creatives report how many text and image assets they carry.

    How to call: meta_ads(action="execute", tool_name="meta_get_ad_creatives", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "One ad whose creative should be shown."
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "An ad set; every ad in it is included."
        },
        "limit": {
          "description": "Maximum ads to inspect (default 25).",
          "type": "integer",
          "minimum": 1,
          "maximum": 50
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_list_instagram_accountsread-onlyfree

    List Instagram accounts an ad can run from

    Lists the Instagram accounts this ad account may use as the ad's identity, both the ones linked directly to the ad account and the ones connected to its Pages. Use when: the user wants the ad to appear from a specific Instagram handle, or a creation tool asked for instagram_account_id. Do not guess an Instagram id from a handle: an ad created from the wrong identity is visible to the public under the wrong name. Returns each account's id and username, and which Page it is connected to where Meta says so. Free: this call does not cost a task. If the list is empty, the ads will run from the Facebook Page alone — say so rather than failing.

    How to call: meta_ads(action="execute", tool_name="meta_list_instagram_accounts", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_list_mediaread-onlyfree

    List the images and videos already in a Meta ad account

    Lists the images and videos that already exist in the ad account's library, with the image hash or video id an ad is built from. Use when: the user says the file is "already in the account", asks to reuse a creative from an earlier ad, or has a file but no public link — uploading it once in Ads Manager and reusing it here needs no hosting anywhere. Do not use to check a file the user has only as a link; that is check_media, which is free. Returns each image's hash, name and pixel size, and each video's id, title and length. Those ids go straight into the create tools as image_hash or video_id, and skip the upload entirely. If the library is empty, say so and offer the two other ways in: a public link to the file, or an upload in Ads Manager first.

    How to call: meta_ads(action="execute", tool_name="meta_list_media", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "kind": {
          "description": "Which library to read. Defaults to both.",
          "type": "string",
          "enum": [
            "image",
            "video",
            "both"
          ]
        },
        "name_contains": {
          "description": "Only list assets whose name contains this text, matched case-insensitively.",
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "limit": {
          "description": "How many of each kind to return, newest first. Defaults to 25.",
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_list_pagesread-onlyfree

    List promotable Facebook Pages

    Lists the Facebook Pages this ad account may advertise from, with any Instagram account linked to each one. Use when: creating an ad (every ad needs a Page), or when a creation attempt failed because no Page was given. Do not guess a Page id from a Page name in a URL — a Page the user administers is not necessarily one this ad account can promote. Returns the Page id for facebook_page_id and the Instagram account id for instagram_account_id. When more than one Page comes back, ask the user which one to advertise from; do not choose for them. If the list is empty the user must add a Page to the ad account's Business portfolio before any ad can be created.

    How to call: meta_ads(action="execute", tool_name="meta_list_pages", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_list_promotable_appsread-onlyfree

    List apps this Meta account can promote

    Lists the mobile apps registered to this ad account's business that can be advertised, with their store URLs. Use when: the user wants to run app-install ads and you need the application_id and the store URL for meta_create_app_install_campaign. Do not invent an application id — an app campaign pointed at the wrong app cannot be fixed after it has spent. Returns each app's id, name and its App Store and Google Play links where Meta holds them. Free: this call does not cost a task. If nothing comes back, the app has not been added to the business portfolio; ask the user to add it rather than proceeding.

    How to call: meta_ads(action="execute", tool_name="meta_list_promotable_apps", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

performance2

  • meta_get_ad_performanceread-onlyfree

    Meta ad performance

    Ad-level results joined with the creative behind each ad (creative name and thumbnail), plus the previous-period comparison. Use when: choosing which creative to keep, refresh or turn off, or when the user asks which ad is working. Do not use to compare audiences — that is meta_get_adset_performance with a breakdown. Returns frequency alongside CTR: a rising frequency with a falling CTR is the usual signature of creative fatigue, and is worth saying out loud when the numbers show it. Scope with ad_set_id or campaign_id; ad-level reporting across a whole account is slow and rarely what the user means.

    How to call: meta_ads(action="execute", tool_name="meta_get_ad_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Only ads in this campaign."
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Only ads in this ad set."
        },
        "limit": {
          "description": "Maximum ads (default 20, ordered by spend).",
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_get_adset_performanceread-onlyfree

    Meta ad set performance

    Ad-set level results with the objective's own KPI and a previous-period comparison, optionally split by a breakdown (age and gender, publisher platform and position, device, or country). Use when: one campaign is under- or over-performing and you need to know which audience or placement is responsible. Do not use with a breakdown to judge overall performance — breakdown rows do not sum to the campaign total for every metric, and some metrics are unavailable when a breakdown is applied. Returns each ad set's spend, results, cost per result and ROAS where the objective supports it. If the user asks "why is this campaign expensive", run this with the placement breakdown before recommending anything.

    How to call: meta_ads(action="execute", tool_name="meta_get_adset_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Only ad sets in this campaign."
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Report on a single ad set."
        },
        "breakdown": {
          "description": "Split each ad set by this dimension. Omit for a plain ad set table.",
          "type": "string",
          "enum": [
            "age,gender",
            "publisher_platform,platform_position",
            "device_platform",
            "country"
          ]
        },
        "limit": {
          "description": "Maximum rows (default 25, ordered by spend).",
          "type": "integer",
          "minimum": 1,
          "maximum": 200
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

structure5

  • meta_get_lead_form_submissionsread-onlyfree

    Read submissions from a Meta lead form

    Reads the submissions of one lead form: when each lead arrived, which ad and campaign produced it, and the answers given. Use when: the user wants to know how many leads arrived, which ads produced them, or needs the answers to pass into their own system. Do not paste lead answers into a chat summary. The markdown reports counts, field names and timing only; the answers are in the structured data, and it is the user's decision what happens to them. Do not use it as a CRM sync — this is a read of what Meta holds, one page at a time. Returns the count, the per-campaign split, the field names, and each lead's answers in the data payload. Meta expires lead data after 90 days. If a form shows leads but none come back, say that rather than reporting zero as if no one applied.

    How to call: meta_ads(action="execute", tool_name="meta_get_lead_form_submissions", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "form_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "The lead form to read, from meta_list_lead_forms."
        },
        "limit": {
          "description": "Maximum leads to fetch (default 100). Meta pages them in blocks of 100.",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "since": {
          "description": "Only leads created on or after this date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "form_id"
      ]
    }
  • meta_list_ad_setsread-onlyfree

    List Meta ad sets

    Lists ad sets with budget, optimisation goal, billing event, a one-line targeting summary, schedule and learning-phase state. Use when: you need ad set ids, want to know what an ad set optimises for before changing its budget, or need to know whether it is still learning. Do not use for results — pair it with meta_get_adset_performance. Returns learning stage: an ad set in LEARNING has not yet had enough conversions for stable delivery, so budget or targeting edits restart the phase. Say so before recommending a change. Pass campaign_id to scope the list; without it you get every ad set in the account.

    How to call: meta_ads(action="execute", tool_name="meta_list_ad_sets", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Only ad sets in this campaign."
        },
        "status": {
          "description": "Comma-separated effective_status filter, e.g. \"ACTIVE\". Omit (or pass ALL) for everything except deleted and archived; an archived ad set is only visible when read by id.",
          "type": "string"
        },
        "limit": {
          "description": "Maximum ad sets to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_list_adsread-onlyfree

    List Meta ads

    Lists individual ads with delivery status, the creative behind each one and a preview link the user can open. Use when: you need ad ids to pause or compare specific ads, or the user asks what creative is live. Do not use to judge creative quality — get the numbers from meta_get_ad_performance first. Returns effective_status, which is where you see review problems: DISAPPROVED or PENDING_REVIEW explain zero delivery far more often than budget does. Scope with ad_set_id or campaign_id; without either you get the whole account.

    How to call: meta_ads(action="execute", tool_name="meta_list_ads", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "ad_set_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Only ads in this ad set."
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Only ads in this campaign."
        },
        "status": {
          "description": "Comma-separated effective_status filter, e.g. \"ACTIVE,PAUSED\". Omit (or pass ALL) for everything except deleted and archived.",
          "type": "string"
        },
        "limit": {
          "description": "Maximum ads to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_list_campaignsread-onlyfree

    List Meta campaigns

    Lists the campaigns in a Meta ad account with objective, delivery status, budget (as a decimal in the account currency), bid strategy and any Advantage+ state the account exposes. Use when: you need campaign ids before reading performance or changing anything, or the user asks what is running. Do not use to compare results — that is meta_get_campaign_performance; this call returns configuration only, no spend or conversions. Returns one row per campaign plus the campaign id you must pass to other tools. Budgets set at campaign level mean the campaign uses campaign budget optimisation and its ad sets have no budgets of their own. If unsure which account: call list_connected_accounts first rather than guessing an id.

    How to call: meta_ads(action="execute", tool_name="meta_list_campaigns", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "status": {
          "description": "Comma-separated effective_status filter, e.g. \"ACTIVE\" or \"ACTIVE,PAUSED\". Omit (or pass ALL) for everything except deleted and archived. Archived objects never appear in a listing: read one by id if you need it.",
          "type": "string"
        },
        "objective": {
          "description": "Filter to one objective, e.g. OUTCOME_SALES.",
          "type": "string"
        },
        "limit": {
          "description": "Maximum campaigns to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_list_lead_formsread-onlyfree

    List Meta lead forms

    Lists the instant (lead gen) forms attached to the Pages this ad account can advertise from, with how many leads each has collected and what it asks for. Use when: the user asks about lead forms, wants a form id for meta_get_lead_form_submissions, or wants to know which form a campaign should use. Do not use to read the leads themselves — that is meta_get_lead_form_submissions, and it returns personal data. Returns each form's id, name, status, lead count and question labels. Question labels are metadata, not answers, so nothing personal appears here. Reading forms needs leads_retrieval and pages_manage_ads on the connection and leads access to the Page. If Meta refuses, say which permissions the connection needs rather than guessing the form list.

    How to call: meta_ads(action="execute", tool_name="meta_list_lead_forms", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "page_id": {
          "type": "string",
          "minLength": 1,
          "pattern": "^[0-9]+$",
          "description": "Limit to one Page, from meta_list_pages."
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

audiences2

  • meta_list_custom_audiencesread-onlyfree

    List Meta custom and lookalike audiences

    Lists the saved custom and lookalike audiences on the ad account with their type, approximate size and whether Meta considers them usable right now. Use when: planning retargeting or exclusions, or when the user refers to an audience by name and you need its id. Do not assume an audience is usable because it exists — Meta refuses to deliver to audiences below roughly a thousand people, and the delivery status says so. Returns each audience id plus its subtype (website, customer list, engagement, lookalike), which decides whether it makes sense as a target or only as an exclusion. If an audience is too small, say so and suggest broadening rather than launching against it.

    How to call: meta_ads(action="execute", tool_name="meta_list_custom_audiences", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • meta_list_saved_audiencesread-onlyfree

    List saved Meta audiences

    Lists the saved audiences in the ad account: the reusable targeting definitions someone built in Ads Manager, with their size estimate. Use when: the user mentions an audience by name, or you want to know whether a definition already exists before rebuilding it interest by interest. Do not confuse these with custom audiences (meta_list_custom_audiences): a saved audience is a targeting recipe, a custom audience is a list of people. Returns the id, name, description and approximate size of each saved audience, plus its targeting summary. Free: this call does not cost a task. Saved audiences cannot be applied to an ad set through this server yet — say so rather than implying they can.

    How to call: meta_ads(action="execute", tool_name="meta_list_saved_audiences", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "limit": {
          "description": "Maximum audiences to return (default 25).",
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

conversions1

  • meta_list_pixelsread-onlyfree

    List Meta pixels

    Lists the pixels on the ad account with the time each one last received an event, and the standard events it has recently recorded where Meta exposes them. Use when: setting up a sales or leads campaign — those objectives cannot optimise without a pixel and an event — or when diagnosing why conversions are not being recorded. Do not treat a pixel that exists as a pixel that works: a pixel with no recent fire time will not deliver conversions no matter what the campaign says. Returns the pixel id needed by meta_create_image_campaign and the event names that are safe to optimise for. If no pixel has fired recently, tell the user that a conversion campaign will not optimise and suggest a traffic objective instead.

    How to call: meta_ads(action="execute", tool_name="meta_list_pixels", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "Meta ad account id such as act_1234567890. Omit it to use the primary account; if several accounts are active and none is primary the call fails and lists the valid ids.",
          "type": "string"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

ChatGPT Ads22

Reads run through chatgpt_ads, changes through chatgpt_ads_write. Call chatgpt_ads(action="list_tools") for the same list at runtime — it is free.

management10

  • chatgpt_archive_campaigndestructive write1 task

    Archive a ChatGPT Ads campaign permanently

    Archives one campaign permanently. This cannot be undone on this platform: the campaign and everything under it stop delivering for good, cannot be resumed, renamed or reused, and only its historical numbers remain. Use when: the user has explicitly asked to remove or clean up a campaign they are certain about, and understands it is permanent. Requires confirm_delete: true, which you may only set after they have said so in their own words. Do not use it to stop spend — chatgpt_pause_campaign does that, reversibly, and is almost always what "stop", "turn off" or "kill" actually means. If there is any doubt at all, pause instead and say why. Do not archive to tidy up on your own initiative, and never archive something you created in this conversation without being asked twice. Returns the campaign read back from the platform in its archived state. Once that read-back says archived, there is nothing anyone can do to bring it back — say that plainly rather than offering to undo it.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_archive_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The campaign to archive permanently, from chatgpt_list_campaigns."
        },
        "confirm_delete": {
          "description": "Must be true to remove anything. Ask the user first.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • chatgpt_pause_addestructive write1 task

    Pause a ChatGPT Ads ad

    Stops delivery on one ad and leaves everything around it running. Nothing is deleted: the creative, its review verdict and its history stay, and chatgpt_resume_ad starts it again. Use when: one creative is clearly worse than the others in the same ad group, and you have shown the user the numbers at ad level from chatgpt_get_performance. Do not pause the only ad in an ad group and call the ad group fixed — an ad group with no active ad delivers nothing at all; say that plainly instead. Do not judge a creative on a day of data. This platform's daily numbers are noisy at small budgets. Returns the ad read back from the platform, which is how the pause is confirmed rather than assumed.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_pause_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "ad_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The ad to act on, from chatgpt_list_ads."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_id"
      ]
    }
  • chatgpt_pause_ad_groupdestructive write1 task

    Pause a ChatGPT Ads ad group

    Stops delivery on one ad group without touching the rest of the campaign. Spend on it drops to zero; nothing is deleted, the ads underneath keep their configuration and review history, and chatgpt_resume_ad_group starts it again. Use when: the user wants to stop one part of a campaign — a test that is not working, an audience that is too expensive — and you have shown them what it is currently spending. Do not pause on one or two days of data: this platform's daily numbers are noisy at small budgets. Show last_7_days from chatgpt_get_performance at ad_group level first. Do not use this as a way to delete. Pausing is reversible; archiving is not. Returns the ad group read back from the platform, which is how the pause is confirmed rather than assumed.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_pause_ad_group", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The ad group to act on, from chatgpt_list_ad_groups."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_group_id"
      ]
    }
  • chatgpt_pause_campaigndestructive write1 task

    Pause a ChatGPT Ads campaign

    Stops delivery on one campaign by setting it to paused. Spend stops; nothing is deleted, every ad group and ad underneath keeps its configuration and history, and the campaign can be started again with chatgpt_resume_campaign. Use when: the user has explicitly asked to stop something, and you have shown them what it is currently spending. Do not pause on the strength of a day or two of data — this platform's daily numbers are noisy at small budgets. Show the user last_7_days from chatgpt_get_performance and let them decide. Do not use this as a way to delete. Pausing is reversible; archiving is not, and no tool here archives anything. Returns the campaign read back from the platform, which is how you confirm the pause actually applied rather than reporting an intention as a fact.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_pause_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The campaign to act on, from chatgpt_list_campaigns."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • chatgpt_resume_adwrite1 task

    Resume a ChatGPT Ads ad

    Sets one ad back to active so it can be shown again, inside whatever its ad group and campaign are doing. Use when: the user has reviewed a paused creative — often one this server created — and wants it delivering. Do not assume resuming makes it live. An ad in review, a paused ad group or a paused campaign each keep it at zero; the read-back and chatgpt_list_ads say which. Do not resume a rejected ad and expect a different answer: fix the copy or the destination with chatgpt_update_ad first, which restarts review. Returns the ad read back from the platform with its status and review verdict.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_resume_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "ad_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The ad to act on, from chatgpt_list_ads."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_id"
      ]
    }
  • chatgpt_resume_ad_groupwrite1 task

    Resume a ChatGPT Ads ad group

    Sets one ad group back to active so it can deliver again. Real money is spent as soon as the platform approves delivery. Use when: the user has reviewed something paused — often an ad group this server created — and wants it live. Do not resume without telling the user what it bids and what the campaign's spend limits are: the campaign budget is shared with every other ad group under it. Do not assume "active" means "delivering". An ad group inside a paused campaign still delivers nothing, and an unapproved ad delivers nothing either — check the read-back and chatgpt_list_ads. Returns the ad group read back from the platform with its bidding configuration and any serving issue.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_resume_ad_group", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The ad group to act on, from chatgpt_list_ad_groups."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_group_id"
      ]
    }
  • chatgpt_resume_campaignwrite1 task

    Resume a ChatGPT Ads campaign

    Sets one campaign back to active so it can deliver again. Real money starts being spent as soon as the platform approves delivery. Use when: the user has reviewed something paused — often a campaign this server created — and wants it live. Do not resume without telling the user the spend limits first: resuming restarts the whole structure underneath, and the daily limit applies from that moment. Do not assume "active" means "delivering". Check the read-back: an unapproved account, an unapproved ad or a serving issue will keep an active campaign at zero, and the serving issues in the read-back say which. Returns the campaign read back from the platform, including its spend limits and any serving issue.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_resume_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The campaign to act on, from chatgpt_list_campaigns."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • chatgpt_update_adwrite1 task

    Edit the copy on a ChatGPT Ads ad

    Changes the headline, body copy or destination URL on an existing chat card. The creative is replaced as a whole, so everything not being changed — the uploaded image above all — is sent back with it and nothing is silently dropped. Use when: the user wants different copy on an ad that already exists, or the landing page has moved. Read the ad first with chatgpt_list_ads so the before/after you show is real. Do not use it to change the image: an image is uploaded when the ad is created, so a new picture means a new ad with chatgpt_create_ad. Do not use it to pause or resume — those tools state the risk properly. Editing a creative sends the ad back into review on this platform. An approved ad stops delivering until it is approved again, which is a real cost on a good ad — say so before the user approves the change. Returns the ad read back from the platform, including its review verdict, so the new copy and the new review state are confirmed rather than assumed. The limits are the same as when creating: title 3–50, body at most 100, https destination. At least one of title, body or target_url must be given. Do not rewrite the user's wording to fit a limit — tell them it is too long and ask.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_update_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "ad_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The ad to edit, from chatgpt_list_ads."
        },
        "title": {
          "description": "New headline, 3–50 characters.",
          "type": "string"
        },
        "body": {
          "description": "New body copy, at most 100 characters.",
          "type": "string"
        },
        "target_url": {
          "description": "New destination URL. Must be https.",
          "type": "string",
          "format": "uri"
        },
        "name": {
          "description": "New ad name in Ads Manager. Cosmetic; does not affect delivery.",
          "type": "string",
          "minLength": 3,
          "maxLength": 1000
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_id"
      ]
    }
  • chatgpt_update_ad_groupwrite1 task

    Edit a ChatGPT Ads ad group

    Changes an ad group's name, maximum bid or context hints, and — because geo targeting is a campaign setting on this platform — optionally the locations the whole parent campaign runs in. Use when: the user has named what to change and what to change it to. Read the ad group first with chatgpt_list_ad_groups so the diff is against something you have shown them. Do not use it to pause or resume: chatgpt_pause_ad_group and chatgpt_resume_ad_group state the risk of that change properly. Do not use it for budgets — those are on the campaign, so use chatgpt_update_campaign. Do not ask it for automatic bidding: Adako only sets fixed bids here, so automatic_bidding: true is refused. The platform's own Maximize Results bidding needs a campaign on a daily budget and has to be set in Ads Manager. The bid's unit follows the campaign objective — a CPM (per 1,000 impressions) for reach, per click for clicks, per conversion for conversions — and the preview shows the old and new bid in that unit. Do not pass place names in the location arguments; resolve them with chatgpt_geo_lookup first. Changing locations here replaces the campaign's whole include or exclude list, country-level targeting included, and therefore affects every ad group under it. The preview shows exactly what is replaced. Returns the ad group read back from the platform, so the new bid is confirmed rather than assumed. A maximum bid far below the market stops winning auctions altogether, which looks like a broken ad group; the preview warns when a change looks like that. At least one field must be given. If the user says "bid higher" without a number, ask how much.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_update_ad_group", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The ad group to edit, from chatgpt_list_ad_groups."
        },
        "name": {
          "description": "New name.",
          "type": "string",
          "minLength": 3,
          "maxLength": 1000
        },
        "max_bid": {
          "description": "New maximum bid, a decimal in the account currency. Its meaning follows the campaign objective: reach → the most to pay per 1,000 impressions (CPM, 60 means 60.00 per thousand); clicks → the most to pay per click (2.5 means 2.50 per click); conversions → the target cost per conversion (100 means 100.00 per conversion, although billing is still per click).",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "automatic_bidding": {
          "description": "Not supported here: Adako only sets fixed bids on this platform, so true is refused. Pass max_bid with the amount the user wants instead.",
          "type": "boolean"
        },
        "context_hints": {
          "description": "Replaces the context hints entirely. Pass an empty array to clear them.",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          }
        },
        "geo_location_ids": {
          "description": "Location ids from chatgpt_geo_lookup that the ads may run in. Ids only — a place name here is refused. Targeting lives on the campaign on this platform, so this replaces the campaign’s whole location list (country-level targeting included) and applies to every ad group under it.",
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "excluded_geo_location_ids": {
          "description": "Location ids from chatgpt_geo_lookup the ads must not run in. Ids only. Like the include list, this is campaign-wide and replaces the campaign’s whole exclude list, excluded countries included.",
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_group_id"
      ]
    }
  • chatgpt_update_campaignwrite1 task

    Edit a ChatGPT Ads campaign

    Changes a campaign's name, status or spend limit. Both limits are decimals in the account currency: the lifetime limit is a hard ceiling on everything the campaign will ever spend, the daily limit paces it per day. A campaign has one or the other, never both, so pass only one of lifetime_budget and daily_budget — passing both is refused. Setting a daily budget on a lifetime campaign replaces the ceiling permanently: the platform cannot switch back, so read chatgpt_list_campaigns first and tell the user which kind of limit the campaign has now. Use when: renaming for clarity, or the user has decided to scale a campaign up or down and named the amount. Do not use it to pause or resume — chatgpt_pause_campaign and chatgpt_resume_campaign exist so the risk of that change is stated correctly to the user before they approve it. Do not lower the lifetime ceiling below what the campaign has already spent: the platform refuses it, and once a campaign is at its ceiling it stops delivering. Check spend with chatgpt_get_performance first. Returns the campaign read back from the platform after the change, so the new values are confirmed rather than assumed. At least one of name, status, lifetime_budget or daily_budget must be given. If the user says "increase the budget" without a number, ask which limit and how much. Never pick an amount for them.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_update_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The campaign to edit."
        },
        "name": {
          "description": "New campaign name.",
          "type": "string",
          "minLength": 3,
          "maxLength": 1000
        },
        "status": {
          "description": "New status. Prefer chatgpt_pause_campaign / chatgpt_resume_campaign, which explain the risk properly.",
          "type": "string",
          "enum": [
            "active",
            "paused"
          ]
        },
        "lifetime_budget": {
          "description": "Hard ceiling on total spend, decimal in the account currency (25 means twenty-five, not 2500). Only for a campaign that already has a lifetime ceiling; a daily-budget campaign cannot be switched back. Never pass this together with daily_budget.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "daily_budget": {
          "description": "Daily spending limit, decimal in the account currency. Replaces a lifetime ceiling permanently if the campaign has one. Never pass this together with lifetime_budget.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }

creation3

  • chatgpt_create_adwrite3 tasks

    Create a ChatGPT Ads chat card ad

    Adds one paused chat card ad to an ad group that already exists: the image is downloaded, checked and uploaded, then the ad is created. Nothing delivers and nothing spends until the user resumes it. Use when: the user wants another creative in a structure they already have — a second headline to test, a replacement for a rejected ad. Get the ad group id from chatgpt_list_ad_groups. Do not use it to build a campaign from nothing: that is chatgpt_launch_ad. Do not invent copy, a destination or an image the user did not give; if any of the four is missing, ask. The limits are hard and checked before anything is created, so a rejection here costs nothing: the title must be 3–50 characters, the body at most 100, the destination an https URL, and the image square. Run chatgpt_validate_chat_card first if you want to check copy for free. Returns the new ad id, read back from the platform with its paused state and its review verdict. A new ad always starts in review; it delivers nothing until the platform approves it, so do not tell the user it is live until chatgpt_list_ads says approved.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_create_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The ad group to add the ad to, from chatgpt_list_ad_groups."
        },
        "title": {
          "type": "string",
          "description": "Chat card headline, 3–50 characters. This is the line people read first."
        },
        "body": {
          "type": "string",
          "description": "Chat card body copy, at most 100 characters. Put the offer in the first half."
        },
        "target_url": {
          "type": "string",
          "format": "uri",
          "description": "Where a click goes. Must be https and reachable without a login."
        },
        "image_url": {
          "type": "string",
          "format": "uri",
          "description": "The card image. It must be square; its bytes are checked here before anything is created. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet."
        },
        "name": {
          "description": "Ad name in Ads Manager. Defaults to the headline.",
          "type": "string",
          "minLength": 3,
          "maxLength": 1000
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "ad_group_id",
        "title",
        "body",
        "target_url",
        "image_url"
      ]
    }
  • chatgpt_create_ad_groupwrite2 tasks

    Create a ChatGPT Ads ad group

    Adds one paused ad group to a campaign that already exists, with its own bidding configuration and optional context hints. Nothing delivers until the user resumes it, and creating it spends nothing. Use when: the user wants to test a different audience, context or bid inside a campaign they already have. Get the campaign id from chatgpt_list_campaigns first. Do not use it to set a budget — budgets live on the campaign on this platform, so a "budget for this ad group" is really chatgpt_update_campaign. Do not use it to build a whole campaign from scratch: that is chatgpt_launch_ad. Do not pass a place name in geo_location_ids. Resolve every place with chatgpt_geo_lookup first; ids only. Because geo targeting is a campaign setting here, passing locations rewrites where the **whole campaign** runs — the preview says so explicitly, so read it to the user before they approve. max_bid is required: this tool creates a fixed-bid ad group, which the platform will not accept without a maximum bid, so a call without one is refused before anything is created. Its unit follows the campaign objective — a CPM (per 1,000 impressions) for reach, per click for clicks, per conversion for conversions — and the preview shows it with that unit. Returns the new ad group id, read back from the platform with its paused state and its bid confirmed. If the user has not said what the ad group is for or what to bid, ask. Do not invent a bid.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_create_ad_group", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The campaign to add the ad group to, from chatgpt_list_campaigns."
        },
        "name": {
          "type": "string",
          "minLength": 3,
          "maxLength": 1000,
          "description": "Ad group name shown in Ads Manager. Say what it targets."
        },
        "max_bid": {
          "description": "Maximum bid, a decimal in the account currency. Required, because this tool creates a fixed-bid ad group: without it the call is refused, so ask the user for the number. Its meaning follows the campaign objective: reach → the most to pay per 1,000 impressions (CPM, 60 means 60.00 per thousand); clicks → the most to pay per click (2.5 means 2.50 per click); conversions → the target cost per conversion (100 means 100.00 per conversion, although billing is still per click).",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "context_hints": {
          "description": "Short free-text hints about the audience or context the ads suit. This platform has no keywords; these are the nearest thing.",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          }
        },
        "geo_location_ids": {
          "description": "Location ids from chatgpt_geo_lookup that the ads may run in. Ids only — a place name here is refused. Targeting lives on the campaign on this platform, so this replaces the campaign’s whole location list (country-level targeting included) and applies to every ad group under it.",
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "excluded_geo_location_ids": {
          "description": "Location ids from chatgpt_geo_lookup the ads must not run in. Ids only. Like the include list, this is campaign-wide and replaces the campaign’s whole exclude list, excluded countries included.",
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id",
        "name"
      ]
    }
  • chatgpt_launch_adwrite8 tasks

    Launch a ChatGPT Ads chat card ad

    Builds a complete, paused campaign on ChatGPT Ads: image upload, campaign, ad group and one chat card ad. The campaign is created paused and its ad group and ad active inside it, so nothing delivers and nothing spends until the campaign is turned on, and turning it on launches everything. Say this explicitly when you report the result, with the two links the result gives (ChatGPT Ads Manager and the campaign's page in Adako), and offer "enable all": one chatgpt_resume_campaign call, listed in the result as go_live.enable_all, one approval. Use when: the user has given a budget, a maximum bid, a destination, ad copy and a square image, and has decided to launch. Resolve every place they named with chatgpt_geo_lookup first — this tool takes location ids and refuses names. Do not call it twice for the same brief. If a step fails, the error lists what already exists; finish or remove that rather than starting again, or you will create a second campaign spending a second budget. Pass exactly one of lifetime_budget and daily_budget: a campaign on this platform can have a total ceiling or a daily limit, never both, and passing both is refused before anything is created. Do not invent copy, a budget, a bid, a destination or a location the user did not give. If any of those is missing, ask. max_bid is required because this tool creates a fixed-bid ad group, and its unit follows the objective: a CPM (per 1,000 impressions) for reach, per click for clicks, per conversion for conversions. Before the user approves, confirm with them that every market in the targeting is enabled for advertising on their account: a valid location id does not mean the account may advertise there. The preview lists each location id with its place and country when a recent chatgpt_geo_lookup returned it. The card limits are hard: the title must be 3–50 characters, the body at most 100, and the image must be square. These, the bid and the schedule are all checked before anything is created, so a rejection here costs nothing. Schedule dates are days in the ad account time zone, and a start date already past there is refused. Returns the ids of the four objects created and reads all of them back so their state is confirmed rather than assumed. Budgets and bids are decimals in the account currency.

    How to call: chatgpt_ads_write(action="execute", tool_name="chatgpt_launch_ad", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "campaign_name": {
          "type": "string",
          "minLength": 3,
          "maxLength": 1000,
          "description": "Name shown in Ads Manager. Say what it sells and to whom."
        },
        "lifetime_budget": {
          "description": "Hard ceiling on everything this campaign will ever spend, as a decimal in the account currency (100 means one hundred, not 10000). A campaign takes either this or daily_budget, never both — pass exactly one.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "daily_budget": {
          "description": "Daily spending limit, decimal in the account currency, with no total ceiling. A campaign takes either this or lifetime_budget, never both — pass exactly one.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "objective": {
          "description": "What the platform optimises for: reach, clicks or conversions. Defaults to clicks. Conversions also needs conversion_event_setting_id. It also decides what max_bid means.",
          "type": "string",
          "enum": [
            "reach",
            "clicks",
            "conversions"
          ]
        },
        "conversion_event_setting_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The conversion event to optimise for. Required when objective is conversions."
        },
        "geo_location_ids": {
          "description": "Location ids from chatgpt_geo_lookup to run in. Ids only — a place name here is refused. Omit to run wherever the account is enabled to advertise.",
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "excluded_geo_location_ids": {
          "description": "Location ids from chatgpt_geo_lookup the campaign must not run in, for example a region inside an included country. Ids only; an id cannot be both included and excluded.",
          "maxItems": 100,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "ad_group_name": {
          "description": "Ad group name. Defaults to the campaign name with a suffix.",
          "type": "string",
          "minLength": 3,
          "maxLength": 1000
        },
        "context_hints": {
          "description": "Short free-text hints about the audience or context the ad suits. This platform has no keywords; these are the nearest thing.",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          }
        },
        "max_bid": {
          "description": "Maximum bid, a decimal in the account currency. This tool creates a fixed-bid ad group, which the platform will not accept without one, so a launch without it is refused before anything is created — ask the user for the number, never invent one. Its meaning follows the campaign objective: reach → the most to pay per 1,000 impressions (CPM, 60 means 60.00 per thousand); clicks → the most to pay per click (2.5 means 2.50 per click); conversions → the target cost per conversion (100 means 100.00 per conversion, although billing is still per click).",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "title": {
          "type": "string",
          "description": "Chat card headline, 3–50 characters. This is the line people read first."
        },
        "body": {
          "type": "string",
          "description": "Chat card body copy, at most 100 characters. Put the offer in the first half."
        },
        "target_url": {
          "type": "string",
          "format": "uri",
          "description": "Where a click goes. Must be https and reachable without a login."
        },
        "image_url": {
          "type": "string",
          "format": "uri",
          "description": "The card image. It must be square; its bytes are checked here before anything is created. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet."
        },
        "start_date": {
          "description": "First day delivery may start, YYYY-MM-DD, in the ad account time zone. Must be today or later there. Defaults to whenever the user activates it.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day of delivery, YYYY-MM-DD, inclusive, in the ad account time zone.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_name",
        "title",
        "body",
        "target_url",
        "image_url"
      ]
    }

targeting1

  • chatgpt_geo_lookupread-onlyfree

    Find ChatGPT Ads location ids

    Turns a place name into the location ids the platform targets by. "Berlin", "California", "Japan" go in; ids, canonical names and location types come out. Use when: the user names any place the ad should run in, always before chatgpt_launch_ad. The launch tool accepts ids only and refuses anything that looks like a name. Do not guess an id, reuse one from another ad platform, or pass a place name straight into a write — a wrong id spends real money in the wrong country and the platform will not warn you. Returns every match with its type and country code, so you can tell one Springfield from another. Types include country, region, city and dma (a US media market covering several cities, which is often what a metro name resolves to). If several matches look plausible, show them and ask the user which one they mean rather than taking the first. A match only proves the place exists. It does not mean this ad account can advertise there: ask the user to confirm the market is enabled for their account before launching in it. Costs nothing, so resolve every place the user mentions, one place per call.

    How to call: chatgpt_ads(action="execute", tool_name="chatgpt_geo_lookup", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "query": {
          "type": "string",
          "minLength": 1,
          "maxLength": 80,
          "description": "The place name to look up, as the user said it. One place per call."
        },
        "limit": {
          "description": "Maximum matches to return (default 10).",
          "type": "integer",
          "minimum": 1,
          "maximum": 100
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "query"
      ]
    }

discovery2

  • chatgpt_get_accountread-onlyfree

    Show the connected ChatGPT Ads account

    Reads the advertiser account the stored key belongs to: id, name, website, currency, timezone, account status and the platform's review verdict on the account. This is the same call the key was verified with when the user connected it. Use when: starting work on this platform, confirming which account you are about to change, or checking whether a failing call is an access problem rather than a data problem. Run it before any write if you are not certain which account the key points at. Do not use it to see what is running — that is chatgpt_list_campaigns. Do not use it to test a key the user pasted into chat: keys are only ever stored through the Accounts page and are never passed to a tool. Returns the account currency and timezone. Every budget and every spend figure on this platform is a decimal in that currency, and every reporting window is resolved in that timezone. If the account review status is not "approved", nothing will deliver no matter what is created — say so before building anything. Costs nothing, so run it whenever the answer would otherwise be a guess. If it fails with a reauth error the key was rotated or revoked and the user must paste a new one.

    How to call: chatgpt_ads(action="execute", tool_name="chatgpt_get_account", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • chatgpt_get_account_limitsread-onlyfree

    Show what this ChatGPT Ads account can do

    Lists what this advertiser account supports: the campaign objectives, what the platform can bid towards, what it bills on, the minimum spend limit, the chat card and image rules, and the account's own currency, timezone and review state. Use when: planning anything before creating it — choosing an objective, quoting a minimum budget, or answering "can this platform do X". Run it instead of guessing; guessing produces a proposal the user approves and the platform then refuses. Do not use it to read what exists (chatgpt_list_campaigns) or how it performs (chatgpt_get_performance). Do not present these limits as advice: they are what the API accepts, and a value outside them is refused before anything is created. Returns the minimum spend limit as a decimal in the account currency, and flags an account whose review verdict means nothing will deliver whatever is built. A conversions objective additionally needs a conversion event — find one with chatgpt_get_pixel_settings. Costs nothing and makes one cached read, so run it whenever the answer would otherwise be a guess.

    How to call: chatgpt_ads(action="execute", tool_name="chatgpt_get_account_limits", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

conversions2

  • chatgpt_get_pixel_settingsread-onlyfree

    Read one ChatGPT Ads pixel and its conversion events

    Reads one pixel in detail: when it last received an event, whether a Conversions API key exists, and the conversion event settings on it — the ids a conversions campaign optimises towards. Use when: the user is about to launch or move a campaign to the conversions objective, or asks which events are being measured. Pass the id from chatgpt_list_pixels; with exactly one pixel on the account you can omit it. Do not use it to judge results — how many conversions were recorded is chatgpt_get_performance. Do not ask the user for their Conversions API key and never put one in a tool argument: this tool reports only whether a key exists. Returns the event settings with their ids, so a conversion_event_setting_id for chatgpt_launch_ad never has to be guessed. An event marked not eligible for optimisation cannot be used as a campaign's objective even though it is listed. If the account is not enabled for this part of the API the tool says so plainly; that answer is final, so relay it instead of retrying.

    How to call: chatgpt_ads(action="execute", tool_name="chatgpt_get_pixel_settings", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "pixel_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "The pixel to read, from chatgpt_list_pixels. Omit it when the account has exactly one pixel."
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • chatgpt_list_pixelsread-onlyfree

    List ChatGPT Ads pixels

    Lists the measurement pixels on the advertiser account, when each last received an event, and whether a Conversions API key exists for it. The key itself is never read or shown — only whether one is there. Use when: the user asks about conversion tracking, or before launching anything with a conversions objective, because that objective needs a conversion event to optimise towards. Do not use it to read results — conversions that were recorded show up in chatgpt_get_performance. Do not tell the user tracking "works" because a pixel exists: a pixel that has never fired is not tracking anything, which is what the last-event column is for. Returns one row per pixel. Pixel management has to be enabled on the ad account; when it is not, the platform answers 404 and the tool says so explicitly instead of failing — that answer is final, so relay it and move on rather than retrying. The platform publishes how to create a pixel but not how to list them, so treat an empty result as "nothing visible to the API", not as proof that nothing exists.

    How to call: chatgpt_ads(action="execute", tool_name="chatgpt_list_pixels", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

structure3

  • chatgpt_list_ad_groupsread-onlyfree

    List ChatGPT Ads ad groups

    Lists ad groups with status, bidding configuration (what the account is billed on and the maximum bid as a decimal in the account currency, in its unit), the free-text context hints that steer where the ads appear, and any serving issue. Use when: you need ad group ids, or want to know how an ad group bids before changing anything above it. Do not use for results — pair it with chatgpt_get_performance at ad-group level. Returns max_bid with bid_unit, which follows the campaign objective: cpm (per 1,000 impressions) for reach, cpc (per click) for clicks, cpa (per conversion) for conversions. The bid is where "why is this not spending" often lives: an ad group bidding far below the market simply never wins. bid_strategy says how it bids: fixed_bid carries a maximum bid, while maximize_clicks and maximize_conversions let the platform set the bid and show none. Note that geo and audience targeting are set on the **campaign** on this platform, not here — read chatgpt_list_campaigns for where an ad group is allowed to run. Pass campaign_id to scope the list.

    How to call: chatgpt_ads(action="execute", tool_name="chatgpt_list_ad_groups", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "Only ad groups in this campaign."
        },
        "status": {
          "description": "Only objects in this state. Statuses are lowercase on this platform. Defaults to all except archived.",
          "type": "string",
          "enum": [
            "active",
            "paused",
            "archived",
            "all"
          ]
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • chatgpt_list_adsread-onlyfree

    List ChatGPT Ads ads

    Lists individual ads with their status, the chat card behind each one (title, body, destination) and — most usefully — the platform's review verdict and reason. Use when: you need ad ids, or the user asks what creative is live, or an ad is not delivering and you need to know whether review is the reason. Do not use to judge creative quality — get the numbers from chatgpt_get_performance first, then look at the copy. Returns review_status: an ad that is in_review is not delivering yet, and one that is rejected never will until the copy or destination changes. The rejection reason is the single most useful field on this platform, so quote it to the user verbatim rather than paraphrasing. Pass ad_group_id to scope the list; without it you get every ad in the account.

    How to call: chatgpt_ads(action="execute", tool_name="chatgpt_list_ads", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "ad_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "Only ads in this ad group."
        },
        "status": {
          "description": "Only objects in this state. Statuses are lowercase on this platform. Defaults to all except archived.",
          "type": "string",
          "enum": [
            "active",
            "paused",
            "archived",
            "all"
          ]
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • chatgpt_list_campaignsread-onlyfree

    List ChatGPT Ads campaigns

    Lists the campaigns in the advertiser account with status, objective, what the platform bids towards, the lifetime and daily spend limits as decimals in the account currency, the schedule, where they are allowed to run, and any serving issue the platform reports. Use when: you need campaign ids before reading performance or changing anything, or the user asks what is running. Do not use to compare results — that is chatgpt_get_performance; this returns configuration only, with no spend or clicks in it. Returns one row per campaign plus the id every other tool needs. Serving issues are the fastest explanation for a campaign that exists but delivers nothing: an exhausted budget, a missing payment method or an unapproved account will all show up here rather than in the numbers. Archived campaigns are hidden unless you ask for them, because archiving is irreversible on this platform and an archived campaign is never the answer to "what is running".

    How to call: chatgpt_ads(action="execute", tool_name="chatgpt_list_campaigns", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "ChatGPT Ads advertiser account id. One advertiser key belongs to one account, so this is only needed when the user has connected more than one key. Omit it to use the primary account.",
          "type": "string"
        },
        "status": {
          "description": "Only objects in this state. Statuses are lowercase on this platform. Defaults to all except archived.",
          "type": "string",
          "enum": [
            "active",
            "paused",
            "archived",
            "all"
          ]
        },
        "name": {
          "description": "Only campaigns whose name matches this text, as the platform matches it.",
          "type": "string"
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

assets1

  • chatgpt_validate_chat_cardread-onlyfree

    Check chat card copy and image before building an ad

    Checks a chat card against the rules before anything is created: the headline is 3–50 characters, the body at most 100, the destination an https URL of at most 2048 characters, and the image square, at least 400 px a side, under 10 MB and one of image/jpeg, image/png, image/webp. Use when: you have drafted copy, or the user pasted copy or an image URL, always before chatgpt_create_ad or chatgpt_launch_ad. Call it with no arguments at all to get the limits themselves. Do not use it for policy questions — it checks lengths, formats and reachability, never whether the claim in the copy is allowed. Do not use it to check a live ad: read that with chatgpt_list_ads. Returns every problem at once, split into things that will be refused and advice. Fix the refusals before creating anything; the writers apply exactly these rules and will refuse the whole call otherwise. Calls no ad platform, needs no connection and costs nothing, so run it as often as it helps. If the copy is too long, say so and ask the user how to shorten it — never rewrite their wording to fit.

    How to call: chatgpt_ads(action="execute", tool_name="chatgpt_validate_chat_card", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "title": {
          "description": "Headline to check.",
          "type": "string"
        },
        "body": {
          "description": "Body copy to check.",
          "type": "string"
        },
        "target_url": {
          "description": "Destination URL a click goes to.",
          "type": "string"
        },
        "image_url": {
          "description": "The card image. Only its header bytes are read. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet.",
          "type": "string"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

LinkedIn Ads33

Reads run through linkedin_ads, changes through linkedin_ads_write. Call linkedin_ads(action="list_tools") for the same list at runtime — it is free.

creation6

  • linkedin_add_creativewrite1 task

    Add a creative (ad) to a LinkedIn campaign (ad set)

    Adds one more creative (an ad in Campaign Manager) to an existing campaign (ad set) — either from a new image URL (uploaded and posted as a dark post) or from a post URN that already exists. LinkedIn refuses a paused creative before its review, so inside a campaign that is not live it is created ACTIVE (reviewed now, held back by the campaign) and inside a campaign that is already ACTIVE it is created DRAFT. Use when: adding a variant to test against the current ad, or finishing a campaign whose create chain failed partway. This is the tool that repairs a campaign with no ad in it; do not re-run the create tool for that. Do not add a fifth variant to a campaign: LinkedIn's delivery optimisation splits budget across creatives and beyond three or four none of them gets enough impressions to prove anything. Do not add a creative whose image has not been checked — a failed upload here wastes a task and leaves nothing useful behind. Do not use it on a LEAD_GENERATION campaign: every ad there needs a Lead Gen Form this server cannot attach, so it is refused; add those ads in Campaign Manager. Returns the creative read back with its review status. A DRAFT creative serves nothing until the user approves linkedin_resume_creative; an ACTIVE one serves once its campaign is resumed and LinkedIn approves it.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_add_creative", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign to add the creative to. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "post_urn": {
          "description": "An existing post (urn:li:share:… or urn:li:ugcPost:…) to sponsor. Use this or image_url.",
          "type": "string"
        },
        "organization_urn": {
          "description": "Company page that authors the new post. Required when image_url is used.",
          "type": "string"
        },
        "image_url": {
          "description": "A new creative image. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet.",
          "type": "string",
          "format": "uri"
        },
        "headline": {
          "description": "Headline for the new image.",
          "type": "string",
          "minLength": 1,
          "maxLength": 200
        },
        "commentary": {
          "description": "Post text above the new image. Sent as plain text: #hashtags and @mentions are not linked.",
          "type": "string",
          "minLength": 1,
          "maxLength": 3000
        },
        "landing_page_url": {
          "description": "Where the new creative sends people.",
          "type": "string",
          "format": "uri"
        },
        "name": {
          "description": "A name for the creative, for your own reference.",
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • linkedin_create_campaign_groupwrite1 task

    Create a LinkedIn campaign group (Campaign Manager campaign)

    Creates a campaign group (a campaign in Campaign Manager) — the container every LinkedIn campaign (ad set) must sit inside, and the place a shared lifetime budget can be set across several campaigns. Use when: starting a distinct initiative (a launch, a quarter, a market) that should be budgeted and reported as one thing, or when a create tool needs a group and no sensible existing one is there. Do not create a group per campaign: that is what campaigns are for, and a forest of one-campaign groups makes the account unreadable. Run linkedin_list_campaign_groups first and reuse. Do not set a total budget the user has not named. A group budget is a hard ceiling: when it is reached every campaign inside stops, whatever their own budgets say. LinkedIn only accepts a total budget together with an end date; if the user gave no end date, ask for one. The group is created ACTIVE, because LinkedIn creates groups only as ACTIVE or DRAFT. It holds no campaigns, so nothing spends, and every campaign created inside it starts paused. Returns the group read back from LinkedIn.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_create_campaign_group", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "name": {
          "type": "string",
          "minLength": 2,
          "maxLength": 255,
          "description": "Group name as it appears in Campaign Manager. At most 200 bytes of UTF-8: about 200 Latin letters, fewer in scripts such as Japanese."
        },
        "total_budget": {
          "description": "Lifetime ceiling across every campaign in the group, a decimal in the account currency. Only set it if the user named a number. Requires end_date.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "start_date": {
          "description": "YYYY-MM-DD; defaults to today.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "YYYY-MM-DD, optional. Required when total_budget is set.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "name"
      ]
    }
  • linkedin_create_carousel_campaignwrite1 task

    Create a LinkedIn carousel campaign (ad set)

    Builds a carousel sponsored-content campaign from two to ten square images, each with its own headline and destination — campaign group (a campaign in Campaign Manager), campaign (ad set), uploads, dark post and creative (ad). The campaign starts paused and holds everything back (LinkedIn creates groups and creatives only active or draft). Use when: the content is genuinely sequential (a process, a product range, several proof points). A carousel is more work than a single image and only beats one when each card earns its swipe. Do not use mixed aspect ratios: carousel cards render square (1200×1200) and anything else is cropped inconsistently across cards. Run linkedin_validate_assets on every URL first. Do not exceed ten cards or go below two — LinkedIn refuses both, and the refusal comes after the campaign has been created. Do not call it twice after a failure; it names what exists and linkedin_add_creative finishes it. Returns every id created. Nothing delivers until the user resumes the campaign. Say that the campaign is paused when you report the result, with the two links the result gives (LinkedIn Campaign Manager and the campaign's page in Adako), and offer "enable all": one linkedin_resume_campaign call, listed in the result as go_live.enable_all, one approval.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_create_carousel_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "name": {
          "type": "string",
          "minLength": 2,
          "maxLength": 255,
          "description": "Campaign name."
        },
        "campaign_group_id": {
          "description": "Existing campaign group id. Omit and a group named after the campaign is created first — prefer reusing one from linkedin_list_campaign_groups.",
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        },
        "objective": {
          "type": "string",
          "enum": [
            "BRAND_AWARENESS",
            "WEBSITE_VISIT",
            "ENGAGEMENT",
            "VIDEO_VIEW",
            "LEAD_GENERATION",
            "WEBSITE_CONVERSION",
            "JOB_APPLICANT"
          ],
          "description": "Campaign objective. It cannot be changed later — confirm it with linkedin_explain_objectives if unsure. LEAD_GENERATION is refused: its ads need a Lead Gen Form, which this server cannot attach yet."
        },
        "daily_budget": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Daily budget, a decimal in the account currency (50 means fifty). Must clear the LinkedIn per-currency minimum."
        },
        "total_budget": {
          "description": "Optional lifetime ceiling for this campaign.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "bid": {
          "description": "Manual bid, a decimal in the account currency: per click with cost_type CPC, per 1,000 impressions with CPM, per view with CPV. Setting it puts the campaign on manual bidding, where LinkedIn never bids above it. Omit it for LinkedIn's automated bidding, which optimises for the objective and always bills CPM. Text ads need a bid.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "cost_type": {
          "description": "How a manual bid is billed; defaults to what the objective prefers. Only with bid: without one the campaign bids automatically and is billed CPM.",
          "type": "string",
          "enum": [
            "CPM",
            "CPC",
            "CPV"
          ]
        },
        "targeting": {
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "description": "Targeting entity URNs per facet, exactly as linkedin_search_targeting returned them (for example locations: [\"urn:li:geo:103644278\"]). Never free text. locations is required. Entities inside one facet widen the audience (title A or title B); separate facets narrow it (location and title)."
        },
        "exclude": {
          "description": "Facets to exclude from the audience.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "start_date": {
          "description": "YYYY-MM-DD; defaults to today.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "YYYY-MM-DD, optional.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "language": {
          "description": "Two-letter language members use LinkedIn in; only they see the ads (default \"en\"). LinkedIn supports en, ar, zh, cs, da, id, ms, nl, fr, fi, de, it, ja, ko, no, pl, pt, ro, ru, es, sv, th, tl, tr.",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        },
        "country": {
          "description": "Not where the ads run — that is targeting.locations. Only matters for Chinese: \"TW\" or \"HK\" for Traditional, otherwise Simplified. Usually omit.",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        },
        "audience_expansion": {
          "description": "Let LinkedIn deliver to similar members outside the spec. Off by default; only turn it on if the user asked.",
          "type": "boolean"
        },
        "audience_network": {
          "description": "Buy impressions on third-party apps and sites too (LinkedIn Audience Network). Off by default. LinkedIn refuses it for text ads, carousels on WEBSITE_VISIT or ENGAGEMENT, and lead generation.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        },
        "organization_urn": {
          "type": "string",
          "description": "The company page that authors the post, from linkedin_get_organizations."
        },
        "cards": {
          "minItems": 2,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "image_url": {
                "type": "string",
                "format": "uri",
                "description": "This card's image (1200×1200). Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet."
              },
              "headline": {
                "type": "string",
                "minLength": 1,
                "maxLength": 45,
                "description": "This card's headline. LinkedIn truncates beyond 45 characters."
              },
              "landing_page_url": {
                "type": "string",
                "format": "uri",
                "description": "Where this card sends people."
              }
            },
            "required": [
              "image_url",
              "headline",
              "landing_page_url"
            ]
          },
          "description": "Two to ten cards, in the order they should be swiped."
        },
        "commentary": {
          "type": "string",
          "minLength": 1,
          "maxLength": 3000,
          "description": "The post text above the carousel. Sent as plain text: #hashtags and @mentions are not linked."
        }
      },
      "required": [
        "name",
        "objective",
        "daily_budget",
        "targeting",
        "organization_urn",
        "cards",
        "commentary"
      ]
    }
  • linkedin_create_image_campaignwrite1 task

    Create a LinkedIn single-image campaign (ad set)

    Builds a complete single-image sponsored-content campaign: campaign group (a campaign in Campaign Manager, reused or created), campaign (ad set), image upload, dark post authored by a company page, and the creative (ad). The campaign starts paused and holds everything back: LinkedIn creates groups and creatives only active or draft, so those are ACTIVE. Use when: the user has given an objective, a budget, an image URL, a destination URL, the post text, and targeting resolved through linkedin_search_targeting. Check the image with linkedin_validate_assets first; a failure mid-chain leaves a campaign behind with no ad in it. The preview counts the audience and refuses targeting under 300 members, so nothing is created against a spec LinkedIn would reject. Do not call it with targeting the user has not confirmed, or with any value you did not get from a resolver — LinkedIn takes URNs literally and a wrong one spends real money on the wrong people. Do not call it twice. If it fails partway it says exactly what exists; finish that with linkedin_add_creative rather than starting again, or the user pays for two campaigns. Do not pick the company page yourself when linkedin_get_organizations returns more than one — the page name is on the ad. Returns every id it created and the campaign read back. Nothing delivers until the campaign is resumed. Say that the campaign is paused when you report the result, with the two links the result gives (LinkedIn Campaign Manager and the campaign's page in Adako), and offer "enable all": one linkedin_resume_campaign call, listed in the result as go_live.enable_all, one approval.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_create_image_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "name": {
          "type": "string",
          "minLength": 2,
          "maxLength": 255,
          "description": "Campaign name."
        },
        "campaign_group_id": {
          "description": "Existing campaign group id. Omit and a group named after the campaign is created first — prefer reusing one from linkedin_list_campaign_groups.",
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        },
        "objective": {
          "type": "string",
          "enum": [
            "BRAND_AWARENESS",
            "WEBSITE_VISIT",
            "ENGAGEMENT",
            "VIDEO_VIEW",
            "LEAD_GENERATION",
            "WEBSITE_CONVERSION",
            "JOB_APPLICANT"
          ],
          "description": "Campaign objective. It cannot be changed later — confirm it with linkedin_explain_objectives if unsure. LEAD_GENERATION is refused: its ads need a Lead Gen Form, which this server cannot attach yet."
        },
        "daily_budget": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Daily budget, a decimal in the account currency (50 means fifty). Must clear the LinkedIn per-currency minimum."
        },
        "total_budget": {
          "description": "Optional lifetime ceiling for this campaign.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "bid": {
          "description": "Manual bid, a decimal in the account currency: per click with cost_type CPC, per 1,000 impressions with CPM, per view with CPV. Setting it puts the campaign on manual bidding, where LinkedIn never bids above it. Omit it for LinkedIn's automated bidding, which optimises for the objective and always bills CPM. Text ads need a bid.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "cost_type": {
          "description": "How a manual bid is billed; defaults to what the objective prefers. Only with bid: without one the campaign bids automatically and is billed CPM.",
          "type": "string",
          "enum": [
            "CPM",
            "CPC",
            "CPV"
          ]
        },
        "targeting": {
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "description": "Targeting entity URNs per facet, exactly as linkedin_search_targeting returned them (for example locations: [\"urn:li:geo:103644278\"]). Never free text. locations is required. Entities inside one facet widen the audience (title A or title B); separate facets narrow it (location and title)."
        },
        "exclude": {
          "description": "Facets to exclude from the audience.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "start_date": {
          "description": "YYYY-MM-DD; defaults to today.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "YYYY-MM-DD, optional.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "language": {
          "description": "Two-letter language members use LinkedIn in; only they see the ads (default \"en\"). LinkedIn supports en, ar, zh, cs, da, id, ms, nl, fr, fi, de, it, ja, ko, no, pl, pt, ro, ru, es, sv, th, tl, tr.",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        },
        "country": {
          "description": "Not where the ads run — that is targeting.locations. Only matters for Chinese: \"TW\" or \"HK\" for Traditional, otherwise Simplified. Usually omit.",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        },
        "audience_expansion": {
          "description": "Let LinkedIn deliver to similar members outside the spec. Off by default; only turn it on if the user asked.",
          "type": "boolean"
        },
        "audience_network": {
          "description": "Buy impressions on third-party apps and sites too (LinkedIn Audience Network). Off by default. LinkedIn refuses it for text ads, carousels on WEBSITE_VISIT or ENGAGEMENT, and lead generation.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        },
        "organization_urn": {
          "type": "string",
          "description": "The company page that authors the post, from linkedin_get_organizations (urn:li:organization:…)."
        },
        "image_url": {
          "type": "string",
          "format": "uri",
          "description": "The creative image (1200×627 or 1200×1200). Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet."
        },
        "headline": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Headline shown under the image."
        },
        "commentary": {
          "type": "string",
          "minLength": 1,
          "maxLength": 3000,
          "description": "The post text above the image, in the brand's voice. Sent as plain text: #hashtags and @mentions are not linked."
        },
        "landing_page_url": {
          "type": "string",
          "format": "uri",
          "description": "Where the ad sends people."
        }
      },
      "required": [
        "name",
        "objective",
        "daily_budget",
        "targeting",
        "organization_urn",
        "image_url",
        "headline",
        "commentary",
        "landing_page_url"
      ]
    }
  • linkedin_create_text_campaignwrite1 task

    Create a LinkedIn text ad campaign (ad set)

    Creates a TEXT_AD campaign (an ad set in Campaign Manager) — the small desktop-sidebar unit with a 25-character headline and a 75-character description. Campaign group (campaign), campaign and creative (ad); the campaign starts paused and holds everything back. Use when: the user has no company page to post from (text ads need none), or wants a cheap desktop-only test of a message before committing to sponsored content. CPCs are far lower than feed placements and so are click volumes. Do not use it as the main channel for a serious campaign: text ads do not appear on mobile at all, which is most of LinkedIn's traffic. Say that before recommending it. Do not exceed 25 characters of headline or 75 of description — LinkedIn truncates silently and the ad reads as an accident. Do not call it without a bid: LinkedIn text ads only bid manually (CPC or CPM), and a manual campaign without a bid never delivers. If the user has no figure, get LinkedIn's suggested range from linkedin_forecast_campaign_supply with campaign_type TEXT_AD and ask. Text ads run with BRAND_AWARENESS, WEBSITE_VISIT or WEBSITE_CONVERSION, never on the Audience Network. Returns the campaign and creative ids. Nothing delivers until the campaign is resumed. Say that the campaign is paused when you report the result, with the two links the result gives (LinkedIn Campaign Manager and the campaign's page in Adako), and offer "enable all": one linkedin_resume_campaign call, listed in the result as go_live.enable_all, one approval.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_create_text_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "name": {
          "type": "string",
          "minLength": 2,
          "maxLength": 255,
          "description": "Campaign name."
        },
        "campaign_group_id": {
          "description": "Existing campaign group id. Omit and a group named after the campaign is created first — prefer reusing one from linkedin_list_campaign_groups.",
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        },
        "objective": {
          "type": "string",
          "enum": [
            "BRAND_AWARENESS",
            "WEBSITE_VISIT",
            "ENGAGEMENT",
            "VIDEO_VIEW",
            "LEAD_GENERATION",
            "WEBSITE_CONVERSION",
            "JOB_APPLICANT"
          ],
          "description": "Campaign objective. It cannot be changed later — confirm it with linkedin_explain_objectives if unsure. LEAD_GENERATION is refused: its ads need a Lead Gen Form, which this server cannot attach yet."
        },
        "daily_budget": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Daily budget, a decimal in the account currency (50 means fifty). Must clear the LinkedIn per-currency minimum."
        },
        "total_budget": {
          "description": "Optional lifetime ceiling for this campaign.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "bid": {
          "description": "Manual bid, a decimal in the account currency: per click with cost_type CPC, per 1,000 impressions with CPM, per view with CPV. Setting it puts the campaign on manual bidding, where LinkedIn never bids above it. Omit it for LinkedIn's automated bidding, which optimises for the objective and always bills CPM. Text ads need a bid.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "cost_type": {
          "description": "How a manual bid is billed; defaults to what the objective prefers. Only with bid: without one the campaign bids automatically and is billed CPM.",
          "type": "string",
          "enum": [
            "CPM",
            "CPC",
            "CPV"
          ]
        },
        "targeting": {
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "description": "Targeting entity URNs per facet, exactly as linkedin_search_targeting returned them (for example locations: [\"urn:li:geo:103644278\"]). Never free text. locations is required. Entities inside one facet widen the audience (title A or title B); separate facets narrow it (location and title)."
        },
        "exclude": {
          "description": "Facets to exclude from the audience.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "start_date": {
          "description": "YYYY-MM-DD; defaults to today.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "YYYY-MM-DD, optional.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "language": {
          "description": "Two-letter language members use LinkedIn in; only they see the ads (default \"en\"). LinkedIn supports en, ar, zh, cs, da, id, ms, nl, fr, fi, de, it, ja, ko, no, pl, pt, ro, ru, es, sv, th, tl, tr.",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        },
        "country": {
          "description": "Not where the ads run — that is targeting.locations. Only matters for Chinese: \"TW\" or \"HK\" for Traditional, otherwise Simplified. Usually omit.",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        },
        "audience_expansion": {
          "description": "Let LinkedIn deliver to similar members outside the spec. Off by default; only turn it on if the user asked.",
          "type": "boolean"
        },
        "audience_network": {
          "description": "Buy impressions on third-party apps and sites too (LinkedIn Audience Network). Off by default. LinkedIn refuses it for text ads, carousels on WEBSITE_VISIT or ENGAGEMENT, and lead generation.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        },
        "headline": {
          "type": "string",
          "minLength": 1,
          "maxLength": 25,
          "description": "Headline, at most 25 characters."
        },
        "description": {
          "type": "string",
          "minLength": 1,
          "maxLength": 75,
          "description": "Description, at most 75 characters."
        },
        "landing_page_url": {
          "type": "string",
          "format": "uri",
          "description": "Where the ad sends people."
        }
      },
      "required": [
        "name",
        "objective",
        "daily_budget",
        "targeting",
        "headline",
        "description",
        "landing_page_url"
      ]
    }
  • linkedin_create_video_campaignwrite1 task

    Create a LinkedIn video campaign (ad set)

    Builds a video sponsored-content campaign: campaign group (a campaign in Campaign Manager), campaign (ad set), video upload (or an existing video URN), dark post and creative (ad). The campaign starts paused and holds everything back (LinkedIn creates groups and creatives only active or draft). Use when: the user has a video and an objective that permits it (VIDEO_VIEW, BRAND_AWARENESS, ENGAGEMENT, WEBSITE_VISIT or WEBSITE_CONVERSION; LEAD_GENERATION needs a Lead Gen Form this server cannot attach, so it is refused). Prefer video_urn when the video is already in the account: uploads are the slowest and most fragile step of this chain. Do not use it for a large file. This server uploads a video in one part; anything LinkedIn wants chunked is refused with instructions to upload once in Campaign Manager and pass the URN instead. That refusal happens after the campaign exists, so pass video_urn when in doubt. Do not call it twice after a failure — it reports what exists and linkedin_add_creative finishes the job. Returns every id created. Judge a video campaign with linkedin_get_engagement_metrics, not CTR: a video ad's job is watch time. Nothing delivers until the campaign is resumed. Say that the campaign is paused when you report the result, with the two links the result gives (LinkedIn Campaign Manager and the campaign's page in Adako), and offer "enable all": one linkedin_resume_campaign call, listed in the result as go_live.enable_all, one approval.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_create_video_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "name": {
          "type": "string",
          "minLength": 2,
          "maxLength": 255,
          "description": "Campaign name."
        },
        "campaign_group_id": {
          "description": "Existing campaign group id. Omit and a group named after the campaign is created first — prefer reusing one from linkedin_list_campaign_groups.",
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        },
        "objective": {
          "type": "string",
          "enum": [
            "BRAND_AWARENESS",
            "WEBSITE_VISIT",
            "ENGAGEMENT",
            "VIDEO_VIEW",
            "LEAD_GENERATION",
            "WEBSITE_CONVERSION",
            "JOB_APPLICANT"
          ],
          "description": "Campaign objective. It cannot be changed later — confirm it with linkedin_explain_objectives if unsure. LEAD_GENERATION is refused: its ads need a Lead Gen Form, which this server cannot attach yet."
        },
        "daily_budget": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Daily budget, a decimal in the account currency (50 means fifty). Must clear the LinkedIn per-currency minimum."
        },
        "total_budget": {
          "description": "Optional lifetime ceiling for this campaign.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "bid": {
          "description": "Manual bid, a decimal in the account currency: per click with cost_type CPC, per 1,000 impressions with CPM, per view with CPV. Setting it puts the campaign on manual bidding, where LinkedIn never bids above it. Omit it for LinkedIn's automated bidding, which optimises for the objective and always bills CPM. Text ads need a bid.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "cost_type": {
          "description": "How a manual bid is billed; defaults to what the objective prefers. Only with bid: without one the campaign bids automatically and is billed CPM.",
          "type": "string",
          "enum": [
            "CPM",
            "CPC",
            "CPV"
          ]
        },
        "targeting": {
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "description": "Targeting entity URNs per facet, exactly as linkedin_search_targeting returned them (for example locations: [\"urn:li:geo:103644278\"]). Never free text. locations is required. Entities inside one facet widen the audience (title A or title B); separate facets narrow it (location and title)."
        },
        "exclude": {
          "description": "Facets to exclude from the audience.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "start_date": {
          "description": "YYYY-MM-DD; defaults to today.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "YYYY-MM-DD, optional.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "language": {
          "description": "Two-letter language members use LinkedIn in; only they see the ads (default \"en\"). LinkedIn supports en, ar, zh, cs, da, id, ms, nl, fr, fi, de, it, ja, ko, no, pl, pt, ro, ru, es, sv, th, tl, tr.",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        },
        "country": {
          "description": "Not where the ads run — that is targeting.locations. Only matters for Chinese: \"TW\" or \"HK\" for Traditional, otherwise Simplified. Usually omit.",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        },
        "audience_expansion": {
          "description": "Let LinkedIn deliver to similar members outside the spec. Off by default; only turn it on if the user asked.",
          "type": "boolean"
        },
        "audience_network": {
          "description": "Buy impressions on third-party apps and sites too (LinkedIn Audience Network). Off by default. LinkedIn refuses it for text ads, carousels on WEBSITE_VISIT or ENGAGEMENT, and lead generation.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        },
        "organization_urn": {
          "type": "string",
          "description": "The company page that authors the post, from linkedin_get_organizations."
        },
        "video_url": {
          "description": "The video file. Omit when passing video_urn. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet.",
          "type": "string",
          "format": "uri"
        },
        "video_urn": {
          "description": "An existing urn:li:video:… already in the account. Preferred over uploading.",
          "type": "string"
        },
        "headline": {
          "type": "string",
          "minLength": 1,
          "maxLength": 200,
          "description": "Headline shown under the video."
        },
        "commentary": {
          "type": "string",
          "minLength": 1,
          "maxLength": 3000,
          "description": "The post text above the video. Sent as plain text: #hashtags and @mentions are not linked."
        },
        "landing_page_url": {
          "type": "string",
          "format": "uri",
          "description": "Where the ad sends people."
        }
      },
      "required": [
        "name",
        "objective",
        "daily_budget",
        "targeting",
        "organization_urn",
        "headline",
        "commentary",
        "landing_page_url"
      ]
    }

analysis2

  • linkedin_analyze_creative_performanceread-onlyfree

    Rank LinkedIn creatives (ads) and flag fatigue

    Ranks the creatives (ads in Campaign Manager) of one campaign (ad set) against each other on CTR and cost per lead, and flags the ones whose CTR fell sharply in the second half of the window — the signature of creative fatigue on LinkedIn's small, repeatedly-reached audiences. Use when: deciding which ad to keep, which to refresh, and which to leave alone. It is the read to run before linkedin_pause_creative. Do not act on a creative marked "too_early": under 1,000 impressions the numbers are noise, and pausing it destroys the test. Do not treat fatigue as a reason to cut spend — on LinkedIn it means the same people have seen the ad too often, and a new creative into the same audience is usually the fix. Do not use it to compare campaigns; that is linkedin_get_campaign_performance. Returns a verdict per creative (winner / steady / underperforming / fatiguing / too_early) with the number behind it, plus the campaign average it was judged against. If every creative is "too_early", say the campaign needs more time and recommend nothing.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_analyze_creative_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign whose creatives to rank. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • linkedin_analyze_wasted_spendread-onlyfree

    Find LinkedIn spend that produced nothing

    Finds campaigns (ad sets in Campaign Manager) spending without result: impressions with no clicks, clicks with no leads or conversions, and cost per lead well above the account average or a ceiling you give. Use when: the user asks where money is going, before a budget review, or when spend rose without results. Run it on last_30_days — a shorter window on LinkedIn's volumes produces confident nonsense. Do not act on its findings without checking tracking first: "clicks but no leads" is a conversion-rule problem as often as a creative one, and pausing a campaign whose leads simply are not being recorded destroys a working campaign. The tool says so in every such finding. Do not present the recoverable figure as guaranteed savings — it is what would stop being spent, not profit. Returns a ranked list of findings with the spend behind each and a specific next step. Campaigns below the minimum spend are excluded rather than judged on noise. If nothing is found, say the account is clean for this window instead of manufacturing a recommendation.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_analyze_wasted_spend", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "max_cost_per_lead": {
          "description": "The highest cost per lead the user is willing to pay, a decimal in the account currency. Only pass it if they named a number.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "min_spend": {
          "description": "Ignore campaigns that spent less than this in the window, a decimal in the account currency. Default: five days of LinkedIn's minimum daily budget in that currency (50 in USD, EUR or GBP), or 2% of the window's spend for a currency without a known minimum.",
          "type": "number",
          "minimum": 0
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

conversions3

  • linkedin_associate_conversionwrite1 task

    Attach a conversion rule to a LinkedIn campaign (ad set)

    Attaches a conversion rule to a campaign (an ad set in Campaign Manager), or detaches it, so LinkedIn reports that conversion against that campaign's spend. Use when: a campaign reports no conversions although the rule exists and fires, and whenever a new campaign should be measured against existing tracking. LinkedIn associates rules with campaigns individually and a new campaign inherits none — attaching is a separate, deliberate act, and this is the tool for it. Do not detach a rule to "clean up" reporting: the campaign's conversion history stops being reported against it and the numbers the user saw last month will no longer reconcile. Do not attach every rule to every campaign. A campaign attributed to five rules counts five different things as success and its cost per conversion becomes meaningless. Returns the rule read back with its campaign list, so the attachment is confirmed rather than assumed. Attribution is not retroactive in reporting for periods before the association — say so when a user expects last week's conversions to appear.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_associate_conversion", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "conversion_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The conversion rule, from linkedin_list_conversions. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign to attach it to. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "detach": {
          "description": "Set true to remove the association instead of creating it. Ask the user first.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "conversion_id",
        "campaign_id"
      ]
    }
  • linkedin_list_conversionsread-onlyfree

    List LinkedIn conversion rules

    Lists the account's conversion rules with their type, tracking method, attribution windows, assigned value and — most importantly — which campaigns (ad sets in Campaign Manager) each one is attached to. Use when: a campaign shows clicks but no conversions, before creating a WEBSITE_CONVERSION campaign, and whenever you are about to tell a user their ads are not working. On LinkedIn a rule reports nothing against a campaign it is not associated with, and new campaigns inherit no rules at all, so an unattached rule looks exactly like a failing campaign. Do not conclude "the ads do not convert" until you have checked the campaign appears in a rule's campaign list here. Do not create a duplicate rule because an existing one looks unattached — attach it with linkedin_associate_conversion instead. Returns one row per rule, plus a warning naming any rule attached to nothing. If the account has no rules at all, conversion reporting is impossible and a WEBSITE_CONVERSION campaign cannot optimise — say so before anything is built.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_list_conversions", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "enabled_only": {
          "description": "Only rules that are switched on.",
          "type": "boolean"
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • linkedin_manage_conversionswrite1 task

    Create or edit a LinkedIn conversion rule

    Creates a conversion rule on the account, or edits an existing one's name, value, attribution windows or enabled state. Use when: the user needs a conversion to track before running a WEBSITE_CONVERSION campaign, or wants to change what a conversion is worth. A new rule is created **enabled but attached to nothing** — it records nothing until linkedin_associate_conversion attaches it to a campaign, and this tool says so in its result. Do not create a second rule for something already tracked: duplicate rules double-count and make every cost-per-conversion figure wrong. Run linkedin_list_conversions first, every time. Do not change an attribution window on a live rule without telling the user the reported history will shift — LinkedIn re-attributes, and last month's numbers will no longer match what they saw. Do not use this for Lead Gen Form leads: those are counted by LinkedIn automatically and need no rule. Pass url_rules for a rule the site-wide Insight Tag fires by page URL (a thank-you page, say). A rule without URL rules is event-specific: it records nothing until the site runs that rule's own event snippet from Campaign Manager — say so to the user. On an existing rule, url_rules replaces the whole list. Attribution windows are 1, 7 or 30 days (post-click default 30, view-through default 7). Returns the rule read back from the platform. A rule requires the Insight Tag to be installed on the site; this tool cannot verify that, so say it plainly if the user is unsure.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_manage_conversions", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "conversion_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The rule to edit. Omit to create a new one. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "name": {
          "description": "Rule name as it appears in reporting.",
          "type": "string",
          "minLength": 2,
          "maxLength": 100
        },
        "type": {
          "description": "What the conversion represents (default LEAD for a new rule). LinkedIn documents ADD_TO_CART, DOWNLOAD, INSTALL, KEY_PAGE_VIEW, LEAD, PURCHASE, SIGN_UP, OTHER; the other values are refused with the closest one named.",
          "type": "string",
          "enum": [
            "LANDING",
            "DOWNLOAD",
            "SIGN_UP",
            "PURCHASE",
            "ADD_TO_CART",
            "KEY_PAGE_VIEW",
            "LEAD",
            "JOB_APPLY",
            "INSTALL",
            "PHONE_CALL",
            "TALK_TO_HUMAN",
            "OTHER"
          ]
        },
        "value": {
          "description": "What one conversion is worth, a decimal in the account currency.",
          "type": "number",
          "minimum": 0
        },
        "post_click_window_days": {
          "description": "Post-click attribution window in days: 1, 7 or 30 (LinkedIn defaults to 30). 90 is refused.",
          "anyOf": [
            {
              "type": "number",
              "const": 1
            },
            {
              "type": "number",
              "const": 7
            },
            {
              "type": "number",
              "const": 30
            },
            {
              "type": "number",
              "const": 90
            }
          ]
        },
        "view_through_window_days": {
          "description": "View-through attribution window in days (1, 7 or 30; LinkedIn defaults to 7).",
          "anyOf": [
            {
              "type": "number",
              "const": 1
            },
            {
              "type": "number",
              "const": 7
            },
            {
              "type": "number",
              "const": 30
            }
          ]
        },
        "enabled": {
          "description": "Switch the rule on or off.",
          "type": "boolean"
        },
        "url_rules": {
          "description": "Page URLs that count as this conversion when the Insight Tag sees them; a visit matching any one rule counts. Omit for an event-specific rule the site fires itself. On an existing rule this replaces every current URL rule.",
          "minItems": 1,
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "match_type": {
                "type": "string",
                "enum": [
                  "EXACT",
                  "STARTS_WITH",
                  "CONTAINS"
                ],
                "description": "EXACT (the whole URL), STARTS_WITH (a prefix) or CONTAINS (a fragment)."
              },
              "value": {
                "type": "string",
                "minLength": 1,
                "maxLength": 500,
                "description": "The URL text to match, without the protocol: \"example.com/thank-you\". No regular expressions."
              }
            },
            "required": [
              "match_type",
              "value"
            ]
          }
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

management9

  • linkedin_batch_update_campaignswrite1 task

    Change status or budget on many LinkedIn campaigns (ad sets)

    Applies one status or one daily budget to up to 50 campaigns (ad sets in Campaign Manager) at once, reporting each campaign's result separately. Use when: the user has decided the same thing about several named campaigns — pausing a whole test, or standardising a budget. It is one approval instead of twenty. Do not use it to apply a budget the user has not named per campaign: the same figure across different audiences is rarely right, and "£50 each" across ten campaigns is £500 a day. State the total in the confirmation. Do not use it to pause an entire account — pause the campaign group (the Campaign Manager campaign) instead, which is one reversible change rather than fifty. Do not treat it as atomic. LinkedIn is called once per campaign, so a failure partway leaves earlier campaigns changed; the result lists exactly which succeeded and which did not. Never re-run the whole batch after a partial failure — re-run only the failures. Returns per-campaign success or failure with the reason.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_batch_update_campaigns", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_ids": {
          "minItems": 1,
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The campaigns to change, at most 50."
        },
        "status": {
          "description": "Set every listed campaign to this status.",
          "type": "string",
          "enum": [
            "ACTIVE",
            "PAUSED"
          ]
        },
        "daily_budget": {
          "description": "Set every listed campaign to this daily budget, a decimal in the account currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_ids"
      ]
    }
  • linkedin_clone_campaignwrite1 task

    Copy a LinkedIn campaign (ad set)

    Copies the settings of an existing campaign (an ad set in Campaign Manager) — objective, type, format, bidding (automatic, or manual with its cost type and bid), budgets, language, delivery settings and targeting — into a new **PAUSED** campaign, with optional overrides for name, budget, schedule, language, Audience Network and targeting. Use when: an approach works and should be repeated for another market, audience or offer. Cloning keeps the settings that are working and makes the difference explicit. For another market: put the new locations in add_targeting.locations AND the original ones in remove_targeting.locations — add_targeting alone keeps the original market too — and pass language when that market uses LinkedIn in another language. The preview names the locations the copy will target; read them to the user before they approve. Do not expect the creatives (ads) to come with it: LinkedIn creatives belong to a campaign, and the clone starts empty. Add one with linkedin_add_creative or it can never spend, however healthy the settings look. Do not clone to make a small change to the original — that is linkedin_update_campaign. Two near-identical campaigns split the audience, compete in the same auction, and make both look worse. Do not clone into the same audience without changing the targeting; state plainly that the two will overlap if the user asks for it anyway. The preview counts the copy's audience and refuses targeting under LinkedIn's 300-member minimum. Returns the new campaign id and what was copied versus overridden. Nothing spends until it is resumed and has a creative.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_clone_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign to copy. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "name": {
          "type": "string",
          "minLength": 2,
          "maxLength": 255,
          "description": "Name for the new campaign."
        },
        "campaign_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "Put the copy in this group. Defaults to the original's group. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "daily_budget": {
          "description": "Override the daily budget on the copy.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "add_targeting": {
          "description": "Entity URNs to add to the copied targeting. New locations are added to the original ones: to move market, also remove those with remove_targeting.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "remove_targeting": {
          "description": "Entity URNs to remove from the copied targeting.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "start_date": {
          "description": "Override the start date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Override the end date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "language": {
          "description": "Two-letter language members of the new market use LinkedIn in (for example \"de\"); only they see the ads. Defaults to the original's language. LinkedIn supports en, ar, zh, cs, da, id, ms, nl, fr, fi, de, it, ja, ko, no, pl, pt, ro, ru, es, sv, th, tl, tr.",
          "type": "string",
          "minLength": 2,
          "maxLength": 2
        },
        "audience_network": {
          "description": "Override the LinkedIn Audience Network setting on the copy. Defaults to the original's; LinkedIn refuses it for text ads, lead generation, and carousels on WEBSITE_VISIT or ENGAGEMENT.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id",
        "name"
      ]
    }
  • linkedin_delete_creativedestructive write1 task

    Delete a LinkedIn creative (ad)

    Permanently removes a creative (an ad in Campaign Manager) from a campaign (ad set). **This cannot be undone**, and LinkedIn stops reporting its past performance against the campaign, so the campaign's historical totals change. A creative still in draft is deleted outright; one that has been out of draft is marked PENDING_DELETION, which stops it serving at once and has LinkedIn remove it afterwards — so it can still appear in linkedin_list_creatives with that status for a while. The result says which happened. Use when: the user has explicitly asked to delete — a creative published with the wrong asset, or something that must not exist for a legal or brand reason. Requires confirm_delete: true. Do not use it to stop an ad. linkedin_pause_creative stops delivery instantly, is reversible, and keeps the history — that is what "turn this off" means, every time. Do not delete a creative that has spent money without telling the user that the campaign's reported history will change afterwards. Show them linkedin_get_creative_performance first so the numbers are recorded somewhere before they vanish. Never call this because a creative is underperforming. Losing a test result is worse than the £30 the ad cost. Returns confirmation and what was removed. Show the user exactly which creative, on which campaign, and get an explicit yes before calling with confirm_delete: true.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_delete_creative", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "creative_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The creative to delete permanently. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "confirm_delete": {
          "description": "Must be true to remove anything. Ask the user first.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "creative_id"
      ]
    }
  • linkedin_pause_campaigndestructive write1 task

    Pause a LinkedIn campaign (ad set)

    Stops delivery on one campaign (an ad set in Campaign Manager) by setting it to PAUSED. Spend stops; nothing is deleted, the creatives and history stay, and linkedin_resume_campaign starts it again. Use when: the user has explicitly asked to stop something and you have shown them what it is currently spending and producing. Do not pause on a few days of data. LinkedIn's B2B volumes are small and a week is the minimum honest window — show last_7_days from linkedin_get_campaign_performance, or last_30_days for anything involving cost per lead, and let the user decide. Do not pause a campaign that reports no conversions until linkedin_list_conversions confirms a rule is attached: an untracked campaign looks identical to a failing one. Do not use pausing as deletion. Pausing is reversible; nothing in this adapter deletes a campaign at all. Returns the campaign read back from LinkedIn, which is how you confirm the pause applied rather than reporting an intention as a fact.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_pause_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign to act on. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • linkedin_pause_creativedestructive write1 task

    Pause a LinkedIn creative (ad)

    Sets one creative (an ad in Campaign Manager) to PAUSED so it stops serving while its campaign (ad set) keeps running on its other creatives. Nothing is deleted and the performance history stays. Use when: a creative is clearly losing on enough data to say so — check linkedin_analyze_creative_performance first, which will not give a verdict below 1,000 impressions. Do not pause the last active creative on a live campaign: the campaign stays ACTIVE, spends nothing, and looks healthy in every list. Add the replacement first. Do not pause a "fatiguing" creative as the fix. Fatigue means the audience has seen it too often; a fresh creative into the same audience is what helps, and pausing without a replacement just shrinks delivery. LinkedIn only allows an ad to be paused once its review is APPROVED: one still in review, or rejected, is refused with that explanation rather than attempted, and neither is serving meanwhile. To stop delivery whatever the ads say, pause the campaign (ad set). Returns the creative read back, so the pause is confirmed.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_pause_creative", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "creative_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The creative to act on, from linkedin_list_creatives. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "creative_id"
      ]
    }
  • linkedin_resume_campaignwrite1 task

    Resume a LinkedIn campaign (ad set)

    Sets a campaign (an ad set in Campaign Manager) to ACTIVE so it can deliver. Real money starts being spent as soon as LinkedIn approves the creatives. Use when: the user has reviewed something paused — usually a campaign this server created — and wants it live. Do not resume without telling the user the daily budget first, and without checking three things that quietly keep an ACTIVE campaign at zero: the campaign group (the Campaign Manager campaign) must also be active, at least one creative (ad) must exist and be resumed, and LinkedIn must have approved it. linkedin_get_campaign_structure and linkedin_list_creatives answer all three. Do not report "resumed" as "running". Check the read-back: the serving issues it returns are LinkedIn saying why nothing is delivering. A campaign whose end date has already passed is refused rather than resumed: LinkedIn would accept it and serve nothing. Give it a new end date with linkedin_update_campaign first. Returns the campaign read back, including its budget and any serving issue.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_resume_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign to act on. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • linkedin_resume_creativewrite1 task

    Resume a LinkedIn creative (ad)

    Sets one creative (an ad in Campaign Manager) to ACTIVE so it can serve, subject to the status of its campaign (ad set) and LinkedIn's review. Use when: a newly created creative has been reviewed by the user and should go live, or a paused variant is being brought back. Do not assume it will serve. A creative only delivers when its campaign is ACTIVE, its campaign group (the Campaign Manager campaign) is ACTIVE, and LinkedIn's review has passed — a creative moving from DRAFT to ACTIVE starts a review that takes up to 24 hours. Check with linkedin_list_creatives rather than promising the user it is live. Do not resume several variants at once on a small budget: LinkedIn splits delivery between them and none gets enough impressions to prove anything. Three or four active creatives per campaign is the practical ceiling. Returns the creative read back with its review status.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_resume_creative", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "creative_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The creative to act on, from linkedin_list_creatives. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "creative_id"
      ]
    }
  • linkedin_update_campaignwrite1 task

    Edit a LinkedIn campaign (ad set)

    Changes the name, daily or lifetime budget, bid, run dates, targeting or delivery settings of a campaign (an ad set in Campaign Manager). Targeting is **merged**, never replaced: facets you do not mention are left exactly as they are. Use when: the user has named a specific change. Read the campaign first with linkedin_list_campaigns so you are changing what is actually there. Do not use it to pause or resume — linkedin_pause_campaign and linkedin_resume_campaign exist so the risk of that change is stated properly before approval. Do not add targeting without resolving URNs through linkedin_search_targeting; free text is refused. After any targeting change, re-check the audience with linkedin_estimate_audience_size — a merge can push it under the platform minimum or make it far broader than intended. Do not double a budget casually: LinkedIn re-enters a learning phase after a large change and costs rise for a few days first. If the user says "increase the budget" without a number, ask. Do not set a bid on a campaign that bids automatically without saying so: it switches the campaign to manual bidding, and the preview refuses until cost_type says whether the bid is per click or per 1,000 impressions — ask the user. Returns the campaign read back from LinkedIn, so the new values are confirmed rather than assumed. The objective, type and format of an existing campaign cannot be changed at all — that needs a new campaign.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_update_campaign", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign to edit. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "name": {
          "description": "New campaign name.",
          "type": "string",
          "minLength": 2,
          "maxLength": 255
        },
        "daily_budget": {
          "description": "New daily budget, a decimal in the account currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "total_budget": {
          "description": "New lifetime ceiling for this campaign.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "bid": {
          "description": "New manual bid, a decimal in the account currency, billed the way the campaign already is. On a campaign using LinkedIn's automated bidding this switches it to manual bidding and needs cost_type.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "cost_type": {
          "description": "How the new bid is billed: CPC per click, CPM per 1,000 impressions, CPV per video view. Only with bid. Required when the campaign bids automatically today — ask the user, never guess.",
          "type": "string",
          "enum": [
            "CPM",
            "CPC",
            "CPV"
          ]
        },
        "start_date": {
          "description": "New start date, YYYY-MM-DD. Leave it out to keep the current start exactly as it is.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "New end date, YYYY-MM-DD. Changing only this keeps the current start untouched.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "add_targeting": {
          "description": "Entity URNs to ADD to the existing targeting.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "remove_targeting": {
          "description": "Entity URNs to REMOVE from the existing targeting.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "exclude_targeting": {
          "description": "Entity URNs to add to the exclusion list.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "audience_expansion": {
          "description": "Turn LinkedIn audience expansion on or off.",
          "type": "boolean"
        },
        "audience_network": {
          "description": "Turn off-LinkedIn delivery (LinkedIn Audience Network) on or off. LinkedIn refuses it for text ads, lead generation, and carousels on WEBSITE_VISIT or ENGAGEMENT.",
          "type": "boolean"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }
  • linkedin_update_campaign_groupwrite1 task

    Edit a LinkedIn campaign group (Campaign Manager campaign)

    Changes the name, lifetime budget, run dates or status of a campaign group (a campaign in Campaign Manager). Use when: the shared ceiling across several campaigns needs to move, or a whole initiative should stop or start at once. Changing the group is the efficient way to act on many campaigns together. Do not lower a group budget below what it has already spent: every campaign inside stops immediately and lowering it further will not bring them back. Check spend with linkedin_get_campaign_performance first. Do not pause a group to stop one campaign — that takes everything inside down with it. Use linkedin_pause_campaign. Setting the group ACTIVE does not start anything on its own: each campaign inside keeps its own status. Setting it PAUSED stops everything inside regardless of theirs. Returns the group read back from LinkedIn.

    How to call: linkedin_ads_write(action="execute", tool_name="linkedin_update_campaign_group", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign group to edit. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "name": {
          "description": "New group name, at most 200 bytes of UTF-8.",
          "type": "string",
          "minLength": 2,
          "maxLength": 255
        },
        "total_budget": {
          "description": "New lifetime ceiling across the group. LinkedIn requires an end date with it: pass end_date too if the group has none.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "status": {
          "description": "New group status. PAUSED stops every campaign inside; ACTIVE only lifts the block.",
          "type": "string",
          "enum": [
            "ACTIVE",
            "PAUSED"
          ]
        },
        "start_date": {
          "description": "New start date, YYYY-MM-DD.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "New end date, YYYY-MM-DD.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "idempotency_key": {
          "description": "Optional caller-supplied key; identical keys never execute twice.",
          "type": "string",
          "minLength": 8,
          "maxLength": 128
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_group_id"
      ]
    }

targeting3

  • linkedin_estimate_audience_sizeread-onlyfree

    Estimate a LinkedIn audience size

    Counts how many LinkedIn members a targeting spec reaches, before a campaign is created against it. Use when: after resolving URNs with linkedin_search_targeting and before any create or targeting update. LinkedIn refuses a campaign under 300 members, and a spec just above that floor will deliver expensively and exhaust itself in days — this is the check that catches both. Do not skip it because the spec "looks broad": stacking three facets multiplies narrowly, and an audience of 400 is the single most common reason a carefully built LinkedIn campaign underperforms. Do not pass names instead of URNs; this tool refuses free text for the same reason the writers do. Returns the estimated audience, whether it clears the platform minimum, and a plain-language read on whether it is big enough to spend against. If it comes back under a few thousand, tell the user which facet to widen before you build anything — do not create the campaign and hope.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_estimate_audience_size", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "targeting": {
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "description": "Targeting entity URNs per facet, exactly as linkedin_search_targeting returned them (for example locations: [\"urn:li:geo:103644278\"]). Never free text. locations is required. Entities inside one facet widen the audience (title A or title B); separate facets narrow it (location and title)."
        },
        "exclude": {
          "description": "Facets to exclude from the audience.",
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "targeting"
      ]
    }
  • linkedin_forecast_campaign_supplyread-onlyfree

    Forecast LinkedIn bid and budget ranges

    Asks LinkedIn what the auction currently costs for a targeting spec: the suggested bid and its range, the lowest and highest bid allowed, and the daily budget limits, for the objective and cost type you intend to use. It does not forecast impressions, clicks or leads. Use when: the user asks what a campaign will cost before it exists, or when choosing a bid. It is the only honest answer to "what CPC should I set" — LinkedIn's ranges vary enormously by audience and season, and a remembered benchmark is worse than no number. Do not present the forecast as a promise. It is the platform's estimate for an unproven audience, it moves week to week, and actual cost per lead depends on the landing page, which LinkedIn knows nothing about. Do not use it in place of linkedin_estimate_audience_size — a forecast against a sub-minimum audience is meaningless. Returns the suggested bid with its range, the allowed bid range and the daily budget limits, in the account currency. If LinkedIn returns nothing for this spec (it does for very narrow audiences), say the forecast is unavailable and fall back to the audience size plus the platform minimum daily budget. Do not invent a number.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_forecast_campaign_supply", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "targeting": {
          "type": "object",
          "properties": {
            "locations": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "titles": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "industries": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "seniorities": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "staffCountRanges": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "skills": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interests": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "memberBehaviors": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "employers": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "degrees": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "fieldsOfStudy": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "interfaceLocales": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "description": "Targeting entity URNs per facet, exactly as linkedin_search_targeting returned them (for example locations: [\"urn:li:geo:103644278\"]). Never free text. locations is required. Entities inside one facet widen the audience (title A or title B); separate facets narrow it (location and title)."
        },
        "objective": {
          "type": "string",
          "enum": [
            "BRAND_AWARENESS",
            "WEBSITE_VISIT",
            "ENGAGEMENT",
            "VIDEO_VIEW",
            "LEAD_GENERATION",
            "WEBSITE_CONVERSION",
            "JOB_APPLICANT"
          ],
          "description": "The objective the campaign will use — the forecast depends on it."
        },
        "cost_type": {
          "description": "How the campaign will be billed (default CPC).",
          "type": "string",
          "enum": [
            "CPM",
            "CPC",
            "CPV"
          ]
        },
        "campaign_type": {
          "description": "Campaign type (default SPONSORED_UPDATES).",
          "type": "string",
          "enum": [
            "SPONSORED_UPDATES",
            "TEXT_AD",
            "SPONSORED_INMAILS",
            "DYNAMIC"
          ]
        },
        "daily_budget": {
          "description": "The daily budget being considered, a decimal in the account currency.",
          "type": "number",
          "exclusiveMinimum": 0
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "targeting",
        "objective"
      ]
    }
  • linkedin_search_targetingread-onlyfree

    Resolve LinkedIn targeting entities

    Turns plain words into the LinkedIn targeting URNs a campaign actually accepts — job titles, industries, seniorities, company-size bands, locations, skills, interests, member behaviours, employers, degrees, fields of study and interface locales. Use when: before every create or update that touches targeting, without exception. A campaign payload takes URNs and nothing else, and there is no fuzzy match on the write path: an invented URN either errors or targets strangers. Do not guess a URN, reuse one from a demographic report (those are report labels), or copy one between accounts without checking it here first. Do not pass a user's phrase straight into a create tool — that is what this tool exists to prevent. Returns matching entities with their URN and display name — a location's name already carries its parents ("Sacramento, California, United States"). Seniorities, staffCountRanges and interfaceLocales are short fixed lists: when nothing matches the query, the whole list comes back for the user to choose from. interfaceLocales is the language members use LinkedIn in (one locale per language, e.g. urn:li:locale:en_US for all English), never a country. Costs nothing, so run it as many times as the audience needs facets. If a search returns nothing, try a shorter or more common phrasing (LinkedIn matches on its own canonical titles), and if it still fails tell the user that facet cannot be targeted rather than substituting something close.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_search_targeting", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "facet": {
          "type": "string",
          "enum": [
            "locations",
            "titles",
            "industries",
            "seniorities",
            "staffCountRanges",
            "skills",
            "interests",
            "memberBehaviors",
            "employers",
            "degrees",
            "fieldsOfStudy",
            "interfaceLocales"
          ],
          "description": "Which targeting dimension to search: locations (always required on a campaign), titles, industries, seniorities, staffCountRanges, skills, interests, memberBehaviors, employers, degrees, fieldsOfStudy or interfaceLocales."
        },
        "query": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100,
          "description": "What to search for, in the user's own words (\"marketing manager\", \"Berlin\", \"software\")."
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "facet",
        "query"
      ]
    }

discovery2

  • linkedin_explain_objectivesread-onlyfree

    What each LinkedIn objective permits

    Explains LinkedIn's campaign objectives: what each one optimises towards, which ad formats and cost types it allows, when a marketer should pick it, and whether it needs a conversion rule to work at all. Use when: choosing an objective before creating a campaign, or explaining why a format the user asked for is not available. The objective is the single least reversible choice on a LinkedIn campaign — it cannot be changed after creation, so getting it right here saves rebuilding the campaign later. Do not use it as a source of costs or benchmarks; it describes what the platform permits, not what anything will cost. Do not pick an objective for the user when their goal is ambiguous — show the two candidates and ask which outcome they are paying for. Returns the matrix for one objective, or all of them. Costs nothing. WEBSITE_CONVERSION only optimises once a conversion rule with real volume is attached — if the user has no tracking, say so and recommend WEBSITE_VISIT instead of setting them up to fail. LEAD_GENERATION needs a Lead Gen Form on every ad, which this server cannot attach yet: its create tools refuse that objective, so a lead-gen campaign is built in Campaign Manager. A campaign created without a bid uses LinkedIn's automated bidding for its objective and is billed CPM; a bid switches it to manual bidding on the cost type given. Text ads only bid manually.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_explain_objectives", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "objective": {
          "description": "One objective to explain. Omit for the whole matrix.",
          "type": "string",
          "enum": [
            "BRAND_AWARENESS",
            "WEBSITE_VISIT",
            "ENGAGEMENT",
            "VIDEO_VIEW",
            "LEAD_GENERATION",
            "WEBSITE_CONVERSION",
            "JOB_APPLICANT"
          ]
        },
        "goal": {
          "description": "The user's goal in their own words, echoed back with the recommendation.",
          "type": "string",
          "maxLength": 200
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • linkedin_get_organizationsread-onlyfree

    List the LinkedIn pages this login can post as

    Lists the LinkedIn organizations (company pages) the connected login administers, with their URNs. Use when: before creating any image, video or carousel campaign. Every sponsored post on LinkedIn is authored by a page, and the page URN is a required input to linkedin_create_image_campaign and its siblings — this is the only tool that produces valid ones. Do not use it to pick a page on the user's behalf when more than one comes back: show the list and ask which brand the ad should speak as. Getting this wrong publishes an ad under the wrong company name. Returns id, URN, name, vanity name and the login's role on each page. Costs nothing. If the list is empty the login administers no page: the user must be given a Sponsored Content Poster or Page Admin role on the page in LinkedIn, then reconnect. A text-ad campaign (linkedin_create_text_campaign) needs no page and is the only thing that can run meanwhile.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_get_organizations", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

audiences1

  • linkedin_get_audience_insightsread-onlyfree

    LinkedIn audience demographics

    Breaks delivery down by who saw it: job title, industry, seniority, company size, country, job function or company. This is the demographic report LinkedIn is uniquely good at, and the main reason to advertise there. Use when: deciding whether the targeting actually reached the intended buyer, finding a segment worth its own campaign, or finding one worth excluding. It is the evidence behind every targeting change you propose. Do not read a segment with a handful of impressions as a signal — LinkedIn reports a long tail of tiny segments and the bottom of the table is noise. Do not treat these as targeting URNs: they are report labels, and a write needs URNs from linkedin_search_targeting. Returns the segments sorted by spend with impressions, clicks, CTR, leads and cost per lead per segment. LinkedIn ad accounts carry no reporting timezone, so every window here is UTC days. If the user has not said which dimension, ask — job title and company size lead to different decisions. Do not guess.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_get_audience_insights", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "pivot": {
          "type": "string",
          "enum": [
            "MEMBER_JOB_TITLE",
            "MEMBER_INDUSTRY",
            "MEMBER_SENIORITY",
            "MEMBER_COMPANY_SIZE",
            "MEMBER_COUNTRY_V2",
            "MEMBER_JOB_FUNCTION",
            "MEMBER_COMPANY"
          ],
          "description": "Which dimension to break delivery down by: MEMBER_JOB_TITLE, MEMBER_INDUSTRY, MEMBER_SENIORITY, MEMBER_COMPANY_SIZE, MEMBER_COUNTRY_V2, MEMBER_JOB_FUNCTION or MEMBER_COMPANY."
        },
        "campaign_ids": {
          "description": "Limit to these campaigns.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "pivot"
      ]
    }

structure4

  • linkedin_get_campaign_structureread-onlyfree

    Show the LinkedIn account tree

    Returns the whole account in one call: every campaign group (a campaign in Campaign Manager), the campaigns (ad sets) inside it, and the creatives (ads) inside those, with status and budget at each level. Naming: since October 2025 Campaign Manager calls the three levels campaign → ad set → ad, while the API and these tools say campaign group → campaign → creative. A user's "campaign" may mean a campaign group, and their "ad set" or "ad" means a campaign or a creative here, so check which level they mean before acting on it. Use when: taking over an unfamiliar account, auditing how something is set up, or answering "where does this campaign sit". One call here replaces three list calls and keeps the ids consistent between levels. Do not use it on a very large account when you only need one branch — pass campaign_group_id, or use linkedin_list_campaigns, so the answer stays readable. Do not use it for performance. Returns the tree plus counts, and flags every level that is not delivering. If a campaign has no creatives it can never spend, however healthy its budget looks — say so rather than reporting it as ready.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_get_campaign_structure", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "Limit the tree to one campaign group. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "include_creatives": {
          "description": "Read the creatives under each campaign (default true). Turn off for a fast overview.",
          "type": "boolean"
        },
        "status": {
          "description": "Only objects in this state. LinkedIn statuses are upper case. Defaults to everything except archived, cancelled and removed.",
          "type": "string",
          "enum": [
            "ACTIVE",
            "PAUSED",
            "DRAFT",
            "ARCHIVED",
            "COMPLETED",
            "all"
          ]
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • linkedin_list_campaign_groupsread-onlyfree

    List LinkedIn campaign groups (Campaign Manager campaigns)

    Lists the campaign groups (campaigns in Campaign Manager) in the ad account with their status, lifetime budget and run dates. Naming: Campaign Manager calls a campaign group a "campaign" and an API campaign an "ad set", while these tools keep the API names. When a user says "campaign" they may mean a group listed here, and "ad set" always means a campaign from linkedin_list_campaigns, so confirm the level before changing anything. Use when: orienting yourself in a new account, or before creating a campaign — every LinkedIn campaign must belong to a group, and reusing the right existing group is almost always better than creating another one. Do not read performance from here: a group's numbers come from linkedin_get_campaign_performance with the group's campaigns. Do not confuse a group's total budget with a campaign's daily budget; the group ceiling caps everything beneath it. Returns id, name, status, total budget and schedule per group, plus any serving issue LinkedIn reports. If the account has no groups, say so and offer linkedin_create_campaign_group — do not create one unprompted.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_list_campaign_groups", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "status": {
          "description": "Only objects in this state. LinkedIn statuses are upper case. Defaults to everything except archived, cancelled and removed.",
          "type": "string",
          "enum": [
            "ACTIVE",
            "PAUSED",
            "DRAFT",
            "ARCHIVED",
            "COMPLETED",
            "all"
          ]
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • linkedin_list_campaignsread-onlyfree

    List LinkedIn campaigns (ad sets)

    Lists campaigns (ad sets in Campaign Manager) with their group, status, objective, format, bid, daily and total budget, schedule, audience-expansion settings and a one-line targeting summary. Use when: the user asks what is running, before any change to a campaign (you need its real current settings, not a remembered one), and to get the campaign ids every other LinkedIn tool takes. Do not use it for spend or results — it returns configuration only. Performance comes from linkedin_get_campaign_performance. Do not assume a campaign with status ACTIVE is delivering: check the serving issues column, which is where LinkedIn reports an exhausted group budget or a creative still in review. Returns one row per campaign. Money is a decimal in the account currency. If nothing comes back, check the status filter before telling the user the account is empty — DRAFT campaigns are excluded from an ACTIVE filter.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_list_campaigns", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_group_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "Only campaigns in this campaign group. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "status": {
          "description": "Only objects in this state. LinkedIn statuses are upper case. Defaults to everything except archived, cancelled and removed.",
          "type": "string",
          "enum": [
            "ACTIVE",
            "PAUSED",
            "DRAFT",
            "ARCHIVED",
            "COMPLETED",
            "all"
          ]
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • linkedin_list_creativesread-onlyfree

    List the creatives (ads) on a LinkedIn campaign (ad set)

    Lists the creatives (ads in Campaign Manager) attached to one or more campaigns (ad sets), with their intended status, whether they are actually serving, their review verdict and any rejection reason. Use when: a campaign is ACTIVE but spending nothing (the answer is usually a creative in review or rejected), before pausing or deleting a creative, and to get the creative ids the pause/resume/delete tools take. Do not read performance here — that is linkedin_get_creative_performance. Do not treat "intended status ACTIVE" as live: the is_serving column is the truth, and a paused parent campaign overrides an active creative. Returns one row per creative, including the post URN it renders so you can tell two similar ads apart. If a creative is rejected, report the rejection reason verbatim and stop — appealing is done in Campaign Manager, not through this server.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_list_creatives", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign whose creatives to list. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "campaign_ids": {
          "description": "Several campaigns at once, instead of campaign_id.",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "status": {
          "description": "Only objects in this state. LinkedIn statuses are upper case. Defaults to everything except archived, cancelled and removed.",
          "type": "string",
          "enum": [
            "ACTIVE",
            "PAUSED",
            "DRAFT",
            "ARCHIVED",
            "COMPLETED",
            "all"
          ]
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

performance1

  • linkedin_get_creative_performanceread-onlyfree

    LinkedIn creative (ad) performance

    Reports spend, impressions, clicks, CTR, CPC, leads and cost per lead for each creative (ad in Campaign Manager) in a campaign (ad set). Use when: a campaign's totals look fine but you need to know which ad is carrying it, or before pausing an ad. Pair it with linkedin_list_creatives so a creative with no delivery can be explained (review pending, paused, or simply out-competed by a sibling). Do not compare two creatives on a handful of clicks: give a LinkedIn creative at least 1,000 impressions before calling a winner, and say so when the data is thinner than that. Do not pause the loser immediately in a campaign with `creativeSelection: OPTIMIZED` — LinkedIn is already shifting delivery, and pausing removes a control for the next test. Returns one row per creative, sorted by spend, plus the campaign totals. LinkedIn ad accounts carry no reporting timezone, so every window here is UTC days. If nothing has delivered, the answer is a structural one — check linkedin_list_creatives for review status rather than reporting zero results.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_get_creative_performance", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The campaign whose creatives to report on. LinkedIn ids are numeric strings; a full urn:li:… value is also accepted."
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "campaign_id"
      ]
    }

reporting1

  • linkedin_get_engagement_metricsread-onlyfree

    LinkedIn engagement and video metrics

    Reports the social half of LinkedIn delivery: reactions, comments, shares, page follows, total engagements and engagement rate, plus video views and completions where the creative is a video. Use when: the objective is BRAND_AWARENESS, ENGAGEMENT or VIDEO_VIEW — for those, clicks and leads are the wrong scoreboard. Also use it to explain a campaign whose CTR looks weak but whose content is being shared. Do not use it to judge a lead-generation or conversion campaign: engagement there is a vanity number, and linkedin_get_campaign_performance has the metrics that matter. Returns totals and a per-campaign (ad set) table. Engagement rate is engagements ÷ impressions. Video completion rate is completions ÷ views, which on LinkedIn counts a view at two seconds, so completion rate reads low compared with other platforms. LinkedIn ad accounts carry no reporting timezone, so every window here is UTC days. If video metrics are all zero on a video campaign, the creative is probably an image — check linkedin_list_creatives.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_get_engagement_metrics", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "campaign_ids": {
          "description": "Limit to these campaigns.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "date_range": {
          "description": "Preset window. The \"last N days\" ones end yesterday, so no partial day is mixed in. Either this or start_date + end_date, never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "description": "First day, YYYY-MM-DD. Needs end_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "description": "Last day, YYYY-MM-DD, inclusive. Needs start_date.",
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "limit": {
          "description": "Maximum rows to return (default 100).",
          "type": "integer",
          "minimum": 1,
          "maximum": 500
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }

assets1

  • linkedin_validate_assetsread-onlyfree

    Check images before a LinkedIn ad is built

    Checks image URLs against LinkedIn's sponsored-content specs before anything is created: reachable over https, an accepted file type, under the size limit, and either 1.91:1 (1200×627) or square (1200×1200) so the feed does not choose its own crop. Use when: the user supplies creative URLs, and always before linkedin_create_image_campaign or linkedin_add_creative. An image that fails here fails mid-way through a create, after the campaign has already been made — which leaves half a campaign behind. Do not treat a warning as a blocker: a non-standard ratio uploads fine and simply gets cropped, and the user may be happy with that. Errors are blockers; warnings are the user's call. Do not use it for video — LinkedIn's video specs are not checked here. Returns per-URL content type, byte size, dimensions, which recommended size it matches, and separate error and warning lists. Costs nothing and downloads only the file header. If dimensions cannot be read, say so plainly rather than assuming the image is fine.

    How to call: linkedin_ads(action="execute", tool_name="linkedin_validate_assets", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "ad_account_id": {
          "description": "LinkedIn ad account id — the numeric id from Campaign Manager, as a string (\"506699162\"). Omit it to use the primary account; pass it when the user manages several.",
          "type": "string"
        },
        "image_urls": {
          "minItems": 1,
          "maxItems": 10,
          "type": "array",
          "items": {
            "type": "string",
            "format": "uri"
          },
          "description": "The images to check. Adako hosts no files: the platform downloads this URL itself, from its own servers and often hours later, so it must be a public link straight to the file — not a file on the computer, not a Drive, Dropbox or design-tool share page, not a link that expires or asks for a login. Call check_media first: it repairs the usual share links, checks the file against this platform, and says where to put a file that has no link yet."
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "image_urls"
      ]
    }

Monitoring and reporting13

Reads run through monitoring, changes through monitoring_write. Call monitoring(action="list_tools") for the same list at runtime — it is free.

monitoring8

  • create_monitorwritefreepro plan

    Create a daily monitor on an ad account

    Saves a rule that Adako evaluates once a day against its synced copy of the account — no platform call, no task cost. When the rule holds it emails the operator, and when it carries an action it creates a proposal they can approve. Use when: the user asks to be told about a metric ("alert me if…", "watch…", "let me know when…") and has named a metric, a direction and a number. Do not use when: the user wants a one-off answer about the last few days — read the data instead. Do not create a second monitor for a rule that already exists; call list_monitors first. Returns the monitor, the rule in plain words, and the day it will first be evaluated. Monitors read yesterday in the account's timezone once that day has fully synced (a day later for accounts far behind UTC), compared with a baseline of the previous baseline_days days. A monitor with an action still changes nothing on its own: the action becomes a proposal the user approves. Actions need exactly one campaign id so the proposal names one object, and they run on Google Ads, Meta Ads, LinkedIn Ads and ChatGPT Ads; a monitor on any other platform emails and proposes nothing. Watching an agency's whole platform: pass all_accounts instead of account_id. One monitor then covers every active account on that platform, each judged on its own numbers and currency, and one email a day lists the accounts that fired. Leave platform out as well and it covers every platform the user has connected. Either way it cannot carry an action or campaign ids, because both name one account. If you are unsure which metric, account or threshold the user means, ask. Never invent a threshold.

    How to call: monitoring_write(action="execute", tool_name="create_monitor", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100,
          "description": "Short name the operator will recognise in email, for example \"ROAS floor — Brand\"."
        },
        "platform": {
          "description": "Which ad platform the monitor watches. Leave it out only with all_accounts, which then covers every synced platform this user has connected.",
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads"
          ]
        },
        "account_id": {
          "description": "Platform account id (Google customer id, Meta act_…). Defaults to the primary active account on the platform.",
          "type": "string",
          "minLength": 1
        },
        "all_accounts": {
          "description": "Watch every active account on the platform instead of one, including accounts connected later. Each account is judged on its own numbers, in its own currency and its own timezone, so a money threshold means that number of each account's own currency. Leave it out, or pass false, to watch a single account.",
          "type": "boolean"
        },
        "metric": {
          "type": "string",
          "enum": [
            "spend",
            "clicks",
            "impressions",
            "conversions",
            "conv_value",
            "ctr",
            "cpc",
            "cpa",
            "cpm",
            "roas",
            "conversion_rate",
            "cost_per_lead",
            "budget_utilization"
          ],
          "description": "What to watch. Rates are computed from the window totals: ctr = clicks/impressions, cpc = spend/clicks, cpa = spend/conversions, cpm = spend per 1,000 impressions, roas = conversion value/spend, conversion_rate = conversions/clicks, cost_per_lead = spend/leads, budget_utilization = spend/daily budget."
        },
        "operator": {
          "type": "string",
          "enum": [
            "less_than",
            "greater_than",
            "changes_by",
            "drops_by",
            "rises_by"
          ],
          "description": "How to compare. less_than/greater_than use the threshold as a level; changes_by, drops_by and rises_by read the threshold as a percent move against the baseline."
        },
        "threshold": {
          "type": "number",
          "description": "The number to compare against: a value in the account currency for money metrics, a count for volume metrics, a fraction for ctr / conversion_rate / budget_utilization (0.02 means 2%, never 2), a multiplier of the baseline when threshold_type is relative, or a percent move for changes_by / drops_by / rises_by."
        },
        "threshold_type": {
          "description": "absolute (default): the threshold is a value in the account currency or the metric's own unit. relative: the threshold is a multiplier of the baseline, so 0.5 means \"half of the usual\".",
          "type": "string",
          "enum": [
            "absolute",
            "relative"
          ]
        },
        "baseline_days": {
          "description": "Days of history the baseline averages. Default 30.",
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "consecutive_days": {
          "description": "Require the rule to hold this many days in a row before alerting. Default 1.",
          "type": "integer",
          "minimum": 1,
          "maximum": 14
        },
        "campaign_ids": {
          "description": "Platform campaign ids as strings. Leave empty to watch the whole account.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "conditions": {
          "description": "Optional extra conditions, for example \"ROAS below 2 AND spend above 100\".",
          "type": "object",
          "properties": {
            "mode": {
              "type": "string",
              "enum": [
                "and",
                "or"
              ],
              "description": "How the extra conditions combine with the main one."
            },
            "conditions": {
              "minItems": 1,
              "maxItems": 3,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "metric": {
                    "type": "string",
                    "enum": [
                      "spend",
                      "clicks",
                      "impressions",
                      "conversions",
                      "conv_value",
                      "ctr",
                      "cpc",
                      "cpa",
                      "cpm",
                      "roas",
                      "conversion_rate",
                      "cost_per_lead",
                      "budget_utilization"
                    ],
                    "description": "What to watch. Rates are computed from the window totals: ctr = clicks/impressions, cpc = spend/clicks, cpa = spend/conversions, cpm = spend per 1,000 impressions, roas = conversion value/spend, conversion_rate = conversions/clicks, cost_per_lead = spend/leads, budget_utilization = spend/daily budget."
                  },
                  "operator": {
                    "type": "string",
                    "enum": [
                      "less_than",
                      "greater_than",
                      "changes_by",
                      "drops_by",
                      "rises_by"
                    ],
                    "description": "How to compare. less_than/greater_than use the threshold as a level; changes_by, drops_by and rises_by read the threshold as a percent move against the baseline."
                  },
                  "threshold": {
                    "type": "number"
                  },
                  "thresholdType": {
                    "type": "string",
                    "enum": [
                      "absolute",
                      "relative"
                    ]
                  }
                },
                "required": [
                  "metric",
                  "operator",
                  "threshold"
                ]
              }
            }
          },
          "required": [
            "mode",
            "conditions"
          ]
        },
        "notify_email": {
          "description": "Where to send the alert. Defaults to the account owner’s email. Any other address gets a confirmation link first, and alerts go to the owner’s email until someone at that address confirms.",
          "type": "string",
          "format": "email",
          "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
        },
        "action": {
          "description": "Optional. When set, a firing monitor creates a proposal the user still has to approve (in chat or at /approvals). Requires exactly one campaign id. Available on Google Ads, Meta Ads, LinkedIn Ads and ChatGPT Ads; on any other platform a monitor emails and proposes nothing.",
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "pause_campaign",
                "lower_budget"
              ],
              "description": "What to propose when the monitor fires. Nothing is applied automatically."
            },
            "percent": {
              "description": "lower_budget only: how much to cut the daily budget by, in percent. Default 20.",
              "type": "number",
              "minimum": 1,
              "maximum": 90
            }
          },
          "required": [
            "type"
          ]
        },
        "enabled": {
          "description": "Create it switched off by passing false. Default true.",
          "type": "boolean"
        }
      },
      "required": [
        "name",
        "metric",
        "operator",
        "threshold"
      ]
    }
  • delete_monitordestructive writefree

    Delete a monitor

    Removes a monitor and its evaluation history for good. Adako settings only; nothing on the ad platform changes, and any proposals it already created stay in the approvals inbox. Use when: the user has said to delete this specific monitor and you have shown them its name and rule. Do not use when: they only want it quiet for a while — update_monitor with enabled: false pauses it and keeps the history. Requires confirm_delete: true. Ask first; there is no undo.

    How to call: monitoring_write(action="execute", tool_name="delete_monitor", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "monitor_id": {
          "type": "string",
          "minLength": 8,
          "description": "Id from list_monitors."
        },
        "confirm_delete": {
          "description": "Must be true to remove anything. Ask the user first.",
          "type": "boolean"
        }
      },
      "required": [
        "monitor_id"
      ]
    }
  • get_monitor_historyread-onlyfree

    Show what a monitor saw each day

    Returns one row per day the monitor was evaluated: the value, the baseline, whether the rule held and whether an alert was sent. Free. Use when: the user asks why an alert did or did not fire, or wants to see the trend the monitor is watching. A row can say "skipped" — no synced data that day, or a ROAS rule on an account that records no conversion value. That is not a failure; it is the monitor refusing to judge on nothing.

    How to call: monitoring(action="execute", tool_name="get_monitor_history", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "monitor_id": {
          "type": "string",
          "minLength": 8,
          "description": "Id from list_monitors."
        },
        "limit": {
          "description": "How many days back. Default 30.",
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "monitor_id"
      ]
    }
  • list_monitorsread-onlyfree

    List the monitors on this account

    Lists every monitor with its rule, whether it is on, when it last ran and when it last fired. Free; reads Adako's database only. Use when: the user asks what they are watching, or before creating a monitor, to avoid a duplicate. Returns ids you can pass to get_monitor_history, test_monitor, update_monitor and delete_monitor.

    How to call: monitoring(action="execute", tool_name="list_monitors", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • list_pending_actionsread-onlyfree

    List the changes monitors have proposed

    Lists the proposals that monitors created when they fired: which monitor, which day, what it wants to change, and when it expires. Free. Use when: the user asks what their alerts have proposed, or after an alert email mentions a pending change. Do not use when: the user wants every pending write, including the ones they asked for in chat — that is list_pending_proposals. Nothing here has been applied. Approve one with manage_action after the user says yes to that specific change.

    How to call: monitoring(action="execute", tool_name="list_pending_actions", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {}
    }
  • manage_actiondestructive writefree

    Approve or reject a change a monitor proposed

    Applies or declines one proposal that a monitor created. Approving executes it exactly once against the ad platform and reads the object back; rejecting marks it so it can never execute. Use when: the user has explicitly said yes or no to the specific change — quote its summary from list_pending_actions first. Do not use when: the user is still deciding, or the proposal came from a tool call they made themselves (use approve_proposal / reject_proposal, which say the same thing in that context). Approving is real money: a pause stops delivery, a budget cut changes what the campaign can spend. If the proposal expired or was already decided, this returns an error — say so rather than silently recreating it.

    How to call: monitoring_write(action="execute", tool_name="manage_action", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "proposal_id": {
          "type": "string",
          "minLength": 8,
          "description": "Id from list_pending_actions."
        },
        "action": {
          "type": "string",
          "enum": [
            "approve",
            "reject"
          ],
          "description": "approve applies the change; reject retires the proposal."
        },
        "reason": {
          "description": "Why it was rejected. Stored with the proposal; it improves later proposals.",
          "type": "string",
          "maxLength": 500
        }
      },
      "required": [
        "proposal_id",
        "action"
      ]
    }
  • test_monitorread-onlyfree

    Dry-run a monitor against the last week

    Runs a monitor — a saved one, or one described inline — against the synced data for each of the last 7 days and says which days it would have alerted on. Nothing is saved and no email is sent. Free. Use when: the user is choosing a threshold, or says an alert is too noisy or too quiet. Always dry-run an inline rule before create_monitor when the user is unsure of the number. Returns a day-by-day table with the value, the baseline and the verdict, plus the same one-line message the alert email would carry. Pass monitor_id for a saved monitor, or the inline fields (platform, metric, operator, threshold) for a rule that does not exist yet. If you are unsure which metric, account or threshold the user means, ask. Never invent a threshold.

    How to call: monitoring(action="execute", tool_name="test_monitor", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "monitor_id": {
          "description": "A saved monitor to dry-run. Omit to describe one inline.",
          "type": "string",
          "minLength": 8
        },
        "platform": {
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads"
          ],
          "description": "Which ad platform the monitor watches."
        },
        "account_id": {
          "description": "Platform account id (Google customer id, Meta act_…). Defaults to the primary active account on the platform.",
          "type": "string",
          "minLength": 1
        },
        "metric": {
          "type": "string",
          "enum": [
            "spend",
            "clicks",
            "impressions",
            "conversions",
            "conv_value",
            "ctr",
            "cpc",
            "cpa",
            "cpm",
            "roas",
            "conversion_rate",
            "cost_per_lead",
            "budget_utilization"
          ],
          "description": "What to watch. Rates are computed from the window totals: ctr = clicks/impressions, cpc = spend/clicks, cpa = spend/conversions, cpm = spend per 1,000 impressions, roas = conversion value/spend, conversion_rate = conversions/clicks, cost_per_lead = spend/leads, budget_utilization = spend/daily budget."
        },
        "operator": {
          "type": "string",
          "enum": [
            "less_than",
            "greater_than",
            "changes_by",
            "drops_by",
            "rises_by"
          ],
          "description": "How to compare. less_than/greater_than use the threshold as a level; changes_by, drops_by and rises_by read the threshold as a percent move against the baseline."
        },
        "threshold": {
          "type": "number"
        },
        "threshold_type": {
          "description": "absolute (default): the threshold is a value in the account currency or the metric's own unit. relative: the threshold is a multiplier of the baseline, so 0.5 means \"half of the usual\".",
          "type": "string",
          "enum": [
            "absolute",
            "relative"
          ]
        },
        "baseline_days": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "consecutive_days": {
          "type": "integer",
          "minimum": 1,
          "maximum": 14
        },
        "campaign_ids": {
          "description": "Platform campaign ids as strings. Leave empty to watch the whole account.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "conditions": {
          "description": "Optional extra conditions, for example \"ROAS below 2 AND spend above 100\".",
          "type": "object",
          "properties": {
            "mode": {
              "type": "string",
              "enum": [
                "and",
                "or"
              ],
              "description": "How the extra conditions combine with the main one."
            },
            "conditions": {
              "minItems": 1,
              "maxItems": 3,
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "metric": {
                    "type": "string",
                    "enum": [
                      "spend",
                      "clicks",
                      "impressions",
                      "conversions",
                      "conv_value",
                      "ctr",
                      "cpc",
                      "cpa",
                      "cpm",
                      "roas",
                      "conversion_rate",
                      "cost_per_lead",
                      "budget_utilization"
                    ],
                    "description": "What to watch. Rates are computed from the window totals: ctr = clicks/impressions, cpc = spend/clicks, cpa = spend/conversions, cpm = spend per 1,000 impressions, roas = conversion value/spend, conversion_rate = conversions/clicks, cost_per_lead = spend/leads, budget_utilization = spend/daily budget."
                  },
                  "operator": {
                    "type": "string",
                    "enum": [
                      "less_than",
                      "greater_than",
                      "changes_by",
                      "drops_by",
                      "rises_by"
                    ],
                    "description": "How to compare. less_than/greater_than use the threshold as a level; changes_by, drops_by and rises_by read the threshold as a percent move against the baseline."
                  },
                  "threshold": {
                    "type": "number"
                  },
                  "thresholdType": {
                    "type": "string",
                    "enum": [
                      "absolute",
                      "relative"
                    ]
                  }
                },
                "required": [
                  "metric",
                  "operator",
                  "threshold"
                ]
              }
            }
          },
          "required": [
            "mode",
            "conditions"
          ]
        },
        "days": {
          "description": "How many past days to test. Default 7.",
          "type": "integer",
          "minimum": 1,
          "maximum": 14
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • update_monitorwritefreepro plan

    Change or pause a monitor

    Changes a monitor's threshold, rule or on/off state. Adako settings only; nothing on the ad platform changes. Use when: the user says the alert is too noisy, wants a different number, or wants it switched off for a while. Do not use when: the user wants the monitor gone for good — that is delete_monitor, and it needs confirm_delete. Pass only the fields that change; everything else is left alone. Returns the monitor with its new rule in plain words. If you are unsure which metric, account or threshold the user means, ask. Never invent a threshold.

    How to call: monitoring_write(action="execute", tool_name="update_monitor", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "monitor_id": {
          "type": "string",
          "minLength": 8,
          "description": "Id from list_monitors."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        },
        "metric": {
          "type": "string",
          "enum": [
            "spend",
            "clicks",
            "impressions",
            "conversions",
            "conv_value",
            "ctr",
            "cpc",
            "cpa",
            "cpm",
            "roas",
            "conversion_rate",
            "cost_per_lead",
            "budget_utilization"
          ],
          "description": "What to watch. Rates are computed from the window totals: ctr = clicks/impressions, cpc = spend/clicks, cpa = spend/conversions, cpm = spend per 1,000 impressions, roas = conversion value/spend, conversion_rate = conversions/clicks, cost_per_lead = spend/leads, budget_utilization = spend/daily budget."
        },
        "operator": {
          "type": "string",
          "enum": [
            "less_than",
            "greater_than",
            "changes_by",
            "drops_by",
            "rises_by"
          ],
          "description": "How to compare. less_than/greater_than use the threshold as a level; changes_by, drops_by and rises_by read the threshold as a percent move against the baseline."
        },
        "threshold": {
          "type": "number"
        },
        "threshold_type": {
          "description": "absolute (default): the threshold is a value in the account currency or the metric's own unit. relative: the threshold is a multiplier of the baseline, so 0.5 means \"half of the usual\".",
          "type": "string",
          "enum": [
            "absolute",
            "relative"
          ]
        },
        "baseline_days": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        },
        "consecutive_days": {
          "type": "integer",
          "minimum": 1,
          "maximum": 14
        },
        "campaign_ids": {
          "description": "Platform campaign ids as strings. Leave empty to watch the whole account.",
          "maxItems": 50,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "notify_email": {
          "description": "Where to send the alert. Defaults to the account owner’s email. Any other address gets a confirmation link first, and alerts go to the owner’s email until someone at that address confirms.",
          "type": "string",
          "format": "email",
          "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
        },
        "enabled": {
          "description": "false pauses the monitor without deleting it.",
          "type": "boolean"
        }
      },
      "required": [
        "monitor_id"
      ]
    }

reporting5

  • generate_report_nowwritefreepro plan

    Compose a performance report now

    Composes a report from Adako's synced data and stores it, so it also has a permanent web page. Returns immediately — no platform calls, no waiting. Free. Use when: the user asks how a period went, wants a summary to forward, or asks for "a report" or "a brief" — including when they name one platform, because this covers every connected account and keeps the page. Prefer it over the per-platform performance tools whenever the answer is meant to be kept or sent on: those read one platform live and store nothing. Do not use when: the user asks about today — today is still syncing; use the live performance tools for "right now". report_type: performance_brief (totals, per account, top campaigns), detailed_analysis (adds the biggest movers), executive_summary (totals and a few lines). Pass either date_range or start_date + end_date, never both. Every period is compared with the equally long period immediately before it. Periods follow each account's own timezone and cover complete days only: a day that has not ended in an account's timezone is left out, and the report says when an account's days differ from the headline period. Returns the report as markdown plus a link to its page. The user can email it to themselves from that page; for recurring delivery use schedule_brief. If you are unsure which accounts, cadence or period the user means, ask rather than guessing.

    How to call: monitoring_write(action="execute", tool_name="generate_report_now", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "report_type": {
          "type": "string",
          "enum": [
            "performance_brief",
            "detailed_analysis",
            "executive_summary"
          ],
          "description": "performance_brief: totals, per account and the top campaigns. detailed_analysis: adds the biggest movers. executive_summary: totals and three lines, nothing else."
        },
        "date_range": {
          "description": "Preset range. Alternatively pass start_date and end_date (YYYY-MM-DD). Never both.",
          "type": "string",
          "enum": [
            "today",
            "yesterday",
            "last_7_days",
            "last_14_days",
            "last_30_days",
            "this_month",
            "last_month"
          ]
        },
        "start_date": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "end_date": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "platforms": {
          "description": "Which platforms to cover. Leave empty for every platform with an active account.",
          "maxItems": 5,
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "google_ads",
              "meta_ads",
              "chatgpt_ads",
              "tiktok_ads",
              "linkedin_ads"
            ]
          }
        },
        "account_ids": {
          "description": "Platform account ids to cover. Leave empty for every active account.",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "delivery_email": {
          "description": "Recorded on the report as its intended recipient. On-demand sending happens from the report page; use schedule_brief for recurring email.",
          "type": "string",
          "format": "email",
          "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      },
      "required": [
        "report_type"
      ]
    }
  • list_reportsread-onlyfree

    List the reports Adako has composed

    Lists reports — scheduled briefs and on-demand ones — newest first, with the period each covers and a link to its page. Free. Use when: the user refers to "the last report" or wants to reread one. Returns links; open a link to read the whole report, or call generate_report_now for a fresh one.

    How to call: monitoring(action="execute", tool_name="list_reports", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "limit": {
          "description": "How many to list. Default 10.",
          "type": "integer",
          "minimum": 1,
          "maximum": 50
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • list_scheduled_tasksread-onlyfree

    List scheduled briefs, reports and monitors

    Lists everything Adako runs on its own for this user: recurring briefs and reports with their cadence, address and next run, and the monitors that judge an account once a day. Free; reads Adako's database only. Use when: the user asks what they are subscribed to, what is being watched, or before scheduling or creating another one. Do not use when: the user wants the day-by-day record of one monitor — that is get_monitor_history — or the reports already composed, which is list_reports. include narrows the list: all (the default: briefs and monitors), briefs, monitors. Leaving it out lists both. Returns ids for manage_scheduled_task, which pauses, resumes or deletes either kind, and the same monitor ids update_monitor and delete_monitor take.

    How to call: monitoring(action="execute", tool_name="list_scheduled_tasks", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "include": {
          "description": "Which kinds to list. Default all: scheduled briefs and monitors together.",
          "type": "string",
          "enum": [
            "all",
            "briefs",
            "monitors"
          ]
        },
        "raw_data": {
          "description": "Return compact JSON only (no markdown). Use when you will compute on the result.",
          "type": "boolean"
        }
      }
    }
  • manage_scheduled_taskwritefree

    Pause, resume or delete a scheduled brief or a monitor

    Pauses, resumes or deletes one recurring brief or one monitor. Adako settings only; nothing on the ad platform changes, past reports stay readable and proposals a monitor already made stay in the approvals inbox. Use when: the user says a brief is too frequent, an alert is too noisy, or wants either one back. Name what you are acting on: task_id for a brief, monitor_id for a monitor. Both come from list_scheduled_tasks; monitor ids also come from list_monitors. Pass one, never both. Prefer pause over delete: a paused brief keeps its settings, a paused monitor keeps its history, and either resumes in one call. Deleting needs confirm_delete: true — a schedule cannot be recovered and a deleted monitor takes its evaluation history with it. Do not use when: the user wants a monitor's threshold or rule changed — that is update_monitor. Returns the new state of whichever one you named. If you are unsure whether the user means a brief or a monitor, call list_scheduled_tasks and ask.

    How to call: monitoring_write(action="execute", tool_name="manage_scheduled_task", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "task_id": {
          "description": "Id of a scheduled brief, from list_scheduled_tasks. Pass this or monitor_id.",
          "type": "string",
          "minLength": 8
        },
        "monitor_id": {
          "description": "Id of a monitor, from list_scheduled_tasks or list_monitors. Pass this or task_id.",
          "type": "string",
          "minLength": 8
        },
        "action": {
          "type": "string",
          "enum": [
            "pause",
            "resume",
            "delete"
          ],
          "description": "pause stops delivery or evaluation and keeps the settings; delete removes the schedule or the monitor."
        },
        "confirm_delete": {
          "description": "Must be true to remove anything. Ask the user first.",
          "type": "boolean"
        }
      },
      "required": [
        "action"
      ]
    }
  • schedule_briefwritefreepro plan

    Schedule a recurring performance brief by email

    Sets up a daily or weekly brief composed from Adako's synced data and emailed to the operator. Adako settings only; nothing on the ad platform changes. Use when: the user asks for a recurring summary ("every Monday", "each morning"). Confirm the cadence, the hour and the timezone before saving; the timezone defaults to UTC, so ask for it rather than relying on the default. Do not use when: the user wants one report right now — that is generate_report_now. The brief compares the period with the one before it: a daily brief covers yesterday against the day before, a weekly one the last 7 days against the previous 7. The evaluation job runs once a day, so the hour schedules the day rather than the minute: the brief goes out on the first daily pass at or after your hour. report_type is saved with the schedule and every run composes that type: performance_brief (the default: totals, per account, top campaigns), detailed_analysis (adds the biggest movers), executive_summary (totals and a few lines). Returns the schedule in plain words and when it will first run. If you are unsure which accounts, cadence or period the user means, ask rather than guessing.

    How to call: monitoring_write(action="execute", tool_name="schedule_brief", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "cadence": {
          "type": "string",
          "enum": [
            "daily",
            "weekly"
          ],
          "description": "How often the brief goes out."
        },
        "weekday": {
          "description": "Weekly only: 0 = Sunday … 6 = Saturday. Default Monday (1).",
          "type": "integer",
          "minimum": 0,
          "maximum": 6
        },
        "hour": {
          "description": "Local hour, 0–23. Default 7.",
          "type": "integer",
          "minimum": 0,
          "maximum": 23
        },
        "timezone": {
          "description": "IANA timezone, for example Europe/Warsaw or America/New_York. Defaults to UTC; ask the user for theirs rather than guessing.",
          "type": "string",
          "minLength": 1
        },
        "platforms": {
          "description": "Which platforms to cover. Leave empty for every platform with an active account.",
          "maxItems": 5,
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "google_ads",
              "meta_ads",
              "chatgpt_ads",
              "tiktok_ads",
              "linkedin_ads"
            ]
          }
        },
        "account_ids": {
          "description": "Platform account ids to cover. Leave empty for every active account.",
          "maxItems": 20,
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "delivery_email": {
          "description": "Where to send it. Defaults to the account owner’s email. Any other address gets a confirmation link first, and the brief goes to the owner’s email until someone at that address confirms.",
          "type": "string",
          "format": "email",
          "pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
        },
        "report_type": {
          "type": "string",
          "enum": [
            "performance_brief",
            "detailed_analysis",
            "executive_summary"
          ],
          "description": "performance_brief: totals, per account and the top campaigns. detailed_analysis: adds the biggest movers. executive_summary: totals and three lines, nothing else."
        }
      },
      "required": [
        "cadence"
      ]
    }

Diagnostics8

Reads run through diagnostics. Call diagnostics(action="list_tools") for the same list at runtime — it is free.

diagnostics8

  • explain_errorread-onlyfree

    Explain a platform error

    Translates a raw error from any connected ad platform into a plain-language cause, a concrete fix, and the Adako tool to call next. Deterministic rule table, no guessing. Covers Google Ads error codes, Meta Graph codes, ChatGPT Ads HTTP shapes, TikTok numeric codes and LinkedIn codes. Use when: a tool returned platform_error, or the user pastes an error from the platform UI. When not to use: the failure was an Adako error (quota, validation, not connected) — call why_did_this_fail instead, which reads the real call log. Returns the best-matching rule with cause, fix and next tool. If nothing matches it says so and lists what it does know for that platform; never invent a cause.

    How to call: diagnostics(action="execute", tool_name="explain_error", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "error_text": {
          "type": "string",
          "minLength": 3,
          "maxLength": 4000,
          "description": "The raw error message or JSON, pasted verbatim."
        },
        "platform": {
          "description": "Narrows the rule table. Pass it whenever you know the platform: HTTP statuses like 401 mean different things on different platforms.",
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads"
          ]
        }
      },
      "required": [
        "error_text"
      ]
    }
  • get_campaign_specread-onlyfree

    What a campaign needs before it can be created

    Returns the full specification for one campaign type: required and optional fields, text and asset limits, the objective matrix, the minimum budget, what Adako creates in what order, and the pitfalls that cause most rejections. Free, instant, offline — it calls no platform. Use when: the user wants to create a campaign and you need to know what to ask them for; before writing any draft; when a create was rejected for a length, a count or an objective pairing. When not to use: you already have a draft — run validate_campaign_draft instead, which checks the draft against this same spec. Returns markdown tables plus the machine-readable spec. If the (platform, campaign_type) pair is unknown it errors and lists the valid pairs; it never improvises a spec for a campaign type Adako has not encoded.

    How to call: diagnostics(action="execute", tool_name="get_campaign_spec", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "platform": {
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads"
          ],
          "description": "Which ad platform."
        },
        "campaign_type": {
          "type": "string",
          "minLength": 1,
          "maxLength": 40,
          "description": "Campaign type on that platform. Known types: search, performance_max, image, video, carousel, chat_card, text."
        }
      },
      "required": [
        "platform",
        "campaign_type"
      ]
    }
  • list_what_i_can_doread-onlyfree

    Capability menu for the platforms this user has connected

    Lists what Adako can actually do for THIS user: the tools available on the platforms they have connected, grouped by job, plus the Adako-level abilities that work without any platform. Free; reads the tool registry and the user's connections. Use when: the user asks what you can do, has just connected an account, or you need to know whether a capability exists before promising it. When not to use: you are looking for one specific tool — search_tools ranks by intent and is better for that. Returns counts per platform and a short grouped menu with the exact call line, since most platform tools are reached through their platform router rather than by name. It lists only what exists; it never promises a capability Adako has not shipped.

    How to call: diagnostics(action="execute", tool_name="list_what_i_can_do", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "platform": {
          "description": "Only this platform. Omit for everything the user has connected.",
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads"
          ]
        }
      }
    }
  • suggest_next_actionread-onlyfree

    Three concrete next steps from the real account state

    Looks at this user's actual state — connections, active accounts, pending approvals, the last ten calls and the remaining quota — and returns three concrete next steps, each with the tool to call and what it costs. Free; reads Adako's own database only. Use when: the user asks what to do next, has just connected an account, seems stuck, or you have finished a task and want to offer a sensible follow-up. When not to use: the user has already said what they want — just do that. Returns three ranked steps with the reason each one is next. Deterministic: the same state always gives the same advice, and it never invents work that the account state does not support.

    How to call: diagnostics(action="execute", tool_name="suggest_next_action", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {}
    }
  • usage_value_summaryread-onlyfree

    What this account has actually used Adako for

    Summarises this user's real activity over a period: tasks by platform and by kind of work, reads versus writes, proposals created, approved, rejected and executed, the five most-used tools, and a plain statement of what those calls did. Free; reads Adako's own call log and proposal store. Use when: the user asks what they have used Adako for, whether it is earning its keep, or wants a summary before a renewal decision. When not to use: the user wants campaign results — that is a performance tool on the platform, not this. Returns counts only. It never estimates money saved or performance gained: Adako cannot attribute a result to a tool call, and inventing a number would be a lie the user might repeat.

    How to call: diagnostics(action="execute", tool_name="usage_value_summary", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "period": {
          "default": "last_30_days",
          "description": "this_month is the calendar month to date; last_30_days is a rolling window. Default last_30_days.",
          "type": "string",
          "enum": [
            "this_month",
            "last_30_days"
          ]
        }
      }
    }
  • validate_campaign_draftread-onlyfree

    Dry-run a campaign draft against the platform rules

    Checks a campaign draft field by field against Adako's spec for that platform and campaign type, and returns every problem with a severity and what to do about it. Pure dry run: it contacts no platform, changes nothing and costs nothing. Catches: missing required fields, text over the character limit, lists with too few or too many items, budgets under the platform floor, objective and optimisation-goal pairings the platform refuses, ids passed as numbers, money that looks like micros or cents, and place or interest names where an id belongs. Budget floors: pass currency (the ad account's ISO code, from list_connected_accounts or the account read). The floors Adako knows offline are USD figures, so they are enforced for USD only; in any other or unknown currency the draft is not told it clears the floor — the create tool's preview checks the account's own minimum. Use when: you have assembled a draft and before you call any create tool. Run it again after fixing, until there are no blockers. When not to use: you do not have a draft yet — call get_campaign_spec to learn what to ask the user for. Returns per-field issues (blocker / warning / note) and the resolver or tool to call next. A clean result means the draft satisfies the rules Adako knows; the platform is still the final judge, so a create can still be rejected for policy or account state.

    How to call: diagnostics(action="execute", tool_name="validate_campaign_draft", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "platform": {
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads"
          ],
          "description": "Which ad platform the draft is for."
        },
        "campaign_type": {
          "type": "string",
          "minLength": 1,
          "maxLength": 40,
          "description": "Campaign type, e.g. search, image, video, carousel, chat_card."
        },
        "draft": {
          "type": "object",
          "propertyNames": {
            "type": "string"
          },
          "additionalProperties": {},
          "description": "The draft campaign as an object: the arguments you intend to pass to the create tool."
        },
        "currency": {
          "description": "ISO 4217 code of the ad account currency, e.g. USD, EUR, JPY. Omit when unknown: USD budget floors are then not enforced, and large whole numbers are only flagged as possible micros.",
          "type": "string",
          "pattern": "^[A-Za-z]{3}$"
        }
      },
      "required": [
        "platform",
        "campaign_type",
        "draft"
      ]
    }
  • verify_campaign_is_liveread-onlyfree

    Check whether a campaign actually exists and is running

    Answers one question with real data: does this campaign exist, is it switched on, and does it have a budget it can spend? Free when Adako has synced history for the account; otherwise it falls back to the platform's own list tool, which is free too, and says which source it used. Use when: a create or a resume has just run and the user wants proof; the user says "nothing is spending"; or you need to confirm state before proposing another change. When not to use: the user wants performance numbers — this is an existence and status check, not a report. Returns exists / status / budget / whether it should be serving, with what to do when it is not. It never changes anything, and never claims a campaign is live without having read it back.

    How to call: diagnostics(action="execute", tool_name="verify_campaign_is_live", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "platform": {
          "type": "string",
          "enum": [
            "google_ads",
            "meta_ads",
            "chatgpt_ads",
            "tiktok_ads",
            "linkedin_ads"
          ],
          "description": "Which ad platform the campaign is on."
        },
        "campaign_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "description": "The campaign id, as a string, exactly as the platform returned it."
        },
        "account_id": {
          "description": "Ad account id. Defaults to the primary active account on that platform.",
          "type": "string",
          "minLength": 1,
          "maxLength": 64
        }
      },
      "required": [
        "platform",
        "campaign_id"
      ]
    }
  • why_did_this_failread-onlyfree

    Explain the recent failed calls

    Reads this user's real Adako call log, finds the calls that failed or were blocked, and explains each one: the cause, the fix, and the tool to call next. Free; reads Adako's own database and contacts no platform. Use when: the user says "that didn't work", "why did that fail" or "it's broken", or after any tool returned an error and you want the history rather than one message. When not to use: the user pasted a raw platform error — explain_error decodes that directly. And this cannot see failures from other conversations of other users, only this user's own calls. Returns one row per failed call, newest first, with the redacted arguments so the user recognises it. If nothing has failed it says so plainly rather than inventing a problem.

    How to call: diagnostics(action="execute", tool_name="why_did_this_fail", arguments={...})

    Input schema
    {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "type": "object",
      "properties": {
        "limit": {
          "default": 5,
          "description": "How many failed calls to explain, newest first. Default 5.",
          "type": "integer",
          "minimum": 1,
          "maximum": 20
        },
        "tool_name": {
          "description": "Only failures of this tool, e.g. google_create_search_campaign.",
          "type": "string",
          "minLength": 1,
          "maxLength": 64
        }
      }
    }

Last updated