# Adspirer REST API — Full Reference

> Complete inlined reference for the Adspirer REST API. 178 tools across Google Ads, Meta, LinkedIn, TikTok, Monitoring, and Audit.
> See also: https://api.adspirer.ai/openapi.json (machine-readable) · https://api.adspirer.ai/reference/ (HTML)

## Core concepts

- **Base URL:** `https://api.adspirer.ai`
- **Endpoint pattern:** `POST /api/v1/tools/<tool_name>/execute`
- **Auth:** `Authorization: Bearer sk_live_...`
- **Body envelope:** `{"arguments": {...}}`
- **Idempotency:** `Idempotency-Key: <uuid>` on writes
- **Response envelope (success):** `{"success": true, "data": {"text": "...", "quota": {...}}, "tool": "..."}`
- **Response envelope (error):** `{"success": false, "error": "...", "is_error": true, "tool": "..."}`
- **HTTP status codes:** 200 success, 400 tool error, 401 bad auth, 402 quota exhausted, 404 unknown tool, 429 upstream rate limit, 500 server error

---

# Audit

## audit_conversion_tracking

**Platform:** Audit

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/audit_conversion_tracking/execute`

Review your conversion tracking setup across ad platforms. Checks Meta Pixel (event volume, CAPI status, dedup, diagnostics), LinkedIn (conversion rules, CAPI, Insight Tag), and Google Ads (conversion actions, enhanced conversions, attribution). Returns a health score (0-100), grade (A-F), detailed findings, and suggestions with self-validation steps for each platform.

Note: Some checks rely on API data availability and may not capture all configurations. Where findings are inconclusive, the tool provides guidance on how to verify directly in the platform's dashboard.

Parameters:
- platform (optional): 'google_ads', 'meta_ads', or 'linkedin_ads'. If omitted, reviews all connected platforms.
- lookback_days (optional): 7, 14, 30, 60, or 90 days (default: 30)

Execution time: ~5-15 seconds (includes live API calls to ad platforms).

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `platform` | `object` | no | Ad platform to audit: 'google_ads', 'meta_ads', or 'linkedin_ads'. If omitted, audits all connected platforms. |
| `lookback_days` | `object` | no | Number of days to look back for trend analysis (7, 14, 30, 60, 90). Default: 30. |

### Example request

```json
{
  "arguments": {
    "platform": "string",
    "lookback_days": 30
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for audit_conversion_tracking)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "audit_conversion_tracking"
}
```

---
# General (Account Management)

## get_connections_status

**Platform:** General (Account Management)

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_connections_status/execute`

View connected ad accounts and OAuth connections.

Shows all connected ad platforms (Google Ads, TikTok Ads, Meta Ads, LinkedIn Ads) with:
- Connection status and account details
- Primary account indicator
- Option to switch primary account
- Quick links to connect new platforms

Use this tool when user asks about:
- "how many accounts do I have" / "how many ad accounts"
- "what accounts are connected" / "connected accounts"
- "which account am I using" / "current account"
- "show my connections" / "list my accounts"
- "connection status" / "account status"
- "what platforms do I have connected"
- Account count, account list, or connection overview

Do not use for:
- Discovering campaigns, ads, or keywords (use discover_existing_assets)
- Performance data or metrics (use performance tools)
- Creating or managing campaigns

This is specifically for OAuth connections and account management.

### Arguments

_No arguments._

### Example request

```json
{
  "arguments": {}
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_connections_status)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_connections_status"
}
```

---
## list_connected_accounts

**Platform:** General (Account Management)

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_connected_accounts/execute`

List all connected ad accounts across platforms.

Returns account IDs, names, and platforms for all active accounts. Use this at the start
of every conversation to know which accounts are available.

CRITICAL for multi-account users (agencies):
- Always call this first to discover available accounts
- Use the returned account IDs when calling any platform tool
- If user mentions a business name, match it to an account from this list

Returns for each account:
- platform: google_ads, meta_ads, tiktok_ads, linkedin_ads
- account_id: Platform account ID (pass to tools as customer_id/ad_account_id/advertiser_id/account_id)
- account_name: Display name
- account_tier: primary, secondary, or active
- status: connected, needs_reauth

Zero API calls — reads directly from database.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `platform` | `object` | no | Filter by platform: google_ads, meta_ads, tiktok_ads, or linkedin_ads. If not specified, returns accounts across all platforms. |

### Example request

```json
{
  "arguments": {
    "platform": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_connected_accounts)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_connected_accounts"
}
```

---
## switch_primary_account

**Platform:** General (Account Management)

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/switch_primary_account/execute`

Activate one or more ad accounts for a platform.

Activated accounts are available for all ad operations. Pass a single account_id
to activate one account, or pass account_ids (list) to activate multiple accounts
for multi-account management.

Use this tool when:
- User wants to switch to a different ad account
- User says "use my other account" or "activate these accounts"
- User wants to manage multiple accounts simultaneously
- User specifies which accounts to work with

Accepts either account_id (single) or account_ids (list of IDs from list_connected_accounts).

IMPORTANT: Returns updated connections list after switching.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `platform` | `string` | yes | Platform to switch primary account for: google_ads, tiktok_ads, meta_ads, or linkedin_ads |
| `account_id` | `object` | no | Single account ID to activate (e.g., '1234567890' for Google Ads). Use this OR account_ids, not both. |
| `account_ids` | `object` | no | List of account IDs to activate. All listed accounts become active, all others become inactive. Use this for multi-account selection. Get account IDs from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "platform": "string",
    "account_id": "string",
    "account_ids": [
      "string"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for switch_primary_account)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "switch_primary_account"
}
```

---
# Google Ads

## add_callout_extensions

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_callout_extensions/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Add callout extensions to a campaign.

Callouts are short, non-clickable highlights that appear below your ad.
They communicate quick trust signals and differentiators.

**IMPORTANT RULES (from google-ads-creative-guidelines.md):**
- Max 25 characters each
- 4-8 callouts recommended
- Should NOT be CTAs (no "click", "buy", "order", "call")
- Should NOT end with periods

**GOOD EXAMPLES:**
- "Free Shipping"
- "24/7 Support"
- "No Code Required"
- "30 Second Setup"
- "Award Winning"

**BAD EXAMPLES (will be rejected):**
- "Click Now" (CTA word)
- "Buy Today" (CTA word)
- "Free Shipping." (ends with period)

**Parameters:**
- campaign_id: The campaign ID (REQUIRED). Get from list_campaigns.
- callouts: List of callout text strings (REQUIRED). Each max 25 chars.
- customer_id: Optional Google Ads customer ID

**Execution time:** 2-5 seconds

**When to use:**
- User wants to add callouts/highlights to their campaign
- User asks about extensions or ad enhancements
- After creating a campaign, suggest adding callouts

**Example:**
User: "Add some callouts to my campaign"
Agent:
1. Uses list_campaigns to get campaign_id
2. Uses add_callout_extensions with relevant callouts

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to add callouts to. Get from list_campaigns. |
| `callouts` | `array` | yes | List of callout text strings. Each max 25 characters. Example: ['Free Shipping', '24/7 Support', 'No Code Required'] |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "callouts": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_callout_extensions)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_callout_extensions"
}
```

---
## add_demandgen_ad_group

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_demandgen_ad_group/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Add a new ad group with targeting and ads to an EXISTING Demand Gen campaign.

Use this after create_demandgen_campaign to add additional ad groups. Each ad group can have:
- **Different location targeting** (e.g., US cities vs India cities vs UAE cities)
- **Different audience targeting** (e.g., marketing pros vs tech founders vs agency owners)
- **Same or different ad creative** (headlines, descriptions, images, videos)

**Reuse Existing Audiences:**
To attach an existing audience (from Google Ads UI or a previous campaign), pass:
- audience_segments.existing_audience_id: numeric audience ID
- audience_segments.existing_audience_resource_name: full resource name

**Create New Audiences:**
To create a new audience from interest segments:
- audience_segments.in_market_audience_ids: [80517, 80520]
- audience_segments.affinity_audience_ids: [92948]
Use search_audiences to find segment IDs.

**Parameters:**
- campaign_id: Demand Gen campaign ID (from create_demandgen_campaign or list_campaigns)
- ad_group_name: Descriptive name
- Same creative fields as create_demandgen_campaign (headlines, descriptions, images, etc.)
- target_locations: Location targeting specific to this ad group
- audience_segments: Audience targeting specific to this ad group

**Execution time:** 10-20 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Demand Gen campaign ID to add the ad group to. Get from list_campaigns or get_campaign_structure. |
| `ad_group_name` | `string` | yes | Name for the new ad group (e.g., 'US Metro - Marketing Pros') |
| `ad_format` | `object` | no | Ad format: 'multi_asset' (image ads, default), 'video_responsive' (video ads). |
| `final_url` | `string` | yes | Landing page URL. Example: 'https://example.com/product' |
| `business_name` | `string` | yes | Business name, max 25 characters. |
| `headlines` | `array` | yes | MUST BE JSON ARRAY: 1-5 headlines, each max 40 characters. |
| `descriptions` | `array` | yes | MUST BE JSON ARRAY: 1-5 descriptions, each max 90 characters. |
| `long_headlines` | `object` | no | Optional 1-5 long headlines, max 90 chars each. For video_responsive format. |
| `call_to_action` | `object` | no | CTA label: LEARN_MORE, SHOP_NOW, SIGN_UP, SUBSCRIBE, DOWNLOAD, BOOK_NOW, etc. |
| `target_locations` | `object` | no | Geographic targets for THIS ad group (e.g., ['New York', 'Bangalore']). Each ad group can target different locations. |
| `target_languages` | `object` | no | Language targets as ISO codes (e.g., ['en']). Defaults to English. |
| `channels` | `object` | no | Optional channel controls for this ad group. |
| `marketing_images` | `object` | no | Landscape images (1.91:1) as [{"url": "...", "name": "img1"}]. |
| `square_marketing_images` | `object` | no | Square images (1:1) as [{"url": "...", "name": "img1"}]. |
| `portrait_marketing_images` | `object` | no | Portrait images (4:5) as [{"url": "...", "name": "img1"}]. |
| `existing_images` | `object` | no | Existing image asset resource names to reuse. Keys: marketing_images_landscape, marketing_images_square, marketing_images_portrait, logos_square. |
| `youtube_video_ids` | `object` | no | YouTube video IDs for video_responsive format. 1-5 videos. |
| `logo_asset_id` | `object` | no | Logo image asset ID from discover_existing_assets. |
| `logo_images` | `object` | no | New logo images as [{"url": "...", "name": "logo1"}]. |
| `asset_bundle_id` | `object` | no | Asset bundle ID from validate_and_prepare_assets. |
| `audience_segments` | `object` | no | Audience targeting for THIS ad group. CRITICAL: Only use segment IDs returned by search_audiences tool. NEVER fabricate or guess IDs -- wrong IDs target unrelated audiences and waste budget. If search_audiences returns no results, skip audi |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "ad_group_name": "string",
    "final_url": "https://example.com",
    "business_name": "string",
    "headlines": [
      "string"
    ],
    "descriptions": [
      "string"
    ],
    "ad_format": "multi_asset",
    "long_headlines": [
      "string"
    ],
    "call_to_action": "string",
    "target_locations": [
      "string"
    ],
    "target_languages": [
      "string"
    ],
    "channels": [
      "YOUTUBE",
      "DISCOVER",
      "GMAIL"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_demandgen_ad_group)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_demandgen_ad_group"
}
```

---
## add_keywords

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_keywords/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Add keywords to an existing ad group.

⚠️ **CRITICAL: Run `research_keywords` BEFORE using this tool!**

**MANDATORY WORKFLOW:**
1. Call `get_campaign_structure` to get ad_group_id and see existing keywords
2. Call `research_keywords` with business context to get quality keyword suggestions
3. Show keyword suggestions to user with CPC, search volume, intent level
4. Get user approval on which keywords to add
5. ONLY THEN call this tool with approved keywords

**Why research first?**
- Ensures keywords have real search volume
- Shows CPC costs so user understands budget impact
- Provides HIGH/MEDIUM/LOW intent classification
- Prevents adding low-quality or irrelevant keywords

**Requires:**
- ad_group_id: Get from get_campaign_structure (REQUIRED)
- keywords: List of keywords to add (from research_keywords results)

**Each keyword needs:**
- text: The keyword phrase (e.g., "buy running shoes online")
- match_type: EXACT, PHRASE, or BROAD (default: BROAD)
- cpc_bid_micros: Optional bid override (1 USD = 1,000,000 micros)

**Match Type Guide:**
- BROAD: Ads show for related searches (widest reach) - Google's 2025 recommendation
- PHRASE: Ads show when query contains keyword phrase
- EXACT: Ads show only for exact query or close variants

**IMPORTANT:**
- Keyword text and match_type CANNOT be changed after creation
- To "change" a keyword, remove the old one and add a new one
- Get ad_group_id from get_campaign_structure first

**Execution time:** 2-4 seconds

**Example Workflow:**
User: "Add more keywords to my plumbing campaign"

Agent Steps:
1. Call get_campaign_structure → get ad_group_id, see existing keywords
2. Call research_keywords with business_description="plumbing services"
3. Show user: "I found these keyword suggestions from Keyword Planner:
   - emergency plumber near me (HIGH intent, $45 CPC, 12K searches/mo)
   - 24 hour plumber (HIGH intent, $38 CPC, 8K searches/mo)
   Which would you like to add?"
4. User selects keywords
5. Call add_keywords with selected keywords

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_group_id` | `string` | yes | The ad group ID to add keywords to. Use get_campaign_structure first to find ad_group_id. |
| `keywords` | `array` | yes | List of keywords to add. Each keyword: {text, match_type (EXACT/PHRASE/BROAD), cpc_bid_micros (optional)} |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_group_id": "string",
    "keywords": [
      {
        "text": "running shoes",
        "match_type": "PHRASE",
        "cpc_bid_micros": 500000
      },
      {
        "text": "nike air max",
        "match_type": "EXACT"
      }
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_keywords)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_keywords"
}
```

---
## add_negative_keywords

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_negative_keywords/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Add negative keywords to a campaign.

Negative keywords prevent your ads from showing for certain searches.
They help save budget by avoiding irrelevant clicks.

**Parameters:**
- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)
- keywords: List of negative keywords to add
- customer_id: Optional

**Each keyword needs:**
- text: The keyword phrase (e.g., "free", "cheap", "diy")
- match_type: EXACT, PHRASE, or BROAD (default: BROAD)

**Campaign-level vs Ad Group-level:**
This adds negative keywords at CAMPAIGN level, affecting ALL ad groups.

**Common negative keywords:**
- "free", "cheap", "discount" (for premium brands)
- "jobs", "salary", "careers" (for product companies)
- "diy", "how to", "tutorial" (for service providers)

**Execution time:** 2-4 seconds

**Example:**
User: "Block searches containing 'free' and 'cheap'"
Agent: Uses add_negative_keywords with campaign_id and keywords list

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to add negative keywords to. Use list_campaigns first. |
| `keywords` | `array` | yes | List of negative keywords. Each: {text, match_type (EXACT/PHRASE/BROAD)}. These prevent ads from showing for unwanted searches. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "keywords": [
      {
        "text": "free",
        "match_type": "BROAD"
      },
      {
        "text": "cheap",
        "match_type": "PHRASE"
      }
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_negative_keywords)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_negative_keywords"
}
```

---
## add_pmax_audience_signal

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_pmax_audience_signal/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Add an audience signal to an existing Performance Max campaign.

Audience signals hint Google AI about who your ideal customers are.
Uses in-market segments (actively researching), affinity segments (long-term interests),
and custom audiences (remarketing lists, customer match).

Use search_audiences tool first to discover relevant audience segment IDs.

**Parameters:**
- campaign_id: The PMax campaign ID
- audience_config: Dict with segment IDs

**Execution time:** 2-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Google Ads campaign ID (numeric). Example: '21854471508' |
| `audience_config` | `object` | yes | Audience configuration with segment IDs. CRITICAL: Only use IDs returned by search_audiences tool. NEVER fabricate or guess IDs -- wrong IDs target unrelated audiences. Keys: 'in_market_audience_ids' (List[int]), 'affinity_audience_ids' (Li |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "audience_config": {
      "in_market_audience_ids": [
        90013100
      ],
      "affinity_audience_ids": [
        10003100
      ],
      "custom_audience_ids": [
        "customers/1234567890/customAudiences/555"
      ],
      "user_list_ids": [
        "customers/1234567890/userLists/999"
      ]
    },
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_pmax_audience_signal)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_pmax_audience_signal"
}
```

---
## add_pmax_search_themes

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_pmax_search_themes/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Add search themes to an existing Performance Max campaign.

Search themes hint Google AI about what customers search for. Max 50 per asset group.
NOT keywords — no match types, no bids, no quality scores.

**Best practices:**
- Derive from keyword research results, business profile, or user input
- Keep themes specific but not too narrow (2-5 words ideal)
- Avoid generic themes like "buy stuff" or overly specific ones like "buy red nike air max 97 size 12"

**Parameters:**
- campaign_id: The PMax campaign ID
- search_themes: List of theme strings to add

**Execution time:** 2-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Google Ads campaign ID (numeric). Example: '21854471508' |
| `search_themes` | `array` | yes | List of search themes to add. Max 50 total per asset group. NOT keywords — these are hints for Google AI about what customers search. Example: ['AI ad management', 'automate google ads'] |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "search_themes": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_pmax_search_themes)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_pmax_search_themes"
}
```

---
## add_sitelinks

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_sitelinks/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Add sitelink extensions to a campaign.

Sitelinks are clickable links to specific pages on your website.
Recommended 4-6 sitelinks per campaign.

**CONSTRAINTS:**
- link_text max 25 characters
- description1 max 35 characters (optional)
- description2 max 35 characters (required if description1 is set)

**EXAMPLE SITELINKS:**
{
    "link_text": "How It Works",
    "final_url": "https://example.com/how-it-works",
    "description1": "See the platform in action",
    "description2": "Step-by-step walkthrough"
}

{
    "link_text": "Pricing",
    "final_url": "https://example.com/pricing",
    "description1": "Affordable plans for all",
    "description2": "Start free today"
}

**Parameters:**
- campaign_id: The campaign ID (REQUIRED). Get from list_campaigns.
- sitelinks: List of sitelink objects with link_text, final_url, and optional descriptions.
- customer_id: Optional Google Ads customer ID

**Execution time:** 2-5 seconds

**When to use:**
- User wants to add sitelinks to their campaign
- User asks about adding more links to ads
- After creating a campaign, suggest adding sitelinks

**Example:**
User: "Add sitelinks to my campaign"
Agent:
1. Uses list_campaigns to get campaign_id
2. Uses add_sitelinks with relevant page links

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to add sitelinks to. Get from list_campaigns. |
| `sitelinks` | `array` | yes | List of sitelink objects. Each has 'link_text' (max 25 chars), 'final_url', and optional 'description1'/'description2' (max 35 chars each). |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "sitelinks": [
      {
        "link_text": "Shop Running",
        "final_url": "https://example.com/running"
      },
      {
        "link_text": "New Arrivals",
        "final_url": "https://example.com/new"
      }
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_sitelinks)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_sitelinks"
}
```

---
## add_structured_snippets

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_structured_snippets/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Add structured snippet extensions to a campaign.

Structured snippets highlight specific aspects of your products/services.

**VALID HEADERS:**
- AMENITIES, BRANDS, COURSES, DEGREE_PROGRAMS, DESTINATIONS
- FEATURED_HOTELS, INSURANCE_COVERAGE, MODELS, NEIGHBORHOODS
- SERVICE_CATALOG, SHOWS, STYLES, TYPES

**RULES:**
- Header must be from predefined list
- 3-10 values required per snippet
- Each value max 25 characters

**EXAMPLES:**
For SaaS/Platform:
    Header: "Types"
    Values: ["Google Ads", "Meta Ads", "LinkedIn Ads", "TikTok Ads"]

For Services:
    Header: "Services" (maps to SERVICE_CATALOG)
    Values: ["Campaign Launch", "Analytics", "Optimization", "Reporting"]

For Brands:
    Header: "Brands"
    Values: ["Nike", "Adidas", "Puma", "New Balance"]

**Parameters:**
- campaign_id: The campaign ID (REQUIRED). Get from list_campaigns.
- snippets: List of {header, values} objects (REQUIRED).
- customer_id: Optional Google Ads customer ID

**Execution time:** 2-5 seconds

**When to use:**
- User wants to add structured snippets
- User asks to highlight product types, services, or brands
- After creating a campaign, suggest adding snippets

**Example:**
User: "Add structured snippets showing our ad platform types"
Agent:
1. Uses list_campaigns to get campaign_id
2. Uses add_structured_snippets with header "Types" and relevant values

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to add snippets to. Get from list_campaigns. |
| `snippets` | `array` | yes | List of snippet objects. Each has 'header' (from valid list) and 'values' (3-10 strings, each max 25 chars). Example: [{'header': 'Types', 'values': ['Google Ads', 'Meta Ads', 'LinkedIn Ads']}] |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "snippets": [
      {
        "header": "Brands",
        "values": [
          "Nike",
          "Adidas",
          "Puma"
        ]
      }
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_structured_snippets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_structured_snippets"
}
```

---
## analyze_search_terms

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/analyze_search_terms/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Discover keyword opportunities and optimize match types by analyzing actual search terms.

⚠️ IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times. Google Ads only - NOT available for TikTok.

🎯 **What This Tool Does (Performance Agent - Phase 1 Feature 3):**
- Identifies OPPORTUNITY keywords: Converting search terms NOT in your keyword list
- Identifies NEGATIVE keywords: Expensive terms with zero conversions
- Analyzes MATCH TYPE performance: Exact vs Phrase vs Broad efficiency
- Provides specific recommendations with suggested bids and match types
- Ranks opportunities by profit potential (not just ROAS)

**Returns comprehensive keyword intelligence:**
- Top 20 opportunity keywords with suggested bids and match types
- Top 20 negative keyword candidates with wasted cost breakdown
- Match type performance comparison (Exact/Phrase/Broad)
- Broad→Exact conversion recommendations
- Total opportunity value and wasted spend amounts
- Actionable recommendations for immediate implementation

🔍 **What Search Term Mining Reveals:**

**Opportunity Keywords (Money Left on Table):**
- Search terms that ARE converting but you're NOT bidding on them directly
- These are queries triggering your ads via Broad/Phrase match, but should be added as Exact keywords
- Example: User searches "enterprise security software pricing" → converts → but you only have "security software" as keyword
- **Action:** Add high-converting search terms as new keywords with suggested bids

**Negative Keywords (Money Wasted):**
- Search terms costing >$10 with ZERO conversions
- Low-intent queries like "free", "download", "crack", "how to"
- Example: "free security software download" → 120 clicks, $540 spent, 0 conversions
- **Action:** Add as negative keywords to stop wasting budget

**Match Type Optimization:**
- Compares performance of Exact vs Phrase vs Broad match
- Identifies Broad keywords that should be converted to Exact for better control
- Example: Broad "security software" has 45% wasted spend, but top search terms have 20x ROAS
- **Action:** Convert high-performing Broad keywords to Exact match

**Parameters:**
- lookback_days: 7, 30, 60, 90, or 120 days (default: 30)
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- analysis_type: 'opportunities', 'negatives', 'match_types', 'all', or **'raw_report'** (default: 'all')
  - Use **'raw_report'** when the user wants to SEE their actual search terms — the words customers type into Google
- force_refresh: true to trigger immediate API collection (default: false, uses cached data)
- customer_id: Optional (uses connected account if omitted)
- **campaign_id**: Optional — filter search terms to a specific campaign (get from list_campaigns)
- **page**: Page number for raw_report pagination (default: 1)
- **page_size**: Results per page for raw_report (1-100, default: 50)
- **sort_by**: Sort for raw_report: 'cost' (default), 'clicks', 'impressions', 'conversions'
- **min_clicks**: Minimum clicks filter for raw_report (default: 0)

**Execution time:** 1-3 seconds (cached database query)
**Data source:** search_term_daily_metrics table (updated nightly, 120-day retention)

**⚡ RAW REPORT PAGINATION CONTRACT:**
- When using analysis_type='raw_report', results are paginated
- Check `pagination.has_more` — if true, call again with next `page`
- Continue until `has_more` is false, then present consolidated results
- Use `campaign_id` to focus on a specific campaign's search terms

**Use this tool when:**
- User asks "what keywords should I add?"
- User wants to find wasted spend at keyword level
- User asks "what should I add as negative keywords?"
- User wants to improve match type efficiency
- User asks "which Broad keywords should be Exact?"
- User wants to discover hidden keyword opportunities
- **User asks "show me my search terms" or "what are people searching for?"** → use analysis_type='raw_report'
- **User asks "show me search terms for campaign X"** → use analysis_type='raw_report' with campaign_id

⚠️ **Platform Limitation:**
This tool ONLY works for Google Ads. TikTok Ads does not provide search term data due to privacy restrictions.

📊 **AFTER calling this tool, help the user understand:**

**Opportunity Keywords:**
- **What it means:** These are winning search terms you're missing
- **Profit potential:** Shows expected additional profit if added
- **Suggested action:** Add as new keywords with recommended bid and match type
- **Priority:** Start with highest opportunity_value (profit potential)

**Example:**
Search term: "enterprise security software pricing"
- 8 conversions, $240 cost, $4,800 value
- Opportunity Value: $4,560 profit potential
- Suggested Bid: $6.00 (based on current CPC and ROAS)
- Suggested Match Type: EXACT (high intent, proven converter)
- **Action:** Add this as an Exact match keyword with $6 bid

**Negative Keywords:**
- **What it means:** These queries waste money and never convert
- **Cost:** Total wasted spend on non-converting terms
- **Common patterns:** "free", "download", "crack", "review", "how to"
- **Action:** Add as negative keywords (campaign or account level)

**Example:**
Search term: "free security software"
- 120 clicks, $540 wasted, 0 conversions
- Reason: Low purchase intent (free)
- **Action:** Add "free" as negative keyword at campaign level

**Match Type Performance:**
- **Exact:** Tightest control, lowest waste, highest ROAS (recommended)
- **Phrase:** Moderate flexibility, some waste, decent ROAS
- **Broad:** Most volume, highest waste, lowest ROAS (use sparingly)

**Example Analysis:**
- Exact: 450 keywords, ROAS 4.8x, 2% wasted spend ✅
- Phrase: 280 keywords, ROAS 3.2x, 8% wasted spend ⚠️
- Broad: 120 keywords, ROAS 1.8x, 45% wasted spend 🔴

**Quick Actions:**
1. Add top 5 opportunity keywords as Exact match
2. Block top 10 negative keywords at campaign level
3. Convert high-performing Broad keywords to Exact
4. Review Phrase keywords with >10% wasted spend

**Visualization Tip:**
For opportunity keywords, suggest creating a scatter plot (x=ROAS, y=Spend, size=conversions) to visualize profit potential.

**Implementation Steps:**
1. Review top opportunities and negatives
2. Start with 5-10 new keywords (don't overwhelm account)
3. Add negatives in batches (test impact over 7 days)
4. Convert Broad→Exact gradually (monitor volume drop)
5. Re-run analysis monthly to discover new opportunities

**Best Practices:**
- Focus on opportunity_value (profit) not just ROAS
- Start with Exact match for all new keywords (tightest control)
- Add negatives at campaign level first (easier to undo than account level)
- Monitor match type distribution: Aim for 60%+ Exact, 30% Phrase, 10% Broad
- Review search terms every 2 weeks during active optimization

💬 **Community**: For keyword optimization discussions, visit our Discord: https://discord.gg/dH3Qt4YS

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze search term data (7, 30, 60, 90, or 120 days). Default is 30 days. |
| `analysis_type` | `string` | no | Type of analysis: 'opportunities', 'negatives', 'match_types', 'all', or 'raw_report'. Use 'raw_report' to see all actual search terms with metrics (what Vijay wants). Default is 'all'. |
| `force_refresh` | `boolean` | no | If true, triggers immediate API collection from Google Ads (not yet implemented - currently uses cached data). Default is false. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |
| `campaign_id` | `object` | no | Optional campaign ID to filter search terms to a specific campaign. Get from list_campaigns. |
| `page` | `object` | no | Page number for raw_report (1-based). Default: 1 |
| `page_size` | `object` | no | Results per page for raw_report (1-100). Default: 50 |
| `sort_by` | `object` | no | Sort field for raw_report: 'cost' (default), 'clicks', 'impressions', 'conversions' |
| `min_clicks` | `object` | no | Minimum clicks filter for raw_report. Default: 0 (show all) |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "analysis_type": "all"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for analyze_search_terms)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "analyze_search_terms"
}
```

---
## analyze_wasted_spend

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/analyze_wasted_spend/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Analyze wasted ad spend and identify campaigns losing money or underperforming.

⚠️ IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.

🎯 **What This Tool Does (Performance Agent - Phase 1):**
- Identifies campaigns LOSING money (ROAS < 1.0x)
- Identifies campaigns UNDERPERFORMING (1.0x ≤ ROAS < target)
- Separates true waste from opportunity cost
- Provides severity-based classification (CRITICAL/HIGH/MEDIUM)
- Generates actionable recommendations with expected impact
- Shows top performing campaigns for budget reallocation

**Returns detailed analysis:**
- Total wasted spend (campaigns with ROAS < 1.0)
- Total underperforming spend (profitable but below target)
- Campaign-by-campaign breakdown with severity levels
- Specific recommendations (PAUSE/REDUCE/OPTIMIZE)
- Quick actions for immediate implementation
- Budget reallocation suggestions

**Target ROAS Resolution (3-tier priority):**
1. Account goals table (user-set or API-pulled)
2. 90-day historical average ROAS
3. Default to 2.0x if no data available

**Severity Levels:**
- 🚨 CRITICAL: ROAS < 0.5x (losing money severely)
- 🔴 HIGH: ROAS < 1.0x (unprofitable/breakeven)
- 🟡 MEDIUM: 1.0x ≤ ROAS < target (profitable but underperforming)

**Parameters:**
- lookback_days: 7, 30, 60, 90, or 120 days (default: 30)
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- target_roas: Optional override (e.g., 3.0 for 3.0x ROAS)
- customer_id: Optional (uses connected account if omitted)

**Execution time:** 1-3 seconds (cached database query)
**Data source:** campaign_daily_metrics table (updated nightly)

**Use this tool when:**
- User asks "where is my money going?"
- User wants to identify wasted spend
- User wants to optimize campaign budgets
- User asks which campaigns to pause/reduce
- User wants to know which campaigns are losing money

📊 **AFTER calling this tool, help the user understand:**

**Wasted vs. Underperforming:**
- **Wasted Spend** = Actual losses (ROAS < 1.0, you're losing money)
- **Underperforming Spend** = Opportunity cost (profitable but below target)

**Example:**
- Campaign A: Spent $1000, got $600 back (ROAS 0.6x) → $400 WASTED
- Campaign B: Spent $1000, got $1500 back (ROAS 1.5x, target 3.0x) → $0 wasted, but $500 opportunity cost

**Quick Actions (IMPORTANT — read severity context first):**
- ⏳ LEARNING campaigns → Do NOT pause. Monitor for 14+ days before judging.
- ❓ INSUFFICIENT_DATA campaigns → Need more spend before analysis is meaningful.
- 🚨 CRITICAL campaigns (established, 14+ days, ZERO conversions) → Consider pausing
- 🚨 CRITICAL campaigns (established, 14+ days, HAS conversions) → Review performance, verify revenue in ad platform before reducing budget
- 🔴 HIGH severity (established, 14+ days) → Consider reducing budget by 50-70%
- 🟡 MEDIUM → Optimize targeting, ad copy, landing pages
- Top performers → Consider increasing budget

⚠️ **NEVER say "pause" for a campaign that has conversions.** Say "review" or "reduce budget" instead.
⚠️ **NEVER recommend pausing a campaign in LEARNING phase.**
⚠️ **If ALL campaigns are LEARNING or INSUFFICIENT_DATA, tell the user their account is too new for waste analysis and recommend checking back in 2 weeks.**
⚠️ **Consider campaign objective: brand awareness campaigns will not have ROAS data. This is normal.**
⚠️ **When data confidence is MEDIUM or LOW, soften all recommendations and add verification prompts.**

**Visualization Tip:**
For 10+ campaigns, suggest creating a treemap visualization to show wasted spend by campaign size.

💬 **Community**: For optimization discussions, visit our Discord: https://discord.gg/dH3Qt4YS

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 30, 60, 90, or 120 days). Default is 30 days. |
| `target_roas` | `object` | no | Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "target_roas": 1.0
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for analyze_wasted_spend)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "analyze_wasted_spend"
}
```

---
## create_ad

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_ad/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Create a new Responsive Search Ad (RSA) in an existing ad group.

Useful for A/B testing different ad copy or adding variation to an ad group.

**Parameters:**
- ad_group_id: The ad group to add the ad to (REQUIRED)
- headlines: 3-15 headlines (REQUIRED, max 30 chars each)
- descriptions: 2-4 descriptions (REQUIRED, max 90 chars each)
- final_urls: Landing page URLs (REQUIRED, at least one)
- path1: Optional display path 1 (max 15 chars)
- path2: Optional display path 2 (max 15 chars)
- customer_id: Optional

**Execution time:** 3-5 seconds

**New ad goes through Google's review process.**

**Example:**
User: "Create a new ad to test different messaging"
Agent:
1. Gets ad_group_id from get_campaign_structure
2. Prepares headlines and descriptions
3. Creates new ad with create_ad

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_group_id` | `string` | yes | The ad group ID to add the ad to. Get from get_campaign_structure. |
| `headlines` | `array` | yes | List of 3-15 headlines. Each max 30 characters. Include mix of: product identity, benefits, and CTAs. |
| `descriptions` | `array` | yes | List of 2-4 descriptions. Each max 90 characters. |
| `final_urls` | `array` | yes | Landing page URLs. At least one required. |
| `path1` | `object` | no | Optional display path 1 (max 15 chars). Shows in ad URL. |
| `path2` | `object` | no | Optional display path 2 (max 15 chars). Shows after path1. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_group_id": "string",
    "headlines": [
      "string"
    ],
    "descriptions": [
      "string"
    ],
    "final_urls": [
      "string"
    ],
    "path1": "string",
    "path2": "string",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_ad)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_ad"
}
```

---
## create_demandgen_campaign

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_demandgen_campaign/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

🔄 LONG-RUNNING TOOL: Creates a Demand Gen campaign across ALL Google channels — YouTube, Discover, Gmail, Display, and Maps. Emits MCP progress updates during creation (typically 10-20 seconds).

⚠️ CRITICAL WARNING ⚠️
- Call this tool ONLY ONCE per campaign
- Creates REAL campaigns that cost REAL money
- Do NOT retry automatically if errors occur
- Report errors to user instead of retrying

🌐 **Demand Gen Campaign Channels (ALL enabled by default):**
✅ YouTube In-Feed (search results & related videos)
✅ YouTube In-Stream (before/during/after videos)
✅ YouTube Shorts (Shorts feed)
✅ Gmail (Promotions & Social tabs)
✅ Discover (Google Discover feed)
✅ Display (Google Display Network)
❌ Maps (opt-in, disabled by default)

📋 **YOUR CRITICAL ROLE: Campaign Strategist & Text Creator**

**STEP 1: Collect Campaign Details from User**

YOU MUST collect these from the user:
1. **Campaign Name** - Descriptive and unique
2. **Daily Budget** - In account's native currency (recommended $20+/day)
3. **Ad Format** - multi_asset (images) or video_responsive (videos)
4. **Final URL** - Landing page
5. **Business Name** - Max 25 characters

**STEP 2: Discover Existing Assets**
🔴 MANDATORY: Call discover_existing_assets to find existing logos/images.
- If logos found → use logo_asset_id or existing_images.logos_square
- If NO logos → ask user for logo URL, run validate_and_prepare_assets

**STEP 3: Generate Ad Copy**
- Headlines (1-5): STRICT 40 character max each
- Descriptions (1-5): STRICT 90 character max each
- Long Headlines (optional, for video_responsive): 90 char max each

**STEP 4: Prepare Images/Videos**
For multi_asset: Need landscape (1.91:1) OR square (1:1) marketing images + logo
For video_responsive: Need YouTube video IDs (validate first) + logo

**STEP 5: Call create_demandgen_campaign with complete payload**

**Bidding Options (your choice is honored — engine is advisory only):**
- MAXIMIZE_CLICKS: Default, works without conversion tracking
- MAXIMIZE_CONVERSIONS: Requires conversion tracking
- TARGET_CPA: Requires target_cpa value
- MAXIMIZE_CONVERSION_VALUE: Requires conversion tracking
- TARGET_ROAS: Requires target_roas value

**Channel Controls (optional):**
Pass channels dict to disable specific channels:
{"gmail": false, "display": false} → YouTube + Discover only

**STEP 6: MANDATORY — Add Extensions After Creation**
After success, add sitelinks, callouts, and structured snippets.

**Execution Time:** 10-20 seconds

**CRITICAL REMINDERS:**
- Campaign starts PAUSED for user safety
- This costs real money — be transparent
- Never retry on failure — report error to user

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (e.g., 'DemandGen Summer Promo 2026') |
| `budget_daily` | `number` | yes | Daily budget in the account's native currency. IMPORTANT: Do NOT convert currencies — pass the user's amount as-is. Example: if user says '₹1000/day', pass 1000 (not a USD conversion). Google recommends minimum ~$20/day USD equivalent for D |
| `ad_format` | `object` | no | Ad format: 'multi_asset' (image ads, default), 'video_responsive' (video ads). multi_asset requires marketing images. video_responsive requires YouTube video IDs. |
| `final_url` | `string` | yes | Landing page URL (must match verified domain). Example: 'https://example.com/product' |
| `business_name` | `string` | yes | Business name, max 25 characters. Example: 'Adspirer' |
| `headlines` | `array` | yes | MUST BE JSON ARRAY: 1-5 headlines, each EXACTLY 40 characters maximum. Example: ["AI Ad Management", "Skip the Dashboard"]. COUNT CHARACTERS! ANY over 40 will be REJECTED! |
| `descriptions` | `array` | yes | MUST BE JSON ARRAY: 1-5 descriptions, each EXACTLY 90 characters maximum. Example: ["Manage ads with AI - no dashboard needed."]. COUNT CHARACTERS BEFORE CALLING! |
| `long_headlines` | `object` | no | MUST BE JSON ARRAY: Optional 1-5 long headlines, max 90 characters each. Required for video_responsive format. Falls back to headlines if not provided. |
| `call_to_action` | `object` | no | Call-to-action label. Options: LEARN_MORE, SHOP_NOW, SIGN_UP, SUBSCRIBE, DOWNLOAD, BOOK_NOW, CONTACT_US, GET_QUOTE, APPLY_NOW |
| `target_locations` | `object` | no | Geographic targets globally (e.g., ['India', 'United States', 'London']). Supports countries, states, cities worldwide. Defaults to United States. |
| `target_languages` | `object` | no | Language targets as ISO codes (e.g., ['en', 'es']). Defaults to English. |
| `bidding_strategy` | `object` | no | Bidding strategy: MAXIMIZE_CLICKS (default), MAXIMIZE_CONVERSIONS, TARGET_CPA, MAXIMIZE_CONVERSION_VALUE, TARGET_ROAS. Your explicit choice is honored — the engine is advisory only. |
| `target_cpa` | `object` | no | Target CPA in account's native currency (only for TARGET_CPA bidding). Example: 25.0 |
| `target_roas` | `object` | no | Target ROAS multiplier (only for TARGET_ROAS bidding). Example: 3.5 means 350% return on ad spend. |
| `channels` | `object` | no | Optional channel controls. Keys: youtube_in_feed, youtube_in_stream, youtube_shorts, gmail, discover, display. All default to true (full reach). Example: {"gmail": false, "display": false} to exclude those channels. |
| `marketing_images` | `object` | no | Landscape images (1.91:1) as [{"url": "https://...", "name": "img1"}]. At least landscape OR square images required for multi_asset. |
| `square_marketing_images` | `object` | no | Square images (1:1) as [{"url": "https://...", "name": "img1"}]. |
| `portrait_marketing_images` | `object` | no | Portrait images (4:5) as [{"url": "https://...", "name": "img1"}]. Optional. |
| `existing_images` | `object` | no | Existing image asset resource names to reuse (from discover_existing_assets). Keys: marketing_images_landscape, marketing_images_square, marketing_images_portrait, logos_square. Example: {"marketing_images_landscape": ["customers/123/assets |
| `youtube_video_ids` | `object` | no | YouTube video IDs (each 11 chars) for video_responsive format. 1-5 videos. Must be validated first using validate_video tool. |
| `logo_asset_id` | `object` | no | Logo image asset ID from discover_existing_assets. |
| `logo_images` | `object` | no | New logo images as [{"url": "https://...", "name": "logo1"}]. |
| `asset_bundle_id` | `object` | no | Asset bundle ID (UUID) from validate_and_prepare_assets. |
| `audience_segments` | `object` | no | Optional: Audience targeting for the campaign. CRITICAL: Only use segment IDs returned by search_audiences tool. NEVER fabricate or guess IDs -- wrong IDs target unrelated audiences and waste budget. If search_audiences returns no results,  |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "budget_daily": 1.0,
    "final_url": "https://example.com",
    "business_name": "string",
    "headlines": [
      "string"
    ],
    "descriptions": [
      "string"
    ],
    "ad_format": "multi_asset",
    "long_headlines": [
      "string"
    ],
    "call_to_action": "string",
    "target_locations": [
      "string"
    ],
    "target_languages": [
      "string"
    ],
    "bidding_strategy": "MAXIMIZE_CLICKS"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_demandgen_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_demandgen_campaign"
}
```

---
## create_pmax_campaign

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_pmax_campaign/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

🔄 LONG-RUNNING TOOL: Creates a Google Performance Max campaign with validated images. Emits MCP progress updates during authentication, asset upload, and campaign creation (typically 15-30 seconds). Progress stages: validate → commit.

⚠️ CRITICAL PREREQUISITES:
1. MUST have valid asset_bundle_id from validate_and_prepare_assets tool OR existing_image_ids from discover_existing_assets
2. MUST have all campaign text details from user
3. Creates REAL campaigns that cost REAL money
4. Call ONLY ONCE per campaign - do NOT retry automatically

🎥 **VIDEOS ARE OPTIONAL BUT RECOMMENDED:**
- Videos significantly improve PMAX performance (higher CTR, better engagement)
- **Always ask the user**: "Would you like to add YouTube videos to this campaign?"
- If yes: validate with `validate_video` tool, then include `youtube_video_ids` parameter
- If no: proceed without videos (campaigns work fine either way)
- See STEP 3.5 below for complete video guidance

⚠️ GOOGLE ADS POLICY NOTE:
Avoid keywords related to health conditions, medical treatments, financial hardship, or political topics. These may trigger policy violations. Use general service terms instead.
Example: Use "senior care services" not "nursing care", "home services" not "medical services"

📋 **YOUR CRITICAL ROLE: Campaign Strategist & Text Creator**

**STEP 1: Verify Prerequisites**

Before calling this tool, ensure you have:
- ✅ Valid asset_bundle_id from validate_and_prepare_assets (max 1 hour old)
- ✅ All required campaign details from user (see below)

If asset_bundle_id is missing or expired:
- Guide user to re-upload images
- Call validate_and_prepare_assets again
- Get new asset_bundle_id

**STEP 2: Collect Campaign Details from User**

YOU MUST collect these from the user (ask if not provided):

**REQUIRED Fields:**
1. **Campaign Name**
   - Example: "Premium Watches Summer 2025"
   - Make it descriptive and unique

2. **Daily Budget**
   - Google recommends ~$50/day USD equivalent for Performance Max
   - IMPORTANT: Accept the user's budget in their stated currency — do NOT convert to USD
   - Ask: "What's your daily advertising budget?"

3. **Final URL** (Landing Page)
   - Must match verified domain in Google Ads
   - Example: "https://example.com/watches"
   - Ask: "What's the landing page URL for this campaign?"

4. **Business Name**
   - Maximum 25 characters
   - Example: "Luxury Watch Co"
   - Will appear in ads

**STEP 3: Generate High-Quality Ad Copy**

As an expert copywriter, you MUST create compelling ad text:

**🚨 BEFORE GENERATING TEXT - READ CHARACTER LIMITS 🚨**

**Headlines (3-15 required):**
- **STRICT LIMIT: 30 characters maximum per headline**
- Count characters BEFORE calling this tool!
- Examples:
  - "Premium Luxury Watches" (22 chars) ✅
  - "Free Shipping" (13 chars) ✅
  - "Certified Authentic" (19 chars) ✅

**Descriptions (2-4 required):**
- **STRICT LIMIT: 80 characters maximum per description**
- Count characters BEFORE calling this tool!
- Examples:
  - "Shop authentic luxury timepieces with free worldwide shipping." (63 chars) ✅
  - "Expert-curated Swiss watches. Certified authentic. Shop now." (61 chars) ✅

**Long Headlines (1-5 REQUIRED):**
- **STRICT LIMIT: 90 characters maximum per long headline**
- At least 1 is REQUIRED for Performance Max
- Count characters BEFORE calling this tool!
- Example: "Premium Swiss Watches - Certified Authentic, Free Worldwide Shipping" (70 chars) ✅

**⚠️ WILL BE REJECTED IF YOU EXCEED LIMITS - NO RETRIES!**
**⚠️ COUNT CHARACTERS CAREFULLY - VALIDATION IS STRICT!**

**How to Count Characters:**
- Use extended thinking to count each text element
- Double-check your character counts before calling
- If close to limit, shorten it to be safe
- Do NOT assume backend will fix it - it won't!
- Use clear, concise language
- Focus on user benefits

**OPTIONAL Fields (use defaults if not provided):**
- target_locations: Defaults to ["United States"]
- target_languages: Defaults to ["English"]

**🎥 STEP 3.5: YouTube Videos (OPTIONAL but Highly Recommended)**

**⚠️ IMPORTANT: Always ask the user about videos!**

Videos significantly improve Performance Max campaign performance:
- **Higher CTR**: Video ads stand out more than static images
- **Better engagement**: Users watch videos longer
- **Wider reach**: Serves on YouTube, Display, Discover, Gmail, Search
- **Lower CPA**: Video ads often convert better

**When to Ask:**
- AFTER discovering existing assets
- AFTER user uploads images
- BEFORE calling create_pmax_campaign

**What to Say:**
"I've prepared your images. Would you like to add YouTube videos to this campaign? Videos can significantly improve performance (higher CTR and engagement). They're optional but recommended.

If yes:
- You'll need videos uploaded to YouTube (public or unlisted)
- Minimum 10 seconds duration
- I can validate them for you

If no:
- We can proceed with images only
- You can add videos later if needed"

**How to Include Videos:**

1. **User provides YouTube video URL or ID**
   - Example: "https://youtu.be/eIZtladpm6c"
   - Example: "eIZtladpm6c"

2. **You validate it**
   ```
   Call: validate_video
   Arguments: {
     "video_url_or_id": "eIZtladpm6c",
     "platform": "pmax"
   }
   ```

3. **Include in campaign**
   - Add `youtube_video_ids` parameter: `["eIZtladpm6c"]`
   - Can include up to 5 videos

**Video Specifications:**
- **Aspect Ratios**: Landscape (16:9), Square (1:1), Vertical (9:16)
- **Duration**: Minimum 10 seconds (no maximum)
- **Maximum**: 5 videos per campaign
- **Source**: Must be on YouTube (public or unlisted, NOT private)
- **Validation**: Happens via Google Ads API during campaign creation

**If user doesn't have videos:**
- Proceed without them (campaigns work fine without videos)
- Mention they can add videos later
- Note: Google may auto-generate videos from images if none provided

**STEP 4: Call create_pmax_campaign**

After you have ALL details:
- Validate character counts yourself BEFORE calling
- Call create_pmax_campaign with complete payload
- Wait for response (may take 15-30 seconds)

**STEP 5: Handle Response**

**If SUCCESS:**
- Celebrate with user! 🎉
- Show campaign ID, name, budget, status
- Explain campaign starts PAUSED for safety
- 🔴 **IMMEDIATELY proceed to STEP 6 (Extensions)** — do NOT stop here!

**If FAILURE:**

1. **Asset Bundle Expired:**
   - Error: "Asset bundle not found or expired"
   - Action: Guide user to re-upload images
   - Call validate_and_prepare_assets again

2. **Character Limit Errors:**
   - Error: "Headline exceeds 30 characters"
   - Action: Shorten the text and try again
   - Show user which field caused the error

3. **Google Ads API Errors:**
   - Domain not verified
   - Budget too low
   - Account suspended
   - Action: Explain error clearly, provide troubleshooting steps

4. **Authentication Errors:**
   - Error: "No Google Ads account connected"
   - Action: Guide user to connect their Google Ads account

🔴 **STEP 6: MANDATORY — Add Extensions After Creation**
After the campaign is successfully created, you MUST:
1. Crawl the user's website to gather relevant page links, features, and service categories
2. Add sitelinks: at least 4 links to key pages (add_sitelinks tool)
3. Add callout extensions: 4-6 business highlights (add_callout_extensions tool)
4. Add structured snippets: categorized features (add_structured_snippets tool)
5. Verify with list_campaign_extensions to confirm all extensions are attached
Extensions are FREE and increase ad visibility by 15-25%. NEVER skip this step.

**DO NOT:**
- Retry automatically on failure
- Call multiple times for same campaign
- Skip collecting user's campaign details
- Proceed without valid asset_bundle_id
- Skip adding extensions after campaign creation

**Campaign Creation Best Practices:**

🔴 **PRE-STEP: ALWAYS Discover Assets First**
   - Before ANY PMAX campaign, call discover_existing_assets
   - Show user what assets were found (images, logos with dimensions)
   - Ask: "Would you like to reuse these existing assets, upload new ones, or mix both?"
   - Based on answer:
     - Reuse → Use existing_image_ids in create_pmax_campaign
     - Upload new → Use validate_and_prepare_assets → asset_bundle_id
     - Mix both → Use both parameters (hybrid mode)
   - 🚫 NEVER skip this step

1. **Research First:**
   - Understand the business, products, audience
   - Review website messaging
   - Identify key selling points

2. **Strategic Ad Copy:**
   - Use action verbs
   - Include numbers/stats if available
   - Highlight benefits over features
   - Create urgency when appropriate
   - Match brand voice

3. **Quality Over Quantity:**
   - Better to have 5 great headlines than 15 mediocre ones
   - Each headline should be unique and valuable
   - Avoid repetition

4. **User Safety:**
   - Confirm budget with user before creating
   - Explain campaigns start PAUSED
   - Remind them this costs real money

**Example Complete Flow:**

User: "Create a PMax campaign for my luxury watch store"

You: "I'll help you create a PMax campaign! First, let me check if you have any existing assets we can reuse."

[Call discover_existing_assets(target_domain="luxurywatches.com")]

[Returns: 8 images found]

You: "Great news! I found 8 validated images from your previous campaigns:
- 4 landscape images (1200x628)
- 2 square images (1200x1200)
- 2 logo images (1200x1200)

Would you like to:
A) Reuse these existing images (saves time, consistent branding)
B) Upload new images
C) Mix both (reuse some + add new ones)"

[User chooses A - Reuse]

You: "Perfect! Now I need a few details:
1. What daily budget would you like? (minimum $10, recommended $50+)
2. What's your landing page URL?
3. What locations should we target? (e.g., 'Bay Area', 'San Francisco', 'United States')

I'll create compelling headlines and descriptions based on your luxury watch business."

[User provides budget: $75, URL: https://luxurywatches.com, Location: Bay Area]

You: "Great! Here's what I'll create:

**Campaign Name:** Premium Watches Campaign 2025
**Budget:** $75/day
**Landing Page:** https://luxurywatches.com
**Locations:** Bay Area (targeted precisely)

**Headlines I've generated:**
- Premium Luxury Watches
- Certified Authentic
- Free Worldwide Shipping
[...12 more]

**Descriptions:**
- Shop authentic Swiss timepieces with lifetime warranty and expert curation.
- Discover premium watches from top brands. Free shipping. 30-day returns.
[...2 more]

**Using existing images:** 8 validated images (reuse mode)

Ready to create this campaign? (It will start PAUSED so you can review first)"

[User confirms]

[Call create_pmax_campaign with existing_image_ids from discovery]

**Returns:**
- Campaign ID, name, status
- Asset group summary
- Next steps for activation

**Execution Time:** 15-30 seconds (direct backend API call)

**Authentication:** Required (MCP OAuth 2.1)

**CRITICAL REMINDERS:**
- Asset bundle expires after 1 hour
- Campaign starts PAUSED for user safety
- This costs real money - be transparent
- Never retry on failure - report error to user
- Provide excellent user experience throughout

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `asset_bundle_id` | `object` | no | Asset bundle ID from validate_and_prepare_assets tool (UUID format). Optional if existing_image_ids provided. |
| `existing_image_ids` | `object` | no | Resource names of existing images to reuse (from discover_existing_assets). Optional if asset_bundle_id provided. ⚠️ MUST include 'logos_square' — Google Ads REQUIRES a logo for every PMax asset group. If discover_existing_assets found no l |
| `campaign_name` | `string` | yes | Campaign name (e.g., 'Premium Watches Summer 2025') |
| `budget_daily` | `number` | yes | Daily budget in the account's native currency. IMPORTANT: Do NOT convert currencies — pass the user's amount as-is. Example: if user says '₹5000/day', pass 5000 (not a USD conversion). The Google Ads API interprets this in the account's cur |
| `headlines` | `object` | no | MUST BE JSON ARRAY: 3-15 headlines, each EXACTLY 30 characters maximum. REQUIRED for single-group mode. Omit if using asset_groups array. Commas ARE allowed within headlines. Example: ["Luxury Watches", "Swiss Made"]. COUNT CHARACTERS! ANY  |
| `descriptions` | `object` | no | MUST BE JSON ARRAY: 2-4 descriptions, each EXACTLY 80 characters maximum (STRICT!). REQUIRED for single-group mode. Omit if using asset_groups array. Commas ARE allowed within descriptions. Example: ["Shop authentic luxury timepieces with e |
| `business_name` | `object` | no | Business name, max 25 characters. REQUIRED for single-group mode. Omit if using asset_groups array. Commas ARE allowed. |
| `final_url` | `object` | no | Landing page URL. REQUIRED for single-group mode. Omit if using asset_groups array. |
| `long_headlines` | `object` | no | MUST BE JSON ARRAY: REQUIRED 1-5 long headlines, each EXACTLY 90 characters maximum. REQUIRED for single-group mode. Omit if using asset_groups array. Google Ads REQUIRES at least 1 long headline for Performance Max campaigns. Commas ARE al |
| `target_locations` | `object` | no | Optional: Geographic targets -- supports countries, states, cities, and regions globally. Examples: ['India'], ['Bangalore, India'], ['Karnataka'], ['New York, NY'], ['United States', 'Canada']. Defaults to United States. |
| `target_languages` | `object` | no | Optional: Language targets (e.g., ['English', 'Spanish']). Defaults to English. |
| `youtube_video_ids` | `object` | no | Optional: List of YouTube video IDs (11 characters each). Videos must be validated first using validate_video tool. Maximum 5 videos per campaign. Example: ['dQw4w9WgXcQ', 'jNQXAC9IVRw']. Videos are OPTIONAL for PMAX - campaigns can be crea |
| `search_themes` | `object` | no | Optional: Search themes hint Google AI about what your customers search for. Max 50 per asset group. NOT keywords — no match types, no bids. Derive from keyword research, business profile, or user input. Example: ['AI ad management', 'autom |
| `audience_signals` | `object` | no | Optional: Audience signals tell Google AI who your ideal customers are. CRITICAL: Only use segment IDs returned by the search_audiences tool. NEVER fabricate, guess, or invent audience IDs. Wrong IDs will target completely unrelated audienc |
| `bidding_strategy` | `object` | no | Bidding strategy for the campaign. Options: 'MAXIMIZE_CONVERSIONS' - Get the most conversions within budget. 'MAXIMIZE_CONVERSION_VALUE' - Optimize for highest value conversions. If not specified, the system auto-selects based on account da |
| `target_roas` | `object` | no | Target ROAS (Return on Ad Spend) for MAXIMIZE_CONVERSION_VALUE bidding. Example: 3.0 means you want $3 revenue for every $1 spent. Only used when bidding_strategy='MAXIMIZE_CONVERSION_VALUE'. |
| `asset_groups` | `object` | no | OPTIONAL: Multiple asset groups for a single PMax campaign. When provided, this OVERRIDES the flat headline/description/business_name/final_url fields. Each group is a dict with keys: group_name (required, unique), headlines (3-15), descrip |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "budget_daily": 1.0,
    "asset_bundle_id": "string",
    "existing_image_ids": [
      "customers/1234567890/assets/12345678901",
      "customers/1234567890/assets/12345678902"
    ],
    "headlines": [
      "string"
    ],
    "descriptions": [
      "string"
    ],
    "business_name": "string",
    "final_url": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_pmax_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_pmax_campaign"
}
```

---
## create_search_campaign

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_search_campaign/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

🔄 LONG-RUNNING TOOL: Creates a Google Ads Search campaign with full structure.

Emits MCP progress updates during authentication and campaign creation (typically 5-10 seconds).
Progress stages: validate → commit.

⚠️ CRITICAL WARNING ⚠️
- Call this tool ONLY ONCE per campaign
- Creates REAL campaigns that cost REAL money
- Do NOT retry automatically if errors occur
- Report errors to user instead of retrying

⚠️ GOOGLE ADS POLICY NOTE:
Avoid keywords related to health conditions, medical treatments, financial hardship, or political topics. These may trigger policy violations. Use general service terms instead.
Example: Use "senior care services" not "nursing care", "home services" not "medical services"

YOUR ROLE: Expert Google Ads Campaign Strategist

BEFORE calling this tool, YOU MUST:
1. **Research the business thoroughly:**
   - Understand products/services, target audience, value propositions
   - Analyze competitive landscape and market positioning
   - Review website messaging and offers
   - Consider seasonal factors and current trends

2. **🔍 KEYWORD RESEARCH (CRITICAL - Call research_keywords tool FIRST):**
   Do NOT use generic SEO keywords. Get HIGH-INTENT keywords with real CPC data!

   **YOU MUST call the research_keywords tool BEFORE this tool:**

   Steps:
   a) Call the research_keywords tool:
      ```
      Tool: research_keywords
      Arguments: {
        "business_description": "Emergency plumbing services for homeowners",
        "website_url": "https://example.com",
        "target_location": "New York, NY"
      }
      ```

   b) The tool will return:
      - Keyword table with dynamic CPC thresholds (adapts to industry)
      - HIGH/MEDIUM/LOW intent keywords
      - Budget recommendations (Conservative/Moderate/Aggressive)
      - Top 15-20 recommended keywords

   c) Show the keyword table to the user

   d) Ask user if they want to modify keyword selection:
      - Can add/remove specific keywords
      - Can use only HIGH intent keywords
      - Default: use recommended keywords

   e) Extract keyword texts from the research_keywords result
      - Use the recommended keywords for this tool's 'keywords' parameter
      - Max 15-20 keywords per ad group
      - Will use BROAD match automatically (Google's 2025 recommendation)

3. **Analyze Target Demographics:**
   For each ad group, consider who the ideal customer is:
   - **Age**: What age groups are most likely to buy? (e.g., luxury products → 35-54, gaming → 18-34)
   - **Gender**: Is the product/service gender-specific? (e.g., women's fashion → FEMALE)
   - **Income**: What income level can afford this? (e.g., luxury → TOP_10/80_90, budget → 0_50/50_60)

   **Examples:**
   - Luxury watches: Males 35-54, income top 20%
   - Budget fashion: Females 18-34, all income levels
   - Senior services: Age 65+, income 50%+
   - Gaming products: 18-34, all genders, middle income

   If unclear, leave demographics empty for broad reach.

3. **Generate complete campaign structure with ALL details:**

**Required Fields (all mandatory):**
1. **campaign_name** - Unique descriptive name
2. **business_description** - What the business sells
3. **website_url** - Landing page URL
4. **budget_daily** - Daily budget in the account's native currency (do NOT convert — pass user's amount as-is)
5. **target_locations** - Geographic targets (cities/states/countries)
6. **ad_groups** - Array of 2-4 ad groups, each containing:
   - name: Ad group theme (e.g., "Premium Care Services")
   - keywords: 15-20 keywords from Keyword Planner research (will use BROAD match - Google's 2025 recommendation)
   - headlines: Exactly 15 headlines (MAXIMUM 30 chars each - NO EXCEPTIONS)
   - descriptions: Exactly 4 descriptions (MAXIMUM 80 chars each - NO EXCEPTIONS)
   - final_url: Landing page for this ad group
   - sitelinks (optional): 2-4 additional links below ad
   - audiences (optional): Demographics and audience targeting
     - demographics: {age_ranges: [...], genders: [...], income_ranges: [...]}
     - custom_audiences: [...], in_market_audiences: [...], affinity_audiences: [...]
7. **negative_keywords** (optional) - Terms to exclude

**🚨 CRITICAL CHARACTER LIMITS - COUNT BEFORE CALLING 🚨**
- **Headlines:** MAXIMUM 30 characters (NO EXCEPTIONS - will be rejected!)
- **Descriptions:** MAXIMUM 80 characters (NO EXCEPTIONS - will be rejected!)
- **Long Headlines (PMAX):** MAXIMUM 90 characters (NO EXCEPTIONS - will be rejected!)
- Generate ALL content BEFORE calling this tool
- COUNT the characters in each headline and description
- If ANY exceed limits, you MUST shorten them BEFORE calling
- Do NOT rely on retry - fix it the first time!
- ✅ Commas ARE ALLOWED and encouraged for natural language
- ❌ Do NOT use pipes (|) as separators - they will appear literally in ads
- ❌ Do NOT send comma-delimited strings - use proper JSON arrays
- Example: ["Expert Care, 24/7 Support", "Premium Service Available"]
- Do NOT call this tool multiple times - it creates REAL campaigns
- If errors occur, show them to the user - do NOT retry automatically
- Each call costs money and creates actual Google Ads campaigns

**SITELINK RULES (if using):**
⚠️ Google will REJECT sitelinks that violate these rules:
- ALL sitelink URLs MUST be same domain as final_url
  ✅ Good: final_url='https://example.com' → sitelink='https://example.com/shipping'
  ❌ Bad: final_url='https://example.com' → sitelink='https://different-site.com'
- Each sitelink must link to a UNIQUE, WORKING page (no duplicates, no 404s)
- Link text must be DESCRIPTIVE (not "Click Here", "Learn More")
- If you add description1, you MUST also add description2
- Only add sitelinks if you've verified the URLs exist and work

**Execution Time:** 15-30 seconds

**What happens:**
- Campaign created immediately in Google Ads
- Real money will be spent on the campaign
- Returns campaign_id for tracking

**Use for:**
- Launching new advertising campaigns
- Testing new market segments
- Scaling successful products

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (e.g., 'Luxury Watches Q4 2025') |
| `business_description` | `string` | yes | What does your business sell? (e.g., 'Luxury watch retailer specializing in Rolex') |
| `website_url` | `string` | yes | Your business website URL (landing page for ads) |
| `budget_daily` | `number` | yes | Daily budget in the account's native currency. IMPORTANT: Do NOT convert currencies — pass the user's amount as-is. Example: if user says '₹1000/day', pass 1000 (not a USD conversion). The Google Ads API interprets this in the account's cur |
| `target_locations` | `array` | yes | Geographic targets — supports countries, states, cities, and regions globally. Examples: ['India'], ['Mumbai, India'], ['New York, NY'], ['United States', 'Canada'] |
| `ad_groups` | `array` | yes | 1-4 themed ad groups, each with keywords, headlines, and descriptions. Each ad group represents a theme/audience segment. |
| `negative_keywords` | `object` | no | Campaign-level negative keywords (optional, e.g., ['free', 'cheap', 'used']) |
| `bidding_strategy` | `string` | no | Bidding strategy for the campaign. Options: 'MAXIMIZE_CONVERSIONS' (default) - Get the most conversions within budget. 'MAXIMIZE_CONVERSION_VALUE' - Optimize for highest value conversions (requires target_roas). Recommended: MAXIMIZE_CONVER |
| `objective` | `string` | no | Campaign objective/goal. Options: 'CONVERSIONS' (default) - Track and optimize for sales, leads, or sign-ups. 'CLICKS' - Maximize website traffic. Recommended: CONVERSIONS for performance-focused campaigns. |
| `target_roas` | `object` | no | Target ROAS (Return on Ad Spend) for MAXIMIZE_CONVERSION_VALUE bidding. Example: 3.0 means you want $3 revenue for every $1 spent. Only required when bidding_strategy='MAXIMIZE_CONVERSION_VALUE'. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "business_description": "string",
    "website_url": "https://example.com",
    "budget_daily": 1.0,
    "target_locations": [
      "string"
    ],
    "ad_groups": [
      {
        "name": "string",
        "keywords": [
          "string"
        ],
        "headlines": [
          "string"
        ],
        "descriptions": [
          "string"
        ],
        "final_url": "https://example.com",
        "sitelinks": [
          {
            "link_text": null,
            "final_url": null,
            "description1": null,
            "description2": null
          }
        ],
        "audiences": {
          "demographics": null,
          "custom_audiences": null,
          "in_market_audiences": null,
          "affinity_audiences": null
        }
      }
    ],
    "negative_keywords": [
      "string"
    ],
    "bidding_strategy": "MAXIMIZE_CONVERSIONS",
    "objective": "CONVERSIONS",
    "target_roas": 1.0,
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_search_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_search_campaign"
}
```

---
## create_youtube_campaign

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_youtube_campaign/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

🔄 LONG-RUNNING TOOL: Creates a YouTube Video campaign using Google Ads Demand Gen format with YouTube-only placements. Emits MCP progress updates during authentication and campaign creation (typically 10-20 seconds). Progress stages: validate → commit.

⚠️ CRITICAL WARNING ⚠️
- Call this tool ONLY ONCE per campaign
- Creates REAL campaigns that cost REAL money
- Do NOT retry automatically if errors occur
- Report errors to user instead of retrying

⚠️ CRITICAL PREREQUISITE:
- MUST validate the YouTube video first using validate_video tool
- Video must be public or unlisted on YouTube (NOT private)

🎬 **YouTube Campaign Placements:**
✅ YouTube In-Feed (appears in search results & related videos)
✅ YouTube In-Stream (plays before/during/after videos)
✅ YouTube Shorts (appears in Shorts feed)
❌ Gmail (disabled for YouTube campaigns)
❌ Discover (disabled for YouTube campaigns)
❌ Display (disabled for YouTube campaigns)

📋 **YOUR CRITICAL ROLE: Campaign Strategist & Text Creator**

**STEP 1: Validate Video First**

Before calling this tool:
1. Get user's YouTube video URL or ID
2. Call validate_video tool with platform="pmax" to verify it
3. Confirm video is accessible (public or unlisted)

🔴 **STEP 1.5: MANDATORY — Discover Existing Assets**
BEFORE proceeding, you MUST call discover_existing_assets to check for existing image/logo assets:
1. Call: discover_existing_assets(asset_types=["image", "logo"], target_domain="<user's domain>")
2. Present findings to the user:
   - Show all available logos and images with dimensions
   - Show count of assets found per type
3. Ask the user:
   "I found [N] existing assets in your account. Would you like to:
   A) Reuse an existing logo (saves time, consistent branding)
   B) Upload a new logo image
   C) Upload a new logo from URL"
4. Based on answer:
   - Reuse → Use logo_asset_id from discovered assets
   - Upload new → Use validate_and_prepare_assets with logos_square → asset_bundle_id
5. 🚫 NEVER skip this step — even if the user didn't mention assets

**STEP 2: Collect Campaign Details from User**

YOU MUST collect these from the user (ask if not provided):

**REQUIRED Fields:**
1. **Campaign Name** - Descriptive and unique
2. **Daily Budget** - Minimum $15/day (recommended $50+)
3. **YouTube Video ID** - 11-character video ID (validated in Step 1)
4. **Final URL** - Landing page (must match verified domain)
5. **Business Name** - Max 25 characters

**STEP 3: Generate High-Quality Ad Copy**

**🚨 BEFORE GENERATING TEXT - READ CHARACTER LIMITS 🚨**

**Headlines (1-5 required):**
- **STRICT LIMIT: 40 characters maximum per headline**
- Count characters BEFORE calling this tool!
- Examples:
  - "Shop Premium Watches Today" (26 chars) ✅
  - "Free Worldwide Shipping Now" (27 chars) ✅

**Descriptions (1-5 required):**
- **STRICT LIMIT: 90 characters maximum per description**
- Count characters BEFORE calling this tool!
- Examples:
  - "Shop authentic luxury timepieces with expert curation and free worldwide shipping." (82 chars) ✅

**Long Headlines (optional, 1-5):**
- **STRICT LIMIT: 90 characters maximum per long headline**
- Falls back to regular headlines if not provided

**⚠️ WILL BE REJECTED IF YOU EXCEED LIMITS - NO RETRIES!**

**LOGO (REQUIRED - one of two options):**
- **Option A** (existing logo): logo_asset_id from discover_existing_assets — use this if the account already has logo images
- **Option B** (new upload): asset_bundle_id from validate_and_prepare_assets — use this if the account has NO existing logos (e.g., Search-only accounts)

**Workflow:**
1. First try discover_existing_assets to find existing logos
2. If logos found → use logo_asset_id
3. If NO logos found → ask user for a logo image URL, run validate_and_prepare_assets with logos_square, then use the returned asset_bundle_id

**OPTIONAL Fields (use defaults if not provided):**
- call_to_action: Default "LEARN_MORE". Options: SHOP_NOW, SIGN_UP, SUBSCRIBE, DOWNLOAD, BOOK_NOW, CONTACT_US, GET_QUOTE, APPLY_NOW, WATCH_NOW, ORDER_NOW, BUY_NOW, SEE_MORE, START_NOW, VISIT_SITE, REGISTER
- target_locations: Defaults to ["United States"]
- target_languages: Defaults to ["en"]
- additional_video_ids: Up to 4 more videos (5 total max)
- bidding_strategy: MAXIMIZE_CLICKS (default), MAXIMIZE_CONVERSIONS (needs conversion tracking), TARGET_CPA
- target_cpa: Required only for TARGET_CPA bidding

**STEP 4: Call create_youtube_campaign**

After you have ALL details:
- Validate character counts yourself BEFORE calling
- Call create_youtube_campaign with complete payload
- Wait for response (may take 10-20 seconds)

**STEP 5: Handle Response**

**If SUCCESS:**
- Show campaign ID, name, budget, status
- Explain YouTube-only placements (In-Feed, In-Stream, Shorts)
- Explain campaign starts PAUSED for safety
- Provide next steps (review, activate, monitor)

**If FAILURE:**
- Show error message clearly
- Do NOT retry automatically
- Guide user to fix the issue

**Example Conversation Flow:**

User: "I want to run YouTube ads for my product"

You: "I'd be happy to help create a YouTube video campaign! First, can you share your YouTube video URL so I can validate it?"

[User provides: https://youtu.be/eIZtladpm6c]

[Call validate_video with video_url_or_id="eIZtladpm6c", platform="pmax"]

You: "Your video is validated and ready! Now I need a few details:
1. Campaign name?
2. Daily budget? (minimum $15, recommended $50+)
3. Landing page URL?
4. Business name?

I'll create compelling headlines and descriptions for your YouTube ads."

[User provides details]

[Call create_youtube_campaign with all details]

**Video Specifications:**
- Must be on YouTube (public or unlisted, NOT private)
- Minimum 10 seconds duration recommended
- Maximum 5 videos per campaign (1 primary + 4 additional)
- Validated via validate_video tool before campaign creation

**Bidding Options:**
- MAXIMIZE_CLICKS: Best for most campaigns (default, works without conversion tracking)
- MAXIMIZE_CONVERSIONS: Best when conversion tracking is set up on the account
- TARGET_CPA: Best when you know your target cost per acquisition (requires conversion tracking)

**Campaign Creation Best Practices:**
1. Always validate video first with validate_video tool
2. 🔴 MANDATORY: Run discover_existing_assets to find logos/images — present results to user, ask if they want to reuse or upload new
3. If reuse → use logo_asset_id; if upload new → validate_and_prepare_assets → asset_bundle_id
4. Confirm budget with user ($15/day minimum)
5. Create compelling, benefit-focused ad copy
6. Campaign starts PAUSED - explain this to user
7. This costs real money - be transparent
8. 🔴 AFTER campaign creation: Add extensions (sitelinks, callouts, structured snippets) — see STEP 6 below

**STEP 6: MANDATORY — Add Extensions After Creation**
After the campaign is successfully created, you MUST:
1. Crawl the user's website to gather relevant page links, features, and service categories
2. Add sitelinks: at least 4 links to key pages (add_sitelinks tool)
3. Add callout extensions: 4-6 business highlights (add_callout_extensions tool)
4. Add structured snippets: categorized features (add_structured_snippets tool)
5. Verify with list_campaign_extensions to confirm all extensions are attached
Extensions are FREE and increase ad visibility by 15-25%. NEVER skip this step.

**Execution Time:** 10-20 seconds (direct backend API call)

**Authentication:** Required (MCP OAuth 2.1)

**CRITICAL REMINDERS:**
- Campaign starts PAUSED for user safety
- This costs real money - be transparent with user
- Never retry on failure - report error to user
- Always validate video BEFORE creating campaign

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (e.g., 'YouTube Summer Promo 2025') |
| `budget_daily` | `number` | yes | Daily budget in the account's native currency. IMPORTANT: Do NOT convert currencies — pass the user's amount as-is. Example: if user says '₹1000/day', pass 1000 (not a USD conversion). The Google Ads API interprets this in the account's cur |
| `youtube_video_id` | `string` | yes | Primary YouTube video ID (exactly 11 characters). Must be validated first using validate_video tool. Example: 'dQw4w9WgXcQ' |
| `final_url` | `string` | yes | Landing page URL (must match verified domain). Example: 'https://example.com/product' |
| `business_name` | `string` | yes | Business name, max 25 characters. Example: 'Luxury Watch Co' |
| `headlines` | `array` | yes | MUST BE JSON ARRAY: 1-5 headlines, each EXACTLY 40 characters maximum. Commas ARE allowed within headlines. Example: ["Shop Premium Watches Today", "Free Shipping"]. COUNT CHARACTERS! ANY over 40 will be REJECTED! |
| `descriptions` | `array` | yes | MUST BE JSON ARRAY: 1-5 descriptions, each EXACTLY 90 characters maximum. Commas ARE allowed within descriptions. Example: ["Shop authentic luxury timepieces with expert curation and free shipping."]. COUNT CHARACTERS BEFORE CALLING - ANY o |
| `long_headlines` | `object` | no | MUST BE JSON ARRAY: Optional 1-5 long headlines, each EXACTLY 90 characters maximum. Falls back to regular headlines if not provided. Example: ["Premium Swiss Watches - Certified Authentic, Free Worldwide Shipping"]. COUNT CHARACTERS! ANY o |
| `call_to_action` | `object` | no | Call-to-action button label. Options: LEARN_MORE (default), SHOP_NOW, SIGN_UP, SUBSCRIBE, DOWNLOAD, BOOK_NOW, CONTACT_US, GET_QUOTE, APPLY_NOW, WATCH_NOW, ORDER_NOW, BUY_NOW, SEE_MORE, START_NOW, VISIT_SITE, REGISTER |
| `target_locations` | `object` | no | Optional: Geographic targets -- supports countries, states, cities, and regions globally. Examples: ['India'], ['Bangalore, India'], ['Karnataka'], ['New York, NY'], ['United States', 'Canada']. Defaults to United States. |
| `target_languages` | `object` | no | Optional: Language targets as ISO codes (e.g., ['en', 'es']). Defaults to English. |
| `additional_video_ids` | `object` | no | Optional: Up to 4 additional YouTube video IDs (max 5 total including primary). Each must be exactly 11 characters. Example: ['abc123xyz00', 'def456uvw00'] |
| `bidding_strategy` | `object` | no | Bidding strategy: MAXIMIZE_CLICKS (default, works without conversion tracking), MAXIMIZE_CONVERSIONS (requires conversion tracking), or TARGET_CPA (requires target_cpa). TARGET_CPA requires target_cpa parameter. |
| `target_cpa` | `object` | no | Target CPA in USD (only required when bidding_strategy='TARGET_CPA'). Example: 25.0 means target $25 per conversion. |
| `logo_asset_id` | `object` | no | Logo image asset ID (1:1 square aspect ratio) from discover_existing_assets. Either this OR asset_bundle_id is required. Example: '123456789' |
| `asset_bundle_id` | `object` | no | Asset bundle ID (UUID) from validate_and_prepare_assets. Use this when the account has no existing logo images. The bundle must contain a square logo (logos_square). Either this OR logo_asset_id is required. Example: '7df76fef-9d1f-4218-8bf |
| `audience_segments` | `object` | no | Optional: Audience targeting for the campaign. CRITICAL: Only use segment IDs returned by search_audiences tool. NEVER fabricate or guess IDs -- wrong IDs target unrelated audiences and waste budget. If search_audiences returns no results,  |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "budget_daily": 1.0,
    "youtube_video_id": "string",
    "final_url": "https://example.com",
    "business_name": "string",
    "headlines": [
      "string"
    ],
    "descriptions": [
      "string"
    ],
    "long_headlines": [
      "string"
    ],
    "call_to_action": "LEARN_MORE",
    "target_locations": [
      "string"
    ],
    "target_languages": [
      "string"
    ],
    "additional_video_ids": [
      "string"
    ],
    "bidding_strategy": "MAXIMIZE_CLICKS"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_youtube_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_youtube_campaign"
}
```

---
## discover_existing_assets

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/discover_existing_assets/execute`

🔍 Discover existing assets in the Google Ads account (images, sitelinks, callouts, structured snippets).

**Phase 0: Asset Discovery** - Read-only tool, safe to call anytime.

🔴 **MANDATORY WORKFLOW - ALWAYS DO THIS FIRST:**

BEFORE creating ANY Performance Max campaign, you MUST:
1. Call this tool: discover_existing_assets(target_domain="<user's domain>")
2. Show the user what was found (images, sitelinks)
3. Ask: "Would you like to reuse these assets or upload new ones?"
4. Based on answer:
   - Reuse → Use existing_image_ids in create_pmax_campaign
   - Upload new → Use validate_and_prepare_assets → asset_bundle_id
   - Mix both → Use both parameters (hybrid mode)

🚫 **NEVER create PMAX without discovering assets first** (unless user explicitly says "skip discovery, upload new")

**What This Tool Does:**
- Queries Google Ads Asset Library for existing validated assets
- Uses SMART 2-TIER FILTERING to show only reliable assets
- Filters out invalid/test data automatically
- Returns assets that were successfully attached to previous campaigns

**2-Tier Smart Discovery:**

**Tier 1: Campaign-Validated Assets (High Confidence)** ✅
- Assets currently attached to ENABLED, PAUSED, or REMOVED campaigns
- These have been validated by Google and won't fail
- Includes domain filtering for sitelinks
- Best option for reuse

**Tier 2: No Validated Assets (Ask User)** ⚠️
- Account has no suitable assets in campaigns
- Recommends uploading fresh assets or website research
- Safer than using orphaned assets from library

**Parameters:**
- target_domain: Optional domain to filter sitelinks (e.g., "rooterhero.com")
  - **CRITICAL for multi-domain accounts!** Google rejects wrong-domain sitelinks
  - **Extract the base domain** from campaign URL (just the domain part)
  - Examples: "https://www.rooterhero.com/services" → "rooterhero.com", "sahaayak.life" → "sahaayak.life"
  - Only sitelinks matching this domain will be returned
  - Prevents Google API errors for domain mismatches

**Returns:**
- images: List of validated images (landscape, square, portrait, logos)
- sitelinks: List of validated sitelinks (filtered by domain if provided)
- callouts: List of validated callouts
- structured_snippets: List of validated structured snippets
- recommendation: Smart guidance on whether to reuse or upload new

**Example ChatGPT Flow:**

User: "Create a campaign for rooterhero.com" (or "https://www.rooterhero.com/services")

Step 1: Extract base domain → "rooterhero.com"
Step 2: Call discover_existing_assets(target_domain="rooterhero.com")

If Tier 1 (validated assets found):
  ChatGPT: "I found 8 sitelinks and 11 images from your previous Rooter Hero campaigns.
            These were validated by Google. Would you like to reuse them, or upload fresh assets?"

  User: "Reuse them" → Use those assets in Phase 1
  User: "Upload new" → Follow current upload flow

If Tier 2 (no validated assets):
  ChatGPT: "No validated assets found for rooterhero.com in your campaigns.
            Options:
            1. Upload fresh images/sitelinks
            2. Let me research rooterhero.com and suggest sitelinks
            3. Create campaign without extensions"

**When to Call:**
- BEFORE creating Search campaigns (to discover sitelinks, callouts, snippets)
- BEFORE creating PMAX campaigns (to discover images)
- When user asks "What assets do I have?"
- When user wants to reuse existing assets

**When NOT to Call:**
- User explicitly says "I'll upload new images"
- User wants to create minimal campaign without extensions

**Execution Time:** 1-3 seconds (read-only query)

**Authentication:** Required (MCP OAuth 2.1)

**Note:** This is Phase 0 - discovery only. Phase 1 will enable reusing discovered assets in campaigns.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `target_domain` | `string` | no | Optional domain to filter sitelinks. **Extract the base domain from the campaign URL.** Examples: 'https://www.rooterhero.com/services' → 'rooterhero.com', 'sahaayak.life' → 'sahaayak.life'. Only sitelinks matching this domain will be retur |
| `asset_types` | `array` | no | Asset types to discover. Default: all types. |
| `customer_id` | `string` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "asset_types": [
      "images",
      "sitelinks",
      "callouts",
      "structured_snippets"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for discover_existing_assets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "discover_existing_assets"
}
```

---
## explain_performance_anomaly

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/explain_performance_anomaly/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Explain why a performance metric changed using statistical analysis and historical context.

⚠️ IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times. Uses statistical analysis only (no ML models).

🎯 **What This Tool Does (Performance Agent - Phase 1 Feature 4):**
- Explains why metrics changed (ROAS, CTR, CPC, conversions, conversion rate)
- Compares current period to historical averages (30/60/90-day)
- Identifies contributing factors with severity levels
- Detects campaign changes (paused, new, budget changes)
- Finds similar historical periods (seasonality detection)
- Provides actionable recommendations to address issues

**Returns comprehensive anomaly explanation:**
- Current metric value vs historical averages
- Deviation percentages (how much it changed)
- Contributing factors:
  - CPC changes (>15% = auction competition shifts)
  - Conversion rate changes (>10% = landing page/seasonality issues)
  - Campaign changes (paused high-performers, new campaigns, budget changes)
  - Day-of-week patterns (weekend vs weekday effects)
- Similar historical periods for context
- Assessment (normal variation vs requires action)
- Specific recommendations to fix the issue

🔍 **How Anomaly Detection Works:**

**Historical Comparison:**
- Compares current period to 30/60/90-day averages
- ±15% deviation considered "normal variation"
- >15% deviation flagged as requiring attention

**Contributing Factor Detection:**
1. **CPC Changes** (>15% threshold)
   - Increased CPC = auction competition increased
   - Decreased CPC = auction competition decreased or bid adjustments

2. **Conversion Rate Changes** (>10% threshold)
   - Decreased = landing page issues, seasonality, audience quality
   - Increased = landing page improved, better targeting

3. **Campaign Changes:**
   - Paused high-performers (ROAS > 3.0x) = lost revenue driver
   - New campaigns (>$1K spend) = learning phase affecting overall performance
   - Budget changes (>20%) = delivery and auction participation affected

4. **Day-of-Week Patterns:**
   - Weekend-heavy periods often show different performance
   - Normal for B2C (higher weekend conversion)
   - Normal for B2B (lower weekend conversion)

5. **Seasonality Detection:**
   - Finds similar historical periods (±10% metric value)
   - Helps identify if drop is seasonal vs real issue

**Parameters:**
- metric: 'roas', 'ctr', 'cpc', 'conversions', 'conversion_rate' (REQUIRED)
- period_start: Start date in YYYY-MM-DD format (REQUIRED)
- period_end: End date in YYYY-MM-DD format (REQUIRED, max 30 days period)
- customer_id: Optional (uses connected account if omitted)

**Execution time:** 2-4 seconds (statistical analysis + database queries)
**Data source:** campaign_daily_metrics table (updated nightly, 120-day retention)
**Analysis method:** Statistical comparison (no ML models)
**Trigger:** Reactive (user asks "why?"), not proactive alerts

**Use this tool when:**
- User asks "why did my ROAS drop?"
- User asks "why did my CTR increase?"
- User notices unexpected metric changes
- User wants to understand performance fluctuations
- After seeing performance changes in dashboards

📊 **AFTER calling this tool, help the user understand:**

**Normal vs Abnormal Variation:**
- **Normal**: ±15% deviation, typical day-of-week/seasonal patterns
- **Abnormal**: >15% deviation with specific contributing factors

**Severity Levels:**
- 🔴 **HIGH**: >25% change with clear cause (CPC spike, campaign paused)
- 🟡 **MEDIUM**: 15-25% change or multiple minor factors
- 🟢 **LOW**: <15% change, likely normal variation

**Example Interpretation:**
"Your ROAS dropped 33% from 4.2x to 2.8x. This was caused by two high-severity factors:
1. CPC increased 25% (auction competition spike during holiday season)
2. Your top campaign 'Brand - Exact' was paused, losing $240/day in high-ROAS revenue

This is NOT normal variation - these are actionable issues. Recommendations:
- Reduce bids by 10-15% to counter CPC inflation
- Re-enable 'Brand - Exact' campaign if budget allows
- Review landing page conversion rate (also dropped 12%)"

**Similar Period Context:**
If tool finds similar historical periods, explain seasonality:
"Your ROAS was similarly low (2.7x) on December 18, 2024, which was also during the holiday shopping season. This suggests some of the drop is seasonal, but the campaign pause is amplifying the effect."

**Quick Actions:**
Based on contributing factors, prioritize:
1. **Campaign paused** → Re-enable high performers immediately
2. **CPC spike** → Adjust bids, improve Quality Score
3. **Conversion rate drop** → Review landing page, check for bugs
4. **Budget changes** → Monitor delivery as it stabilizes
5. **Weekend effect** → Normal variation, no action needed

**Visualization Tip:**
Suggest creating a line chart showing the metric over time with 30-day average band and annotations for detected factors (e.g., "Campaign paused here", "CPC spike started here").

**Important Notes:**
- This is REACTIVE explanation, not proactive monitoring
- Max 120-day historical lookback (database retention limit)
- Uses simple statistics (mean, variance), no ML predictions
- Focus on actionable factors user can control
- Consider seasonality when interpreting results

**Best Practices:**
- Run this tool when you notice >15% metric changes
- Compare multiple time periods to confirm trends
- Cross-reference with other tools (wasted spend, budget optimizer)
- Use for post-mortem analysis of performance changes
- Help user distinguish normal variation from real issues

💬 **Community**: For anomaly analysis discussions, visit our Discord: https://discord.gg/dH3Qt4YS

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `metric` | `string` | yes | Metric to explain: 'roas' (Return on Ad Spend), 'ctr' (Click-Through Rate), 'cpc' (Cost Per Click), 'conversions', or 'conversion_rate' |
| `period_start` | `string` | yes | Start date of the anomaly period (ISO format: YYYY-MM-DD, e.g., '2025-01-15') |
| `period_end` | `string` | yes | End date of the anomaly period (ISO format: YYYY-MM-DD, e.g., '2025-01-21'). Maximum 30 days between start and end. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "metric": "string",
    "period_start": "string",
    "period_end": "string",
    "customer_id": "string",
    "raw_data": false
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for explain_performance_anomaly)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "explain_performance_anomaly"
}
```

---
## get_benchmark_context

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_benchmark_context/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Get industry benchmark context for AI-powered recommendations.

⚠️ IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.

🎯 **What This Tool Does (Performance Agent - Phase 1 Feature 5):**
- Generates contextual benchmark data for the user's business
- Combines business profile with industry benchmarks
- Provides recommended ROAS targets based on vertical
- Returns formatted context for AI prompts

**Returns benchmark context:**
- Business vertical and size context
- Industry-specific ROAS benchmarks (typical, good, excellent)
- CTR benchmarks by industry
- CPC expectations for the vertical
- Seasonality considerations
- Custom recommendations based on profile

**Industry Benchmarks Included:**
| Vertical | Typical ROAS | Good ROAS | Excellent ROAS |
|----------|--------------|-----------|----------------|
| Retail | 4.0x | 6.0x | 8.0x |
| Services | 3.0x | 5.0x | 7.0x |
| Technology | 3.5x | 5.5x | 8.0x |
| Healthcare | 2.5x | 4.0x | 6.0x |
| Finance | 5.0x | 8.0x | 12.0x |
| Education | 2.0x | 3.5x | 5.0x |

**Parameters:**
- **include_recommendations**: Include performance recommendations (default: true)
- **customer_id**: Optional (uses connected account if omitted)

**Use this tool when:**
- Before providing performance analysis
- When comparing user's metrics to industry standards
- User asks "how am I doing compared to others?"
- You need context for optimization recommendations

**Integration with Other Tools:**
Call this BEFORE or AFTER these tools for enhanced recommendations:
- `analyze_wasted_spend` - Contextualize waste against industry norms
- `optimize_budget_allocation` - Use industry-appropriate ROAS targets
- `get_campaign_performance` - Compare metrics to benchmarks
- `explain_performance_anomaly` - Understand if changes are industry-wide

**Execution time:** 1-2 seconds (profile lookup + benchmark calculation)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `include_recommendations` | `boolean` | no | Include performance recommendations based on benchmarks (default: true) |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "include_recommendations": true,
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_benchmark_context)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_benchmark_context"
}
```

---
## get_business_profile

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_business_profile/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Get the user's business profile for contextual recommendations.

⚠️ IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.

🎯 **What This Tool Does (Performance Agent - Phase 1 Feature 5):**
- Retrieves the user's saved business profile
- Returns business vertical, size, goals, target audience
- Provides context for more relevant recommendations
- Shows profile source (user-set, inferred, or none)

**Returns business context:**
- Business vertical (retail, services, technology, etc.)
- Business size (small, medium, large)
- Primary goal (leads, sales, awareness, traffic)
- Target audience description
- Geographic focus (local, regional, national, international)
- Seasonality patterns
- Profile confidence level

**Use this tool when:**
- You need business context for recommendations
- Before providing industry-specific advice
- User asks "what type of business am I?"
- You want to personalize optimization suggestions

**If no profile exists:**
- Use `infer_business_profile` to automatically detect from campaign data
- Or ask user to provide business details via `save_business_profile`

**Execution time:** 1-2 seconds (database lookup)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_business_profile)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_business_profile"
}
```

---
## get_campaign_performance

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_campaign_performance/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Analyze Google Ads campaign performance with comprehensive insights and recommendations.

⚠️ IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.

**Returns detailed analysis:**
- Campaign structure and ad group organization
- Keyword performance with quality scores and match types
- Ad group performance breakdown
- Performance metrics (CTR, conversions, CPC, cost, ROAS)
- Optimization recommendations with actionable insights
- Performance trends (last 7 days)

**Parameters:**
- lookback_days: 7, 30, 60, 90, or 120 days (default: 30)
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- customer_id: Optional (uses connected account if omitted)

**Execution time:** 2-5 seconds (direct backend API call)
**Data source:** Cached database (updated nightly via metrics collection)

**Use this tool to:**
- Review current campaign performance
- Identify optimization opportunities
- Get data-driven recommendations
- Analyze keyword and ad effectiveness
- Understand what's working and what needs improvement

📊 **AFTER calling this tool, provide these insights to the user:**

**How to Interpret Metrics:**
- **CTR (Click-Through Rate):** Industry average is 3-5% for search ads
  - Above 5%: Excellent ad relevance
  - Below 2%: Consider improving ad copy or targeting

- **Conversion Rate:** Industry average is 2-5%
  - Low CR + High CTR = Landing page issue
  - Low CR + Low CTR = Ad/targeting issue

- **CPA (Cost Per Acquisition):** Compare to your target CPA
  - Track the trend over time
  - Adjust bids if CPA is consistently too high

💡 **Optimization Tips:**
- Wait at least 2 weeks before making major changes (learning phase)
- Focus on high-performing keywords and pause low performers
- Test different ad copy variations (A/B testing)
- Adjust bids based on device/location performance
- Review search terms report for negative keyword opportunities

💬 **Community**: For optimization discussions and tips, visit our Discord: https://discord.gg/dH3Qt4YS

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 30, 60, or 90 days). Default is 30 days. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_campaign_performance)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_campaign_performance"
}
```

---
## get_campaign_structure

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_campaign_structure/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Get campaign structure with ad groups, keywords, ads, and extensions. Supports pagination for large campaigns.

This tool retrieves READ-ONLY data. Safe to call multiple times.

**Returns campaign hierarchy (paginated by ad groups):**
- Campaign details (name, status, budget, bidding strategy)
- Ad groups with their settings (paginated — default 5 per page)
- Keywords per ad group (including match types, bids, status)
- Ads per ad group (including RSA headlines/descriptions, ad strength)
- Negative keywords per ad group
- Campaign-level extensions and negative keywords (on page 1 only)
- Pagination metadata (page, total_pages, has_more)
- Summary counts (total ad groups, keywords, ads across ALL pages)

**Parameters:**
- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)
- customer_id: Optional (uses connected account if omitted)
- page: Page number, 1-based (default: 1)
- page_size: Ad groups per page, 1-50 (default: 5)
- ad_group_id: Optional — fetch only this single ad group (bypasses pagination)
- include: What detail per ad group: 'all' (default), 'summary' (counts only), 'keywords', 'ads'

**⚡ PAGINATION CONTRACT (IMPORTANT FOR AI AGENTS):**
- The response includes `pagination.has_more` — if true, you MUST call again with `page` incremented
- Continue calling until `has_more` is false
- Then consolidate all pages and present the complete campaign structure to the user
- The `summary` section is always included and shows total counts across ALL ad groups (not just the current page)
- Extensions and campaign-level negative keywords are only returned on page 1
- Use `ad_group_id` to drill into a single ad group when the user asks about a specific one
- Use `include='summary'` for a quick overview without nested keyword/ad data

**Execution time:** 3-8 seconds per page (multiple API queries)

**Use this tool when:**
- User wants to update an existing campaign
- User wants to see current keywords/ads
- User wants to add extensions to existing campaign
- User says "show me what's in this campaign"
- Before making any updates to a campaign

**Important IDs returned:**
- campaign.id - For campaign-level updates (budget, status)
- ad_groups[].id - For adding keywords/ads
- keywords[].id - For keyword updates (status, bids)
- ads[].id - For ad content updates

**Example flow:**
1. User: "I want to update my campaign"
2. Agent: Uses list_campaigns to show all campaigns
3. User: Selects campaign "Summer Sale 2025"
4. Agent: Uses get_campaign_structure with that campaign_id
5. If has_more=true, agent calls again with page=2, page=3, etc.
6. Agent: Shows consolidated structure and asks what to update
7. User: "Change the headlines"
8. Agent: Uses update_ad_headlines with the ad_id from structure

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to get structure for. Use list_campaigns first to get available campaign IDs. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |
| `page` | `object` | no | Page number (1-based). Use to paginate through large campaigns. Default: 1 |
| `page_size` | `object` | no | Number of ad groups per page (1-50). Default: 5 |
| `ad_group_id` | `object` | no | Optional: Fetch only this specific ad group's details (bypasses pagination) |
| `include` | `object` | no | What to include per ad group: 'all' (default), 'summary' (counts only, no nested data), 'keywords' (keywords only), 'ads' (ads only) |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "customer_id": "string",
    "page": 1,
    "page_size": 5,
    "ad_group_id": "string",
    "include": "all"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_campaign_structure)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_campaign_structure"
}
```

---
## get_campaign_targeting

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_campaign_targeting/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Get location/geo targeting for a Google Ads campaign.

This tool retrieves READ-ONLY data. Safe to call multiple times.

**Returns location targeting details:**
- Targeted locations (cities, states, countries, metros the campaign targets)
- Excluded locations (negative geo targets)
- Bid modifiers per location (if set)
- Location type (Country, State, City, Metro, etc.)
- Summary counts (targeted, excluded, total)

**Parameters:**
- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)
- customer_id: Optional (uses connected account if omitted)

**Execution time:** 1-3 seconds (single API query)

**Use this tool when:**
- User asks "where is this campaign targeting?" or "what locations?"
- User wants to see geo targeting for a campaign
- User asks "what countries/cities/states does my campaign target?"
- Before recommending location targeting changes
- To verify location setup after campaign creation

**Returns for each location:**
- name: Short location name (e.g., "New York")
- canonical_name: Full hierarchical name (e.g., "New York,New York,United States")
- country_code: ISO country code (e.g., "US")
- target_type: Location type (Country, State, City, County, Metro, etc.)
- is_negative: true if this location is EXCLUDED
- bid_modifier: Bid adjustment for this location (null if no modifier)

**Example flow:**
1. User: "What locations does my Diamond Ring campaign target?"
2. Agent: Uses get_campaign_targeting with campaign_id
3. Returns: Targeted: United States, New York, Los Angeles; Excluded: none

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to get location targeting for. Use list_campaigns first. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_campaign_targeting)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_campaign_targeting"
}
```

---
## get_pmax_audience_signals

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_pmax_audience_signals/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Get current audience signals for a Performance Max campaign.

Returns audience signal resource names and associated audience resource references.

**Use when:**
- User wants to see what audience signals are on their PMax campaign
- Before adding new signals
- To get resource_name for removal

**Parameters:**
- campaign_id: The PMax campaign ID

**Execution time:** 1-2 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Google Ads campaign ID (numeric). Example: '21854471508' |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_pmax_audience_signals)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_pmax_audience_signals"
}
```

---
## get_pmax_search_themes

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_pmax_search_themes/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Get current search themes for a Performance Max campaign.

Returns each search theme's text and approval_status (APPROVED, LIMITED, DISAPPROVED, UNDER_REVIEW).

**Use when:**
- User wants to see what search themes are set on their PMax campaign
- Before adding new themes (to check current count — max 50)
- To verify theme approval status after adding

**Parameters:**
- campaign_id: The PMax campaign ID (get from list_campaigns)

**Execution time:** 1-2 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Google Ads campaign ID (numeric). Example: '21854471508' |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_pmax_search_themes)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_pmax_search_themes"
}
```

---
## get_usage_status

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_usage_status/execute`

Get your current usage status with interactive quota widget.

This tool shows your tool call usage for the current billing period and provides upgrade options if needed.

**Returns:**
- Current usage (calls used / limit)
- Subscription tier (Free, Plus, Pro, Enterprise)
- Days until quota reset
- Upgrade options with pricing

**Use this tool when:**
- User asks "how many calls do I have left?"
- User asks about their subscription or quota
- User wants to check their usage
- User asks about upgrading their plan

**Widget Display (ChatGPT):**
In ChatGPT, this tool displays an interactive widget with:
- Visual progress bar showing usage
- Upgrade buttons that open Stripe checkout
- Plan comparison with pricing

**Note:** This tool is READ-ONLY and safe to call anytime.

### Arguments

_No arguments._

### Example request

```json
{
  "arguments": {}
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_usage_status)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_usage_status"
}
```

---
## help_user_upload

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/help_user_upload/execute`

Show user instructions for uploading images to postimages.org for Performance Max campaigns.

⚠️ CALL THIS FIRST when user wants to create a PMax campaign!

**YOUR ROLE**: Image Upload Guide

**WHEN TO USE**:
- User says "create PMax campaign" or similar
- Before asking for image uploads
- Anytime user needs help uploading images

**WHAT THIS DOES**:
- Returns clear, step-by-step instructions
- Tells user to upload to postimages.org
- Explains how to get Direct links (not share pages)
- Shows example URL format

**DO NOT**:
- Ask user to upload via ChatGPT's paperclip (won't work with size limits!)
- Request base64 data (too large!)
- Skip this step (user needs clear guidance)

**AFTER THIS**:
- User uploads to postimages.org
- User pastes Direct links in chat
- You call validate_and_prepare_assets with those URLs

**Execution Time**: <1 second (just returns text)
**Authentication**: Not required

### Arguments

_No arguments._

### Example request

```json
{
  "arguments": {}
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for help_user_upload)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "help_user_upload"
}
```

---
## infer_business_profile

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/infer_business_profile/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Automatically infer business profile from campaign data using AI analysis.

⚠️ IMPORTANT: This tool ANALYZES data but may SAVE a profile if confidence is high.

🎯 **What This Tool Does (Performance Agent - Phase 1 Feature 5):**
- Analyzes campaign names, keywords, and ad copy
- Uses Claude AI to classify business type
- Infers business vertical, size, goals, and audience
- Returns confidence level (high, medium, low)
- Auto-saves if confidence is HIGH, asks confirmation for LOW

**Inference Process:**
1. Collects campaign names and keywords from Google Ads
2. Analyzes patterns (B2B vs B2C, product vs service)
3. Uses AI to classify business vertical
4. Estimates business size from ad spend
5. Identifies primary goals from campaign types

**Confidence Levels:**
- **HIGH** (≥0.8): Auto-saves profile, high certainty
- **MEDIUM** (0.5-0.8): Saves profile, reasonable certainty
- **LOW** (<0.5): Returns suggestion, asks user to confirm

**Parameters:**
- **force_save**: Set to true to save even low-confidence profiles
- **customer_id**: Optional (uses connected account if omitted)

**Returns:**
- Inferred business profile (vertical, size, goal, audience)
- Confidence level and reasoning
- Whether profile was saved or needs confirmation
- Suggested confirmation prompt for low-confidence results

**Use this tool when:**
- User doesn't have a business profile set
- User asks "analyze my business"
- You need business context but none exists
- After user connects a new Google Ads account

**Example Flow:**
1. Call `get_business_profile` - returns no profile
2. Call `infer_business_profile` - analyzes campaigns
3. If HIGH confidence: Profile saved automatically
4. If LOW confidence: Ask user to confirm with suggested prompt

**Execution time:** 3-8 seconds (AI analysis + optional save)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `force_save` | `boolean` | no | If true, save the profile even if confidence is low. Default is false (requires user confirmation for low-confidence profiles). |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "force_save": false,
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for infer_business_profile)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "infer_business_profile"
}
```

---
## list_campaign_extensions

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_campaign_extensions/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

List all extensions (sitelinks, callouts, structured snippets) for a campaign.

Returns a summary of all extension types configured on the campaign.

**Parameters:**
- campaign_id: The campaign ID (REQUIRED). Get from list_campaigns.
- customer_id: Optional Google Ads customer ID

**Returns:**
- Sitelinks: Clickable links with text, URL, and descriptions
- Callouts: Non-clickable text highlights
- Structured Snippets: Header + values combinations

**Execution time:** 1-2 seconds (read-only)

**When to use:**
- User asks "what extensions do I have?"
- Before adding extensions, check what already exists
- Auditing campaign setup

**Example:**
User: "Show me the extensions on my campaign"
Agent:
1. Uses list_campaigns to get campaign_id
2. Uses list_campaign_extensions to see all extensions

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to list extensions for. Get from list_campaigns. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_campaign_extensions)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_campaign_extensions"
}
```

---
## list_campaigns

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_campaigns/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

List all Google Ads campaigns for the connected account.

⚠️ CRITICAL: Call this tool BEFORE creating new campaigns to ask the user:
"Would you like to create a new campaign or update an existing one?"

This tool retrieves READ-ONLY data. Safe to call multiple times.

**Returns:**
- List of all campaigns with IDs, names, status, type, budget
- 30-day performance metrics for each campaign (impressions, clicks, cost, conversions)
- Total campaign count

**Parameters:**
- status_filter: ENABLED, PAUSED, or ALL (optional, default: ALL)
- campaign_type: SEARCH, PERFORMANCE_MAX, DISPLAY, SHOPPING, or ALL (optional, default: ALL)
- customer_id: Optional (uses connected account if omitted)

**Execution time:** 2-5 seconds (direct Google Ads API call)

**Campaign Management Guidelines:**

BEFORE Starting Any Campaign Work:
1. ALWAYS use `list_campaigns` first
2. Ask user: "Would you like to create a new campaign or update an existing one?"
3. If updating, use `get_campaign_structure` to see full details

User Intent Mapping:
| User Says | Agent Action |
|-----------|--------------|
| "Create a campaign for X" | List existing first, then ask create vs update |
| "Change the budget" | Get structure, then use update tools |
| "Add more keywords" | Get structure, then add keywords |
| "Update my ads" | Get structure, then update ad content |
| "Pause the campaign" | Use pause_campaign tool |

**Use this tool to:**
- View all existing campaigns before creating new ones
- Find campaign IDs for update operations
- Get a quick overview of account structure
- Identify active vs paused campaigns

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `status_filter` | `object` | no | Filter by status: ENABLED, PAUSED, or ALL (default: ALL) |
| `campaign_type` | `object` | no | Filter by type: SEARCH, PERFORMANCE_MAX, DISPLAY, SHOPPING, or ALL (default: ALL) |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "status_filter": "string",
    "campaign_type": "string",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_campaigns)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_campaigns"
}
```

---
## optimize_budget_allocation

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/optimize_budget_allocation/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Optimize budget allocation across campaigns using linear programming to maximize conversions.

⚠️ IMPORTANT: This tool retrieves READ-ONLY optimization recommendations. Safe to call multiple times. Does NOT automatically change budgets.

🎯 **What This Tool Does (Performance Agent - Phase 1):**
- Uses linear programming to optimize budget distribution
- Maximizes total conversions while respecting constraints
- Provides current vs optimized allocation comparison
- Categorizes campaigns into actions: PAUSE/SCALE/REDUCE/MAINTAIN
- Shows expected conversion lift from optimization
- Generates specific recommendations with budget amounts

**Returns detailed optimization plan:**
- Current allocation (what you have now)
- Optimized allocation (what you should have)
- Expected conversion lift (absolute and percentage)
- Campaign-by-campaign actions with reasoning
- Budget change amounts and percentages
- Specific implementation recommendations

**Optimization Algorithm:**
Uses scipy linear programming with constraints:
1. Sum of budgets = total_budget (you don't overspend)
2. Only campaigns with ROAS >= target get significant budget
3. Min budget per campaign >= min_daily_budget (or $0 to pause)
4. Max change per campaign <= ±max_change_percentage (avoid drastic shifts)

**Target ROAS Resolution (3-tier priority):**
1. User override (if target_roas parameter provided)
2. Account goals table (user-set or API-pulled)
3. 90-day historical average ROAS
4. Default to 1.0x (breakeven) if no data

**Configuration Parameters:**
- **total_budget**: Total monthly budget to allocate (REQUIRED)
- **lookback_days**: 7, 30, 60, 90, or 120 days (default: 30)
- **start_date**: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- **end_date**: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
- **target_roas**: Optional override (e.g., 3.0 for 3.0x)
- **max_change_percentage**: 0.0-1.0 (default: 0.5 = ±50%)
  * 0.3 = Conservative (±30% change, minimal disruption)
  * 0.5 = Balanced (±50% change, standard optimization)
  * 0.7 = Aggressive (±70% change, fast scaling)
- **min_daily_budget**: Minimum $ per campaign (default: $5.00, or $0.00 to allow pausing)
- **customer_id**: Optional (uses connected account if omitted)

**Execution time:** 1-5 seconds (depends on campaign count)
**Data source:** campaign_daily_metrics table (updated nightly)

**Use this tool when:**
- User wants to optimize budget allocation
- User asks "how should I allocate my budget?"
- User wants to maximize conversions with current spend
- User wants data-driven budget recommendations
- After running wasted spend analysis (natural next step)

📊 **AFTER calling this tool, help the user understand:**

**Campaign Actions:**
- **PAUSE**: Campaigns below target ROAS, losing money (ROAS < target)
- **SCALE**: High performers, increase budget by X% (top conversion rates)
- **REDUCE**: Underperformers, decrease budget by X% (low efficiency)
- **MAINTAIN**: Steady performers, keep current budget (±5% change)

**Expected Impact:**
- Current conversions: What you get now
- Optimized conversions: What you could get
- Conversion lift: Additional conversions (+X%)

**Example Interpretation:**
"Implementing this optimization will increase your conversions by 45 (+18.8%) without spending more money. You should scale 'Brand - Exact' campaign by $2,250/month and pause 'Display - Broad' to free up $3,000/month."

**Implementation Steps:**
1. Review recommended changes carefully
2. Start with campaigns marked CRITICAL (pause/scale first)
3. Apply changes gradually if user is risk-averse
4. Monitor performance for 7-14 days after changes
5. Re-run optimization monthly for continuous improvement

**Important Notes:**
- This is a RECOMMENDATION tool, not automated budget application
- User must review and apply changes manually in Google Ads
- Avoid large changes (>50%) for campaigns in learning phase (<7 days)
- Consider seasonality when interpreting results
- Re-optimize every 30 days as performance changes

**Visualization Tip:**
For 5+ campaigns, suggest creating a grouped bar chart showing current vs optimized budgets side-by-side.

**Best Practices:**
- Start conservative (max_change_percentage=0.3) for first optimization
- Increase aggressiveness (0.5-0.7) as you gain confidence
- Use longer lookback_days (60-90) during seasonal changes
- Set realistic target_roas (start with 1.0x breakeven, increase gradually)

💬 **Community**: For optimization discussions, visit our Discord: https://discord.gg/dH3Qt4YS

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `total_budget` | `number` | yes | Total monthly budget to allocate across campaigns (e.g., 15000.00 for $15K/month) |
| `lookback_days` | `integer` | no | Number of days to analyze for historical performance (7, 30, 60, 90, or 120 days). Default is 30 days. |
| `target_roas` | `object` | no | Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average. |
| `max_change_percentage` | `number` | no | Maximum budget change per campaign as a percentage (0.0-1.0). Default 0.5 = ±50%. Use 0.3 for conservative, 0.7 for aggressive. |
| `min_daily_budget` | `number` | no | Minimum daily budget per campaign in dollars. Default is $5.00. Set to 0.0 to allow complete pausing of underperformers. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "total_budget": 1.0,
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "target_roas": 1.0
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for optimize_budget_allocation)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "optimize_budget_allocation"
}
```

---
## pause_ad

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/pause_ad/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Pause an ad to stop it from showing.

This is REVERSIBLE using resume_ad.

**Parameters:**
- ad_id: The ad ID to pause (REQUIRED)
- ad_group_id: The ad group ID (REQUIRED)
- customer_id: Optional

**Execution time:** 1-2 seconds

**When to use:**
- User says "pause this ad", "stop this ad"
- User wants to A/B test by pausing one ad
- Ad is underperforming and needs a break
- Making changes before re-enabling

**Example:**
User: "Pause my underperforming ad"
Agent:
1. Uses get_campaign_structure to find ad_id and ad_group_id
2. Uses pause_ad to stop the ad

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_id` | `string` | yes | The ad ID to pause. Get from get_campaign_structure. |
| `ad_group_id` | `string` | yes | The ad group ID the ad belongs to. Get from get_campaign_structure. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_id": "string",
    "ad_group_id": "string",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for pause_ad)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "pause_ad"
}
```

---
## pause_campaign

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/pause_campaign/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Quickly pause a running campaign.

Pausing a campaign stops all ads from showing immediately.
This is REVERSIBLE using resume_campaign.

**Parameters:**
- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)
- customer_id: Optional (uses connected account if omitted)

**Execution time:** 1-2 seconds

**Returns:**
- Campaign name
- Before status (e.g., ENABLED)
- After status (PAUSED)
- Confirmation message

**When to use:**
- User says "pause the campaign", "stop the ads", "turn it off"
- User wants to temporarily stop spending
- User needs to make changes before ads continue

**Example:**
User: "Pause my summer sale campaign"
Agent: Uses pause_campaign with the campaign_id

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to pause. Use list_campaigns first to get available campaign IDs. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for pause_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "pause_campaign"
}
```

---
## remove_keywords

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/remove_keywords/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Remove keywords from an ad group.

**Parameters:**
- ad_group_id: The ad group containing the keywords (REQUIRED)
- keyword_ids: List of keyword IDs to remove (REQUIRED)
- customer_id: Optional

**Get keyword_ids from:**
Use get_campaign_structure to see all keywords with their IDs.

**WARNING: This is PERMANENT and IRREVERSIBLE.**
Keywords removed cannot be recovered. Always confirm with the user first.

**Execution time:** 2-4 seconds

**Example:**
User: "Remove the underperforming keywords"
Agent:
1. Uses get_campaign_structure to show keywords
2. Confirms with user which ones to remove
3. Uses remove_keywords with the keyword_ids

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_group_id` | `string` | yes | The ad group ID the keywords belong to. Use get_campaign_structure to find this. |
| `keyword_ids` | `array` | yes | List of keyword IDs to remove. Get from get_campaign_structure. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_group_id": "string",
    "keyword_ids": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for remove_keywords)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "remove_keywords"
}
```

---
## remove_negative_keywords

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/remove_negative_keywords/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Remove negative keywords from a campaign.

⚠️ **WARNING: This is PERMANENT and IRREVERSIBLE.**
Once removed, the negative keywords cannot be recovered.

**Parameters:**
- campaign_id: The campaign ID (REQUIRED)
- keyword_ids: List of negative keyword IDs to remove (REQUIRED)
- customer_id: Optional

**Get keyword IDs from:**
Use `get_campaign_structure` to see all campaign negative keywords with their IDs.
Look for the "Campaign Negative Keywords" section.

**When to use:**
- Remove accidentally added negative keywords
- Clean up obsolete negative keywords
- Fix over-blocking that's limiting traffic

**Execution time:** 2-4 seconds

**Example:**
User: "Remove the negative keyword 'discount' I added by mistake"
Agent:
1. Uses get_campaign_structure to find the keyword ID
2. Uses remove_negative_keywords with campaign_id and keyword_ids

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to remove negative keywords from. |
| `keyword_ids` | `array` | yes | List of negative keyword IDs to remove. Get IDs from get_campaign_structure (Campaign Negative Keywords section). |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "keyword_ids": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for remove_negative_keywords)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "remove_negative_keywords"
}
```

---
## remove_pmax_audience_signal

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/remove_pmax_audience_signal/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Remove a specific audience signal from a Performance Max campaign.

Use get_pmax_audience_signals first to get the signal_resource_name.

**Parameters:**
- campaign_id: The PMax campaign ID
- signal_resource_name: Resource name from get_pmax_audience_signals

**Execution time:** 2-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Google Ads campaign ID (numeric). Example: '21854471508' |
| `signal_resource_name` | `string` | yes | The resource name of the audience signal to remove. Get from get_pmax_audience_signals tool. Example: 'customers/1234567890/assetGroupSignals/123~456' |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "signal_resource_name": "string",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for remove_pmax_audience_signal)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "remove_pmax_audience_signal"
}
```

---
## remove_pmax_search_themes

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/remove_pmax_search_themes/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Remove specific search themes from a Performance Max campaign.

Matches themes by text (case-insensitive). Use get_pmax_search_themes first to see current themes.

**Parameters:**
- campaign_id: The PMax campaign ID
- themes_to_remove: List of theme text strings to remove

**Execution time:** 2-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Google Ads campaign ID (numeric). Example: '21854471508' |
| `themes_to_remove` | `array` | yes | List of search theme texts to remove (case-insensitive match). Example: ['AI ad management', 'automate google ads'] |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "themes_to_remove": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for remove_pmax_search_themes)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "remove_pmax_search_themes"
}
```

---
## research_keywords

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/research_keywords/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Research high-intent keywords using Google Keyword Planner API.

⚠️ IMPORTANT: This is a READ-ONLY tool. Safe to call multiple times.

🎯 **What This Tool Does:**
- Researches keywords via Google Keyword Planner API
- Returns keywords with real CPC data, search volume, and competition metrics
- Groups keywords by commercial intent (HIGH/MEDIUM/LOW based on dynamic CPC thresholds)
- Selects top 15-20 keywords optimized for conversions
- Provides budget recommendations based on actual keyword costs

**When to Use:**
- BEFORE creating a Google Search campaign
- When you need data-driven keyword insights
- To understand keyword costs and search volume
- To get budget recommendations

**Parameters:**
- business_description (required): What the business sells/offers
- website_url (optional): Business website for better keyword suggestions
- target_location (optional): Geographic target (default: "United States")
- seed_keywords (optional): 5-10 seed keywords (will auto-extract if not provided)
- customer_id (optional): Google Ads account ID

**Returns:**
- Keyword table with dynamic CPC thresholds (adapts to any industry)
- HIGH/MEDIUM/LOW intent grouping
- Budget recommendations (Conservative/Moderate/Aggressive)
- Top 15-20 recommended keywords for campaign

**Execution time:** 3-8 seconds (calls live Google Ads API)

📊 **Example Usage:**
1. User: "I want to create a campaign for my plumbing business"
2. YOU call: research_keywords with business_description="Emergency plumbing services"
3. Tool returns: Keyword table with 100+ keywords, 20 recommended, budget suggestions
4. YOU show user: The keyword table and ask if they want modifications
5. User approves or requests changes
6. YOU call: create_search_campaign with approved keywords

💡 **Dynamic Thresholds:** This tool automatically adapts CPC thresholds to any industry:
- Plumbing: HIGH ≥$6, MEDIUM $3-6, LOW <$3
- Legal: HIGH ≥$95, MEDIUM $45-95, LOW <$45
- E-commerce: HIGH ≥$2, MEDIUM $0.50-2, LOW <$0.50

All keywords returned will use **BROAD match** (Google's 2025 recommendation with Smart Bidding).

---

📊 **CRITICAL: AFTER calling this tool, YOU MUST explain these insights to the user:**

**1. Keyword Discovery Summary:**
   - "I found [X] keywords from Google Keyword Planner for your [business type] business"
   - "I analyzed real search data and CPC costs from Google Ads"
   - "Here are the top 20 keywords I recommend based on commercial intent"

**2. Seed Keywords Used:**
   - "I used these seed keywords: [list the seeds from the response]"
   - "Google expanded these into [X] keyword suggestions"

**3. CPC Cost Analysis (CRITICAL - Discuss this with user!):**
   - "The median CPC for your industry is $[X]"
   - "Keywords range from $[LOW] to $[HIGH] per click"
   - "HIGH intent keywords (top 25% most expensive) cost $[threshold]+ per click"
   - "These are keywords where advertisers pay more = higher commercial value"

**4. Budget Recommendations (CRITICAL - Explain all 3 tiers!):**
   - "Based on the keyword costs, here are my budget recommendations:"
   - "💰 Conservative ($[X]/day): Safe starting budget based on median CPC, expect ~[Y] clicks/day"
   - "💰 Moderate ($[X]/day): Balanced budget based on average CPC, expect ~[Y] clicks/day"
   - "💰 Aggressive ($[X]/day): Maximum budget to compete for all keywords, expect ~[Y] clicks/day"
   - "I recommend starting with $[conservative]-[moderate]/day"

**5. Keyword Selection Explanation:**
   - "I selected these 20 keywords by prioritizing:"
   - "  • HIGH intent keywords (expensive = high commercial value)"
   - "  • High search volume (more potential customers)"
   - "  • Mix of broad and specific terms"
   - "All keywords will use BROAD match - Google's 2025 recommendation for maximum reach with Smart Bidding"

**6. Ask for User Feedback:**
   - "Would you like to modify this keyword selection?"
   - "Options: Add specific keywords, remove keywords, use only HIGH intent, or proceed with recommendations"

**DO NOT just show the raw table without explanation!**
**Users need YOU to interpret the data and provide strategic guidance!**

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `business_description` | `object` | no | Description of what the business sells/does. Required if seed_keywords not provided. If omitted, inferred from website_url domain. |
| `website_url` | `object` | no | Business website URL (helps generate more relevant keywords) |
| `target_location` | `string` | no | Geographic target for keyword research (e.g., 'New York, NY', 'Chicago, IL', 'United States') |
| `seed_keywords` | `object` | no | Optional: Provide 5-10 seed keywords. If not provided, they will be extracted from business_description automatically. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "business_description": "string",
    "website_url": "string",
    "target_location": "United States",
    "seed_keywords": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for research_keywords)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "research_keywords"
}
```

---
## resume_ad

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/resume_ad/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Resume a paused ad to start showing it again.

Only works on PAUSED ads (not REMOVED).

**Parameters:**
- ad_id: The ad ID to resume (REQUIRED)
- ad_group_id: The ad group ID (REQUIRED)
- customer_id: Optional

**Execution time:** 1-2 seconds

**When to use:**
- User says "resume", "turn it back on", "enable"
- User finished making changes
- User wants to reactivate a paused A/B test variant

**Example:**
User: "Turn my paused ad back on"
Agent:
1. Uses get_campaign_structure to find the paused ad
2. Uses resume_ad to enable it

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_id` | `string` | yes | The ad ID to resume. Get from get_campaign_structure. |
| `ad_group_id` | `string` | yes | The ad group ID the ad belongs to. Get from get_campaign_structure. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_id": "string",
    "ad_group_id": "string",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for resume_ad)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "resume_ad"
}
```

---
## resume_campaign

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/resume_campaign/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Resume a paused campaign.

Resuming a campaign makes ads start showing again.
Only works on PAUSED campaigns (not REMOVED).

**Parameters:**
- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)
- customer_id: Optional (uses connected account if omitted)

**Execution time:** 1-2 seconds

**Returns:**
- Campaign name
- Before status (e.g., PAUSED)
- After status (ENABLED)
- Confirmation message

**When to use:**
- User says "resume", "turn it back on", "start it again"
- User wants to re-enable a paused campaign
- User finished making changes and wants ads to run

**Example:**
User: "Turn my campaign back on"
Agent: Uses resume_campaign with the campaign_id

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to resume. Use list_campaigns first to get available campaign IDs. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for resume_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "resume_campaign"
}
```

---
## save_business_profile

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/save_business_profile/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Save or update the user's business profile with provided details.

⚠️ IMPORTANT: This tool WRITES data. Use when user confirms their business profile.

🎯 **What This Tool Does (Performance Agent - Phase 1 Feature 5):**
- Saves user's business profile to database
- Updates existing profile if one exists
- Sets profile source as 'user_confirmed' or 'user_mcp'
- Enables personalized recommendations going forward

**Required Parameters:**
- **business_vertical**: retail, services, technology, healthcare, finance, education, travel, food_beverage, automotive, real_estate
- **business_size**: small (<$50K/month), medium ($50K-$500K/month), large (>$500K/month)
- **primary_goal**: leads, sales, awareness, traffic, engagement, app_installs

**Optional Parameters:**
- **target_audience**: Free-text description (e.g., "B2B enterprise clients")
- **geographic_focus**: local, regional, national, international
- **seasonality**: none, holiday_heavy, summer_peak, winter_peak, q4_heavy, back_to_school
- **customer_id**: Optional (uses connected account if omitted)

**Use this tool when:**
- User provides their business details
- User confirms an inferred profile
- User wants to update their business profile
- After asking user clarifying questions about their business

**Example Usage:**
User: "I run a small local plumbing business targeting homeowners"
→ Call save_business_profile with:
  - business_vertical: "services"
  - business_size: "small"
  - primary_goal: "leads"
  - target_audience: "homeowners"
  - geographic_focus: "local"

**Execution time:** 1-2 seconds (database write)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `business_vertical` | `string` | yes | Business vertical (e.g., 'retail', 'services', 'technology', 'healthcare', 'finance', 'education', 'travel', 'food_beverage', 'automotive', 'real_estate') |
| `business_size` | `string` | yes | Business size: 'small' (<$50K/month ad spend), 'medium' ($50K-$500K/month), 'large' (>$500K/month) |
| `primary_goal` | `string` | yes | Primary advertising goal: 'leads', 'sales', 'awareness', 'traffic', 'engagement', 'app_installs' |
| `target_audience` | `object` | no | Target audience description (e.g., 'B2B enterprise clients', 'young professionals 25-35') |
| `geographic_focus` | `object` | no | Geographic focus: 'local', 'regional', 'national', 'international' |
| `seasonality` | `object` | no | Seasonality pattern: 'none', 'holiday_heavy', 'summer_peak', 'winter_peak', 'q4_heavy', 'back_to_school' |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "business_vertical": "string",
    "business_size": "string",
    "primary_goal": "string",
    "target_audience": "string",
    "geographic_focus": "string",
    "seasonality": "string",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for save_business_profile)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "save_business_profile"
}
```

---
## search_audiences

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/search_audiences/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Search for available audience segments (in-market, affinity, custom).

Use this tool to discover audience segment IDs for:
- **PMax campaigns:** Use IDs with `add_pmax_audience_signal` (audience signals/hints)
- **Demand Gen / YouTube campaigns:** Pass IDs via `audience_segments` parameter in `create_demandgen_campaign` or `create_youtube_campaign` (direct targeting)

**Audience types:**
- **In-market:** Users actively researching products/services (e.g., "Business Software")
- **Affinity:** Users with long-term interests (e.g., "Technology Enthusiasts")
- **Custom:** Your account's existing remarketing/customer match lists

**Parameters:**
- query: Search term (e.g., "advertising services", "SaaS tools")

**Execution time:** 2-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `query` | `string` | yes | Search query for discovering audiences. Example: 'business software', 'advertising services', 'technology' |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "query": "string",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for search_audiences)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "search_audiences"
}
```

---
## select_google_campaign_type

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/select_google_campaign_type/execute`

**USE THIS TOOL FIRST WHEN:** User wants to create a Google Ads campaign but hasn't specified the campaign type (Search, Performance Max, or YouTube).

**IMPORTANT:** This tool should be called BEFORE any keyword research, asset discovery, or campaign creation when the user says things like:
- "Create a Google Ads campaign"
- "I want to run Google Ads"
- "Set up a Google advertising campaign"
- "Help me create ads on Google"
- "I want to advertise on Google"
- "Create a campaign"
- "Run ads"

This tool guides the user to select their campaign TYPE, then provides a detailed workflow for that specific type.

**Campaign Types Available:**
1. **search** - Text ads in Google Search results (best for high-intent keywords, lead gen, local services)
2. **pmax** - Performance Max across all Google channels (best for ecommerce, brand awareness, multi-channel reach)
3. **youtube** - Video ads on YouTube (best for video content, brand storytelling, product demos)

**Returns:**
- What the campaign type is best for
- Step-by-step workflow with which tools to call
- Requirements (character limits, assets needed, etc.)
- Natural follow-up question to start the creation flow

**Do NOT use this tool if:**
- User specifically asks for "search campaign" or "text ads" → go directly to research_keywords then create_search_campaign
- User specifically asks for "PMax" or "Performance Max" → go directly to discover_existing_assets then create_pmax_campaign
- User specifically asks for "YouTube campaign" or "video campaign" → go directly to validate_video then create_youtube_campaign
- User is asking about performance/analytics → use get_campaign_performance
- User is asking about existing campaigns → use list_campaigns

**Parameters:**
- campaign_type: 'search', 'pmax', or 'youtube'

**Execution time:** Instant (no API call)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_type` | `string` | yes | Type of Google Ads campaign to create. Options: 'search' (text ads in search results), 'pmax' (Performance Max across all Google channels), 'youtube' (video ads on YouTube) |

### Example request

```json
{
  "arguments": {
    "campaign_type": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for select_google_campaign_type)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "select_google_campaign_type"
}
```

---
## suggest_ad_content

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/suggest_ad_content/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Generate AI-suggested headlines and descriptions based on campaign keywords.

Analyzes campaign keywords and generates optimized ad content following creative guidelines:
- 15 headlines across 3 categories (What It Is, Benefits, Pain/Proof/CTA)
- 4 descriptions with value props and CTAs
- All validated for character limits

**Parameters:**
- campaign_id: The campaign to analyze (REQUIRED)
- business_description: Optional context about the business
- key_benefits: Optional list of benefits (e.g., ["fast setup", "no dashboard"])
- proof_points: Optional proof points (e.g., [{"number": "2000+", "metric": "campaigns"}])
- pain_points: Optional pain points (e.g., ["manual ads", "complex UI"])
- customer_id: Optional

**Returns:**
- suggested_headlines: 15 headlines ready to use
- suggested_descriptions: 4 descriptions ready to use
- headline_categories: Headlines organized by category
- keyword_themes: Top themes from campaign keywords

**Use this when:**
- User wants fresh ad copy ideas
- User asks "what should my headlines be?"
- Starting a new A/B test
- Refreshing stale ad content

**Execution time:** 3-5 seconds

**Example:**
User: "Suggest some better headlines for my campaign"
Agent:
1. Uses suggest_ad_content to generate ideas
2. Shows suggestions organized by category
3. User picks favorites
4. Uses update_ad_headlines to apply selected headlines

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to analyze for keyword themes. Use list_campaigns first. |
| `business_description` | `object` | no | Optional: Description of what the business does. Helps generate better suggestions. |
| `key_benefits` | `object` | no | Optional: List of key benefits (e.g., ['30 second launch', 'no dashboard required']). |
| `proof_points` | `object` | no | Optional: Proof points. Each with 'number' and 'metric' (e.g., {'number': '2000+', 'metric': 'campaigns'}). |
| `pain_points` | `object` | no | Optional: Pain points customers face (e.g., ['manual ads', 'complex UI']). |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "business_description": "string",
    "key_benefits": [
      "string"
    ],
    "proof_points": [
      "5-star rated on Trustpilot (4,200 reviews)",
      "Free shipping over $50"
    ],
    "pain_points": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for suggest_ad_content)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "suggest_ad_content"
}
```

---
## update_ad_content

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_ad_content/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Combined update for ad content (headlines, descriptions, and/or final URLs).

Use this for efficiency when updating multiple ad elements at once.
Single API call with combined field mask.

**Parameters:**
- ad_id: The ad ID to update (REQUIRED)
- ad_group_id: The ad group ID (REQUIRED)
- headlines: Optional list of 3-15 headlines (max 30 chars each)
- descriptions: Optional list of 2-4 descriptions (max 90 chars each)
- final_urls: Optional list of landing page URLs
- customer_id: Optional

At least one of headlines, descriptions, or final_urls is required.

**Execution time:** 2-4 seconds

**Example:**
User: "Update both headlines and descriptions for my ad"
Agent: Uses update_ad_content with both headlines and descriptions in one call

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_id` | `string` | yes | The ad ID to update. Get from get_campaign_structure. |
| `ad_group_id` | `string` | yes | The ad group ID the ad belongs to. Get from get_campaign_structure. |
| `headlines` | `object` | no | Optional: 3-15 headlines, each max 30 characters. |
| `descriptions` | `object` | no | Optional: 2-4 descriptions, each max 90 characters. |
| `final_urls` | `object` | no | Optional: Landing page URLs. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_id": "string",
    "ad_group_id": "string",
    "headlines": [
      "string"
    ],
    "descriptions": [
      "string"
    ],
    "final_urls": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_ad_content)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_ad_content"
}
```

---
## update_ad_descriptions

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_ad_descriptions/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Update descriptions for a Responsive Search Ad (RSA).

**Parameters:**
- ad_id: The ad ID to update (REQUIRED - get from get_campaign_structure)
- ad_group_id: The ad group ID (REQUIRED)
- descriptions: List of 2-4 descriptions (REQUIRED)
- customer_id: Optional

**Description Rules:**
- Max 90 characters per description
- 2-4 descriptions required
- Descriptions should expand on headlines
- Include specific details and CTAs
- Formula: [Value Prop]. [Specific Detail]. [CTA].

**Execution time:** 2-4 seconds

**WARNING:** Updated ads go through Google's review process.

**Example:**
User: "Make my ad descriptions more action-oriented"
Agent:
1. Uses get_campaign_structure to find ad_id
2. Shows current descriptions
3. Prepares new descriptions with CTAs
4. Gets approval and updates

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_id` | `string` | yes | The ad ID to update. Get from get_campaign_structure. |
| `ad_group_id` | `string` | yes | The ad group ID the ad belongs to. Get from get_campaign_structure. |
| `descriptions` | `array` | yes | List of 2-4 descriptions. Each description max 90 characters. Descriptions should expand on headlines and include CTAs. |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_id": "string",
    "ad_group_id": "string",
    "descriptions": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_ad_descriptions)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_ad_descriptions"
}
```

---
## update_ad_headlines

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_ad_headlines/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Update headlines for a Responsive Search Ad (RSA).

**MANDATORY WORKFLOW:**
1. Call `get_campaign_structure` to find ad_id and ad_group_id
2. Review current headlines to understand what's there
3. Prepare 3-15 new headlines following the category system:
   - Category A (5): What It Is (product/feature identity)
   - Category B (6): Benefits/Outcomes
   - Category C (4): Pain Points, Proof & CTAs
4. Validate headlines (max 30 chars each, no "!" marks, no unverified superlatives)
5. Get user approval before updating
6. Call this tool with ad_id, ad_group_id, and headlines

**Headline Rules (from creative guidelines):**
- Max 30 characters per headline
- 3-15 headlines required
- Headlines should work in ANY combination
- Avoid exclamation marks (may trigger Google disapproval)
- Avoid unverified superlatives ("Best", "#1", "Leading", "Top")
- Any random pair should make logical sense together

**Parameters:**
- ad_id: The ad ID to update (REQUIRED)
- ad_group_id: The ad group ID (REQUIRED)
- headlines: List of 3-15 headlines (REQUIRED)
- customer_id: Optional

**Execution time:** 2-4 seconds

**WARNING:** Updated ads go through Google's review process (typically 24-48 hours).

**Example:**
User: "Update my ad headlines to be more compelling"
Agent:
1. Uses get_campaign_structure to find ad_id and ad_group_id
2. Shows current headlines to user
3. Prepares new headlines across all 3 categories
4. Gets user approval
5. Uses update_ad_headlines with approved headlines

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_id` | `string` | yes | The ad ID to update. Get from get_campaign_structure (look in ad_groups -> ads section). |
| `ad_group_id` | `string` | yes | The ad group ID the ad belongs to. Get from get_campaign_structure. |
| `headlines` | `array` | yes | List of 3-15 headlines. Each headline max 30 characters. Headlines should work well in any combination. Avoid exclamation marks and unverified superlatives (Best, #1, etc.). |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_id": "string",
    "ad_group_id": "string",
    "headlines": [
      "string"
    ],
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_ad_headlines)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_ad_headlines"
}
```

---
## update_bid_strategy

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_bid_strategy/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Change campaign bidding strategy.

**Available Strategies:**
- MAXIMIZE_CLICKS: Get as many clicks as possible within budget
- MAXIMIZE_CONVERSIONS: Get as many conversions as possible
- TARGET_CPA: Set a target cost per acquisition (requires target_cpa)
- TARGET_ROAS: Set a target return on ad spend (requires target_roas)

**Parameters:**
- campaign_id: The campaign ID (REQUIRED)
- strategy: MAXIMIZE_CLICKS, MAXIMIZE_CONVERSIONS, TARGET_CPA, or TARGET_ROAS (REQUIRED)
- target_cpa: Required if strategy is TARGET_CPA. In dollars, e.g., 25.00 for $25 CPA
- target_roas: Required if strategy is TARGET_ROAS. Multiplier, e.g., 4.0 for 400% ROAS
- customer_id: Optional (uses connected account if omitted)

**IMPORTANT:**
- TARGET_CPA requires conversion tracking to be set up
- TARGET_ROAS requires conversion value tracking
- Changing strategy may take 1-2 weeks to stabilize performance
- Confirm with user before changing

**Execution time:** 2-4 seconds

**Example:**
User: "I want to target $30 CPA"
Agent:
1. Confirms: "I'll change the bidding strategy to TARGET_CPA with a $30 target. This may take 1-2 weeks to optimize. Proceed?"
2. Uses update_bid_strategy with strategy=TARGET_CPA, target_cpa=30.00

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to update. Use list_campaigns first to get available campaign IDs. |
| `strategy` | `string` | yes | Bidding strategy: MAXIMIZE_CLICKS, MAXIMIZE_CONVERSIONS, MAXIMIZE_CONVERSION_VALUE, TARGET_CPA, or TARGET_ROAS |
| `target_cpa` | `object` | no | Target CPA in dollars (required if strategy is TARGET_CPA). Example: 25.00 for $25 CPA |
| `target_roas` | `object` | no | Target ROAS multiplier (required if strategy is TARGET_ROAS). Example: 4.0 for 400% ROAS |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "strategy": "string",
    "target_cpa": 1.0,
    "target_roas": 1.0,
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_bid_strategy)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_bid_strategy"
}
```

---
## update_campaign

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_campaign/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Update an existing campaign's settings.

**Can update:**
- name: Campaign name
- status: ENABLED or PAUSED
- budget_amount: Daily budget in dollars

**IMPORTANT:**
- Confirm with user BEFORE making changes
- Use list_campaigns first to get campaign_id
- At least one field (name, status, or budget_amount) is required

**Parameters:**
- campaign_id: The campaign ID (REQUIRED - get from list_campaigns)
- name: New campaign name (optional)
- status: ENABLED or PAUSED (optional)
- budget_amount: Daily budget in dollars, e.g., 50.00 for $50/day (optional)
- customer_id: Optional (uses connected account if omitted)

**Execution time:** 2-4 seconds

**Example:**
User: "Increase the budget to $100/day"
Agent:
1. Uses list_campaigns to find the campaign
2. Confirms with user: "I'll update 'Summer Sale' budget from $50 to $100/day. Proceed?"
3. Uses update_campaign with campaign_id and budget_amount=100.00

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The campaign ID to update. Use list_campaigns first to get available campaign IDs. |
| `name` | `object` | no | New campaign name (optional) |
| `status` | `object` | no | New status: ENABLED or PAUSED (optional) |
| `budget_amount` | `object` | no | New daily budget in dollars, e.g., 50.00 for $50/day (optional) |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "name": "string",
    "status": "string",
    "budget_amount": 1.0,
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_campaign"
}
```

---
## update_keyword

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_keyword/execute`

🚨 **IF THIS TOOL RETURNS A QUOTA ERROR:**
- The error message will include a clickable upgrade link
- Show the FULL error message to the user (it contains the upgrade link)
- DO NOT attempt to work around the error or use alternative data
- DO NOT create campaigns or perform actions without valid tool data
- STOP and direct the user to upgrade via the provided link

Update keyword bid or status.

**Parameters:**
- keyword_id: The keyword to update (REQUIRED - get from get_campaign_structure)
- ad_group_id: The ad group containing the keyword (REQUIRED)
- cpc_bid_micros: New bid in micros (1 USD = 1,000,000). Example: 2500000 = $2.50
- status: ENABLED or PAUSED
- customer_id: Optional

**Can update:**
- Bid amount (cpc_bid_micros)
- Status (ENABLED/PAUSED)

**CANNOT update (immutable):**
- Keyword text
- Match type

**Execution time:** 2-3 seconds

**Example:**
User: "Increase the bid on 'running shoes' to $3"
Agent:
1. Uses get_campaign_structure to find the keyword_id
2. Uses update_keyword with cpc_bid_micros=3000000

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `keyword_id` | `string` | yes | The keyword ID to update. Use get_campaign_structure to find this. |
| `ad_group_id` | `string` | yes | The ad group ID the keyword belongs to. |
| `cpc_bid_micros` | `object` | no | New bid in micros (1 USD = 1,000,000 micros). Example: 2500000 for $2.50 |
| `status` | `object` | no | New status: ENABLED or PAUSED |
| `customer_id` | `object` | no | Google Ads customer ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "keyword_id": "string",
    "ad_group_id": "string",
    "cpc_bid_micros": 1,
    "status": "string",
    "customer_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_keyword)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_keyword"
}
```

---
## validate_and_prepare_assets

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/validate_and_prepare_assets/execute`

🔄 LONG-RUNNING TOOL: Validates multiple images from URLs for Performance Max campaigns. Emits MCP progress updates while downloading and validating 5-10 images (typically 5-15 seconds). Progress stages: download → validate → commit.

⚠️ CRITICAL: This tool accepts IMAGE URLS (from postimages.org), NOT base64 or file paths!

📋 **YOUR CRITICAL ROLE: URL Validator & Categorizer**

**STEP 1: Call help_user_upload First**

Before anything else:
- Call help_user_upload() to show upload instructions
- User will upload to postimages.org and paste Direct links
- User provides links like: https://i.postimg.cc/ABC123/image.jpg

**STEP 2: Categorize URLs by Examining Images**

When user pastes URLs, YOU MUST:
1. **Request to view each image** (you can see images from URLs)
2. **Calculate aspect ratio** for each: width / height
3. **Categorize based on ratio**:
   - Landscape (1.8-2.0): ~1.91:1 like 1200×628px
   - Square (0.95-1.05): ~1:1 like 1200×1200px
   - Portrait (0.75-0.85): ~0.8:1 like 960×1200px
   - Logo: Small images or very wide (4:1)
4. **Verify minimum requirements**:
   - At least 1 landscape
   - At least 1 square (non-logo)
   - At least 1 logo
5. If missing types, ask user to upload more

2. **Tell them what images they need:**
   - **REQUIRED:**
     - At least 1 landscape image (1.91:1 ratio, like 1200×628px or 600×314px)
     - At least 1 square image (1:1 ratio, like 1200×1200px or 300×300px)
     - At least 1 square logo (1:1 ratio, min 128×128px, recommended 1200×1200px)
   - **OPTIONAL:**
     - Portrait images (4:5 ratio, like 960×1200px or 400×600px)
     - Landscape logos (4:1 ratio, like 1200×300px or 512×128px)

3. **Provide OS-specific help if user asks how to check/resize images:**
   - **Mac users:**
     - "Right-click image → Get Info → shows dimensions"
     - "Use Preview app → Tools → Adjust Size to resize"
     - "Recommended free tool: GIMP (gimp.org) for precise aspect ratio control"

   - **Windows users:**
     - "Right-click image → Properties → Details tab → shows dimensions"
     - "Use Paint → Resize → maintain aspect ratio checkbox"
     - "Recommended free tool: GIMP (gimp.org) or Paint.NET"

   - **Online tools (any OS):**
     - "Canva.com (free, easy templates for exact sizes)"
     - "Photopea.com (free Photoshop alternative)"
     - "ResizePixel.com (quick resize tool)"

4. **CRITICAL: Read, Examine, and Categorize Images BEFORE Calling This Tool:**

   **Step 4a: READ FILE CONTENTS (REQUIRED!)**
   - When user uploads images, ChatGPT stores them as files with paths like `/mnt/data/image.png`
   - **YOU MUST READ EACH FILE** to get the actual image data
   - **DO NOT** send file paths or FILE:file_xxx references to this tool
   - **DO NOT** send data URIs with FILE: references like `data:image/png;base64,FILE:file_xxx`
   - **YOU MUST** read the file contents using your file reading capabilities
   - Convert file contents to base64 string
   - Send actual base64 encoded data (the binary image bytes encoded as base64)

   **Step 4b: EXAMINE AND CATEGORIZE**
   - **USE EXTENDED THINKING** to analyze each image
   - When you read the image file, you can see its dimensions
   - Calculate aspect ratio: width / height
   - Categorize based on aspect ratio:
     * **Landscape** (1.80-2.00 ratio): ~1.91:1 images like 1200×628px
     * **Square** (0.95-1.05 ratio): ~1:1 images like 1200×1200px
     * **Portrait** (0.75-0.85 ratio): ~0.8:1 (4:5) images like 960×1200px
     * **Logo Square** (0.95-1.05 ratio + small size <500px): Small square logos
     * **Logo Landscape** (3.80-4.20 ratio): ~4:1 images like 1200×300px

   **Step 4c: VERIFY MINIMUM REQUIREMENTS**
   - Count images in each category
   - **REQUIRED MINIMUM**:
     * At least 1 landscape image
     * At least 1 square image (non-logo)
     * At least 1 logo (square or landscape)
   - If missing ANY required type:
     * Tell user: "You need at least 1 landscape, 1 square, and 1 logo image"
     * Ask user to upload missing types
     * DO NOT call this tool yet

**STEP 2: Call This Tool with ACTUAL Base64 Data**

⚠️ **CRITICAL - Data Format**:
```python
# ❌ WRONG - DO NOT SEND:
"data:image/png;base64,FILE:file_xxx"  # File reference - will fail!
"/mnt/data/image.png"  # File path - will fail!

# ✅ CORRECT - SEND THIS:
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."  # Actual base64 encoded image bytes
# OR just the base64 string without prefix:
"iVBORw0KGgoAAAANSUhEUgAA..."  # Also works
```

**How to get actual base64**:
1. Read the uploaded file from its path
2. Get the file contents (binary data)
3. Encode to base64 string
4. Pass to this tool

**Example**:
- User uploads `image.png`
- ChatGPT stores as `/mnt/data/image123.png`
- You read the file → get binary PNG data
- You encode to base64 → get "iVBORw0K..."
- You send to tool: `marketing_images_landscape: ["iVBORw0K..."]`

**IMPORTANT**:
- The tool needs ACTUAL image data to validate dimensions and aspect ratios
- File references cannot be validated
- You must convert files to base64 before calling this tool

**Image Requirements (Google Ads PMax specs):**

| Type | Aspect Ratio | Min Size | Recommended | Max File Size |
|------|--------------|----------|-------------|---------------|
| Landscape | 1.91:1 | 600×314px | 1200×628px | 5MB |
| Square | 1:1 | 300×300px | 1200×1200px | 5MB |
| Portrait | 4:5 (0.8:1) | 400×600px | 960×1200px | 5MB |
| Logo Square | 1:1 | 128×128px | 1200×1200px | 5MB |
| Logo Landscape | 4:1 | 512×128px | 1200×300px | 5MB |

- **Tolerance:** ±2% aspect ratio deviation allowed
- **Formats:** JPEG, PNG, WEBP only

**STEP 3: Pre-Flight Checks (BEFORE Calling Tool)**

**Check #1: Count Images**
- Count how many images you categorized in each type
- **REQUIRED MINIMUM**:
  * 1+ landscape images
  * 1+ square images (non-logo)
  * 1+ logo images
- If missing ANY required type:
  * "I see you uploaded [N] images, but I need at least 1 landscape, 1 square, and 1 logo."
  * "Please upload [missing type] image(s)."
  * DO NOT call this tool yet
  * Wait for user to upload more images

**Check #2: Verify Categorization**
- Use extended thinking to double-check your categorization
- Example thinking:
  * "Image 1: 1200×628 = 1.91 ratio → landscape ✓"
  * "Image 2: 1200×1200 = 1.0 ratio, large size → square marketing image ✓"
  * "Image 3: 200×200 = 1.0 ratio, small size → logo ✓"
- If uncertain about categorization, explain to user and ask for clarification

**STEP 4: Handle Validation Results**

**If validation FAILS:**
- Show the user the EXACT error message from this tool
- Explain what the error means in simple terms
- Provide step-by-step instructions to fix:
  - How to check current dimensions
  - What the correct dimensions should be
  - How to resize the image
  - How to re-upload

**If validation SUCCEEDS:**
- Celebrate! ✅
- Show the asset_bundle_id to the user
- Tell them the bundle is valid for 1 hour
- Move to collecting campaign text details (name, headlines, etc.)
- DO NOT call create_pmax_campaign until you have ALL text details

**Returns:**
- SUCCESS: asset_bundle_id (UUID) + summary of validated images
- FAILURE: Specific error for the first invalid image + instructions to fix

**Common Validation Errors & How to Help:**

1. **Wrong aspect ratio**
   - Error: "Expected 1.91:1, got 2.0:1"
   - Help: "Your image is slightly too wide. Crop it to 1200×628px exactly."

2. **Image too small**
   - Error: "Minimum size is 600×314px, got 500×250px"
   - Help: "Use a higher resolution image or upscale this one."

3. **File too large**
   - Error: "File too large. Maximum 5 MB, got 6.2 MB"
   - Help: "Compress the image using an online tool or save as JPEG with lower quality."

4. **Wrong format**
   - Error: "Unsupported format 'BMP'"
   - Help: "Convert to JPEG or PNG format."

**Example User Flow:**

User: "I want to create a PMax campaign"
You: "Great! Let's start by uploading your images. You'll need:
- At least 1 landscape image (1200×628px recommended)
- At least 1 square image (1200×1200px recommended)
- At least 1 square logo (1200×1200px recommended)

Click the paperclip icon (📎) to attach your images."

[User uploads images]

You: "Perfect! I can see your images. Let me validate them..."
[Call validate_and_prepare_assets]

**Execution Time:** 1-3 seconds (fast validation)

**Authentication:** NOT required for this tool (validation only)

**REMEMBER:**
- Be extremely patient and helpful with image validation
- Provide clear, actionable instructions for fixing errors
- Never proceed to create_pmax_campaign without a valid asset_bundle_id
- The bundle expires in 1 hour - if expired, re-validate images

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `marketing_images_landscape` | `array` | yes | Landscape image URLs (1.91:1 ratio, min 600×314px). Direct HTTPS links from postimages.org ending in .jpg/.png/.webp. Minimum 1 required. |
| `marketing_images_square` | `array` | yes | Square image URLs (1:1 ratio, min 300×300px). Direct HTTPS links ending in .jpg/.png/.webp. Minimum 1 required. |
| `marketing_images_portrait` | `object` | no | Portrait image URLs (4:5 ratio = 0.8:1, min 400×600px). Direct HTTPS links. Optional. |
| `logos_square` | `array` | yes | Square logo URLs (1:1 ratio, min 128×128px). Direct HTTPS links. Minimum 1 required. |
| `logos_landscape` | `object` | no | Landscape logo URLs (4:1 ratio, min 512×128px). Direct HTTPS links. Optional. |

### Example request

```json
{
  "arguments": {
    "marketing_images_landscape": [
      "string"
    ],
    "marketing_images_square": [
      "string"
    ],
    "logos_square": [
      "string"
    ],
    "marketing_images_portrait": [
      "string"
    ],
    "logos_landscape": [
      "string"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for validate_and_prepare_assets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "validate_and_prepare_assets"
}
```

---
## validate_video

**Platform:** Google Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/validate_video/execute`

Validate video for ad campaigns (unified tool for all platforms).

⚠️ IMPORTANT: This is a READ-ONLY validation tool. Safe to call multiple times.

🎯 **What This Tool Does:**
- Validates videos for Google Ads PMAX or TikTok campaigns
- For PMAX: Validates YouTube video (privacy, duration, embeddable)
- For TikTok: Validates public video URL is accessible
- Returns metadata (title, duration, privacy, thumbnail)

**DOES NOT upload videos** - user must already have video uploaded:
- PMAX: Video must be on YouTube (public or unlisted)
- TikTok: Video must be on public hosting (Google Drive, Vimeo, etc.)

**Parameters:**
- video_url_or_id (required): YouTube URL/ID (PMAX) or public video URL (TikTok)
- platform (required): 'pmax' or 'tiktok'

**For PMAX:**
Accepts YouTube formats:
- Full URL: https://youtube.com/watch?v=dQw4w9WgXcQ
- Short URL: https://youtu.be/dQw4w9WgXcQ
- Shorts: https://youtube.com/shorts/dQw4w9WgXcQ
- Direct ID: dQw4w9WgXcQ (11 characters)

Validates:
- Video exists and is accessible
- Privacy is Public or Unlisted (NOT Private)
- Embeddable is enabled
- Duration ≥10 seconds (PMAX requirement)

Returns:
- Video title
- Duration
- Privacy status
- Thumbnail URL
- Video ID

**For TikTok:**
Accepts public video file URLs:
- Google Drive: https://drive.google.com/file/d/ABC123/view
- Vimeo: https://vimeo.com/video/123456
- Dropbox: https://dropbox.com/s/abc/video.mp4
- Any publicly accessible video URL

Validates:
- URL is accessible (HTTP 200)
- Content-Type is video/* (when available)

Returns:
- URL validation status
- Content-Type
- File size (if available)

**Execution Time:** 1-3 seconds (YouTube Data API call or HTTP request)

**When to Use:**
- BEFORE creating a campaign with videos
- To verify video meets platform requirements
- To get video metadata (title, duration)

**Example Usage:**

```
User: "I want to use this YouTube video in my PMAX campaign: dQw4w9WgXcQ"
YOU: [Call validate_video with video_url_or_id="dQw4w9WgXcQ", platform="pmax"]
Response: Video validated, title="Product Demo", duration=45s, ready for use
```

```
User: "Can I use this video for TikTok ads: https://drive.google.com/file/d/ABC/view"
YOU: [Call validate_video with video_url_or_id="https://drive...", platform="tiktok"]
Response: URL validated, accessible, ready for campaign
```

**Error Handling:**
- Private videos: Clear error asking user to change privacy to Public/Unlisted
- Short videos (<10s for PMAX): Error with duration requirement
- Invalid URLs: Error with accessibility details
- Not embeddable: Error asking user to enable embedding

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `video_url_or_id` | `string` | yes | YouTube video URL/ID (for PMAX) OR public video file URL (for TikTok). Examples: 'dQw4w9WgXcQ' (YouTube ID), 'https://youtube.com/watch?v=dQw4w9WgXcQ' (YouTube URL), 'https://youtu.be/dQw4w9WgXcQ' (YouTube short URL), 'https://drive.google. |
| `platform` | `string` | yes | Target platform: 'pmax' or 'tiktok'. Determines validation rules and requirements. 'pmax' = Google Ads Performance Max (requires YouTube video). 'tiktok' = TikTok Ads (requires public video URL). |

### Example request

```json
{
  "arguments": {
    "video_url_or_id": "https://example.com",
    "platform": "https://example.com"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for validate_video)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "validate_video"
}
```

---
# LinkedIn Ads

## add_linkedin_campaign_to_group

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_linkedin_campaign_to_group/execute`

**USE THIS TOOL WHEN:** User wants to add a new campaign with DIFFERENT targeting/audience to an EXISTING campaign group. This is the LinkedIn equivalent of Meta's add_ad_set.

LinkedIn hierarchy: Campaign Group → Campaign → Creative.
- Different audiences = different Campaigns in the SAME Campaign Group.
- Different creatives = different Creatives in the SAME Campaign.

**Common scenarios:**
- Audience testing: Same ad format, different targeting per campaign
- Geographic split: Same creative, different locations per campaign
- Multi-market: Same product, different countries
- Scaling: Adding new audiences to existing campaign group

**CRITICAL:** Each create_linkedin_*_campaign creates a new Campaign Group (unless campaign_group_id is passed).
To add more campaigns to the SAME group, you MUST use this tool or pass campaign_group_id to create tools.
NEVER call create_linkedin_*_campaign without campaign_group_id for the same group.

**KEY DISTINCTION:**
- Different audience/targeting → use THIS tool (new campaign in same group)
- Different copy/creative, same audience → use add_linkedin_creative / add_linkedin_video_creative instead

**Supports all 4 ad types:** image, video, carousel, text.
Each campaign has INDEPENDENT: targeting, budget, schedule.
Shared from campaign group: objective, group name.

**Workflow:**
1. Create initial campaign → get campaign_group_id + campaign_id
2. Call this tool with campaign_group_id for each additional audience
3. Use add_linkedin_creative to add more creatives within any campaign

Execution time: 15-120 seconds (depends on ad type)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_group_id` | `string` | yes | Existing Campaign Group ID to add this campaign to. Get this from a prior create_linkedin_*_campaign response. Format: numeric ID or urn:li:sponsoredCampaignGroup:XXXXX. |
| `campaign_name` | `string` | yes | Name for this new campaign (auto-suffixed with timestamp). |
| `daily_budget` | `number` | yes | Daily budget for this campaign in account currency (minimum $10). |
| `organization_id` | `string` | yes | LinkedIn Organization (Company Page) ID for ad authoring. |
| `ad_type` | `string` | yes | Type of ad for this campaign: 'image', 'video', 'carousel', or 'text'. |
| `image_urn` | `object` | no | Image URN for image ads. From discover_linkedin_assets or validate_and_prepare_linkedin_assets. |
| `asset_bundle_id` | `object` | no | Asset bundle ID from validate_and_prepare_linkedin_assets (for new image uploads). |
| `video_urn` | `object` | no | Video URN for video ads. From discover_linkedin_assets. |
| `video_url` | `object` | no | Public URL to upload video from (MP4, max 500MB). |
| `cards` | `object` | no | Carousel cards (2-10). Each: {image_urn or image_url, headline (max 45), landing_page_url}. |
| `text_headline` | `object` | no | Text ad headline (max 25 chars). Required for ad_type='text'. |
| `text_description` | `object` | no | Text ad description (max 75 chars). Required for ad_type='text'. |
| `introductory_text` | `object` | no | Main ad text (up to 600 chars for image/video, 255 for carousel). Required for image, video, and carousel ad types. |
| `headline` | `object` | no | Ad headline (up to 70 chars). Required for image and video ad types. |
| `landing_page_url` | `string` | yes | Destination URL (must be HTTPS). |
| `call_to_action` | `object` | no | CTA button label. Default: LEARN_MORE. |
| `objective` | `object` | no | Campaign objective. If not provided, inherits from the campaign group's objective. Options: BRAND_AWARENESS, ENGAGEMENT, WEBSITE_VISIT, WEBSITE_CONVERSION, VIDEO_VIEW, JOB_APPLICANT. |
| `locations` | `array` | yes | Location URNs for this campaign's targeting. Required. |
| `industries` | `object` | no | Industry URNs. |
| `seniorities` | `object` | no | Seniority URNs. |
| `job_titles` | `object` | no | Job title URNs. |
| `company_sizes` | `object` | no | Company size URNs. |
| `skills` | `object` | no | Skill URNs. |
| `job_functions` | `object` | no | Job function URNs. |
| `interests` | `object` | no | Interest URNs. |
| `degrees` | `object` | no | Degree URNs. |
| `fields_of_study` | `object` | no | Field of study URNs. |
| `employers` | `object` | no | Employer URNs. |
| `member_groups` | `object` | no | LinkedIn group URNs. |
| `age_ranges` | `object` | no | Age range URNs. |
| `genders` | `object` | no | Gender URNs. |
| `schools` | `object` | no | School URNs. |
| `member_behaviors` | `object` | no | Member behavior URNs. |
| `years_of_experience` | `object` | no | Years of experience URNs. |
| `followed_companies` | `object` | no | Followed company URNs. |
| `buyer_groups` | `object` | no | Buyer group URNs (API 2026-03+). |
| `account_id` | `object` | no | LinkedIn Ad Account ID (optional). |
| `currency` | `object` | no | Currency code. Default: USD. |
| `status` | `object` | no | Campaign status. Default: PAUSED. |
| `creative_name` | `object` | no | Name for the first creative. E.g. 'US Audience - Ad 1'. |

### Example request

```json
{
  "arguments": {
    "campaign_group_id": "string",
    "campaign_name": "string",
    "daily_budget": 10.0,
    "organization_id": "string",
    "ad_type": "string",
    "landing_page_url": "https://example.com",
    "locations": [
      "string"
    ],
    "image_urn": "string",
    "asset_bundle_id": "string",
    "video_urn": "string",
    "video_url": "string",
    "cards": [
      {
        "headline": "Download our whitepaper",
        "description": "Actionable insights for 2026",
        "landing_page_url": "https://example.com/whitepaper",
        "image_url": "https://example.com/cover.jpg"
      }
    ],
    "text_headline": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_linkedin_campaign_to_group)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_linkedin_campaign_to_group"
}
```

---
## add_linkedin_carousel_creative

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_linkedin_carousel_creative/execute`

User wants to add another carousel ad variation to an existing carousel campaign.

Add a new carousel creative to a carousel campaign for A/B testing.

LinkedIn recommends 4+ carousel ad variations per campaign for optimal performance.

**DO NOT USE to create a new campaign.** For new campaigns:
- Image → create_linkedin_image_campaign
- Video → create_linkedin_video_campaign
- Carousel → create_linkedin_carousel_campaign
- Text → create_linkedin_text_campaign

**DO NOT USE for different audiences.** For different targeting:
→ Use add_linkedin_campaign_to_group instead.

Required Parameters:
- campaign_id: Carousel campaign to add creative to
- organization_id: Organization for ad authoring
- cards: Array of 2-10 carousel cards (image_urn/url, headline, landing_page_url)
- introductory_text: Main ad text (max 255 chars)
- landing_page_url: Overall landing page URL

Optional Parameters:
- creative_name: Descriptive name (e.g. "Product Showcase - Carousel Ad 2")
- call_to_action: CTA label (default: LEARN_MORE)
- account_id: LinkedIn Ad Account ID

AD POLICY: NEVER use "LinkedIn" in ad copy.

Execution time: 5-15 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Carousel campaign ID to add creative to. |
| `organization_id` | `string` | yes | Organization ID for ad authoring. |
| `account_id` | `object` | no | LinkedIn Ad Account ID (optional, auto-resolved if omitted). |
| `cards` | `array` | yes | Carousel cards (2-10). Each card: {image_urn or image_url, headline (max 45 chars), landing_page_url}. |
| `introductory_text` | `string` | yes | Main ad text (up to 255 characters for carousel ads). |
| `landing_page_url` | `string` | yes | Overall carousel landing page URL (must be HTTPS). |
| `call_to_action` | `object` | no | CTA button label. Default: LEARN_MORE. |
| `creative_name` | `object` | no | Name for this creative. E.g. 'Product Showcase - Carousel Ad 2'. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "organization_id": "string",
    "cards": [
      {
        "headline": "Our product family",
        "description": "Explore the full lineup",
        "landing_page_url": "https://example.com/products",
        "image_url": "https://example.com/family.jpg"
      }
    ],
    "introductory_text": "string",
    "landing_page_url": "https://example.com",
    "account_id": "string",
    "call_to_action": "LEARN_MORE",
    "creative_name": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_linkedin_carousel_creative)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_linkedin_carousel_creative"
}
```

---
## add_linkedin_creative

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_linkedin_creative/execute`

**USE THIS TOOL WHEN:** User wants to add another IMAGE ad/creative to an EXISTING LinkedIn image campaign.

**DO NOT USE to create a new campaign.** For new campaigns:
- Image → `create_linkedin_image_campaign`
- Video → `create_linkedin_video_campaign`
- Carousel → `create_linkedin_carousel_campaign`
- Text → `create_linkedin_text_campaign`

**DO NOT USE for different audiences/targeting.** For different audiences:
→ Use `add_linkedin_campaign_to_group` instead (creates a new campaign in the same group).

**KEY DISTINCTION:**
- Same audience, different creative/copy → use THIS tool
- Different audience/targeting → use `add_linkedin_campaign_to_group` instead

**Common scenarios:**
- A/B test ad copy: Different headlines, introductory text, or CTAs
- A/B test images: Different images for same audience
- LinkedIn recommends 4+ ads per campaign for optimal performance

Required Parameters:
- campaign_id: Campaign to add creative to
- organization_id: Organization for ad authoring
- image_urn: Image asset URN
- introductory_text: Main ad copy
- landing_page_url: Destination URL

AD POLICY: NEVER use "LinkedIn" in ad copy. NEVER mention competitor platforms.

Execution time: 5-10 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to add creative to |
| `organization_id` | `string` | yes | Organization ID for ad authoring |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `image_urn` | `string` | yes | Image asset URN from discover_linkedin_assets or validate_and_prepare_linkedin_assets |
| `introductory_text` | `string` | yes | Main ad text/copy (up to 600 characters) |
| `landing_page_url` | `string` | yes | Destination URL where users will be directed |
| `headline` | `object` | no | Ad headline (up to 70 characters) |
| `call_to_action` | `object` | no | Call-to-action button label |
| `creative_name` | `object` | no | Name for this creative (shown in Campaign Manager UI). E.g. 'Summer Sale - Ad 2' |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "organization_id": "string",
    "image_urn": "string",
    "introductory_text": "string",
    "landing_page_url": "https://example.com",
    "account_id": "string",
    "headline": "string",
    "call_to_action": "LEARN_MORE",
    "creative_name": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_linkedin_creative)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_linkedin_creative"
}
```

---
## add_linkedin_text_creative

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_linkedin_text_creative/execute`

**USE THIS TOOL WHEN:** User wants to add another text ad to an EXISTING TEXT_AD campaign.

**DO NOT USE to create a new campaign.** For new campaigns → `create_linkedin_text_campaign`.
**DO NOT USE for image/video/carousel campaigns** → use `add_linkedin_creative` or `add_linkedin_video_creative`.
**DO NOT USE for different audiences** → use `add_linkedin_campaign_to_group` instead.

**KEY DISTINCTION:**
- Same audience, different headline/description → use THIS tool
- Different audience/targeting → use `add_linkedin_campaign_to_group`

LinkedIn recommends 3-4 text ad variations per campaign.

Required Parameters:
- campaign_id: TEXT_AD campaign to add creative to
- headline: Text ad headline (max 25 chars)
- description: Text ad description (max 75 chars)
- landing_page_url: Destination URL (HTTPS)

AD POLICY: NEVER use "LinkedIn" in headline or description.

Execution time: 3-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to add the text ad creative to |
| `account_id` | `object` | no | LinkedIn Ad Account ID (optional, auto-resolved if omitted) |
| `headline` | `string` | yes | Text ad headline (max 25 characters) |
| `description` | `string` | yes | Text ad description (max 75 characters) |
| `landing_page_url` | `string` | yes | Destination URL (must start with https://) |
| `image_urn` | `object` | no | Optional 100x100 logo/image URN for the text ad (JPG/PNG, max 2MB). Improves CTR. |
| `creative_name` | `object` | no | Name for this creative (shown in Campaign Manager UI). E.g. 'B2B Leads - Ad 2' |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "headline": "string",
    "description": "string",
    "landing_page_url": "https://example.com",
    "account_id": "string",
    "image_urn": "string",
    "creative_name": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_linkedin_text_creative)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_linkedin_text_creative"
}
```

---
## add_linkedin_video_creative

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_linkedin_video_creative/execute`

**USE THIS TOOL WHEN:** User wants to add another video ad to an EXISTING LinkedIn video campaign.

**DO NOT USE to create a new campaign.** For new campaigns → `create_linkedin_video_campaign`.
**DO NOT USE for image campaigns** → use `add_linkedin_creative` instead.
**DO NOT USE for text ads** → use `add_linkedin_text_creative` instead.
**DO NOT USE for different audiences** → use `add_linkedin_campaign_to_group` instead.

**KEY DISTINCTION:**
- Same audience, different video/copy → use THIS tool
- Different audience/targeting → use `add_linkedin_campaign_to_group`

LinkedIn recommends 3-4 video ad variations per campaign.

Required Parameters:
- campaign_id: Video campaign to add creative to
- organization_id: Organization for ad authoring
- video_urn: Video asset URN (urn:li:video:...)
- introductory_text: Main ad copy (max 600 chars)
- landing_page_url: Destination URL (HTTPS)

AD POLICY: NEVER use "LinkedIn" in ad copy.

Execution time: 5-10 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to add the video creative to |
| `organization_id` | `string` | yes | Organization ID for ad authoring |
| `account_id` | `object` | no | LinkedIn Ad Account ID (optional, auto-resolved if omitted) |
| `video_urn` | `string` | yes | Video asset URN (urn:li:video:...) from discover_linkedin_assets or video upload |
| `introductory_text` | `string` | yes | Main ad text/copy (up to 600 characters) |
| `landing_page_url` | `string` | yes | Destination URL (must start with https://) |
| `headline` | `object` | no | Ad headline (up to 70 characters) |
| `call_to_action` | `object` | no | Call-to-action button label |
| `creative_name` | `object` | no | Name for this creative (shown in Campaign Manager UI). E.g. 'Product Demo - Ad 2' |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "organization_id": "string",
    "video_urn": "string",
    "introductory_text": "string",
    "landing_page_url": "https://example.com",
    "account_id": "string",
    "headline": "string",
    "call_to_action": "LEARN_MORE",
    "creative_name": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_linkedin_video_creative)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_linkedin_video_creative"
}
```

---
## analyze_linkedin_creative_performance

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/analyze_linkedin_creative_performance/execute`

User asks about ad/creative performance,
wants to identify winning/losing ad variations, or asks about creative fatigue.

Analyzes LinkedIn creative/ad performance:

Returns:
- Top performing creatives by engagement rate
- Underperforming creatives needing attention
- Creative fatigue indicators (running too long)
- Video performance (views, completion rates)
- Engagement breakdown per creative
- Recommendations for creative refresh

Creative Metrics:
- Impressions, clicks, CTR
- Engagement rate (likes, comments, shares)
- Lead generation (forms, completions)
- Video metrics (views, quartile completions)
- Days running (for fatigue detection)

Parameters:
- lookback_days: Number of days to analyze (7-120). Default: 30
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- campaign_id: Optional filter to specific campaign
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Example Prompts:
- "Which LinkedIn ads are performing best?"
- "Show me creative performance on LinkedIn"
- "Are any LinkedIn creatives fatigued?"
- "Which ads should I refresh on LinkedIn?"

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7-120). Default: 30. |
| `campaign_id` | `object` | no | Optional: Filter to specific campaign |
| `campaign_name` | `object` | no | Filter by campaign name (partial match). |
| `ad_account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "campaign_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for analyze_linkedin_creative_performance)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "analyze_linkedin_creative_performance"
}
```

---
## analyze_linkedin_wasted_spend

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/analyze_linkedin_wasted_spend/execute`

User asks about wasted ad spend, unprofitable campaigns,
where their LinkedIn budget is being wasted, or wants to identify underperformers.

Identifies LinkedIn campaigns and B2B segments wasting money:

Campaign Analysis:
- Campaigns losing money (ROAS < 1.0 = actual loss)
- Campaigns underperforming (ROAS below target = opportunity cost)
- Severity classification (CRITICAL, HIGH, MEDIUM)
- Wasted spend calculation per campaign

B2B Demographic Waste:
- Seniority levels with poor ROAS
- Industries not converting
- Company sizes wasting budget
- Job functions underperforming

Creative Waste:
- Fatigued creatives (14-day threshold)
- Low engagement creatives
- High spend but low lead creatives

Returns:
- Total wasted spend and percentage
- Campaigns to pause/reduce
- Segments to exclude
- Quick actions with monthly savings estimates

Parameters:
- lookback_days: Number of days to analyze (7-120). Default: 30
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- target_roas: Override target ROAS threshold
- include_demographic_breakdown: Include B2B segment waste. Default: true
- include_creative_analysis: Include creative-level waste. Default: true
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Example Prompts:
- "Where am I wasting money on LinkedIn?"
- "Which LinkedIn campaigns are losing money?"
- "Show me LinkedIn wasted spend"
- "Which B2B segments should I stop targeting?"
- "How can I reduce LinkedIn ad waste?"

Execution time: 4-6 seconds

**Quick Actions (IMPORTANT — read severity context first):**
- ⏳ LEARNING campaigns → Do NOT pause. Monitor for 14+ days before judging.
- ❓ INSUFFICIENT_DATA campaigns → Need more spend before analysis is meaningful.
- 🚨 CRITICAL campaigns (established, 14+ days, ZERO conversions) → Consider pausing
- 🚨 CRITICAL campaigns (established, 14+ days, HAS conversions) → Review performance, verify revenue in ad platform before reducing budget
- 🔴 HIGH severity (established, 14+ days) → Consider reducing budget by 50-70%
- 🟡 MEDIUM → Optimize targeting, ad copy, landing pages

⚠️ **NEVER say "pause" for a campaign that has conversions.** Say "review" or "reduce budget" instead.
⚠️ **NEVER recommend pausing a campaign in LEARNING phase.**
⚠️ **If ALL campaigns are LEARNING or INSUFFICIENT_DATA, tell the user their account is too new for waste analysis and recommend checking back in 2 weeks.**
⚠️ **Consider campaign objective: brand awareness campaigns will not have ROAS data. This is normal.**
⚠️ **When data confidence is MEDIUM or LOW, soften all recommendations and add verification prompts.**

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7-120). Default: 30. |
| `target_roas` | `object` | no | Override target ROAS threshold. Campaigns below this ROAS are flagged as underperforming. |
| `include_demographic_breakdown` | `boolean` | no | Include B2B demographic waste analysis (seniority, industry, company size). Default: true |
| `include_creative_analysis` | `boolean` | no | Include creative-level waste analysis (fatigue, low engagement). Default: true |
| `ad_account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "target_roas": 0.1
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for analyze_linkedin_wasted_spend)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "analyze_linkedin_wasted_spend"
}
```

---
## associate_linkedin_conversion

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/associate_linkedin_conversion/execute`

User wants to add conversion tracking to a campaign.

Associate an existing conversion with a campaign.

Parameters:
- campaign_id: Campaign to associate conversion with
- conversion_id: Conversion ID to associate

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to associate conversion with |
| `conversion_id` | `string` | yes | Conversion ID to associate |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "conversion_id": "string",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for associate_linkedin_conversion)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "associate_linkedin_conversion"
}
```

---
## batch_update_linkedin_campaigns

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/batch_update_linkedin_campaigns/execute`

User wants to update multiple LinkedIn campaigns at once (bulk operations).

Batch update status, budget, or other settings across multiple campaigns simultaneously.

Parameters:
- campaign_ids: List of campaign IDs to update (1-50, required)
- status: New status for all campaigns (ACTIVE, PAUSED, or ARCHIVED)
- daily_budget: New daily budget for all campaigns (minimum $10)
- total_budget: New total budget for all campaigns
- account_id: Optional LinkedIn Ad Account ID

Example Prompts:
- "Pause all my LinkedIn campaigns"
- "Set all campaigns to $20/day budget"
- "Archive campaigns 123, 456, and 789"
- "Activate these 5 campaigns"
- "Update budget for all my LinkedIn ads"
- "Bulk pause my campaigns"

Execution time: 5-15 seconds (depends on number of campaigns)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_ids` | `array` | yes | List of campaign IDs to update (1-50) |
| `status` | `object` | no | New status: ACTIVE, PAUSED, or ARCHIVED |
| `daily_budget` | `object` | no | New daily budget for all campaigns |
| `total_budget` | `object` | no | New total budget for all campaigns |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_ids": [
      "string"
    ],
    "status": "string",
    "daily_budget": 10.0,
    "total_budget": 1.0,
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for batch_update_linkedin_campaigns)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "batch_update_linkedin_campaigns"
}
```

---
## clone_linkedin_campaign

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/clone_linkedin_campaign/execute`

User wants to duplicate, copy, or clone a LinkedIn campaign.

Clone an existing campaign with optional overrides for name, budget, locations, and creatives.

Parameters:
- source_campaign_id: Campaign ID to clone (required)
- new_name: Name for the cloned campaign (default: '{original} - Copy')
- daily_budget: Override daily budget for the clone
- locations: Override location targeting for the clone
- status: Status for new campaign (default: PAUSED for safety)
- copy_creatives: Copy creatives from source (default: True)
- account_id: Optional LinkedIn Ad Account ID

The cloned campaign is always created as PAUSED for safety. Activate it when ready.

Example Prompts:
- "Clone my LinkedIn campaign"
- "Duplicate campaign 12345 with a new name"
- "Copy my campaign but change the budget to $30/day"
- "Create a copy of my campaign for a different region"
- "Replicate campaign with different targeting"

Execution time: 5-10 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `source_campaign_id` | `string` | yes | Campaign ID to clone |
| `new_name` | `object` | no | Name for cloned campaign (default: '{original} - Copy') |
| `daily_budget` | `object` | no | New daily budget for clone |
| `locations` | `object` | no | Override locations for cloned campaign |
| `status` | `string` | no | Status for new campaign (default PAUSED for safety) |
| `copy_creatives` | `boolean` | no | Copy creatives from source campaign |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "source_campaign_id": "<campaign_id>",
    "new_name": "string",
    "daily_budget": 10.0,
    "locations": [
      "string"
    ],
    "status": "PAUSED",
    "copy_creatives": true,
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for clone_linkedin_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "clone_linkedin_campaign"
}
```

---
## create_linkedin_carousel_campaign

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_linkedin_carousel_campaign/execute`

**USE THIS TOOL WHEN:** User wants to create a LinkedIn CAROUSEL ad campaign (2-10 swipeable image cards).

**DO NOT USE for image ads** → use `create_linkedin_image_campaign` instead.
**DO NOT USE for video ads** → use `create_linkedin_video_campaign` instead.
**DO NOT USE for text ads** → use `create_linkedin_text_campaign` instead.

**REQUIRED:** Either `campaign_group_name` (creates new group) or `campaign_group_id` (adds to existing group).

IMPORTANT: This creates the campaign with 1 creative (Variation 1). Campaign is created in PAUSED status.

Carousel Specifications:
- 2-10 cards required (3-5 is the sweet spot)
- All images must be 1080x1080 (1:1 aspect ratio)
- Each card has: image, headline (max 45 chars), landing page URL
- Introductory text max: 255 characters

Required Parameters:
- campaign_name, daily_budget, organization_id
- introductory_text (up to 255 chars), landing_page_url (overall)
- cards: Array of 2-10 cards, each with image (urn or url), headline, landing_page_url
- locations (targeting)
- campaign_group_name OR campaign_group_id

Default Objective: WEBSITE_VISIT

AD POLICY: NEVER use "LinkedIn" in ad copy. NEVER mention competitor platforms.

**After Creation — IMPORTANT:**
- This tool created 1 campaign + 1 creative. Campaign Group ID is returned.
- To add MORE carousel creatives (A/B test text): use `add_linkedin_carousel_creative` with the campaign_id
- To add MORE campaigns (different audience): use `add_linkedin_campaign_to_group` with the campaign_group_id
- NEVER call this create tool again without campaign_group_id — that creates a SEPARATE campaign group

Execution time: 15-60 seconds (includes image uploads)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (will be automatically suffixed with timestamp for uniqueness) |
| `daily_budget` | `number` | yes | Daily budget in account currency (minimum 10/day for LinkedIn). Set currency field if not USD. |
| `organization_id` | `string` | yes | LinkedIn Organization (Company Page) ID. |
| `cards` | `array` | yes | List of 2-10 carousel cards. Each card needs: image (urn or url, 1080x1080), headline (max 45 chars), landing_page_url (HTTPS). |
| `introductory_text` | `string` | yes | Main ad text (up to 255 characters for carousel ads). |
| `landing_page_url` | `string` | yes | Overall carousel landing page URL. Must be HTTPS. |
| `call_to_action` | `object` | no | Call-to-action button label. |
| `campaign_group_id` | `object` | no | Existing Campaign Group ID to add this campaign to. If provided, campaign_group_name is NOT needed. |
| `campaign_group_name` | `object` | no | Name for a NEW campaign group (REQUIRED if campaign_group_id not provided). Example: 'Q2 Carousel Ads'. Max 100 characters. |
| `objective` | `object` | no | Campaign objective. Default: WEBSITE_VISIT. Options: BRAND_AWARENESS, ENGAGEMENT, WEBSITE_VISIT, WEBSITE_CONVERSION. |
| `locations` | `array` | yes | List of LinkedIn location URNs to target. Required. |
| `industries` | `object` | no | Optional list of industry URNs. |
| `seniorities` | `object` | no | Optional list of seniority level URNs. |
| `job_titles` | `object` | no | Optional list of job title URNs. |
| `company_sizes` | `object` | no | Optional list of company size URNs. |
| `skills` | `object` | no | Optional list of skill URNs. |
| `job_functions` | `object` | no | Optional list of job function URNs. |
| `interests` | `object` | no | Optional list of interest URNs. |
| `degrees` | `object` | no | Optional list of degree URNs. |
| `fields_of_study` | `object` | no | Optional list of field of study URNs. |
| `employers` | `object` | no | Optional list of employer URNs. |
| `member_groups` | `object` | no | Optional list of LinkedIn group URNs. |
| `age_ranges` | `object` | no | Optional list of age range URNs. |
| `genders` | `object` | no | Optional list of gender URNs. |
| `schools` | `object` | no | Optional list of school URNs. |
| `member_behaviors` | `object` | no | Optional list of member behavior URNs. |
| `years_of_experience` | `object` | no | Optional list of years of experience URNs. |
| `followed_companies` | `object` | no | Optional list of followed company URNs. |
| `buyer_groups` | `object` | no | Optional list of buyer group URNs (API 2026-03+). |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `currency` | `object` | no | Currency code. Default: USD. |
| `status` | `object` | no | Initial campaign status. Default: PAUSED. |
| `locale_country` | `object` | no | Campaign locale country (ISO-3166). Default: US. |
| `locale_language` | `object` | no | Campaign locale language (ISO-639). Default: en. |
| `start_date` | `object` | no | Optional start date in ISO format. |
| `end_date` | `object` | no | Optional end date in ISO format. |
| `total_budget` | `object` | no | Optional total lifetime budget. |
| `creative_name` | `object` | no | Name for the first creative. E.g. 'Product Showcase - Carousel 1'. Defaults to '{campaign_name} - Ad 1'. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "daily_budget": 10.0,
    "organization_id": "string",
    "cards": [
      {
        "image_urn": "string",
        "image_url": "string",
        "headline": "string",
        "landing_page_url": "https://example.com"
      }
    ],
    "introductory_text": "string",
    "landing_page_url": "https://example.com",
    "locations": [
      "string"
    ],
    "call_to_action": "LEARN_MORE",
    "campaign_group_id": "string",
    "campaign_group_name": "string",
    "objective": "WEBSITE_VISIT",
    "industries": [
      "string"
    ],
    "seniorities": [
      "string"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_linkedin_carousel_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_linkedin_carousel_campaign"
}
```

---
## create_linkedin_image_campaign

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_linkedin_image_campaign/execute`

⚠️ STOP - DO NOT CALL THIS TOOL DIRECTLY!

This tool creates REAL LinkedIn campaigns that cost REAL money.
You MUST follow the MANDATORY 9-PHASE WORKFLOW below before calling this tool.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
MANDATORY 9-PHASE WORKFLOW - MUST COMPLETE ALL PHASES WITH USER CONFIRMATION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PHASE 1: DISCOVERY (required)
✓ Call `get_linkedin_organizations` to get Organization ID and Account ID
✓ Call `research_business_for_linkedin_targeting` with user's website URL
✓ Ask user about their business, products, and ideal customer

PHASE 2: TARGETING STRATEGY (required - present ALL options to user)
✓ Ask user which LOCATIONS to target (show common options: US, UK, Canada, EU, etc.)
✓ Present SENIORITY levels with recommendations:
  - Unpaid (urn:li:seniority:1), Training (urn:li:seniority:2), Entry (urn:li:seniority:3)
  - Senior (urn:li:seniority:4), Manager (urn:li:seniority:5), Director (urn:li:seniority:6)
  - VP (urn:li:seniority:7), CXO (urn:li:seniority:8), Partner (urn:li:seniority:9), Owner (urn:li:seniority:10)
  - Recommend for B2B: Manager, Director, VP, CXO
✓ Present ALL COMPANY SIZE tiers (use staffCountRange URNs):
  - 1 employee: urn:li:staffCountRange:(1,1)
  - 2-10: urn:li:staffCountRange:(2,10), 11-50: urn:li:staffCountRange:(11,50)
  - 51-200: urn:li:staffCountRange:(51,200), 201-500: urn:li:staffCountRange:(201,500)
  - 501-1000: urn:li:staffCountRange:(501,1000), 1001-5000: urn:li:staffCountRange:(1001,5000)
  - 5001-10000: urn:li:staffCountRange:(5001,10000), 10000+: urn:li:staffCountRange:(10001,2147483647)
✓ Suggest INDUSTRIES based on research
✓ GET USER CONFIRMATION on targeting before proceeding

PHASE 3: CAMPAIGN OBJECTIVE (required - explain ALL options)
✓ Call `explain_linkedin_objectives` or present this table to user:

| Objective | Best For | Cost |
|-----------|----------|------|
| WEBSITE_VISIT ⭐ | Traffic to website | $5-15 CPC |
| WEBSITE_CONVERSIONS | Sign-ups, purchases | $50-200 CPA |
| LEAD_GENERATION | LinkedIn lead forms | $30-100 CPL |
| BRAND_AWARENESS | Reach & impressions | $8-15 CPM |
| ENGAGEMENT | Likes, shares | $10-20 CPM |
| VIDEO_VIEWS | Video content | $0.05-0.20 CPV |

✓ Recommend ONE objective with reasoning based on user's business
✓ GET USER CONFIRMATION on objective

PHASE 4: BUDGET & SCHEDULE (required)
✓ Ask user for DAILY BUDGET (minimum $10, recommend $30-50 for testing)
✓ Ask for CAMPAIGN DURATION (minimum 7 days for learning phase)
✓ Ask for START DATE (immediate or scheduled)
✓ GET USER CONFIRMATION

PHASE 5: CONVERSION TRACKING (optional but recommended)
✓ Call `list_linkedin_conversions` to check existing conversions
✓ Present existing conversions to user
✓ Ask which conversions to track (or skip if none)

PHASE 6: CREATIVE ASSETS (required)
✓ Call `discover_linkedin_assets` to find existing images
✓ Present available images to user with descriptions
✓ Ask user to SELECT an existing image OR upload new one
✓ If uploading: Use `validate_and_prepare_linkedin_assets`

PHASE 7: AD CREATIVE COPY (required - create 4 variations)
✓ Call `generate_linkedin_ad_creatives` to create 4 variations:
  1. Problem-focused (pain point)
  2. Solution-focused (benefit)
  3. Social proof (results/testimonials)
  4. Curiosity/question hook
✓ Present ALL 4 variations to user
✓ Each variation needs: Introductory text, Headline, CTA
✓ GET USER APPROVAL on ad copy

PHASE 8: FINAL CONFIRMATION (required)
✓ Present COMPLETE CAMPAIGN SUMMARY:
  - Campaign name, objective, daily budget, duration
  - All targeting settings
  - Ad creative content
  - Landing page URL
  - Estimated reach and costs
✓ Ask user: "Ready to create? Type 'Yes' to proceed"
✓ DO NOT PROCEED without explicit user confirmation

PHASE 9: CREATE CAMPAIGN + ALL VARIATIONS (only after all confirmations)
✓ Call this tool with Variation 1 parameters to create the campaign
✓ Then call `add_linkedin_creative` for each remaining variation (2, 3, 4) using the campaign_id
✓ Campaign is created PAUSED by default - ask user if they want to activate

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

CRITICAL RULES:
1. NEVER skip phases - complete ALL 9 phases in order
2. NEVER assume user preferences - ALWAYS ask
3. NEVER call this tool without explicit final confirmation
4. ALWAYS show all options (seniorities, company sizes, objectives)
5. ALWAYS present 4-5 ad creative variations to user BEFORE creating
6. ALWAYS create ALL approved variations (use `add_linkedin_creative` after initial creation)
7. ALWAYS present full summary before creating
8. ALWAYS provide a `creative_name` for each creative (e.g. "Campaign Name - Ad 1", "Campaign Name - Ad 2")
9. NEVER use "LinkedIn" in ad copy (introductory text, headline). LinkedIn's ad policy prohibits it. Also avoid other platform names (Facebook, Google, Instagram, etc.)

Required Parameters:
- campaign_name: Descriptive name (auto-suffixed with timestamp)
- daily_budget: Minimum $10/day
- organization_id: LinkedIn Company Page ID (from Phase 1)
- introductory_text: Main ad copy (up to 600 chars)
- landing_page_url: HTTPS URL
- locations: List of location URNs (required)
- Image source: image_urn OR asset_bundle_id (from Phase 6)

Optional Parameters:
- headline, call_to_action, objective
- industries, seniorities, job_titles, company_sizes
- start_date, end_date, total_budget

Location URNs:
- United States: urn:li:geo:103644278
- United Kingdom: urn:li:geo:101165590
- Canada: urn:li:geo:101174742
- Australia: urn:li:geo:101452733
- Germany: urn:li:geo:101282230
- India: urn:li:geo:102713980

Execution time: 15-30 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (will be automatically suffixed with timestamp for uniqueness) |
| `daily_budget` | `number` | yes | Daily budget in account currency (minimum 10/day for LinkedIn). Set currency field if not USD. |
| `organization_id` | `string` | yes | LinkedIn Organization (Company Page) ID. The organization that will appear as the ad author. Must be a Company Page you have admin access to. |
| `asset_bundle_id` | `object` | no | Asset bundle ID from validate_and_prepare_linkedin_assets tool. Use this for NEW image uploads. Mutually exclusive with image_urn. |
| `image_urn` | `object` | no | Existing LinkedIn image asset URN from discover_linkedin_assets tool. Use this to REUSE an existing image from your LinkedIn asset library. Format: urn:li:digitalmediaAsset:XXX. Mutually exclusive with asset_bundle_id. |
| `introductory_text` | `string` | yes | Main ad text/copy (up to 600 characters). This is the primary message that appears with your ad. Best practice: Use 150 characters or less for optimal engagement. |
| `headline` | `string` | yes | Ad headline (up to 70 characters, REQUIRED). Appears below the image. Keep it concise and action-oriented. Ads without headlines perform significantly worse. |
| `landing_page_url` | `string` | yes | Destination URL where users will be directed. Must be HTTPS. LinkedIn tracks clicks and conversions through this URL. |
| `call_to_action` | `object` | no | Call-to-action button label. Options: APPLY, DOWNLOAD, VIEW_QUOTE, LEARN_MORE, SIGN_UP,SUBSCRIBE, REGISTER, JOIN, ATTEND, REQUEST_DEMO. Default: LEARN_MORE. |
| `campaign_group_id` | `object` | no | Existing Campaign Group ID to add this campaign to (for multi-audience setups). Pass this to create multiple campaigns with different targeting under ONE group. Get this from a prior create_linkedin_*_campaign response. Format: numeric ID o |
| `campaign_group_name` | `object` | no | Name for a NEW campaign group (REQUIRED if campaign_group_id not provided). This is the top-level container that groups related campaigns. Example: 'Q2 B2B SaaS Campaign', 'Summer Product Launch'. Max 100 characters. |
| `objective` | `object` | no | Campaign objective (use SINGULAR form). Options: BRAND_AWARENESS, ENGAGEMENT, WEBSITE_VISIT, WEBSITE_CONVERSION, VIDEO_VIEW, JOB_APPLICANT. Default: WEBSITE_VISIT (optimizes for clicks to your landing page). Note: LEAD_GENERATION is NOT sup |
| `locations` | `array` | yes | List of LinkedIn location URNs to target. Required field. Use search_linkedin_targeting with facet='locations' to find URNs. Supports country, state, city, and metro targeting. Examples: US=urn:li:geo:103644278, California=urn:li:geo:102095 |
| `industries` | `object` | no | Optional list of industry URNs to target. Example: ['urn:li:industry:4'] for Computer Software. Note: industries may NOT be AND'ed with employers targeting. |
| `seniorities` | `object` | no | Optional list of seniority level URNs to target. URNs: Entry=urn:li:seniority:3, Senior=urn:li:seniority:4, Manager=urn:li:seniority:5, Director=urn:li:seniority:6, VP=urn:li:seniority:7, CXO=urn:li:seniority:8, Partner=urn:li:seniority:9,  |
| `job_titles` | `object` | no | Optional list of job title URNs to target. Highly specific targeting. Use targeting search to find title URNs. Note: job titles may NOT be AND'ed with seniorities or job functions. |
| `company_sizes` | `object` | no | Optional list of company size (staff count range) URNs to target. URNs use format urn:li:staffCountRange:(min,max): 1=urn:li:staffCountRange:(1,1), 2-10=urn:li:staffCountRange:(2,10), 11-50=urn:li:staffCountRange:(11,50), 51-200=urn:li:staf |
| `skills` | `object` | no | Optional list of skill URNs. Example: ['urn:li:skill:17']. Use search_linkedin_targeting to discover skill URNs. |
| `job_functions` | `object` | no | Optional list of job function URNs. Example: ['urn:li:function:22']. Note: job functions may NOT be AND'ed with job titles. |
| `interests` | `object` | no | Optional list of interest URNs. Example: ['urn:li:interest:689290']. Target members based on interests expressed on LinkedIn. |
| `degrees` | `object` | no | Optional list of degree URNs. Example: ['urn:li:degree:700']. |
| `fields_of_study` | `object` | no | Optional list of field of study URNs. Example: ['urn:li:fieldOfStudy:100275']. |
| `employers` | `object` | no | Optional list of employer (organization) URNs. Example: ['urn:li:organization:1035']. Note: employers may NOT be AND'ed with industries or staffCountRanges. |
| `member_groups` | `object` | no | Optional list of LinkedIn group URNs. Example: ['urn:li:group:1234']. Include-only facet. Disabled in EEA/CH since May 2024. |
| `age_ranges` | `object` | no | Optional list of age range URNs. Include-only facet. Values: urn:li:ageRange:(18,24), urn:li:ageRange:(25,34), urn:li:ageRange:(35,54), urn:li:ageRange:(55,2147483647). |
| `genders` | `object` | no | Optional list of gender URNs. Include-only facet. Values: urn:li:gender:MALE, urn:li:gender:FEMALE. |
| `schools` | `object` | no | Optional list of school (organization) URNs. Example: ['urn:li:organization:1035']. |
| `member_behaviors` | `object` | no | Optional list of member behavior URNs. Example: ['urn:li:memberBehavior:2'] (Mobile Users). Note: may NOT be AND'ed with contact/website retargeting segments. |
| `years_of_experience` | `object` | no | Optional list of years of experience URNs. Example: ['urn:li:yearsOfExperience:3']. Range: 1-12+ years. Up to 2 URNs for lower/upper limit. |
| `followed_companies` | `object` | no | Optional list of followed company (organization) URNs. Target members who follow specific companies. |
| `buyer_groups` | `object` | no | Optional list of buyer group URNs (API 2026-03+). Example: ['urn:li:standardizedProductCategory:1031']. Target B2B buyers in specific product categories. |
| `account_id` | `object` | no | LinkedIn Ad Account ID (optional - will use primary account if not provided) |
| `currency` | `object` | no | Currency code for budget. Default: USD. |
| `status` | `object` | no | Initial campaign status. Options: ACTIVE, PAUSED, DRAFT. Default: PAUSED (campaign created paused for review before spending money). |
| `locale_country` | `object` | no | Campaign locale country code (ISO-3166 uppercase). Default: US. Examples: US, GB, CA, DE, FR, IN, AU, BR, JP. |
| `locale_language` | `object` | no | Campaign locale language code (ISO-639 lowercase). Default: en. Examples: en, de, fr, es, pt, ja, zh. |
| `start_date` | `object` | no | Optional campaign start date in ISO format (e.g., '2024-01-15T00:00:00Z'). If not provided, campaign starts immediately. |
| `end_date` | `object` | no | Optional campaign end date in ISO format. If not provided, campaign runs indefinitely until paused. |
| `total_budget` | `object` | no | Optional total lifetime budget. If set, campaign will stop after spending this amount. |
| `creative_name` | `object` | no | Name for the first creative (shown in Campaign Manager UI). E.g. 'Summer Sale - Ad 1', 'Long Form — Problem Hook'. If not provided, defaults to '{campaign_name} - Ad 1'. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "daily_budget": 10.0,
    "organization_id": "string",
    "introductory_text": "string",
    "headline": "string",
    "landing_page_url": "https://example.com",
    "locations": [
      "string"
    ],
    "asset_bundle_id": "string",
    "image_urn": "string",
    "call_to_action": "LEARN_MORE",
    "campaign_group_id": "string",
    "campaign_group_name": "string",
    "objective": "WEBSITE_VISIT"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_linkedin_image_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_linkedin_image_campaign"
}
```

---
## create_linkedin_text_campaign

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_linkedin_text_campaign/execute`

**USE THIS TOOL WHEN:** User wants to create a LinkedIn TEXT ad campaign (desktop right rail/top banner).

**DO NOT USE for image ads** → use `create_linkedin_image_campaign` instead.
**DO NOT USE for video ads** → use `create_linkedin_video_campaign` instead.
**DO NOT USE for carousel ads** → use `create_linkedin_carousel_campaign` instead.

**REQUIRED:** Either `campaign_group_name` (creates new group) or `campaign_group_id` (adds to existing group).

IMPORTANT: This creates the campaign with 1 creative (Variation 1). Campaign is created in PAUSED status.

Text Ad Specifications:
- Headline: max 25 characters (required)
- Description: max 75 characters (required)
- Image: 100x100 pixels (optional)
- Landing page URL: HTTPS (required)
- Desktop-only placement (right rail, top banner)

Required Parameters:
- campaign_name, daily_budget, organization_id
- headline (max 25 chars), description (max 75 chars)
- landing_page_url, locations (targeting)
- campaign_group_name OR campaign_group_id

Optional: image_urn or image_url (100x100 image)

Default Objective: WEBSITE_VISIT
Note: VIDEO_VIEW and LEAD_GENERATION objectives are NOT supported for text ads.

AD POLICY: NEVER use "LinkedIn" in ad copy. NEVER mention competitor platforms.

**After Creation — IMPORTANT:**
- This tool created 1 campaign + 1 creative. Campaign Group ID is returned.
- To add MORE text ad creatives (A/B test headlines): use `add_linkedin_text_creative` with the campaign_id
- To add MORE campaigns (different audience): use `add_linkedin_campaign_to_group` with the campaign_group_id
- NEVER call this create tool again without campaign_group_id — that creates a SEPARATE campaign group

Execution time: 10-20 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (will be automatically suffixed with timestamp for uniqueness) |
| `daily_budget` | `number` | yes | Daily budget in account currency (minimum 10/day for LinkedIn). Set currency field if not USD. |
| `organization_id` | `string` | yes | LinkedIn Organization (Company Page) ID. |
| `headline` | `string` | yes | Text ad headline (max 25 characters, required). |
| `description` | `string` | yes | Text ad description (max 75 characters, required). |
| `landing_page_url` | `string` | yes | Destination URL. Must be HTTPS. |
| `image_urn` | `object` | no | Optional 100x100 logo/image URN for the text ad (JPG or PNG, max 2MB). Adds a small image next to the headline. Improves CTR. Use discover_linkedin_assets or validate_and_prepare_linkedin_assets to get URN. |
| `image_url` | `object` | no | Optional public URL to download and upload 100x100 logo/image for the text ad. |
| `campaign_group_id` | `object` | no | Existing Campaign Group ID to add this campaign to. If provided, campaign_group_name is NOT needed. |
| `campaign_group_name` | `object` | no | Name for a NEW campaign group (REQUIRED if campaign_group_id not provided). Example: 'Q2 Text Ads'. Max 100 characters. |
| `objective` | `object` | no | Campaign objective. Default: WEBSITE_VISIT. Options: BRAND_AWARENESS, WEBSITE_VISIT, WEBSITE_CONVERSION. |
| `locations` | `array` | yes | List of LinkedIn location URNs to target. Required. |
| `industries` | `object` | no | Optional list of industry URNs. |
| `seniorities` | `object` | no | Optional list of seniority level URNs. |
| `job_titles` | `object` | no | Optional list of job title URNs. |
| `company_sizes` | `object` | no | Optional list of company size URNs. |
| `skills` | `object` | no | Optional list of skill URNs. |
| `job_functions` | `object` | no | Optional list of job function URNs. |
| `interests` | `object` | no | Optional list of interest URNs. |
| `degrees` | `object` | no | Optional list of degree URNs. |
| `fields_of_study` | `object` | no | Optional list of field of study URNs. |
| `employers` | `object` | no | Optional list of employer URNs. |
| `member_groups` | `object` | no | Optional list of LinkedIn group URNs. |
| `age_ranges` | `object` | no | Optional list of age range URNs. |
| `genders` | `object` | no | Optional list of gender URNs. |
| `schools` | `object` | no | Optional list of school URNs. |
| `member_behaviors` | `object` | no | Optional list of member behavior URNs. |
| `years_of_experience` | `object` | no | Optional list of years of experience URNs. |
| `followed_companies` | `object` | no | Optional list of followed company URNs. |
| `buyer_groups` | `object` | no | Optional list of buyer group URNs (API 2026-03+). |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `currency` | `object` | no | Currency code. Default: USD. |
| `status` | `object` | no | Initial campaign status. Default: PAUSED. |
| `locale_country` | `object` | no | Campaign locale country (ISO-3166). Default: US. |
| `locale_language` | `object` | no | Campaign locale language (ISO-639). Default: en. |
| `start_date` | `object` | no | Optional start date in ISO format. |
| `end_date` | `object` | no | Optional end date in ISO format. |
| `total_budget` | `object` | no | Optional total lifetime budget. |
| `creative_name` | `object` | no | Name for the first creative. E.g. 'B2B Leads - Text Ad 1'. Defaults to '{campaign_name} - Ad 1'. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "daily_budget": 10.0,
    "organization_id": "string",
    "headline": "string",
    "description": "string",
    "landing_page_url": "https://example.com",
    "locations": [
      "string"
    ],
    "image_urn": "string",
    "image_url": "string",
    "campaign_group_id": "string",
    "campaign_group_name": "string",
    "objective": "WEBSITE_VISIT",
    "industries": [
      "string"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_linkedin_text_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_linkedin_text_campaign"
}
```

---
## create_linkedin_video_campaign

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_linkedin_video_campaign/execute`

**USE THIS TOOL WHEN:** User wants to create a LinkedIn VIDEO ad campaign.

**DO NOT USE for image ads** → use `create_linkedin_image_campaign` instead.
**DO NOT USE for carousel ads** → use `create_linkedin_carousel_campaign` instead.
**DO NOT USE for text ads** → use `create_linkedin_text_campaign` instead.

**REQUIRED:** Either `campaign_group_name` (creates new group) or `campaign_group_id` (adds to existing group).

IMPORTANT: This creates the campaign with 1 creative (Variation 1). Campaign is created in PAUSED status.

Video Specifications:
- Format: MP4 (H.264 codec)
- File size: 75KB - 500MB
- Duration: 3 seconds - 30 minutes (recommended: 15-30 seconds)
- Aspect ratios: 16:9, 1:1, 4:5, 9:16

Video Source (one required):
- video_urn: Existing video URN from discover_linkedin_assets
- video_url: Public URL to download and upload MP4 video

Required Parameters:
- campaign_name, daily_budget, organization_id, headline
- introductory_text (up to 600 chars), landing_page_url
- locations (targeting), video source (urn or url)
- campaign_group_name OR campaign_group_id

Default Objective: VIDEO_VIEW (optimizes for video views)

AD POLICY: NEVER use "LinkedIn" in ad copy. NEVER mention competitor platforms.

**After Creation — IMPORTANT:**
- This tool created 1 campaign + 1 creative. Campaign Group ID is returned.
- To add MORE creatives (A/B test copy): use `add_linkedin_video_creative` with the campaign_id
- To add MORE campaigns (different audience): use `add_linkedin_campaign_to_group` with the campaign_group_id
- NEVER call this create tool again without campaign_group_id — that creates a SEPARATE campaign group

Execution time: 30-120 seconds (includes video upload and processing)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (will be automatically suffixed with timestamp for uniqueness) |
| `daily_budget` | `number` | yes | Daily budget in account currency (minimum 10/day for LinkedIn). Set currency field if not USD. |
| `organization_id` | `string` | yes | LinkedIn Organization (Company Page) ID. The organization that will appear as the ad author. |
| `video_urn` | `object` | no | Existing LinkedIn video URN from discover_linkedin_assets tool. Format: urn:li:video:XXX. Mutually exclusive with video_url. |
| `video_url` | `object` | no | Public URL to download and upload video from. Must be MP4 format, H.264 codec, max 500MB, 3s-30min duration. Mutually exclusive with video_urn. |
| `introductory_text` | `string` | yes | Main ad text/copy (up to 600 characters). |
| `headline` | `string` | yes | Ad headline (up to 70 characters, REQUIRED). Ads without headlines perform significantly worse. |
| `landing_page_url` | `string` | yes | Destination URL where users will be directed. Must be HTTPS. |
| `call_to_action` | `object` | no | Call-to-action button label. Options: APPLY, DOWNLOAD, VIEW_QUOTE, LEARN_MORE, SIGN_UP,SUBSCRIBE, REGISTER, JOIN, ATTEND, REQUEST_DEMO. |
| `campaign_group_id` | `object` | no | Existing Campaign Group ID to add this campaign to. Pass this to create multiple campaigns with different targeting under ONE group. If provided, campaign_group_name is NOT needed. |
| `campaign_group_name` | `object` | no | Name for a NEW campaign group (REQUIRED if campaign_group_id not provided). Example: 'Q2 Video Ads Campaign'. Max 100 characters. |
| `objective` | `object` | no | Campaign objective. Default: VIDEO_VIEW for video campaigns. Options: BRAND_AWARENESS, ENGAGEMENT, WEBSITE_VISIT, WEBSITE_CONVERSION, VIDEO_VIEW, JOB_APPLICANT. |
| `locations` | `array` | yes | List of LinkedIn location URNs to target. Required. Example: ['urn:li:geo:103644278'] for United States. |
| `industries` | `object` | no | Optional list of industry URNs. |
| `seniorities` | `object` | no | Optional list of seniority level URNs. |
| `job_titles` | `object` | no | Optional list of job title URNs. |
| `company_sizes` | `object` | no | Optional list of company size URNs. |
| `skills` | `object` | no | Optional list of skill URNs. |
| `job_functions` | `object` | no | Optional list of job function URNs. |
| `interests` | `object` | no | Optional list of interest URNs. |
| `degrees` | `object` | no | Optional list of degree URNs. |
| `fields_of_study` | `object` | no | Optional list of field of study URNs. |
| `employers` | `object` | no | Optional list of employer (organization) URNs. |
| `member_groups` | `object` | no | Optional list of LinkedIn group URNs. |
| `age_ranges` | `object` | no | Optional list of age range URNs. |
| `genders` | `object` | no | Optional list of gender URNs. |
| `schools` | `object` | no | Optional list of school URNs. |
| `member_behaviors` | `object` | no | Optional list of member behavior URNs. |
| `years_of_experience` | `object` | no | Optional list of years of experience URNs. |
| `followed_companies` | `object` | no | Optional list of followed company URNs. |
| `buyer_groups` | `object` | no | Optional list of buyer group URNs (API 2026-03+). |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `currency` | `object` | no | Currency code. Default: USD. |
| `status` | `object` | no | Initial campaign status. Default: PAUSED. |
| `locale_country` | `object` | no | Campaign locale country (ISO-3166). Default: US. |
| `locale_language` | `object` | no | Campaign locale language (ISO-639). Default: en. |
| `start_date` | `object` | no | Optional start date in ISO format. |
| `end_date` | `object` | no | Optional end date in ISO format. |
| `total_budget` | `object` | no | Optional total lifetime budget. |
| `creative_name` | `object` | no | Name for the first creative. E.g. 'Product Demo - Ad 1'. Defaults to '{campaign_name} - Ad 1'. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "daily_budget": 10.0,
    "organization_id": "string",
    "introductory_text": "string",
    "headline": "string",
    "landing_page_url": "https://example.com",
    "locations": [
      "string"
    ],
    "video_urn": "string",
    "video_url": "string",
    "call_to_action": "LEARN_MORE",
    "campaign_group_id": "string",
    "campaign_group_name": "string",
    "objective": "VIDEO_VIEW"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_linkedin_video_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_linkedin_video_campaign"
}
```

---
## delete_linkedin_creative

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/delete_linkedin_creative/execute`

User wants to delete, archive, or remove a LinkedIn ad/creative.

Archive or permanently delete a LinkedIn creative.

Parameters:
- creative_id: Creative ID to archive or delete (required)
- account_id: Optional LinkedIn Ad Account ID
- permanent: If True, permanently deletes (irreversible). If False, archives (default, recoverable).

What happens:
- Default (permanent=False): Creative is archived and can be restored later
- permanent=True: Creative is permanently deleted and cannot be recovered

Example Prompts:
- "Delete creative 12345"
- "Archive my LinkedIn ad"
- "Remove creative from my campaign"
- "Permanently delete ad 12345"

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `creative_id` | `string` | yes | Creative ID to archive or delete |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `permanent` | `boolean` | no | If True, permanently deletes. If False, archives (recoverable). |

### Example request

```json
{
  "arguments": {
    "creative_id": "string",
    "account_id": "string",
    "permanent": false
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for delete_linkedin_creative)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "delete_linkedin_creative"
}
```

---
## discover_linkedin_assets

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/discover_linkedin_assets/execute`

User wants to find existing images/videos in their LinkedIn account to reuse.

Discover previously uploaded image and video assets for a LinkedIn Ad Account.

IMPORTANT:
- Call `get_linkedin_organizations` FIRST to get both organization_id AND account_id
- The account_id is REQUIRED to find images uploaded via the new LinkedIn API

What this tool does:
- Queries LinkedIn for existing assets in the ad account
- Returns images and videos with URNs, dimensions, and status
- Assets can be reused in new campaigns without re-uploading

Parameters:
- organization_id: LinkedIn Organization (Company Page) ID (required)
- account_id: LinkedIn Ad Account (Sponsored Account) ID (REQUIRED for image discovery!)
  - Get this from `get_linkedin_organizations` response
  - Without this, images uploaded via new API won't be found
- asset_type: Filter by type - 'image', 'video', or 'all' (default: all)
- limit: Maximum assets to return (default: 50, max: 100)

Returns:
- List of images with asset_urn, dimensions, status
- List of videos with asset_urn, dimensions, duration, status
- Total count of assets found

Use this tool to:
- Find existing images to reuse (saves time and ensures consistency)
- Check what video assets are available for Video Ad campaigns
- Avoid uploading duplicate assets

Workflow:
1. Call `get_linkedin_organizations` first - get organization_id AND account_id
2. Call `discover_linkedin_assets` with both IDs
3. Use returned `asset_urn` in `create_linkedin_image_campaign`

Execution time: 3-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `organization_id` | `string` | yes | LinkedIn Organization (Company Page) ID. This is the organization that owns the assets. Use the organization ID from your connected LinkedIn account. |
| `account_id` | `object` | no | LinkedIn Ad Account (Sponsored Account) ID. Required to discover images uploaded via the new LinkedIn API. Get this from your LinkedIn Campaign Manager or `get_linkedin_ad_accounts` tool. Format: numeric ID or full URN (urn:li:sponsoredAcco |
| `asset_type` | `object` | no | Type of assets to discover: 'image', 'video', or 'all' (default). Use 'image' for Single Image Ads, 'video' for Video Ads. |
| `limit` | `object` | no | Maximum number of assets to return (default: 50, max: 100) |

### Example request

```json
{
  "arguments": {
    "organization_id": "string",
    "account_id": "string",
    "asset_type": "all",
    "limit": 50
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for discover_linkedin_assets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "discover_linkedin_assets"
}
```

---
## explain_linkedin_anomaly

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/explain_linkedin_anomaly/execute`

User asks why their LinkedIn metrics changed,
wants to understand a performance drop/spike, or says something like
"My LinkedIn leads dropped 40%, why?"

Explains significant metric changes with contributing factors:

LinkedIn-Specific Factors Analyzed:
- CPM changes (auction dynamics, competition)
- CTR changes (creative effectiveness)
- Audience saturation (B2B pools are smaller)
- Seniority targeting shifts
- Industry performance shifts
- Company size targeting changes
- Campaign changes (paused, new, budget)
- B2B seasonal patterns (holidays, fiscal quarters)
- Creative fatigue (14-day threshold)

Returns:
- Metric change summary (current vs previous)
- Severity assessment (CRITICAL, HIGH, MEDIUM, LOW)
- Contributing factors ranked by impact
- Historical context (30/60/90 day averages)
- Similar periods for comparison
- Actionable recommendations
- LinkedIn B2B-specific insights

Parameters:
- metric: Metric to analyze (required). Options:
  roas, ctr, cpc, cpm, conversions, conversion_rate,
  leads, engagement_rate, lead_form_completion_rate
- period_start: Start date (YYYY-MM-DD format, required)
- period_end: End date (YYYY-MM-DD format, required)
- comparison_period_start: Optional comparison start date
- comparison_period_end: Optional comparison end date
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Example Prompts:
- "Why did my LinkedIn ROAS drop last week?"
- "Explain the LinkedIn CTR decline in December"
- "My LinkedIn leads dropped 40%, what happened?"
- "Why did LinkedIn CPC increase?"

Execution time: 4-6 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `metric` | `string` | no | Metric to analyze. Options: roas, ctr, cpc, cpm, conversions, conversion_rate, leads, engagement_rate, lead_form_completion_rate. Default: 'ctr' |
| `period_start` | `object` | no | Start date of analysis period (YYYY-MM-DD format). Defaults to 7 days ago. |
| `period_end` | `object` | no | End date of analysis period (YYYY-MM-DD format). Defaults to yesterday. |
| `comparison_period_start` | `object` | no | Optional start date of comparison period. Defaults to same-length period immediately before analysis period. |
| `comparison_period_end` | `object` | no | Optional end date of comparison period. Defaults to day before period_start. |
| `ad_account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "metric": "ctr",
    "period_start": "string",
    "period_end": "string",
    "comparison_period_start": "string",
    "comparison_period_end": "string",
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for explain_linkedin_anomaly)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "explain_linkedin_anomaly"
}
```

---
## explain_linkedin_objectives

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/explain_linkedin_objectives/execute`

User asks about LinkedIn campaign objectives or which one to choose.

Explain all LinkedIn campaign objectives and recommend the best one.

What this tool does:
- Explains all 8 LinkedIn objectives in detail
- Shows typical costs and expected results
- Provides personalized recommendation based on user's goal
- Helps user decide which objective to use

Parameters:
- business_type: Type of business (optional)
- goal: What user wants to achieve (optional)

Example Prompts:
- "What objectives can I choose for LinkedIn?"
- "Which objective should I use for my SaaS?"
- "Explain LinkedIn campaign objectives"
- "I want to get signups, which objective?"

Execution time: <1 second

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `business_type` | `object` | no | Type of business (e.g., 'B2B SaaS', 'E-commerce', 'Agency') |
| `goal` | `object` | no | What the user wants to achieve (e.g., 'get signups', 'drive traffic', 'build awareness') |

### Example request

```json
{
  "arguments": {
    "business_type": "string",
    "goal": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for explain_linkedin_objectives)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "explain_linkedin_objectives"
}
```

---
## generate_linkedin_ad_creatives

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/generate_linkedin_ad_creatives/execute`

User needs ad copy for LinkedIn campaigns.

Generate multiple LinkedIn ad creative variations with different angles.

What this tool does:
- Creates 4 ad variations (problem-focused, solution-focused, social proof, curiosity)
- Generates introductory text (600 char max)
- Creates headlines (70 char max)
- Suggests appropriate CTAs
- Adds UTM tracking to landing pages

Parameters:
- business_name: Name of the business (required)
- business_description: What the business does (required)
- target_audience: Who to target (required)
- value_proposition: Main benefit (required)
- landing_page_url: Where to send traffic (required)
- campaign_objective: Campaign goal (optional, default: WEBSITE_VISIT)
- tone: Voice style (optional: professional, casual, urgent, inspirational)
- include_stats: Include statistics (optional)
- stats_to_include: Stats to use (optional)

Returns:
- 4 complete ad creative variations
- Each with: intro text, headline, CTA, landing URL with UTM

Example Prompts:
- "Generate ad copy for my LinkedIn campaign"
- "Create ad variations for my B2B SaaS"
- "Help me write LinkedIn ads for my marketing tool"

Execution time: <1 second

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `business_name` | `string` | yes | Name of the business/product being advertised |
| `business_description` | `string` | yes | Brief description of what the business does (1-2 sentences) |
| `target_audience` | `string` | yes | Description of who the ad is targeting (e.g., 'Marketing managers at SaaS companies') |
| `value_proposition` | `string` | yes | Main benefit or value the product/service provides |
| `landing_page_url` | `string` | yes | URL where users will be directed when they click the ad |
| `campaign_objective` | `object` | no | Campaign objective: WEBSITE_VISIT, LEAD_GENERATION, BRAND_AWARENESS, etc. |
| `tone` | `object` | no | Tone of voice: 'professional', 'casual', 'urgent', 'inspirational' |
| `include_stats` | `object` | no | Whether to include statistics/numbers in copy (user should provide if True) |
| `stats_to_include` | `object` | no | Statistics to potentially include (e.g., '500+ customers', '10 hours saved/week') |

### Example request

```json
{
  "arguments": {
    "business_name": "string",
    "business_description": "string",
    "target_audience": "string",
    "value_proposition": "string",
    "landing_page_url": "https://example.com",
    "campaign_objective": "WEBSITE_VISIT",
    "tone": "professional",
    "include_stats": false,
    "stats_to_include": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for generate_linkedin_ad_creatives)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "generate_linkedin_ad_creatives"
}
```

---
## get_linkedin_audience_insights

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_linkedin_audience_insights/execute`

User asks about B2B audience demographics,
which professional segments perform best, targeting optimization, or wants to understand
their LinkedIn audience breakdown.

THIS IS LINKEDIN'S KILLER FEATURE - Professional demographic analysis:

Returns B2B audience performance breakdown by:
- Seniority (Entry, Senior, Manager, Director, VP, C-Suite)
- Industry (Technology, Finance, Healthcare, etc.)
- Company Size (1-10, 11-50, 51-200, 201-500, 501-1000, 1000+)
- Job Function (Marketing, Sales, IT, Engineering, HR, etc.)

Each segment includes:
- Impressions, clicks, spend, conversions
- CTR, CPL, ROAS, engagement rate
- Category: SCALE, MAINTAIN, REDUCE, or EXCLUDE

Identifies:
- Best performing segments (to scale)
- Underperforming segments (to reduce/exclude)
- Wasted spend on B2B segments
- Targeting optimization recommendations

Parameters:
- lookback_days: Number of days to analyze (7-120). Default: 30
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- breakdown_types: Optional list of specific breakdowns. Default: all types
- target_roas: Override target ROAS for categorization
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Example Prompts:
- "Which seniority levels convert best on LinkedIn?"
- "Show me LinkedIn audience breakdown by industry"
- "What company sizes are we reaching on LinkedIn?"
- "Am I reaching decision-makers on LinkedIn?"
- "Which B2B segments should I exclude?"

Execution time: 4-6 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7-120). Default: 30. |
| `breakdown_types` | `object` | no | List of breakdown types to include: 'seniority', 'industry', 'company_size', 'job_function'. Default: all types. |
| `target_roas` | `object` | no | Override target ROAS for categorization. If not provided, uses historical average or 2.0x default. |
| `ad_account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "breakdown_types": [
      "string"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_linkedin_audience_insights)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_linkedin_audience_insights"
}
```

---
## get_linkedin_campaign_performance

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_linkedin_campaign_performance/execute`

User asks about LinkedIn Ads performance, campaign metrics,
B2B engagement, ROAS, or wants to understand how their LinkedIn campaigns are performing.

Returns comprehensive LinkedIn Ads performance metrics including:
- Account summary (total spend, impressions, clicks, CTR, conversions, ROAS)
- LinkedIn-specific metrics (engagements, leads, cost per lead)
- Campaign breakdown with performance categorization
- Trend data (daily metrics, week-over-week, month-over-month changes)
- Actionable recommendations

Parameters:
- lookback_days: Number of days to analyze (7, 14, 30, 60, 90). Default: 30
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- include_campaigns: Include per-campaign breakdown. Default: true
- include_trends: Include trend analysis. Default: true
- include_comprehensive: Include creative/engagement analysis. Default: true
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Example Prompts:
- "How are my LinkedIn ads performing?"
- "Show me LinkedIn campaign performance for the last 30 days"
- "What's my LinkedIn ROAS this month?"
- "Which LinkedIn campaigns are performing best?"

Execution time: 3-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7-120). Default: 30. LinkedIn data is available for up to 10 years but 30 days provides the best balance of recency and data volume. |
| `include_campaigns` | `boolean` | no | Include per-campaign breakdown. Default: true |
| `include_trends` | `boolean` | no | Include trend data (daily metrics, WoW/MoM changes). Default: true |
| `include_comprehensive` | `boolean` | no | Include creative and engagement analysis. Default: true |
| `ad_account_id` | `object` | no | LinkedIn Ads account ID (optional - uses default connected account) |
| `campaign_id` | `object` | no | Filter performance to a specific campaign ID. If not provided, returns account-wide performance. |
| `campaign_name` | `object` | no | Filter performance by campaign name (partial match). If not provided, returns all campaigns. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "include_campaigns": true
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_linkedin_campaign_performance)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_linkedin_campaign_performance"
}
```

---
## get_linkedin_campaign_structure

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_linkedin_campaign_structure/execute`

User wants full details about a specific LinkedIn campaign.

Get complete campaign structure including creatives, targeting, and configuration.

Returns:
- Campaign details (name, status, objective, budget, schedule)
- All creatives/ads linked to campaign
- Full targeting criteria with resolved names
- Conversion tracking setup
- Campaign Manager link

Parameters:
- campaign_id: Campaign ID to fetch (required)
- account_id: Optional LinkedIn Ad Account ID

Example Prompts:
- "Show me details for campaign 123456"
- "What's the targeting for my LinkedIn campaign?"
- "How many ads are in my campaign?"

Execution time: 3-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to fetch structure for |
| `account_id` | `object` | no | LinkedIn Ad Account ID (optional - uses default connected account) |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_linkedin_campaign_structure)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_linkedin_campaign_structure"
}
```

---
## get_linkedin_campaign_targeting

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_linkedin_campaign_targeting/execute`

User wants to copy targeting from one campaign to another.

Get targeting criteria from a campaign in a format ready for reuse.

Returns:
- All targeting URNs with human-readable names
- Format ready to pass to create_linkedin_image_campaign

Use Case:
1. Get targeting from successful campaign
2. Use those URNs in a new campaign

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to get targeting from |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_linkedin_campaign_targeting)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_linkedin_campaign_targeting"
}
```

---
## get_linkedin_engagement_metrics

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_linkedin_engagement_metrics/execute`

User asks specifically about LinkedIn engagement,
social actions, lead generation metrics, or video performance.

Returns detailed LinkedIn engagement breakdown:
- Social engagement (likes, comments, shares, follows, reactions)
- Lead generation (one-click leads, form opens, completion rates)
- Video metrics (views, starts, completions, watch rates)
- Viral metrics (organic share impressions, clicks)
- Engagement rate calculations and benchmarks

Parameters:
- lookback_days: Number of days to analyze (7-120). Default: 30
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Example Prompts:
- "Show me LinkedIn engagement metrics"
- "How many leads did we generate on LinkedIn?"
- "What's our LinkedIn video completion rate?"
- "Are people sharing our LinkedIn ads?"

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7-120). Default: 30. |
| `ad_account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `campaign_id` | `object` | no | Filter to a specific campaign ID. |
| `campaign_name` | `object` | no | Filter by campaign name (partial match). |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_linkedin_engagement_metrics)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_linkedin_engagement_metrics"
}
```

---
## get_linkedin_organizations

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_linkedin_organizations/execute`

Fetch the LinkedIn Organizations (Company Pages) AND Ad Accounts the user can manage.

IMPORTANT: Call this tool during campaign creation discovery phase to get both organization_id AND account_id.

What this tool does:
- Queries LinkedIn for all Company Pages the user has admin access to
- Queries LinkedIn for all Ad Accounts the user can access
- Returns organization IDs, names, AND ad account IDs
- No parameters required - uses connected LinkedIn account

Returns:
- List of organizations with ID, name, and LinkedIn URL
- List of ad accounts with ID, name, status, and linked organization
- Both IDs needed for different operations

Use this tool to:
- Get the organization_id automatically (no need to ask user!)
- Get the account_id for asset discovery (required for finding images/videos!)
- Verify user has access to a Company Page
- Find which page to use for Sponsored Content

After getting organizations & accounts:
- Use the `account_id` in `discover_linkedin_assets` to find existing images/videos
- Use the `organization_id` in whichever campaign creation tool the user selected:
  - `create_linkedin_image_campaign` (Single Image)
  - `create_linkedin_video_campaign` (Video)
  - `create_linkedin_carousel_campaign` (Carousel)
  - `create_linkedin_text_campaign` (Text Ad)
- Assets are tied to ad accounts, not organizations - that's why account_id is needed

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `account_id` | `object` | no | LinkedIn Ad Account ID (required for multi-account users, used for authentication). Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_linkedin_organizations)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_linkedin_organizations"
}
```

---
## list_linkedin_campaign_groups

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_linkedin_campaign_groups/execute`

User wants to see their LinkedIn campaign groups (also called campaign folders or groups).

List all campaign groups with their status, budget, and campaign count.

Parameters:
- account_id: Optional LinkedIn Ad Account ID
- status_filter: Filter by status (ACTIVE, PAUSED, ARCHIVED, or ALL)

Returns:
- All campaign groups with name, ID, status, budget, and campaign count

Example Prompts:
- "Show me my LinkedIn campaign groups"
- "List all campaign groups"
- "What campaign groups do I have?"
- "Show active campaign groups"

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `status_filter` | `string` | no | Filter by status. Default: 'ACTIVE,PAUSED,DRAFT' (excludes deleted/removed clutter). Options: ACTIVE, PAUSED, DRAFT, ARCHIVED, or ALL (includes PENDING_DELETION, REMOVED). |

### Example request

```json
{
  "arguments": {
    "account_id": "string",
    "status_filter": "ACTIVE,PAUSED,DRAFT"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_linkedin_campaign_groups)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_linkedin_campaign_groups"
}
```

---
## list_linkedin_campaigns

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_linkedin_campaigns/execute`

User wants to see all their LinkedIn campaigns with performance metrics.

List all LinkedIn campaigns with summary metrics (clicks, impressions, cost, CTR).

Returns:
- All campaigns grouped by status (Active, Paused, Other)
- 30-day performance metrics for each campaign
- Campaign Manager links

Parameters:
- account_id: Optional LinkedIn Ad Account ID
- status_filter: Filter by status (ACTIVE, PAUSED, ALL)
- limit: Maximum campaigns to return (default: 50)
- lookback_days: Days for metrics (default: 30)

Example Prompts:
- "Show me my LinkedIn campaigns"
- "List all active LinkedIn campaigns"
- "What are my LinkedIn campaign metrics?"

Execution time: 3-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `account_id` | `object` | no | LinkedIn Ad Account ID (optional - uses default connected account) |
| `campaign_group_id` | `object` | no | Filter campaigns by Campaign Group ID. Only returns campaigns that belong to this group. Format: numeric ID or urn:li:sponsoredCampaignGroup:XXXXX. |
| `status_filter` | `object` | no | Filter by status. Default: 'ACTIVE,PAUSED,DRAFT' (excludes deleted/removed clutter). Options: 'ACTIVE', 'PAUSED', 'DRAFT', 'ARCHIVED', 'COMPLETED', 'CANCELED', 'ALL'. Use 'ALL' to include everything including PENDING_DELETION and REMOVED. |
| `limit` | `integer` | no | Maximum campaigns to return (default: 50, max: 100) |
| `lookback_days` | `integer` | no | Number of days for metrics (default: 30) |

### Example request

```json
{
  "arguments": {
    "account_id": "string",
    "campaign_group_id": "string",
    "status_filter": "ACTIVE,PAUSED,DRAFT",
    "limit": 50,
    "lookback_days": 30
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_linkedin_campaigns)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_linkedin_campaigns"
}
```

---
## list_linkedin_conversions

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_linkedin_conversions/execute`

User wants to see available conversion tracking options.

List all conversion rules for a LinkedIn ad account.

Returns:
- All conversions with ID, name, type, status
- Attribution windows
- Can be used to select conversions for campaigns

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_linkedin_conversions)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_linkedin_conversions"
}
```

---
## list_linkedin_creatives

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_linkedin_creatives/execute`

User wants to see all ads in a LinkedIn campaign.

List all creatives/ads for a specific campaign.

Returns:
- All creatives with ID, status, review status
- Text preview and CTA
- Campaign ID

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to list creatives for |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_linkedin_creatives)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_linkedin_creatives"
}
```

---
## manage_linkedin_conversions

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/manage_linkedin_conversions/execute`

User wants to manage LinkedIn conversion tracking - list, create, associate conversions, or set up full conversion tracking.

All-in-one conversion management tool with 4 actions:

Actions:
- `list`: Show all conversions for the account
- `create`: Create a new conversion rule
- `associate`: Link a conversion to a campaign
- `setup`: Full setup - create conversion + associate with campaign in one step

Parameters:
- action: 'list', 'create', 'associate', or 'setup' (required)
- name: Conversion name (for 'create' and 'setup')
- type: Conversion type - LEAD, PURCHASE, SIGN_UP, KEY_PAGE_VIEW, etc. (default: LEAD)
- url: URL for conversion rule matching
- value: Fixed conversion value in USD
- conversion_id: Conversion ID (for 'associate')
- campaign_id: Campaign ID (for 'associate' and 'setup')
- landing_page_url: Landing page URL (for 'setup')
- account_id: Optional LinkedIn Ad Account ID

Example Prompts:
- "Show me my LinkedIn conversions"
- "Set up conversion tracking for my campaign"
- "Create a lead conversion for sign-ups"
- "Associate conversion 123 with campaign 456"
- "Track purchases on my landing page"
- "Help me set up LinkedIn conversion tracking"

Execution time: 2-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `action` | `string` | yes | Action: 'list', 'create', 'associate', or 'setup' |
| `name` | `object` | no | Conversion name (for 'create' action) |
| `type` | `object` | no | Conversion type: LEAD, PURCHASE, SIGN_UP, KEY_PAGE_VIEW, etc. |
| `url` | `object` | no | URL for conversion rule matching |
| `value` | `object` | no | Fixed conversion value |
| `conversion_id` | `object` | no | Conversion ID (for 'associate' action) |
| `campaign_id` | `object` | no | Campaign ID (for 'associate' and 'setup' actions) |
| `landing_page_url` | `object` | no | Landing page URL (for 'setup' action) |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "action": "string",
    "name": "string",
    "type": "LEAD",
    "url": "string",
    "value": 1.0,
    "conversion_id": "string",
    "campaign_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for manage_linkedin_conversions)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "manage_linkedin_conversions"
}
```

---
## optimize_linkedin_budget

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/optimize_linkedin_budget/execute`

User asks how to allocate their LinkedIn budget,
wants budget optimization recommendations, or asks "How should I split my LinkedIn spend?"

Uses linear programming to optimize budget allocation across campaigns:

Optimization Process:
1. Analyzes historical campaign performance
2. Calculates efficiency scores (conversions or leads per dollar)
3. Uses scipy linear programming to find optimal allocation
4. Respects constraints (max change %, min budget, total budget)

Returns:
- Current vs Optimized budget allocation
- Expected impact (conversion/lead lift)
- Campaigns to SCALE, MAINTAIN, REDUCE, or PAUSE
- Projected improvement percentage

Constraints Applied:
- Total budget = user specified amount
- Max change per campaign (default 50%)
- Min daily budget (LinkedIn minimum $10, default $20)
- Only allocate to profitable campaigns (ROAS > 1.0)

Parameters:
- total_budget: Total monthly budget to allocate (required, > 0)
- lookback_days: Historical data period (7-120). Default: 30
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- target_roas: Target ROAS for optimization
- max_change_percentage: Max budget change (0.3-0.7). Default: 0.5
- min_daily_budget: Minimum daily budget. Default: $20
- optimization_goal: 'conversions' or 'leads'. Default: conversions
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Example Prompts:
- "How should I allocate $30K across LinkedIn campaigns?"
- "Optimize my LinkedIn budget for leads"
- "Which LinkedIn campaigns should get more budget?"
- "How can I improve LinkedIn campaign efficiency?"

Execution time: 4-6 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `total_budget` | `number` | yes | Total monthly budget to allocate across campaigns (required, > 0) |
| `lookback_days` | `integer` | no | Number of days of historical data to use for optimization. Default: 30. |
| `target_roas` | `object` | no | Target ROAS for optimization. If not provided, uses historical average. |
| `max_change_percentage` | `number` | no | Maximum budget change per campaign (0.3-0.7). Default: 0.5 (50%) |
| `min_daily_budget` | `number` | no | Minimum daily budget per campaign. LinkedIn minimum is $10. Default: $20 |
| `optimization_goal` | `string` | no | Optimization goal: 'conversions' or 'leads'. Default: conversions |
| `ad_account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "total_budget": 1.0,
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "target_roas": 0.1
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for optimize_linkedin_budget)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "optimize_linkedin_budget"
}
```

---
## pause_linkedin_campaign

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/pause_linkedin_campaign/execute`

User wants to pause an active LinkedIn campaign.

Pause a campaign to stop serving impressions.

Parameters:
- campaign_id: Campaign ID to pause (required)
- account_id: Optional LinkedIn Ad Account ID

What happens:
- Campaign status changes to PAUSED
- No new impressions will be served
- Campaign can be resumed later

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to pause |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for pause_linkedin_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "pause_linkedin_campaign"
}
```

---
## pause_linkedin_creative

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/pause_linkedin_creative/execute`

User wants to pause a specific ad within a campaign.

Pause an individual creative/ad without affecting other ads in the campaign.

Parameters:
- creative_id: Creative ID to pause (required)
- account_id: Optional LinkedIn Ad Account ID

Use Case:
- Pause underperforming ads
- A/B test by pausing certain variations
- Temporarily disable specific content

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `creative_id` | `string` | yes | Creative ID to pause |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "creative_id": "string",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for pause_linkedin_creative)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "pause_linkedin_creative"
}
```

---
## research_business_for_linkedin_targeting

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/research_business_for_linkedin_targeting/execute`

User wants targeting recommendations based on their business.

Analyze a business to derive appropriate LinkedIn targeting recommendations.

What this tool does:
- Analyzes business website URL and description
- Identifies likely target audience based on business vertical
- Recommends seniority levels, job functions, industries, company sizes
- Suggests job titles and interests to search for

Parameters:
- website_url: Business website URL (required)
- business_description: What the business does (optional)
- ideal_customer_description: Who they want to reach (optional)
- competitors: List of competitor names/websites (optional)

Returns:
- Recommended seniority URNs (copy directly)
- Recommended company size URNs (copy directly)
- Industry recommendations
- Job function recommendations
- Job title suggestions (use with search_linkedin_targeting)
- Interest suggestions

Example Prompts:
- "Suggest LinkedIn targeting for my marketing SaaS"
- "Who should I target for my HR software on LinkedIn?"
- "Help me set up LinkedIn targeting for my B2B product"

Execution time: 1-2 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `website_url` | `string` | yes | Customer's website URL to analyze for targeting recommendations |
| `business_description` | `object` | no | Optional description of what the business does |
| `ideal_customer_description` | `object` | no | Optional description of who the business wants to reach |
| `competitors` | `object` | no | Optional list of competitor names or websites |

### Example request

```json
{
  "arguments": {
    "website_url": "https://example.com",
    "business_description": "string",
    "ideal_customer_description": "string",
    "competitors": [
      "string"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for research_business_for_linkedin_targeting)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "research_business_for_linkedin_targeting"
}
```

---
## resume_linkedin_campaign

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/resume_linkedin_campaign/execute`

User wants to resume a paused LinkedIn campaign.

Resume a paused campaign to start serving impressions again.

Parameters:
- campaign_id: Campaign ID to resume (required)
- account_id: Optional LinkedIn Ad Account ID

What happens:
- Campaign status changes to ACTIVE
- Impressions will start serving
- Learning phase resumes

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to resume |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for resume_linkedin_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "resume_linkedin_campaign"
}
```

---
## resume_linkedin_creative

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/resume_linkedin_creative/execute`

User wants to resume a paused ad.

Resume a paused creative/ad to include it in campaign rotation.

Parameters:
- creative_id: Creative ID to resume (required)
- account_id: Optional LinkedIn Ad Account ID

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `creative_id` | `string` | yes | Creative ID to resume |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "creative_id": "string",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for resume_linkedin_creative)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "resume_linkedin_creative"
}
```

---
## search_linkedin_targeting

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/search_linkedin_targeting/execute`

User needs to find targeting URNs for LinkedIn campaigns.

Search for targeting entities like job titles, industries, skills, locations, etc.

Facet Types:
- job_titles, industries, skills, locations
- seniorities, company_sizes, job_functions
- interests, degrees, fields_of_study
- employers, groups

Returns:
- List of matching entities with URNs and names
- URNs can be used in campaign targeting

Example Prompts:
- "Find LinkedIn targeting for software engineers"
- "Search for marketing job titles on LinkedIn"
- "Find LinkedIn industry targeting for SaaS"

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `facet_type` | `string` | yes | Type of targeting to search: job_titles, industries, skills, locations, seniorities, company_sizes, job_functions, interests, etc. |
| `query` | `string` | yes | Search query |
| `limit` | `integer` | no | Maximum results to return (default: 25) |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "facet_type": "string",
    "query": "string",
    "limit": 25,
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for search_linkedin_targeting)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "search_linkedin_targeting"
}
```

---
## select_linkedin_campaign_type

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/select_linkedin_campaign_type/execute`

User wants to create a LinkedIn ad campaign but hasn't specified the campaign type (image, video, carousel, or text).

IMPORTANT: This tool should be called BEFORE any asset discovery or campaign creation when the user says things like:
- "Create a LinkedIn campaign"
- "Run LinkedIn ads"
- "I want to advertise on LinkedIn"
- "Set up a LinkedIn advertising campaign"
- "Help me create ads on LinkedIn"

This tool asks the user what TYPE of campaign they want to create, then provides guidance on the next steps including creative quantity best practices.

Campaign Types Available:
1. image — Single image ad in the feed (most common, 4-5 ad variations recommended)
2. video — Video ad in the feed (good for demos, storytelling, 3-4 variations recommended)
3. carousel — 2-10 swipeable image cards (good for multi-product showcase, 3-5 cards recommended)
4. text — Simple desktop right-rail/top-banner ad (budget-friendly, 3-4 variations recommended)

Returns:
- Confirmation of selected campaign type
- Specific requirements and creative specs for that type
- Recommended number of ad variations per LinkedIn best practices
- Next steps and which tool to use

Do NOT use this tool if:
- User specifically asks for "image campaign" / "single image" - go directly to image workflow
- User specifically asks for "video campaign" / "video ad" - go directly to video workflow
- User specifically asks for "carousel" / "multiple images" - go directly to carousel workflow
- User specifically asks for "text ad" / "simple ad" - go directly to text workflow
- User is asking about performance/analytics - use performance analysis tools

Parameters:
- campaign_type: 'image', 'video', 'carousel', or 'text'

Execution time: Instant (no API call)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_type` | `string` | yes | Type of LinkedIn campaign to create. Options: 'image' (single image ad), 'video' (video ad), 'carousel' (2-10 swipeable cards), 'text' (desktop text ad) |

### Example request

```json
{
  "arguments": {
    "campaign_type": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for select_linkedin_campaign_type)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "select_linkedin_campaign_type"
}
```

---
## update_linkedin_campaign

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_linkedin_campaign/execute`

User wants to modify LinkedIn campaign settings.

Update campaign budget, schedule, targeting, or other settings.

Updatable Fields:
- name: Campaign name
- daily_budget: Daily budget in account currency (min 10)
- total_budget: Lifetime budget
- status: ACTIVE, PAUSED, or ARCHIVED
- start_date / end_date: Schedule
- locations, industries, seniorities, job_titles, company_sizes: Targeting

Parameters:
- campaign_id: Campaign ID to update (required)
- account_id: Optional LinkedIn Ad Account ID
- [any updatable field]: New value

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to update |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `name` | `object` | no | New campaign name |
| `daily_budget` | `object` | no | New daily budget in account currency (minimum 10) |
| `total_budget` | `object` | no | New total/lifetime budget |
| `status` | `object` | no | New status: ACTIVE, PAUSED, or ARCHIVED |
| `start_date` | `object` | no | New start date in ISO format (e.g., '2025-01-15T00:00:00Z') |
| `end_date` | `object` | no | New end date in ISO format |
| `locations` | `object` | no | New list of location URNs (replaces existing) |
| `industries` | `object` | no | New list of industry URNs |
| `seniorities` | `object` | no | New list of seniority URNs |
| `job_titles` | `object` | no | New list of job title URNs |
| `company_sizes` | `object` | no | New list of company size URNs |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "account_id": "string",
    "name": "string",
    "daily_budget": 10.0,
    "total_budget": 1.0,
    "status": "string",
    "start_date": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_linkedin_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_linkedin_campaign"
}
```

---
## update_linkedin_campaign_budget

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_linkedin_campaign_budget/execute`

User wants to change a LinkedIn campaign's budget (daily or total).

Update campaign daily budget, total/lifetime budget, or remove budget caps.

Parameters:
- campaign_id: Campaign ID to update (required)
- daily_budget: New daily budget in USD (minimum $10)
- total_budget: New total/lifetime budget
- remove_total_budget: Remove total budget cap (make unlimited)
- account_id: Optional LinkedIn Ad Account ID

Use this instead of `update_linkedin_campaign` when the user ONLY wants to change budget.

Example Prompts:
- "Increase my LinkedIn campaign budget to $50/day"
- "Set total budget to $5000 for campaign 12345"
- "Change daily budget to $25"
- "Remove the budget cap on my campaign"
- "Double my LinkedIn ad spend"

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to update |
| `daily_budget` | `object` | no | New daily budget (minimum $10) |
| `total_budget` | `object` | no | New total/lifetime budget |
| `remove_total_budget` | `boolean` | no | Remove total budget cap (make unlimited) |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "daily_budget": 10.0,
    "total_budget": 1.0,
    "remove_total_budget": false,
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_linkedin_campaign_budget)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_linkedin_campaign_budget"
}
```

---
## update_linkedin_campaign_group

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_linkedin_campaign_group/execute`

User wants to modify a LinkedIn campaign group (rename, change status, update budget).

Update campaign group name, status, total budget, or end date.

Parameters:
- campaign_group_id: Campaign group ID to update (required)
- name: New name for the group
- status: New status (ACTIVE, PAUSED, or ARCHIVED)
- total_budget: New total budget
- end_date: New end date in ISO format
- account_id: Optional LinkedIn Ad Account ID

Note: Changes to a campaign group affect all campaigns within it.

Example Prompts:
- "Rename my campaign group to Q2 2026"
- "Pause campaign group 12345"
- "Set the budget for my campaign group"
- "Update my LinkedIn campaign group"

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_group_id` | `string` | yes | Campaign group ID to update |
| `name` | `object` | no | New name for the campaign group |
| `status` | `object` | no | New status: ACTIVE, PAUSED, or ARCHIVED |
| `total_budget` | `object` | no | New total budget |
| `end_date` | `object` | no | New end date in ISO format |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_group_id": "string",
    "name": "string",
    "status": "string",
    "total_budget": 1.0,
    "end_date": "string",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_linkedin_campaign_group)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_linkedin_campaign_group"
}
```

---
## update_linkedin_campaign_schedule

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_linkedin_campaign_schedule/execute`

User wants to change a LinkedIn campaign's end date or schedule.

Update the campaign end date.

Parameters:
- campaign_id: Campaign ID to update (required)
- end_date: New end date in ISO format, e.g. '2026-04-15T00:00:00Z' (required)
- account_id: Optional LinkedIn Ad Account ID

Example Prompts:
- "Extend my campaign until April 15"
- "Change the end date of campaign 12345"
- "Set my LinkedIn campaign to end next month"
- "Push the campaign deadline to June"

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to update |
| `end_date` | `string` | yes | New end date in ISO format (e.g. '2026-04-15T00:00:00Z') |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "end_date": "string",
    "account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_linkedin_campaign_schedule)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_linkedin_campaign_schedule"
}
```

---
## update_linkedin_campaign_targeting

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_linkedin_campaign_targeting/execute`

User wants to add or remove targeting criteria from a LinkedIn campaign.

Smart targeting update - incrementally add or remove targeting facets without replacing everything.

Parameters:
- campaign_id: Campaign ID to update targeting for (required)
- add_locations / remove_locations: Location URNs to add/remove
- add_industries / remove_industries: Industry URNs to add/remove
- add_seniorities / remove_seniorities: Seniority URNs to add/remove
- add_job_titles / remove_job_titles: Job title URNs to add/remove
- add_company_sizes / remove_company_sizes: Company size URNs to add/remove
- replace_all: If True, replaces ALL targeting (default: False, incremental)
- account_id: Optional LinkedIn Ad Account ID

Use `search_linkedin_targeting` first to find the correct URNs for targeting criteria.

Example Prompts:
- "Add New York to my campaign targeting"
- "Remove the finance industry from targeting"
- "Target VP and C-suite in my campaign"
- "Change targeting to include tech companies"
- "Add software engineers to my ad targeting"

Execution time: 3-5 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Campaign ID to update targeting for |
| `add_locations` | `object` | no | Location URNs to ADD to targeting |
| `remove_locations` | `object` | no | Location URNs to REMOVE from targeting |
| `add_industries` | `object` | no | Industry URNs to ADD |
| `remove_industries` | `object` | no | Industry URNs to REMOVE |
| `add_seniorities` | `object` | no | Seniority URNs to ADD |
| `remove_seniorities` | `object` | no | Seniority URNs to REMOVE |
| `add_job_titles` | `object` | no | Job title URNs to ADD |
| `remove_job_titles` | `object` | no | Job title URNs to REMOVE |
| `add_company_sizes` | `object` | no | Company size URNs to ADD |
| `remove_company_sizes` | `object` | no | Company size URNs to REMOVE |
| `add_skills` | `object` | no | Skill URNs to ADD |
| `remove_skills` | `object` | no | Skill URNs to REMOVE |
| `add_job_functions` | `object` | no | Job function URNs to ADD |
| `remove_job_functions` | `object` | no | Job function URNs to REMOVE |
| `add_interests` | `object` | no | Interest URNs to ADD |
| `remove_interests` | `object` | no | Interest URNs to REMOVE |
| `add_degrees` | `object` | no | Degree URNs to ADD |
| `remove_degrees` | `object` | no | Degree URNs to REMOVE |
| `add_fields_of_study` | `object` | no | Field of study URNs to ADD |
| `remove_fields_of_study` | `object` | no | Field of study URNs to REMOVE |
| `add_employers` | `object` | no | Employer URNs to ADD |
| `remove_employers` | `object` | no | Employer URNs to REMOVE |
| `add_member_groups` | `object` | no | LinkedIn group URNs to ADD (include-only) |
| `remove_member_groups` | `object` | no | LinkedIn group URNs to REMOVE |
| `add_age_ranges` | `object` | no | Age range URNs to ADD (include-only) |
| `remove_age_ranges` | `object` | no | Age range URNs to REMOVE |
| `add_genders` | `object` | no | Gender URNs to ADD (include-only) |
| `remove_genders` | `object` | no | Gender URNs to REMOVE |
| `add_schools` | `object` | no | School URNs to ADD |
| `remove_schools` | `object` | no | School URNs to REMOVE |
| `add_member_behaviors` | `object` | no | Member behavior URNs to ADD |
| `remove_member_behaviors` | `object` | no | Member behavior URNs to REMOVE |
| `add_years_of_experience` | `object` | no | Years of experience URNs to ADD |
| `remove_years_of_experience` | `object` | no | Years of experience URNs to REMOVE |
| `add_followed_companies` | `object` | no | Followed company URNs to ADD |
| `remove_followed_companies` | `object` | no | Followed company URNs to REMOVE |
| `add_buyer_groups` | `object` | no | Buyer group URNs to ADD (API 2026-03+) |
| `remove_buyer_groups` | `object` | no | Buyer group URNs to REMOVE |
| `replace_all` | `boolean` | no | If True, replaces ALL targeting. If False, adds/removes incrementally. |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "add_locations": [
      "string"
    ],
    "remove_locations": [
      "string"
    ],
    "add_industries": [
      "string"
    ],
    "remove_industries": [
      "string"
    ],
    "add_seniorities": [
      "string"
    ],
    "remove_seniorities": [
      "string"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_linkedin_campaign_targeting)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_linkedin_campaign_targeting"
}
```

---
## update_linkedin_creative

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_linkedin_creative/execute`

User wants to edit a LinkedIn ad/creative.

Update ad copy, headline, CTA, or status.

Updatable Fields:
- status: ACTIVE or PAUSED
- introductory_text: Main ad copy (max 600 chars)
- headline: Ad headline (max 70 chars)
- call_to_action: CTA button label
- landing_page_url: Destination URL

Note: LinkedIn may re-review the creative after content changes.

Execution time: 2-3 seconds

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `creative_id` | `string` | yes | Creative ID to update |
| `campaign_id` | `string` | yes | Parent campaign ID |
| `account_id` | `object` | no | LinkedIn Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `name` | `object` | no | New creative name (shown in Campaign Manager UI). Per LinkedIn API 2026-03, name is updatable via PARTIAL_UPDATE. |
| `status` | `object` | no | New status: ACTIVE or PAUSED |
| `introductory_text` | `object` | no | New ad copy (max 600 characters). Note: LinkedIn may NOT allow updating inline content on non-DRAFT creatives. |
| `headline` | `object` | no | New headline (max 70 characters). Note: LinkedIn may NOT allow updating inline content on non-DRAFT creatives. |
| `call_to_action` | `object` | no | New CTA label |
| `landing_page_url` | `object` | no | New destination URL |

### Example request

```json
{
  "arguments": {
    "creative_id": "string",
    "campaign_id": "<campaign_id>",
    "account_id": "string",
    "name": "string",
    "status": "string",
    "introductory_text": "string",
    "headline": "string",
    "call_to_action": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_linkedin_creative)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_linkedin_creative"
}
```

---
## validate_and_prepare_linkedin_assets

**Platform:** LinkedIn Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/validate_and_prepare_linkedin_assets/execute`

User provides image URLs to validate BEFORE creating LinkedIn image campaign.

Validate and prepare new image assets for LinkedIn ad campaigns.

IMPORTANT:
- Call `get_linkedin_organizations` FIRST to get organization_id AND account_id
- Include `account_id` when calling this tool - it's REQUIRED for images to be discoverable later!

LinkedIn Image Requirements:
- Horizontal (Sponsored Content): 1200x627 pixels
- Square (Sponsored Content): 1080x1080 pixels
- Vertical (Stories/Mobile): 1200x1500 pixels
- Carousel cards: 1080x1080 pixels (each card)
- Maximum file size: 8MB
- Formats: JPEG, PNG, GIF (non-animated)

Parameters:
- image_urls: List of public image URLs (e.g., from postimages.org, imgbb.com)
- organization_id: LinkedIn Organization (Company Page) ID
- account_id: LinkedIn Ad Account ID - REQUIRED for images to be discoverable via `discover_linkedin_assets`!
- ad_type: Target ad type (single_image_horizontal, single_image_square, single_image_vertical, carousel)

Returns:
- Validation results for each image
- asset_bundle_id (valid for 60 minutes)
- Image URNs for reference

Execution time: 5-15 seconds (downloads, validates, and uploads images)

Workflow:
1. Call `get_linkedin_organizations` - get organization_id AND account_id
2. Call `validate_and_prepare_linkedin_assets` with BOTH organization_id AND account_id
3. Use returned `asset_bundle_id` in `create_linkedin_image_campaign`

Why account_id matters:
- Without account_id, images are uploaded but NOT associated with your ad account
- This means `discover_linkedin_assets` won't find them later
- Always include account_id to make images reusable!

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `image_urls` | `array` | yes | List of public image URLs to validate (e.g., from postimages.org). Must be publicly accessible HTTPS URLs. LinkedIn specs: 1200x627 (horizontal), 1080x1080 (square), 1200x1500 (vertical). Max size: 8MB. Formats: JPEG, PNG, GIF (non-animated |
| `organization_id` | `object` | no | LinkedIn Organization (Company Page) ID for image upload. If not provided, will use the primary connected organization. |
| `account_id` | `object` | no | LinkedIn Ad Account (Sponsored Account) ID. REQUIRED for images to be discoverable via `discover_linkedin_assets`. Get this from `get_linkedin_organizations` response. Format: numeric ID or full URN (urn:li:sponsoredAccount:XXXXX). |
| `ad_type` | `object` | no | Target ad type for validation. Options: 'single_image_horizontal' (1200x627), 'single_image_square' (1080x1080), 'single_image_vertical' (1200x1500), 'carousel' (1080x1080 per card). |

### Example request

```json
{
  "arguments": {
    "image_urls": [
      "string"
    ],
    "organization_id": "string",
    "account_id": "string",
    "ad_type": "single_image_horizontal"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for validate_and_prepare_linkedin_assets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "validate_and_prepare_linkedin_assets"
}
```

---
# Meta Ads

## add_meta_ad

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_meta_ad/execute`

User wants to add another ad/creative variation to an EXISTING ad set.

DO NOT USE to create a new campaign. For new campaigns:
- Video campaign - `create_meta_video_campaign`
- Image campaign - `create_meta_image_campaign`
- Carousel campaign - `create_meta_carousel_campaign`

Common scenarios:
- A/B test ad copy: Different headlines, primary text, or CTAs for the same audience
- A/B test creatives: Different images or videos for the same audience
- Format testing within one audience: Image vs video ad in the same ad set
- Dynamic Creative (DCO): Multiple images + text variations in one ad — Meta auto-optimizes

KEY DISTINCTION — add_meta_ad vs add_meta_ad_set:
- Same audience, different creative/copy - use THIS tool (add_meta_ad)
- Different audience/targeting - use `add_meta_ad_set` instead (creates a new ad set)

Ads in the same ad set SHARE: targeting, budget, schedule, pixel tracking.
Each ad has its OWN: creative (image/video/carousel), headline, primary text, CTA, landing page.

IMPORTANT: You need an ad_set_id from a previously created ad set. Use `list_meta_ad_sets` first if you don't have the ad_set_id.

Supports all 3 ad types + Dynamic Creative:
- `image`: Different image + copy variation
- `video`: Different video + copy variation
- `carousel`: Different card set + copy variation
- **Dynamic Creative (DCO)**: Pass `image_urls` (2-10 images), `headlines` (up to 5), `primary_texts` (up to 5), `descriptions` (up to 5). Meta tests ALL combinations and optimizes delivery automatically.

Dynamic Creative Optimization (DCO) workflow:
1. Create ad set with `is_dynamic_creative=true` via `add_meta_ad_set`
2. Call this tool with `image_urls` (list of 2-10 image URLs), plus optional `headlines`, `primary_texts`, `descriptions` arrays
3. Meta will test all image × headline × text combinations and optimize
4. DCO ad sets support only 1 ad — if the ad set already has is_dynamic_creative enabled, this tool auto-detects it
Note: Cannot mix image_urls (DCO) with image_url (single image) or placement-specific images.

Standard workflow:
1. Get ad_set_id from prior campaign creation or `list_meta_ad_sets`
2. Write variant ad copy (different headline/primary_text/image)
3. Call this tool with ad_set_id + new creative + copy

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_set_id` | `string` | yes | The Meta Ad Set ID to add the ad to (required) |
| `ad_type` | `string` | yes | Type of ad: 'image', 'video', or 'carousel' |
| `image_url` | `object` | no | Image URL (for image type) |
| `existing_image_hash` | `object` | no | Existing image hash |
| `asset_bundle_id` | `object` | no | Asset bundle ID |
| `story_image_url` | `object` | no | Image URL for Stories/Reels (9:16, recommended 1080x1920px). Uses asset_feed_spec for placement-specific creatives. |
| `right_column_image_url` | `object` | no | Image URL for Right Column (1.91:1, recommended 1200x628px). Uses asset_feed_spec for placement-specific creatives. |
| `video_url` | `object` | no | Video URL (for video type) |
| `existing_video_id` | `object` | no | Existing video ID |
| `thumbnail_url` | `object` | no | Custom thumbnail URL |
| `cards` | `object` | no | Carousel cards (2-10) |
| `primary_text` | `string` | yes | Primary ad text (max 2200 chars, recommended 125). Supports emojis and line breaks. |
| `headline` | `object` | no | Headline (max 255 chars, recommended 40) |
| `landing_page_url` | `string` | yes | Landing page URL (HTTPS) |
| `call_to_action` | `string` | no | CTA button |
| `description` | `object` | no | Description (max 255 chars, recommended 30) |
| `image_urls` | `object` | no | List of image URLs for Dynamic Creative Optimization (2-10). Meta automatically tests all combinations and optimizes delivery. Mutually exclusive with image_url/existing_image_hash/story_image_url. |
| `headlines` | `object` | no | List of headline variations for Dynamic Creative (up to 5). Used with image_urls for DCO. |
| `primary_texts` | `object` | no | List of primary text variations for Dynamic Creative (up to 5). Used with image_urls for DCO. |
| `descriptions` | `object` | no | List of description variations for Dynamic Creative (up to 5). Used with image_urls for DCO. |
| `lead_form_id` | `object` | no | Lead form ID for OUTCOME_LEADS campaigns. If not provided, auto-fetched from the ad set's campaign. |
| `facebook_page_id` | `object` | no | Facebook Page ID |
| `instagram_account_id` | `object` | no | Instagram account ID |
| `ad_name` | `object` | no | Custom name for the ad. Also accepts 'name' as alias. |
| `ad_account_id` | `object` | no | Meta ad account ID |
| `multi_advertiser` | `object` | no | Set to false to opt out of Meta's multi-advertiser ad format at the creative level. Default (None) = Meta's default behavior (enrolled). |

### Example request

```json
{
  "arguments": {
    "ad_set_id": "string",
    "ad_type": "string",
    "primary_text": "string",
    "landing_page_url": "https://example.com",
    "image_url": "string",
    "existing_image_hash": "string",
    "asset_bundle_id": "string",
    "story_image_url": "string",
    "right_column_image_url": "string",
    "video_url": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_meta_ad)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_meta_ad"
}
```

---
## add_meta_ad_set

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_meta_ad_set/execute`

User wants to add a new ad set to an EXISTING campaign.

Common scenarios:
- Audience testing: Same ad format, different targeting/interests per ad set (e.g., "4 ad sets with different interests")
- Multi-format: Different ad types (image + video + carousel) under one campaign
- Budget split: Same creative, different budgets per audience segment
- Scaling: Adding new audiences/markets to an existing campaign
- Geographic split: Same ad, different locations per ad set
- Dynamic Creative (DCO): Set is_dynamic_creative=true, then use add_meta_ad with image_urls/headlines/primary_texts

CRITICAL: Each `create_meta_*_campaign` creates a NEW campaign.
To add more ad sets to the SAME campaign, you MUST use this tool.
NEVER call create_meta_*_campaign again for the same campaign.

IMPORTANT: You need a campaign_id from a previously created campaign. Use `get_meta_campaign_details` first if you don't have the campaign_id.

Supports all 3 ad types:
- `image`: Provide image_url, existing_image_hash, or asset_bundle_id
- `video`: Provide video_url or existing_video_id
- `carousel`: Provide cards array (2-10 cards)

Dynamic Creative Optimization (DCO):
- Set `is_dynamic_creative=true` when creating the ad set
- Then use `add_meta_ad` with `image_urls` (2-10), `headlines` (up to 5), `primary_texts` (up to 5) to add a DCO ad
- Meta automatically tests all asset combinations and optimizes delivery
- DCO ad sets support only 1 ad — one ad with multiple asset variations

Each ad set has INDEPENDENT: targeting, budget, schedule, pixel/conversion tracking.
Shared from campaign: objective, campaign name.

**CBO (Advantage Campaign Budget) campaigns:**
If the campaign uses CBO (campaign_budget_optimization=true when created), you MUST:
- Set `campaign_budget_optimization: true` on this tool call
- Do NOT set `budget_daily` (budget is managed at campaign level)
- Optionally set `daily_min_spend_target` and `daily_spend_cap` to control spend distribution
If the campaign does NOT use CBO, `budget_daily` is required as usual.

Workflow:
1. Create initial campaign with `create_meta_*_campaign` - get campaign_id
2. Call this tool with the campaign_id for each additional ad set
3. Use `add_meta_ad` to add more ad copies/creatives within any ad set

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Meta Campaign ID to add the ad set to (required). Get this from a prior create_meta_*_campaign call or get_meta_campaign_details. |
| `ad_type` | `string` | yes | Type of ad: 'image', 'video', or 'carousel' |
| `budget_daily` | `object` | no | Daily budget in USD for this ad set (minimum $1). Required for non-CBO campaigns. Do NOT set for CBO campaigns — budget is managed at campaign level. Use daily_min_spend_target/daily_spend_cap instead. |
| `campaign_budget_optimization` | `object` | no | Set to true if the parent campaign uses Advantage Campaign Budget (CBO). When true, budget_daily is not required and bid_strategy is skipped on the ad set. |
| `daily_min_spend_target` | `object` | no | CBO only: minimum daily spend for this ad set in account currency. |
| `daily_spend_cap` | `object` | no | CBO only: maximum daily spend cap for this ad set in account currency. |
| `image_url` | `object` | no | Public URL of image to use (for image ad_type) |
| `existing_image_hash` | `object` | no | Existing Meta image hash (for image ad_type) |
| `asset_bundle_id` | `object` | no | Asset bundle ID from validate_and_prepare_meta_assets (for image ad_type) |
| `video_url` | `object` | no | Public URL of video to upload (for video ad_type) |
| `existing_video_id` | `object` | no | Existing Meta video ID (for video ad_type) |
| `thumbnail_url` | `object` | no | Custom thumbnail URL for video |
| `cards` | `object` | no | Carousel cards (2-10). Each: {image_url, headline, landing_page_url, description?, call_to_action?} |
| `primary_text` | `string` | yes | Primary ad text (max 2200 chars, recommended 125). Supports emojis and line breaks. |
| `headline` | `object` | no | Headline (max 255 chars, recommended 40) |
| `landing_page_url` | `string` | yes | Landing page URL (HTTPS) |
| `call_to_action` | `string` | no | CTA button: LEARN_MORE, SHOP_NOW, SIGN_UP, etc. |
| `description` | `object` | no | Description (max 255 chars, recommended 30) |
| `locations` | `object` | no | Location targeting. Country codes (['US']) or location objects from search_meta_targeting ([{'key': '2421836', 'type': 'city', 'radius': 25, 'distance_unit': 'mile'}]). Default: ['US'] |
| `age_min` | `integer` | no |  |
| `age_max` | `integer` | no |  |
| `genders` | `object` | no |  |
| `interests` | `object` | no |  |
| `behaviors` | `object` | no |  |
| `custom_audiences` | `object` | no |  |
| `excluded_custom_audiences` | `object` | no |  |
| `lead_form_id` | `object` | no | Lead form ID for OUTCOME_LEADS campaigns. If not provided, auto-fetched from existing campaign ads. |
| `pixel_id` | `object` | no | Meta Pixel ID for conversion tracking |
| `pixel_event_name` | `object` | no | Conversion event: PURCHASE, LEAD, ADD_TO_CART, OTHER, etc. Use OTHER with custom_conversion_id for custom events. |
| `custom_conversion_id` | `object` | no | Custom conversion ID for optimizing towards a specific custom conversion. When provided, pixel_event_name is ignored — Meta infers the event type from the custom conversion. |
| `instagram_account_id` | `object` | no | Instagram account ID |
| `facebook_page_id` | `object` | no | Facebook Page ID |
| `ad_set_name` | `object` | no | Custom name for the ad set |
| `ad_name` | `object` | no | Custom name for the ad created inside the ad set |
| `ad_account_id` | `object` | no | Meta ad account ID |
| `multi_share_optimized` | `object` | no | For carousel: optimize card order |
| `budget_lifetime` | `object` | no | Lifetime budget in account currency for this ad set (minimum $1). Mutually exclusive with budget_daily. Requires end_time to be set. Cannot be used with CBO campaigns. |
| `end_time` | `object` | no | End date/time for this ad set in ISO format (e.g., 2026-04-30T23:59:59). Required when using budget_lifetime. Optional with budget_daily. |
| `multi_advertiser` | `object` | no | Set to false to opt out of Meta's multi-advertiser ad format at the creative level. When false, sets contextual_multi_ads.enroll_status=OPT_OUT on the ad creative. Default (None) = Meta's default behavior (enrolled). |
| `publisher_platforms` | `object` | no | Which platforms to run on: ['facebook', 'instagram', 'audience_network', 'messenger']. Only listed platforms are included. Default: automatic (all). |
| `facebook_positions` | `object` | no | Facebook placements: feed, right_hand_column, story, facebook_reels, marketplace, search, etc. Only listed positions are included. |
| `instagram_positions` | `object` | no | Instagram placements: stream (feed), story, reels, explore, explore_home, ig_search, profile_feed. Only listed positions are included. |
| `is_dynamic_creative` | `object` | no | Set to true to enable Dynamic Creative Optimization on this ad set. Meta will test combinations of images, headlines, and text to find the best performer. Only 1 ad is allowed per DCO ad set. Use with add_meta_ad's image_urls/headlines/prim |
| `destination_type` | `object` | no | Override the ad set destination type. Set to 'WEBSITE' for OUTCOME_LEADS campaigns that track conversions on your website via Meta Pixel instead of using Meta's built-in lead form. Options: WEBSITE, ON_AD, WEBSITE_AND_ON_AD. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "ad_type": "string",
    "primary_text": "string",
    "landing_page_url": "https://example.com",
    "budget_daily": 1.0,
    "campaign_budget_optimization": false,
    "daily_min_spend_target": 1.0,
    "daily_spend_cap": 1.0,
    "image_url": "string",
    "existing_image_hash": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_meta_ad_set)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_meta_ad_set"
}
```

---
## analyze_meta_ad_performance

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/analyze_meta_ad_performance/execute`

User wants detailed analysis of specific Meta ads, creative performance, or wants to identify winning/losing ad variations.

This tool provides ad-level and creative-level insights for Meta campaigns.

Returns:
- Top performing ads by CTR and conversion rate
- Underperforming ads that need attention
- Creative fatigue indicators (high frequency, declining CTR)
- Video completion metrics (25%, 50%, 75%, 100% watched)
- Creative optimization recommendations

When to use this tool:
- "Which of my Meta ads are performing best?"
- "Are any of my Facebook ads fatigued?"
- "Show me my Instagram video ad performance"
- "Which creatives should I pause?"
- "Analyze my ad variations"

Parameters:
- lookback_days: 7, 14, 30 (default), 60, or 90 days
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- campaign_id: Optional - filter to specific campaign
- include_video_metrics: true (default) or false
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 1-3 seconds (cached database query)
Data source: ad_group_daily_metrics table (Meta ad sets stored as ad groups)

Creative fatigue detection:
- High frequency (>4.0) with declining CTR
- CTR dropped >20% from first week
- Same creative running >14 days without refresh

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days. |
| `campaign_id` | `object` | no | Filter to specific campaign ID (optional - analyzes all campaigns if not provided) |
| `include_video_metrics` | `boolean` | no | Include video completion metrics (25%, 50%, 75%, 100% watched). Default is true. |
| `limit` | `integer` | no | Maximum number of ads to return (default 100, max 200). Use with offset for pagination. |
| `offset` | `integer` | no | Number of ads to skip for pagination (default 0). Use with limit to get next page. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "campaign_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for analyze_meta_ad_performance)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "analyze_meta_ad_performance"
}
```

---
## analyze_meta_audiences

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/analyze_meta_audiences/execute`

User asks about Meta/Facebook/Instagram audience performance by demographics, age group or gender targeting optimization, audience saturation, or which demographic segments to target or exclude.

This tool provides deep analysis of audience segment performance and detects audience saturation to optimize demographic targeting for Meta Ads.

Returns:
- Age group performance breakdown (18-24, 25-34, 35-44, 45-54, 55-64, 65+)
- Gender performance breakdown (male, female, unknown)
- Age + Gender combination analysis
- Segments categorized as SCALE/MAINTAIN/REDUCE/EXCLUDE based on ROAS
- Audience saturation score (0-100) with contributing factors
- Best performing segments to scale
- Underperforming segments to reduce/exclude
- Targeting optimization recommendations
- Quick actionable items

When to use this tool:
- "Which age groups perform best for my Meta ads?"
- "Should I target men or women on Facebook?"
- "Is my Meta audience saturated?"
- "Which demographics should I exclude?"
- "Analyze my Instagram audience performance"
- "Best demographic targeting for my Facebook campaigns"
- "Age and gender breakdown for my Meta ads"
- "Are my lookalike audiences exhausted?"
- "Which audience segments are wasting money?"

Parameters:
- lookback_days: 7, 14, 30 (default), 60, or 90 days
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- breakdown_type: 'age', 'gender', 'age_gender', or 'all' (default)
- include_saturation: Include saturation analysis (default: True)
- target_roas: Optional override (default: from account goals or 2.0x)
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 2-5 seconds (cached database query with analysis)
Data source: meta_audience_daily_metrics table (demographic-level daily metrics)

ROAS Thresholds for Segment Recommendations:
- 🚀 SCALE (ROAS ≥ 1.5x target): Increase budget to this segment
- ➖ MAINTAIN (ROAS 0.75x-1.5x target): Keep current allocation
- ⚠️ REDUCE (ROAS 0.5x-0.75x target): Decrease budget
- 🔴 EXCLUDE (ROAS < 0.5x target): Remove from targeting

Saturation Score Factors (weighted):
- Frequency Score (35%): How often users see ads (>3 indicates fatigue)
- CTR Decline Score (30%): Week-over-week CTR changes
- CPA Increase Score (25%): Rising cost per acquisition
- Reach Saturation Score (10%): Audience reach exhaustion

Saturation Levels:
- ✅ HEALTHY (<40): Audience is fresh, continue scaling
- 🟡 AT_RISK (40-69): Monitor frequency, prepare new audiences
- 🔴 SATURATED (≥70): Expand targeting or refresh creatives

Common Insights:
- Age 25-44 typically has highest ROAS for e-commerce
- Gender targeting varies significantly by product category
- High frequency (>4) combined with declining CTR indicates saturation
- Lookalike audiences can exhaust within 4-8 weeks at high spend

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days. |
| `breakdown_type` | `string` | no | Type of audience breakdown: 'age' (age groups), 'gender', 'age_gender' (combined), or 'all' (default). |
| `include_saturation` | `boolean` | no | Include audience saturation analysis for lookalike and custom audiences (default: True). |
| `target_roas` | `object` | no | Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "breakdown_type": "all"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for analyze_meta_audiences)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "analyze_meta_audiences"
}
```

---
## analyze_meta_wasted_spend

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/analyze_meta_wasted_spend/execute`

User asks about Meta/Facebook/Instagram ad spend efficiency, wasted money, underperforming campaigns, placement optimization, or creative fatigue.

This tool identifies Meta Ads campaigns and placements that are wasting money by performing below target ROAS or below breakeven (ROAS < 1.0). It extends the base wasted spend detector with Meta-specific features:

Returns:
- Campaign-level wasted spend analysis (ROAS < 1.0 = actual losses)
- Underperforming spend analysis (1.0 <= ROAS < target = opportunity cost)
- Placement-level breakdown (which placements are wasting money: Feed, Stories, Reels, etc.)
- Creative fatigue detection (high frequency + declining CTR)
- Actionable recommendations for campaigns, placements, and creatives
- Quick action items

When to use this tool:
- "Where am I wasting money on Meta/Facebook ads?"
- "Which Meta placements should I exclude?"
- "Are any of my Facebook creatives fatigued?"
- "How can I reduce wasted ad spend on Instagram?"
- "Which Meta campaigns are losing money?"
- "What's my Meta ROAS by placement?"

Parameters:
- lookback_days: 7, 30 (default), 60, 90, or 120 days
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- target_roas: Optional override (e.g., 3.0 for 3.0x ROAS)
- include_placements: true (default) - Include placement-level analysis
- include_fatigue: true (default) - Include creative fatigue analysis
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 2-5 seconds (cached database query with analysis)
Data source: campaign_daily_metrics + meta_placement_daily_metrics + meta_ad_creative_metrics tables

Key definitions:
- Wasted Spend (ROAS < 1.0): Actual money lost - for every $1 spent, getting back <$1
- Underperforming (1.0 <= ROAS < target): Profitable but below target - opportunity cost
- Creative Fatigue: Ads with frequency >4x (cold traffic) or >7x (retargeting) showing declining CTR

Meta-specific placement optimization:
- Audience Network often has lowest ROAS - consider excluding
- Instagram Stories/Reels typically perform differently than Feed
- Facebook Marketplace can waste spend if not relevant

**Quick Actions (IMPORTANT — read severity context first):**
- ⏳ LEARNING campaigns → Do NOT pause. Monitor for 14+ days before judging.
- ❓ INSUFFICIENT_DATA campaigns → Need more spend before analysis is meaningful.
- 🚨 CRITICAL campaigns (established, 14+ days, ZERO conversions) → Consider pausing
- 🚨 CRITICAL campaigns (established, 14+ days, HAS conversions) → Review performance, verify revenue in ad platform before reducing budget
- 🔴 HIGH severity (established, 14+ days) → Consider reducing budget by 50-70%
- 🟡 MEDIUM → Optimize targeting, ad copy, landing pages

⚠️ **NEVER say "pause" for a campaign that has conversions.** Say "review" or "reduce budget" instead.
⚠️ **NEVER recommend pausing a campaign in LEARNING phase.**
⚠️ **If ALL campaigns are LEARNING or INSUFFICIENT_DATA, tell the user their account is too new for waste analysis and recommend checking back in 2 weeks.**
⚠️ **Consider campaign objective: brand awareness campaigns will not have ROAS data. This is normal.**
⚠️ **When data confidence is MEDIUM or LOW, soften all recommendations and add verification prompts.**

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 30, 60, 90, or 120 days). Default is 30 days. |
| `target_roas` | `object` | no | Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average. |
| `include_placements` | `boolean` | no | Include placement-level analysis (Feed, Stories, Reels, etc.). Default is true. |
| `include_fatigue` | `boolean` | no | Include creative fatigue analysis. Default is true. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "target_roas": 1.0
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for analyze_meta_wasted_spend)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "analyze_meta_wasted_spend"
}
```

---
## browse_meta_targeting

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/browse_meta_targeting/execute`

User wants to browse all targeting options in a specific category without a search query.

This tool retrieves all options in a Meta targeting category for exploration and discovery.

Returns:
- List of all targeting options in the category
- IDs, names, and audience sizes
- Useful for discovering targeting options when user doesn't have a specific query

When to use this tool:
- "Show me all available behaviors for Meta targeting"
- "What interest categories are available?"
- "Browse demographic targeting options"
- "List all life events I can target"
- "What income targeting options exist?"

Categories Available:
- interests: All interest targeting categories
- behaviors: All behavior targeting options
- demographics: All demographic targeting options
- life_events: All life event targeting options
- industries: Industry targeting options
- income: Income bracket targeting options
- family_statuses: Family status targeting options

Parameters:
- category: Category to browse (required)
- limit: Maximum results (1-500, default: 100)
- locale: Locale for results (default: en_US)
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 1-3 seconds
Data source: Meta Marketing API Targeting Search

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `category` | `string` | yes | Category to browse: - 'interests': Browse all interest categories - 'behaviors': Browse all behavior categories - 'demographics': Browse all demographic categories - 'life_events': Browse all life event categories - 'industries': Browse ind |
| `limit` | `integer` | no | Maximum number of results to return (1-500). Default is 100. |
| `locale` | `string` | no | Locale for results (e.g., 'en_US', 'es_ES'). Default is 'en_US'. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "category": "string",
    "limit": 100,
    "locale": "en_US",
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for browse_meta_targeting)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "browse_meta_targeting"
}
```

---
## create_meta_carousel_campaign

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_meta_carousel_campaign/execute`

User wants to create a Meta (Facebook/Instagram) carousel ad campaign with multiple images.

REQUIRED: You MUST call `select_meta_campaign_type` first and complete ALL phases it describes (audience targeting research via `search_meta_targeting`/`browse_meta_targeting`, asset discovery via `discover_meta_assets`, and user approval) BEFORE calling this tool.

IMPORTANT: This tool creates REAL campaigns that will spend money once activated. Campaign is created in PAUSED status for review.

DO NOT USE for single image - use `create_meta_image_campaign` instead.
DO NOT USE for video - use `create_meta_video_campaign` instead.

This tool creates a complete Meta carousel campaign with:
1. Campaign (objective, budget)
2. Ad Set (targeting, placements, schedule)
3. Multiple Image Uploads (one per card)
4. Ad Creative (carousel with child_attachments)
5. Ad (linking creative to ad set)

When to use this tool:
- "Create a Facebook carousel ad"
- "Launch an Instagram carousel campaign"
- "Create a multi-product ad"
- "Set up an ad with multiple images"

Required Parameters:
- campaign_name: Name for the campaign
- budget_daily: Daily budget in USD (min $1, recommend $5-20 for testing)
- primary_text: Main ad text (recommended 125 chars for optimal display, longer text shows with "See More")
- cards: Array of 2-10 cards (see card structure below)

Card Structure (each card requires):
- image_url OR image_hash: Image source (one required)
- landing_page_url: Where users go when clicking this card
- headline: Card headline (max 45 chars)
- description: Optional card description (max 20 chars)
- call_to_action: Optional per-card CTA override

Optional Parameters:
- facebook_page_id: Auto-detected from connected account
- instagram_account_id: Enable Instagram placements
- call_to_action: Default CTA for all cards (LEARN_MORE by default)
- objective: OUTCOME_TRAFFIC (default), OUTCOME_SALES, OUTCOME_LEADS
- locations: Country codes (default: ['US'])
- age_min/age_max: Age targeting (18-65)
- genders: ['male'], ['female'], or null for all
- multi_share_optimized: Let Meta optimize card order (default: true)
- pixel_id: Meta Pixel ID for conversion tracking (required for OUTCOME_SALES)
- pixel_event_name: Conversion event (PURCHASE, LEAD, etc.)

After Creation — IMPORTANT:
- This tool created 1 campaign + 1 ad set + 1 ad.
- To add MORE ad sets (different targeting, audiences, or formats), use `add_meta_ad_set` with the returned campaign_id
- To add MORE ads to the same ad set (A/B test copy/creative), use `add_meta_ad` with the returned ad_set_id
- NEVER call this create tool again for the same campaign — that creates a SEPARATE campaign

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (will be automatically suffixed with timestamp for uniqueness) |
| `objective` | `string` | no | Campaign objective. Options: 'OUTCOME_TRAFFIC' (website clicks), 'OUTCOME_SALES' (conversions), 'OUTCOME_LEADS' (lead forms), 'OUTCOME_AWARENESS' (reach), 'OUTCOME_ENGAGEMENT'. Default: OUTCOME_TRAFFIC |
| `budget_daily` | `object` | no | Daily budget in account currency (minimum $1/day, recommended $5-20/day for testing). Mutually exclusive with budget_lifetime — provide exactly one. |
| `budget_lifetime` | `object` | no | Lifetime budget in account currency (total spend over campaign duration). REQUIRES end_time to be set. Mutually exclusive with budget_daily — provide exactly one. |
| `end_time` | `object` | no | Campaign/ad set end date in ISO format (e.g. '2026-04-30T23:59:59'). REQUIRED when using budget_lifetime. Optional with budget_daily. |
| `facebook_page_id` | `object` | no | Facebook Page ID for the ad. Usually auto-detected from your connected account. Only provide if you have multiple pages and want to use a specific one. |
| `primary_text` | `string` | yes | Primary ad text (max 2200 characters, recommended 125 or less for optimal display). Supports emojis, line breaks (\n), and bullet points (e.g. '🔥 Limited Offer!\n\n✅ Free Shipping\n✅ 30-Day Returns'). This is the main message that appears a |
| `cards` | `array` | yes | List of 2-10 carousel cards. Each card should have: image_url OR image_hash, landing_page_url, headline (max 45 chars), optional description (max 20 chars), optional call_to_action. |
| `call_to_action` | `string` | no | Default call-to-action button for cards without specific CTA. Options: 'LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD', 'BOOK_TRAVEL'. Default: LEARN_MORE |
| `instagram_account_id` | `object` | no | Instagram account ID for Instagram placements. If not provided, ad will run on Facebook only. |
| `pixel_id` | `object` | no | Meta Pixel ID for conversion tracking. Required for OUTCOME_SALES campaigns. Use list_meta_pixels to find available pixels for your ad account. |
| `pixel_event_name` | `object` | no | Conversion event to optimize for when pixel_id is provided. Options: PURCHASE, LEAD, COMPLETE_REGISTRATION, ADD_TO_CART, INITIATE_CHECKOUT, ADD_PAYMENT_INFO, SEARCH, VIEW_CONTENT, OTHER. Use OTHER with custom_conversion_id for custom pixel  |
| `custom_conversion_id` | `object` | no | Custom conversion ID for optimizing towards a specific custom conversion. When provided, pixel_event_name is ignored — Meta infers the event type from the custom conversion. Get available custom conversions from your Meta Events Manager. |
| `destination_type` | `object` | no | Override the ad set destination type. By default, OUTCOME_LEADS uses ON_AD (Meta lead forms) and OUTCOME_TRAFFIC uses WEBSITE. Set to 'WEBSITE' for OUTCOME_LEADS campaigns that track conversions on your website via Meta Pixel instead of usi |
| `campaign_budget_optimization` | `boolean` | no | Enable Advantage Campaign Budget (CBO). When true, the daily budget is set at the campaign level and Meta automatically distributes it across ad sets. When false (default), each ad set manages its own budget. |
| `daily_min_spend_target` | `object` | no | CBO only: minimum daily spend for this ad set in account currency. Meta will try to spend at least this amount on this ad set each day. |
| `daily_spend_cap` | `object` | no | CBO only: maximum daily spend cap for this ad set in account currency. Meta will not spend more than this on this ad set each day. |
| `locations` | `object` | no | Location targeting. Accepts country codes (['US', 'CA']) OR location objects from search_meta_targeting ([{'key': '2421836', 'type': 'city', 'radius': 25, 'distance_unit': 'mile'}]). For city/region targeting, first call search_meta_targeti |
| `age_min` | `integer` | no | Minimum age (18-65). Default: 18 |
| `age_max` | `integer` | no | Maximum age (18-65). Default: 65 |
| `genders` | `object` | no | Gender targeting: ['male'], ['female'], or None for all genders |
| `interests` | `object` | no | List of interest targeting objects from search_meta_targeting. Format: [{'id': '6003139266461', 'name': 'Fitness'}]. Use search_meta_targeting with search_type='interests' to find valid IDs. |
| `behaviors` | `object` | no | List of behavior targeting objects from search_meta_targeting. Format: [{'id': '6002714895372', 'name': 'Small business owners'}]. Use search_meta_targeting with search_type='behaviors' to find valid IDs. |
| `job_titles` | `object` | no | List of job title targeting objects for B2B campaigns. Format: [{'id': '123456', 'name': 'Chief Marketing Officer'}]. Use search_meta_targeting with search_type='job_titles' to find valid IDs. |
| `work_employers` | `object` | no | List of employer targeting objects for B2B campaigns. Format: [{'id': '123456', 'name': 'Google'}]. Use search_meta_targeting with search_type='work_employers' to find valid IDs. |
| `life_events` | `object` | no | List of life event targeting objects. Format: [{'id': '123456', 'name': 'Recently engaged'}]. Use search_meta_targeting with search_type='life_events' to find valid IDs. |
| `education_schools` | `object` | no | List of education school targeting objects. Format: [{'id': '123456', 'name': 'Harvard University'}]. Use search_meta_targeting with search_type='education_schools' to find valid IDs. |
| `education_majors` | `object` | no | List of education major targeting objects. Format: [{'id': '123456', 'name': 'Computer Science'}]. Use search_meta_targeting with search_type='education_majors' to find valid IDs. |
| `custom_audiences` | `object` | no | List of Custom Audience IDs to include in targeting. These are audiences created in Meta Ads Manager (website visitors, customer lists, etc.) |
| `excluded_custom_audiences` | `object` | no | List of Custom Audience IDs to exclude from targeting. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `special_ad_categories` | `object` | no | Special ad categories for housing, credit, employment, or social issues. Options: 'HOUSING', 'CREDIT', 'EMPLOYMENT', 'ISSUES_ELECTIONS_POLITICS'. Leave empty for standard ads. |
| `lead_form_id` | `object` | no | Lead form ID to attach when objective is OUTCOME_LEADS. Get available forms using list_meta_lead_forms tool. Only used with OUTCOME_LEADS objective. |
| `multi_share_optimized` | `boolean` | no | Whether to let Meta optimize card order based on performance. Default: true (recommended). |
| `multi_advertiser` | `object` | no | Allow ad to appear in multi-advertiser ad format (multiple ads shown together). Set to false to opt out. Default: Meta's default (enabled). Brands that want exclusive placement should set this to false. |
| `publisher_platforms` | `object` | no | Platforms to show ads on. Options: 'facebook', 'instagram', 'audience_network', 'messenger'. Default: automatic (all). To exclude Audience Network/Apps, use ['facebook', 'instagram']. |
| `facebook_positions` | `object` | no | Facebook placement positions. Options: 'feed', 'right_hand_column', 'marketplace', 'video_feeds', 'story', 'search', 'instream_video', 'facebook_reels'. Only include positions you WANT — omitted positions are excluded. |
| `instagram_positions` | `object` | no | Instagram placement positions. Options: 'stream' (feed), 'story', 'reels', 'explore', 'explore_home', 'ig_search', 'profile_feed'. Only include positions you WANT — omitted positions are excluded. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "primary_text": "string",
    "cards": [
      {
        "image_url": "https://example.com/card1.jpg",
        "landing_page_url": "https://example.com/product/1",
        "headline": "Spring Sale",
        "description": "Up to 40% off",
        "call_to_action": "SHOP_NOW"
      },
      {
        "image_url": "https://example.com/card2.jpg",
        "landing_page_url": "https://example.com/product/2",
        "headline": "New Arrivals"
      }
    ],
    "objective": "OUTCOME_TRAFFIC",
    "budget_daily": 1.0,
    "budget_lifetime": 1.0,
    "end_time": "string",
    "facebook_page_id": "string",
    "call_to_action": "LEARN_MORE"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_meta_carousel_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_meta_carousel_campaign"
}
```

---
## create_meta_dco_ad

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_meta_dco_ad/execute`

User wants Meta to automatically TEST MULTIPLE IMAGES and find the best combination.

This is Dynamic Creative Optimization (DCO). Meta tests all combinations of images × headlines × primary texts and optimizes delivery automatically.

USE THIS TOOL when the user says ANY of these:
- "test these images" / "test multiple images"
- "let Meta find the winner" / "let Meta optimize"
- "Dynamic Creative" / "DCO"
- "which image performs best"
- Provides multiple images and wants Meta to choose the best
- "test combinations" / "mix and match"
- "10 images × 5 headlines"

DO NOT USE add_meta_ad multiple times for this — that creates separate ads (A/B test).
DCO is ONE ad with multiple assets that Meta mixes and optimizes.

Prerequisites:
1. You need an ad_set_id with is_dynamic_creative=true
2. If the user doesn't have one, first call `add_meta_ad_set` with `is_dynamic_creative=true`
3. Then call this tool with the image_urls and text variations

Asset limits (from Meta):
- Images: 2-10 (required)
- Headlines: up to 5 (optional)
- Primary texts: up to 5 (optional)
- Descriptions: up to 5 (optional)
- Total assets: max 30

DCO ad sets support only 1 ad — do NOT add more ads to a DCO ad set.

Example: 5 images × 3 headlines × 2 primary texts = 30 combinations, Meta finds the winner.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_set_id` | `string` | yes | The Meta Ad Set ID. The ad set must have is_dynamic_creative=true (create one with add_meta_ad_set using is_dynamic_creative=true). |
| `image_urls` | `array` | yes | List of 2-10 image URLs for Meta to test. Meta will try all combinations. |
| `primary_text` | `string` | yes | Default primary text (used if primary_texts is not provided). |
| `landing_page_url` | `string` | yes | Landing page URL (HTTPS) |
| `call_to_action` | `string` | no | CTA button |
| `headlines` | `object` | no | List of headline variations (up to 5) for Meta to test. |
| `headline` | `object` | no | Single headline (used if headlines is not provided). |
| `primary_texts` | `object` | no | List of primary text variations (up to 5) for Meta to test. |
| `descriptions` | `object` | no | List of description variations (up to 5) for Meta to test. |
| `description` | `object` | no | Single description (used if descriptions is not provided). |
| `lead_form_id` | `object` | no | Lead form ID for OUTCOME_LEADS campaigns. |
| `facebook_page_id` | `object` | no | Facebook Page ID |
| `instagram_account_id` | `object` | no | Instagram account ID |
| `ad_name` | `object` | no | Custom name for the DCO ad |
| `ad_account_id` | `object` | no | Meta ad account ID |

### Example request

```json
{
  "arguments": {
    "ad_set_id": "string",
    "image_urls": [
      "string"
    ],
    "primary_text": "string",
    "landing_page_url": "https://example.com",
    "call_to_action": "LEARN_MORE",
    "headlines": [
      "string"
    ],
    "headline": "string",
    "primary_texts": [
      "string"
    ],
    "descriptions": [
      "string"
    ],
    "description": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_meta_dco_ad)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_meta_dco_ad"
}
```

---
## create_meta_image_campaign

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_meta_image_campaign/execute`

User wants to create a Meta (Facebook/Instagram) single-image ad campaign.

REQUIRED: You MUST call `select_meta_campaign_type` first and complete ALL phases it describes (audience targeting research via `search_meta_targeting`/`browse_meta_targeting`, asset discovery via `discover_meta_assets`, and user approval) BEFORE calling this tool.

IMPORTANT: This tool creates REAL campaigns that will spend money once activated. Campaign is created in PAUSED status for review.

DO NOT USE for video ads - use `create_meta_video_campaign` instead.
DO NOT USE for carousel/multi-image - use `create_meta_carousel_campaign` instead.

This tool creates a complete Meta campaign with:
1. Campaign (objective, budget)
2. Ad Set (targeting, placements, schedule)
3. Ad Creative (image, text, CTA)
4. Ad (linking creative to ad set)

When to use this tool:
- "Create a Facebook ad campaign"
- "Launch an Instagram image ad"
- "Set up a Meta traffic campaign"
- "Create an ad with this image"

Required Parameters:
- campaign_name: Name for the campaign
- budget_daily: Daily budget in USD (min $1, recommend $5-20 for testing)
- primary_text: Main ad text (recommended 125 chars for optimal display, longer text shows with "See More")
- headline: Headline below image (max 255 chars, recommended 40)
- landing_page_url: Where users go when clicking

Image Source (choose ONE):
- asset_bundle_id: From `validate_and_prepare_meta_assets` (recommended for new images)
- existing_image_hash: From `discover_meta_assets` (for reusing existing images)
- image_url: Direct URL (uploaded during creation - use validate_and_prepare for better error handling)

Optional Parameters:
- facebook_page_id: Auto-detected from connected account. Only provide if multiple pages.
- instagram_account_id: Auto-detected if linked. Enable Instagram placements.
- objective: OUTCOME_TRAFFIC (default), OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_AWARENESS
- call_to_action: LEARN_MORE (default), SHOP_NOW, SIGN_UP, etc.
- locations: Country codes (default: ['US']) or location objects from search_meta_targeting
- age_min/age_max: Age targeting (18-65)
- genders: ['male'], ['female'], or null for all
- pixel_id: Meta Pixel ID for conversion tracking (required for OUTCOME_SALES)
- pixel_event_name: Conversion event (PURCHASE, LEAD, etc.)
- story_image_url: Different image for Stories/Reels (9:16, 1080x1920px). Uses asset_feed_spec.
- right_column_image_url: Different image for Right Column (1.91:1, 1200x628px). Uses asset_feed_spec.

Multi-Placement Creatives:
If the user wants different images for different placements (Feed, Stories, Right Column),
provide story_image_url and/or right_column_image_url. The main image will be used for Feed.
This uses Meta's asset_feed_spec with asset_customization_rules instead of object_story_spec.

After Creation — IMPORTANT:
- This tool created 1 campaign + 1 ad set + 1 ad.
- To add MORE ad sets (different targeting, audiences, or formats), use `add_meta_ad_set` with the returned campaign_id
- To add MORE ads to the same ad set (A/B test copy/creative), use `add_meta_ad` with the returned ad_set_id
- NEVER call this create tool again for the same campaign — that creates a SEPARATE campaign

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (will be automatically suffixed with timestamp for uniqueness) |
| `objective` | `string` | no | Campaign objective. Options: 'OUTCOME_TRAFFIC' (website clicks), 'OUTCOME_SALES' (conversions), 'OUTCOME_LEADS' (lead forms), 'OUTCOME_AWARENESS' (reach), 'OUTCOME_ENGAGEMENT'. Default: OUTCOME_TRAFFIC |
| `budget_daily` | `object` | no | Daily budget in account currency (minimum $1/day, recommended $5-20/day for testing). Mutually exclusive with budget_lifetime — provide exactly one. |
| `budget_lifetime` | `object` | no | Lifetime budget in account currency (total spend over campaign duration). REQUIRES end_time to be set. Mutually exclusive with budget_daily — provide exactly one. |
| `end_time` | `object` | no | Campaign/ad set end date in ISO format (e.g. '2026-04-30T23:59:59'). REQUIRED when using budget_lifetime. Optional with budget_daily. |
| `asset_bundle_id` | `object` | no | Asset bundle ID from validate_and_prepare_meta_assets tool. Use this for NEW image uploads. Mutually exclusive with existing_image_hash and image_url. |
| `existing_image_hash` | `object` | no | Existing Meta image hash from discover_meta_assets tool. Use this to REUSE images from Meta Ad Library. Mutually exclusive with asset_bundle_id and image_url. |
| `image_url` | `object` | no | Public image URL to upload (will be uploaded during campaign creation). Use validate_and_prepare_meta_assets for better error handling. Mutually exclusive with asset_bundle_id and existing_image_hash. |
| `facebook_page_id` | `object` | no | Facebook Page ID for the ad. Usually auto-detected from your connected account. Only provide if you have multiple pages and want to use a specific one. |
| `primary_text` | `string` | yes | Primary ad text (max 2200 characters, recommended 125 or less for optimal display). Supports emojis, line breaks (\n), and bullet points (e.g. '🔥 Limited Offer!\n\n✅ Free Shipping\n✅ 30-Day Returns'). This is the main message that appears a |
| `headline` | `string` | yes | Headline text (max 255 characters, recommended 40). Appears below the image as clickable text. |
| `landing_page_url` | `string` | yes | Landing page URL where users will be directed when clicking the ad. Must be HTTPS. |
| `call_to_action` | `string` | no | Call-to-action button. Options: 'LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD', 'BOOK_TRAVEL', 'CONTACT_US', 'GET_QUOTE', 'SUBSCRIBE', 'WATCH_MORE'. Default: LEARN_MORE |
| `description` | `object` | no | Optional description text (max 255 characters, recommended 30). Appears under the headline on some placements. |
| `story_image_url` | `object` | no | Optional image URL for Stories/Reels placements (9:16 ratio, recommended 1080x1920px). If provided, the main image is used for Feed and this image for Stories/Reels. Meta's asset_feed_spec will be used instead of object_story_spec. |
| `right_column_image_url` | `object` | no | Optional image URL for Right Column placement (1.91:1 ratio, recommended 1200x628px). If provided, Meta's asset_feed_spec will be used instead of object_story_spec. |
| `instagram_account_id` | `object` | no | Instagram account ID for Instagram placements. If not provided, ad will run on Facebook only. |
| `pixel_id` | `object` | no | Meta Pixel ID for conversion tracking. Required for OUTCOME_SALES campaigns. Use list_meta_pixels to find available pixels for your ad account. |
| `pixel_event_name` | `object` | no | Conversion event to optimize for when pixel_id is provided. Options: PURCHASE, LEAD, COMPLETE_REGISTRATION, ADD_TO_CART, INITIATE_CHECKOUT, ADD_PAYMENT_INFO, SEARCH, VIEW_CONTENT, OTHER. Use OTHER with custom_conversion_id for custom pixel  |
| `custom_conversion_id` | `object` | no | Custom conversion ID for optimizing towards a specific custom conversion. When provided, pixel_event_name is ignored — Meta infers the event type from the custom conversion. Get available custom conversions from your Meta Events Manager. |
| `destination_type` | `object` | no | Override the ad set destination type. By default, OUTCOME_LEADS uses ON_AD (Meta lead forms) and OUTCOME_TRAFFIC uses WEBSITE. Set to 'WEBSITE' for OUTCOME_LEADS campaigns that track conversions on your website via Meta Pixel instead of usi |
| `campaign_budget_optimization` | `boolean` | no | Enable Advantage Campaign Budget (CBO). When true, the daily budget is set at the campaign level and Meta automatically distributes it across ad sets. When false (default), each ad set manages its own budget. |
| `daily_min_spend_target` | `object` | no | CBO only: minimum daily spend for this ad set in account currency. Meta will try to spend at least this amount on this ad set each day. |
| `daily_spend_cap` | `object` | no | CBO only: maximum daily spend cap for this ad set in account currency. Meta will not spend more than this on this ad set each day. |
| `locations` | `object` | no | Location targeting. Accepts country codes (['US', 'CA']) OR location objects from search_meta_targeting ([{'key': '2421836', 'type': 'city', 'radius': 25, 'distance_unit': 'mile'}]). For city/region targeting, first call search_meta_targeti |
| `age_min` | `integer` | no | Minimum age (18-65). Default: 18 |
| `age_max` | `integer` | no | Maximum age (18-65). Default: 65 |
| `genders` | `object` | no | Gender targeting: ['male'], ['female'], or None for all genders |
| `interests` | `object` | no | List of interest targeting objects from search_meta_targeting. Format: [{'id': '6003139266461', 'name': 'Fitness'}]. Use search_meta_targeting with search_type='interests' to find valid IDs. |
| `behaviors` | `object` | no | List of behavior targeting objects from search_meta_targeting. Format: [{'id': '6002714895372', 'name': 'Small business owners'}]. Use search_meta_targeting with search_type='behaviors' to find valid IDs. |
| `job_titles` | `object` | no | List of job title targeting objects for B2B campaigns. Format: [{'id': '123456', 'name': 'Chief Marketing Officer'}]. Use search_meta_targeting with search_type='job_titles' to find valid IDs. |
| `work_employers` | `object` | no | List of employer targeting objects for B2B campaigns. Format: [{'id': '123456', 'name': 'Google'}]. Use search_meta_targeting with search_type='work_employers' to find valid IDs. |
| `life_events` | `object` | no | List of life event targeting objects. Format: [{'id': '123456', 'name': 'Recently engaged'}]. Use search_meta_targeting with search_type='life_events' to find valid IDs. |
| `education_schools` | `object` | no | List of education school targeting objects. Format: [{'id': '123456', 'name': 'Harvard University'}]. Use search_meta_targeting with search_type='education_schools' to find valid IDs. |
| `education_majors` | `object` | no | List of education major targeting objects. Format: [{'id': '123456', 'name': 'Computer Science'}]. Use search_meta_targeting with search_type='education_majors' to find valid IDs. |
| `custom_audiences` | `object` | no | List of Custom Audience IDs to include in targeting. These are audiences created in Meta Ads Manager (website visitors, customer lists, etc.) |
| `excluded_custom_audiences` | `object` | no | List of Custom Audience IDs to exclude from targeting. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `special_ad_categories` | `object` | no | Special ad categories for housing, credit, employment, or social issues. Options: 'HOUSING', 'CREDIT', 'EMPLOYMENT', 'ISSUES_ELECTIONS_POLITICS'. Leave empty for standard ads. |
| `lead_form_id` | `object` | no | Lead form ID to attach when objective is OUTCOME_LEADS. Get available forms using list_meta_lead_forms tool. Only used with OUTCOME_LEADS objective. |
| `multi_advertiser` | `object` | no | Allow ad to appear in multi-advertiser ad format (multiple ads shown together). Set to false to opt out. Default: Meta's default (enabled). Brands that want exclusive placement should set this to false. |
| `publisher_platforms` | `object` | no | Platforms to show ads on. Options: 'facebook', 'instagram', 'audience_network', 'messenger'. Default: automatic (all). To exclude Audience Network/Apps, use ['facebook', 'instagram']. |
| `facebook_positions` | `object` | no | Facebook placement positions. Options: 'feed', 'right_hand_column', 'marketplace', 'video_feeds', 'story', 'search', 'instream_video', 'facebook_reels'. Only include positions you WANT — omitted positions are excluded. |
| `instagram_positions` | `object` | no | Instagram placement positions. Options: 'stream' (feed), 'story', 'reels', 'explore', 'explore_home', 'ig_search', 'profile_feed'. Only include positions you WANT — omitted positions are excluded. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "primary_text": "string",
    "headline": "string",
    "landing_page_url": "https://example.com",
    "objective": "OUTCOME_TRAFFIC",
    "budget_daily": 1.0,
    "budget_lifetime": 1.0,
    "end_time": "string",
    "asset_bundle_id": "string",
    "existing_image_hash": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_meta_image_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_meta_image_campaign"
}
```

---
## create_meta_video_campaign

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_meta_video_campaign/execute`

User wants to create a Meta (Facebook/Instagram) video ad campaign.

REQUIRED: You MUST call `select_meta_campaign_type` first and complete ALL phases it describes (audience targeting research via `search_meta_targeting`/`browse_meta_targeting`, asset discovery via `discover_meta_assets`, and user approval) BEFORE calling this tool.

IMPORTANT: This tool creates REAL campaigns that will spend money once activated. Campaign is created in PAUSED status for review.

DO NOT USE for image ads - use `create_meta_image_campaign` instead.
DO NOT USE for carousel - use `create_meta_carousel_campaign` instead.

This tool creates a complete Meta video campaign with:
1. Campaign (objective, budget)
2. Ad Set (targeting, placements, schedule)
3. Video Upload (handles large files with chunked upload)
4. Ad Creative (video, text, CTA)
5. Ad (linking creative to ad set)

When to use this tool:
- "Create a Facebook video ad campaign"
- "Launch an Instagram video ad"
- "Set up a Meta Reels campaign"
- "Create an ad with this video"

Required Parameters:
- campaign_name: Name for the campaign
- budget_daily: Daily budget in USD (min $1, recommend $5-20 for testing)
- primary_text: Main ad text (recommended 125 chars for optimal display, longer text shows with "See More")
- landing_page_url: Where users go when clicking

Video Source (choose ONE):
- video_url: Public video URL (will be uploaded during creation)
- existing_video_id: Existing Meta video ID (for reusing previously uploaded videos)

Optional Parameters:
- facebook_page_id: Auto-detected from connected account
- instagram_account_id: Enable Instagram placements
- thumbnail_url: Custom thumbnail image (Meta auto-generates if not provided)
- headline: Headline below video (optional for video ads)
- description: Description text (max 255 chars, recommended 30)
- call_to_action: WATCH_MORE (default), LEARN_MORE, SHOP_NOW, etc.
- objective: OUTCOME_TRAFFIC (default), OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_AWARENESS
- locations: Country codes (default: ['US'])
- age_min/age_max: Age targeting (18-65)
- genders: ['male'], ['female'], or null for all
- optimize_for_reels: true for vertical (9:16) videos
- pixel_id: Meta Pixel ID for conversion tracking (required for OUTCOME_SALES)
- pixel_event_name: Conversion event (PURCHASE, LEAD, etc.)

Video Specifications:
- Formats: MP4, MOV (recommended: MP4 H.264)
- Max size: 4GB (recommended under 1GB)
- Duration: 1 sec - 240 min (recommended 15-60 sec)
- Feed: 1:1 or 4:5 aspect ratio
- Stories/Reels: 9:16 aspect ratio

After Creation — IMPORTANT:
- This tool created 1 campaign + 1 ad set + 1 ad.
- To add MORE ad sets (different targeting, audiences, or formats), use `add_meta_ad_set` with the returned campaign_id
- To add MORE ads to the same ad set (A/B test copy/creative), use `add_meta_ad` with the returned ad_set_id
- NEVER call this create tool again for the same campaign — that creates a SEPARATE campaign

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (will be automatically suffixed with timestamp for uniqueness) |
| `objective` | `string` | no | Campaign objective. Options: 'OUTCOME_TRAFFIC' (website clicks), 'OUTCOME_SALES' (conversions), 'OUTCOME_LEADS' (lead forms), 'OUTCOME_AWARENESS' (reach), 'OUTCOME_ENGAGEMENT'. Default: OUTCOME_TRAFFIC |
| `budget_daily` | `object` | no | Daily budget in account currency (minimum $1/day, recommended $5-20/day for testing). Mutually exclusive with budget_lifetime — provide exactly one. |
| `budget_lifetime` | `object` | no | Lifetime budget in account currency (total spend over campaign duration). REQUIRES end_time to be set. Mutually exclusive with budget_daily — provide exactly one. |
| `end_time` | `object` | no | Campaign/ad set end date in ISO format (e.g. '2026-04-30T23:59:59'). REQUIRED when using budget_lifetime. Optional with budget_daily. |
| `video_url` | `object` | no | Public video URL to upload. Video will be uploaded during campaign creation. Supports MP4, MOV formats. Max 4GB, recommended under 1GB. Mutually exclusive with existing_video_id. |
| `existing_video_id` | `object` | no | Existing Meta video ID from your Ad Library. Use this to REUSE videos already uploaded to Meta. Mutually exclusive with video_url. |
| `thumbnail_url` | `object` | no | Optional custom thumbnail image URL. If not provided, Meta will auto-generate thumbnails from video. |
| `facebook_page_id` | `object` | no | Facebook Page ID for the ad. Usually auto-detected from your connected account. Only provide if you have multiple pages and want to use a specific one. |
| `primary_text` | `string` | yes | Primary ad text (max 2200 characters, recommended 125 or less for optimal display). Supports emojis, line breaks (\n), and bullet points (e.g. '🔥 Limited Offer!\n\n✅ Free Shipping\n✅ 30-Day Returns'). This is the main message that appears a |
| `headline` | `object` | no | Headline text (max 255 characters, recommended 40). Optional for video ads. Appears below the video on some placements. |
| `landing_page_url` | `string` | yes | Landing page URL where users will be directed when clicking the ad. Must be HTTPS. |
| `call_to_action` | `string` | no | Call-to-action button. Options: 'WATCH_MORE' (default for video), 'LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD', 'BOOK_TRAVEL', 'CONTACT_US'. Default: WATCH_MORE |
| `description` | `object` | no | Optional description text (max 255 characters, recommended 30). Appears under the headline on some placements. |
| `instagram_account_id` | `object` | no | Instagram account ID for Instagram placements. If not provided, ad will run on Facebook only. |
| `pixel_id` | `object` | no | Meta Pixel ID for conversion tracking. Required for OUTCOME_SALES campaigns. Use list_meta_pixels to find available pixels for your ad account. |
| `pixel_event_name` | `object` | no | Conversion event to optimize for when pixel_id is provided. Options: PURCHASE, LEAD, COMPLETE_REGISTRATION, ADD_TO_CART, INITIATE_CHECKOUT, ADD_PAYMENT_INFO, SEARCH, VIEW_CONTENT, OTHER. Use OTHER with custom_conversion_id for custom pixel  |
| `custom_conversion_id` | `object` | no | Custom conversion ID for optimizing towards a specific custom conversion. When provided, pixel_event_name is ignored — Meta infers the event type from the custom conversion. Get available custom conversions from your Meta Events Manager. |
| `destination_type` | `object` | no | Override the ad set destination type. By default, OUTCOME_LEADS uses ON_AD (Meta lead forms) and OUTCOME_TRAFFIC uses WEBSITE. Set to 'WEBSITE' for OUTCOME_LEADS campaigns that track conversions on your website via Meta Pixel instead of usi |
| `campaign_budget_optimization` | `boolean` | no | Enable Advantage Campaign Budget (CBO). When true, the daily budget is set at the campaign level and Meta automatically distributes it across ad sets. When false (default), each ad set manages its own budget. |
| `daily_min_spend_target` | `object` | no | CBO only: minimum daily spend for this ad set in account currency. Meta will try to spend at least this amount on this ad set each day. |
| `daily_spend_cap` | `object` | no | CBO only: maximum daily spend cap for this ad set in account currency. Meta will not spend more than this on this ad set each day. |
| `locations` | `object` | no | Location targeting. Accepts country codes (['US', 'CA']) OR location objects from search_meta_targeting ([{'key': '2421836', 'type': 'city', 'radius': 25, 'distance_unit': 'mile'}]). For city/region targeting, first call search_meta_targeti |
| `age_min` | `integer` | no | Minimum age (18-65). Default: 18 |
| `age_max` | `integer` | no | Maximum age (18-65). Default: 65 |
| `genders` | `object` | no | Gender targeting: ['male'], ['female'], or None for all genders |
| `interests` | `object` | no | List of interest targeting objects from search_meta_targeting. Format: [{'id': '6003139266461', 'name': 'Fitness'}]. Use search_meta_targeting with search_type='interests' to find valid IDs. |
| `behaviors` | `object` | no | List of behavior targeting objects from search_meta_targeting. Format: [{'id': '6002714895372', 'name': 'Small business owners'}]. Use search_meta_targeting with search_type='behaviors' to find valid IDs. |
| `job_titles` | `object` | no | List of job title targeting objects for B2B campaigns. Format: [{'id': '123456', 'name': 'Chief Marketing Officer'}]. Use search_meta_targeting with search_type='job_titles' to find valid IDs. |
| `work_employers` | `object` | no | List of employer targeting objects for B2B campaigns. Format: [{'id': '123456', 'name': 'Google'}]. Use search_meta_targeting with search_type='work_employers' to find valid IDs. |
| `life_events` | `object` | no | List of life event targeting objects. Format: [{'id': '123456', 'name': 'Recently engaged'}]. Use search_meta_targeting with search_type='life_events' to find valid IDs. |
| `education_schools` | `object` | no | List of education school targeting objects. Format: [{'id': '123456', 'name': 'Harvard University'}]. Use search_meta_targeting with search_type='education_schools' to find valid IDs. |
| `education_majors` | `object` | no | List of education major targeting objects. Format: [{'id': '123456', 'name': 'Computer Science'}]. Use search_meta_targeting with search_type='education_majors' to find valid IDs. |
| `custom_audiences` | `object` | no | List of Custom Audience IDs to include in targeting. These are audiences created in Meta Ads Manager (website visitors, customer lists, etc.) |
| `excluded_custom_audiences` | `object` | no | List of Custom Audience IDs to exclude from targeting. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `special_ad_categories` | `object` | no | Special ad categories for housing, credit, employment, or social issues. Options: 'HOUSING', 'CREDIT', 'EMPLOYMENT', 'ISSUES_ELECTIONS_POLITICS'. Leave empty for standard ads. |
| `lead_form_id` | `object` | no | Lead form ID to attach when objective is OUTCOME_LEADS. Get available forms using list_meta_lead_forms tool. Only used with OUTCOME_LEADS objective. |
| `optimize_for_reels` | `boolean` | no | Whether to optimize for Reels placement. Set to true for vertical videos (9:16 aspect ratio). |
| `multi_advertiser` | `object` | no | Allow ad to appear in multi-advertiser ad format (multiple ads shown together). Set to false to opt out. Default: Meta's default (enabled). Brands that want exclusive placement should set this to false. |
| `publisher_platforms` | `object` | no | Platforms to show ads on. Options: 'facebook', 'instagram', 'audience_network', 'messenger'. Default: automatic (all). To exclude Audience Network/Apps, use ['facebook', 'instagram']. |
| `facebook_positions` | `object` | no | Facebook placement positions. Options: 'feed', 'right_hand_column', 'marketplace', 'video_feeds', 'story', 'search', 'instream_video', 'facebook_reels'. Only include positions you WANT — omitted positions are excluded. |
| `instagram_positions` | `object` | no | Instagram placement positions. Options: 'stream' (feed), 'story', 'reels', 'explore', 'explore_home', 'ig_search', 'profile_feed'. Only include positions you WANT — omitted positions are excluded. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "primary_text": "string",
    "landing_page_url": "https://example.com",
    "objective": "OUTCOME_TRAFFIC",
    "budget_daily": 1.0,
    "budget_lifetime": 1.0,
    "end_time": "string",
    "video_url": "string",
    "existing_video_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_meta_video_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_meta_video_campaign"
}
```

---
## detect_meta_creative_fatigue

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/detect_meta_creative_fatigue/execute`

User asks about creative fatigue, ad refresh timing, frequency management, declining CTR, when to replace ads, or audience exhaustion on Meta/Facebook/Instagram.

This tool analyzes all Meta Ads creatives for fatigue indicators using frequency-CTR correlation analysis and provides a fatigue score (0-100) for each ad.

Returns:
- Fatigue score (0-100) for each ad based on weighted factors
- Severely fatigued ads (score ≥80) - immediate action required
- At-risk ads (score 50-79) - plan refresh within 7-14 days
- Healthy ads count
- Daily spend being wasted on fatigued creatives
- Projected monthly waste
- Refresh schedule recommendations
- Contributing factors for each fatigued ad

When to use this tool:
- "Are any of my Meta/Facebook/Instagram ads fatigued?"
- "When should I refresh my creatives?"
- "Why is my CTR declining?"
- "Which ads have high frequency?"
- "How much am I wasting on fatigued ads?"
- "What's my ad refresh schedule?"
- "Are my retargeting ads showing too often?"

Parameters:
- lookback_days: 7, 14, 30 (default), 60, or 90 days
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- frequency_threshold_cold: 2.0-8.0 (default: 4.0) - threshold for cold traffic audiences
- frequency_threshold_retargeting: 4.0-10.0 (default: 7.0) - threshold for retargeting audiences
- ctr_decline_threshold: 0.10-0.50 (default: 0.20 = 20%) - CTR decline to flag as fatigued
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 2-5 seconds (cached database query with analysis)
Data source: meta_ad_creative_metrics table (ad-level daily metrics with frequency, CTR, video completion)

Fatigue Score Calculation:
The fatigue score (0-100) is calculated using weighted factors:
- Frequency (40%): impressions/reach ratio vs threshold
- CTR Decline (35%): % drop in CTR vs previous period
- Days Running (25%): creative age vs optimal refresh timing

Severity Levels:
- 🔴 Score ≥80: Severely fatigued - PAUSE immediately and replace
- 🟡 Score 50-79: At risk - prepare replacement within 7-14 days
- ✅ Score <50: Healthy - continue monitoring

Best practices:
- Cold traffic: Refresh creatives when frequency reaches 3-4x
- Retargeting: Can tolerate higher frequency (up to 6-7x)
- Create 3-5 variations per ad set for automatic rotation
- Monitor weekly for frequency and CTR trends

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days. |
| `frequency_threshold_cold` | `number` | no | Frequency threshold for cold traffic audiences (2.0-8.0). Default is 4.0 - ads showing to cold audiences more than 4x are considered fatigued. |
| `frequency_threshold_retargeting` | `number` | no | Frequency threshold for retargeting audiences (4.0-10.0). Default is 7.0 - retargeting audiences can tolerate higher frequency. |
| `ctr_decline_threshold` | `number` | no | CTR decline threshold to flag as fatigued (0.10-0.50 = 10%-50%). Default is 0.20 (20% decline). |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "frequency_threshold_cold": 4.0
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for detect_meta_creative_fatigue)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "detect_meta_creative_fatigue"
}
```

---
## discover_meta_assets

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/discover_meta_assets/execute`

User wants to browse existing images in their Meta Ad Library for reuse in new campaigns.

This tool retrieves existing images that have been uploaded to Meta's Ad Library, allowing users to reuse them in new campaigns without uploading again.

Returns:
- List of existing images with their hashes
- Image dimensions and thumbnails
- Created timestamps
- Instructions for using images in campaigns

When to use this tool:
- "Show me my existing Meta ad images"
- "What images do I already have in Meta?"
- "I want to reuse an existing image for my campaign"
- "List my Facebook ad library images"
- Before uploading new images - check if they already exist

Parameters:
- ad_account_id: Required for multi-account users. Get from list_connected_accounts
- limit: Max images to return (default 50, max 100)

Execution time: 2-5 seconds
Data source: Meta Ad Library API (live)

Workflow:
1. Use `discover_meta_assets` to find existing images
2. Copy the `image_hash` from an image you want to use
3. Use that hash with `create_meta_image_campaign` via the `existing_image_hash` parameter

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `limit` | `integer` | no | Maximum number of assets to return (default 50, max 100) |

### Example request

```json
{
  "arguments": {
    "ad_account_id": "string",
    "limit": 50
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for discover_meta_assets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "discover_meta_assets"
}
```

---
## duplicate_meta_campaign

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/duplicate_meta_campaign/execute`

User wants to duplicate/copy an existing Meta campaign with all its ad sets, ads, and settings.

IMPORTANT: This creates a NEW real campaign in Meta Ads. The duplicate starts in PAUSED status by default for review.

This tool copies the entire campaign structure including:
- Campaign settings (objective, budget strategy)
- All ad sets (targeting, budgets, schedules)
- All ads (creatives, text, CTAs)

Returns:
- Original and new campaign IDs
- New campaign status
- Ads Manager URL for the new campaign
- Next steps for review and activation

When to use this tool:
- "Duplicate my campaign"
- "Copy campaign [ID]"
- "Create a copy of this campaign"
- "I want to A/B test with a copy of my campaign"
- "Clone my campaign with different targeting"

Parameters:
- campaign_id: The Meta Campaign ID to duplicate (required)
- new_name: Name for the new campaign (optional, defaults to original + " - Copy")
- status: Status for new campaign — 'PAUSED' (default, recommended) or 'ACTIVE'

Execution time: 10-30 seconds (depends on campaign size)
Creates: New real campaign in Meta Ads

Common use cases:
1. A/B Testing: Duplicate, then modify targeting or creative on the copy
2. Seasonal variants: Copy a proven campaign, update copy and dates
3. New market expansion: Duplicate, change location targeting
4. Budget testing: Copy, change budget levels

Workflow:
1. Use `list_meta_campaigns` to find the campaign ID
2. Use `duplicate_meta_campaign` to create the copy
3. Use `update_meta_campaign` or `update_meta_ad_set` to modify the copy
4. Use `resume_meta_campaign` when ready to launch

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Meta Campaign ID to duplicate (required) |
| `new_name` | `object` | no | Name for the new campaign (optional - defaults to original name + ' - Copy') |
| `status` | `string` | no | Status for the new campaign: 'PAUSED' (default, recommended for review) or 'ACTIVE' |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "new_name": "string",
    "status": "PAUSED",
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for duplicate_meta_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "duplicate_meta_campaign"
}
```

---
## explain_meta_anomaly

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/explain_meta_anomaly/execute`

User asks why Meta/Facebook/Instagram performance dropped or changed, what happened to their ROAS/CTR/CPM, or wants to understand why a metric changed during a specific period.

This tool analyzes why a specific metric changed during a specified period by comparing it to historical baselines and identifying contributing factors. It detects Meta-specific causes including creative fatigue, audience saturation, placement mix shifts, and auction competition.

Returns:
- Current metric value vs historical averages (30/60/90-day)
- Deviation percentages from baselines
- Contributing factors ranked by estimated impact
- Factor explanations with details
- Similar historical periods with comparable changes
- Assessment of anomaly severity (CRITICAL/WARNING/MINOR/NORMAL)
- Specific recommendations based on detected factors
- Quick actionable items

When to use this tool:
- "Why did my Meta ROAS drop?"
- "What happened to my Facebook CTR this week?"
- "Why is my Instagram CPM so high?"
- "Explain my Meta performance change"
- "My conversions dropped last week - why?"
- "Why did my frequency spike?"
- "What caused my ROAS to decline 40%?"
- "Diagnose my Meta performance problem"

Parameters:
- metric: Required - 'roas', 'ctr', 'cpc', 'cpm', 'conversions', 'conversion_rate', 'frequency', or 'reach'
- period_start: Required - Start date (YYYY-MM-DD format)
- period_end: Required - End date (YYYY-MM-DD format, must be ≤30 days from start)
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 3-8 seconds (statistical analysis across multiple periods)
Data source: campaign_daily_metrics + meta_placement_daily_metrics + meta_ad_creative_metrics tables

Contributing Factors Detected:
- CPM Change: Auction competition fluctuations (holidays, competitor activity)
- CTR Change: Creative performance (fatigue, messaging, targeting)
- Conversion Rate Change: Landing page or offer issues
- Frequency Change: Audience saturation (Meta-specific)
- Creative Fatigue: High frequency + declining CTR (Meta-specific)
- Placement Mix Change: Shift in placement distribution (Meta-specific)
- Campaign Changes: Paused/new campaigns affecting overall performance

Severity Levels:
- 🔴 CRITICAL (≥40% deviation): Immediate action required
- 🟡 WARNING (25-40% deviation): Review recommended
- 🟢 MINOR (15-25% deviation): Monitor situation
- ✅ NORMAL (<15% deviation): Within normal variation

Similar Historical Periods:
Finds past periods with comparable deviations to provide context (e.g., "similar drop occurred during Black Friday 2024")

Best for:
- Diagnosing sudden performance drops
- Understanding seasonal patterns
- Identifying actionable causes vs. external factors
- Prioritizing optimization efforts

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `metric` | `string` | no | Metric to analyze: 'roas', 'ctr', 'cpc', 'cpm', 'conversions', 'conversion_rate', 'frequency', or 'reach'. Default: 'ctr' |
| `period_start` | `object` | no | Start date of anomaly period (ISO format: YYYY-MM-DD). Defaults to 7 days ago. |
| `period_end` | `object` | no | End date of anomaly period (ISO format: YYYY-MM-DD). Defaults to yesterday. Period must be ≤30 days. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "metric": "ctr",
    "period_start": "string",
    "period_end": "string",
    "ad_account_id": "string",
    "raw_data": false
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for explain_meta_anomaly)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "explain_meta_anomaly"
}
```

---
## get_meta_ad_creatives

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_meta_ad_creatives/execute`

User wants to see their Meta ad creatives, ad copy, media URLs, or creative performance.

Returns ad-level data including full creative content and media URLs alongside performance metrics.

When to use this tool:
- "Show me my Meta ad creatives"
- "What ads are running and how are they performing?"
- "Show me the headlines and images for my ads"
- "Which ad creatives are performing best?"
- "What's the creative content for campaign X?"
- "Show me ad performance with creative details"
- "Get me the image URLs for my ads"
- "Export my ad copy"
- "What images and videos are my ads using?"

Returns per ad — ALWAYS display ALL of these fields when present:
- Ad copy: headline, primary_text, description, call_to_action_type
- Media URLs: image_url (direct CDN link to ad image), thumbnail_url, video_url (playable video source), video_id
- Landing page: landing_page_url
- Carousel cards: carousel_cards array with per-card image_url, headline, description, landing_page_url
- Performance: spend, impressions, clicks, CTR, CPC, reach, frequency
- Video engagement: 25%, 50%, 75%, 100% watched (for video ads)
- Creative metadata: creative_type (image/video/carousel/dynamic_creative), first_seen_date

IMPORTANT: Always show the full image_url, thumbnail_url, video_url, and landing_page_url values — these are direct CDN links users need to download/export their creative media. Do not summarize or omit URLs.

Parameters:
- lookback_days: Number of days to analyze (7, 14, 30, 60, 90). Default: 30
- campaign_id: Optional filter to specific campaign
- ad_set_id: Optional filter to specific ad set
- ad_account_id: Required for multi-account users. Get from list_connected_accounts
- start_date/end_date: Optional custom date range (overrides lookback_days)
- limit: Max ads per page (default 20, max 50). Use for pagination.
- offset: Number of ads to skip (default 0). Use with limit to page through results.

Pagination: Results are paginated. Response includes total_ads count and has_more flag. To get next page, increase offset by limit.

Execution time: 1-3 seconds
Data source: Cached database (collected daily)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `campaign_id` | `object` | no | Filter to specific campaign ID (optional) |
| `ad_set_id` | `object` | no | Filter to specific ad set ID (optional) |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days. |
| `limit` | `integer` | no | Maximum number of ads to return per page (default 20, max 50). Use with offset for pagination. |
| `offset` | `integer` | no | Number of ads to skip for pagination (default 0). Use with limit to page through results. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "campaign_id": "string",
    "ad_set_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_meta_ad_creatives)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_meta_ad_creatives"
}
```

---
## get_meta_audience_insights

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_meta_audience_insights/execute`

User asks about audience demographics, which placements perform best, device breakdown, or targeting optimization for Meta ads.

This tool provides audience and placement analysis for Meta campaigns.

Returns:
- Age group performance (18-24, 25-34, 35-44, 45-54, 55-64, 65+)
- Gender performance breakdown
- Placement breakdown (Facebook Feed, Instagram Feed, Stories, Reels, Messenger)
- Device breakdown (Mobile, Desktop, Tablet)
- Best performing audience segments
- Targeting recommendations

When to use this tool:
- "What age group performs best for my Meta ads?"
- "Should I target men or women?"
- "Which placements should I use?"
- "Do my Instagram Stories ads perform well?"
- "Mobile vs desktop performance on Facebook?"

Parameters:
- lookback_days: 7, 14, 30 (default), 60, or 90 days
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- breakdown_type: 'age', 'gender', 'placement', 'device', or 'all' (default)
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 1-3 seconds (cached database query)
Data source: Cached database (aggregate metrics only - detailed breakdown requires live API)

Common insights:
- Meta audiences typically skew mobile (70-85%)
- Instagram tends to perform better with 18-34 age groups
- Facebook Feed often has highest reach but Stories may have better engagement
- Reels placement growing rapidly in 2024-2025

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days. |
| `breakdown_type` | `string` | no | Type of breakdown: 'age', 'gender', 'placement', 'device', or 'all' (default). Use 'all' for comprehensive audience analysis. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "breakdown_type": "all"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_meta_audience_insights)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_meta_audience_insights"
}
```

---
## get_meta_campaign_details

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_meta_campaign_details/execute`

User wants to see detailed information about a specific Meta campaign, including its full structure (ad sets, ads, targeting, budgets).

This tool retrieves comprehensive details about a single campaign. With `include_hierarchy=true`, it shows the complete campaign tree: Campaign - Ad Sets - Ads.

Returns:
- Campaign settings (name, status, objective, budget, bid strategy, schedule)
- Performance summary (impressions, clicks, spend, reach, CTR, CPC)
- Ads Manager URL
- When include_hierarchy=true: Full structure with all ad sets and their ads, targeting details, hierarchy stats

When to use this tool:
- "Show me details for campaign [ID]"
- "What's the structure of my campaign?"
- "How many ad sets and ads does this campaign have?"
- "What targeting is set on my campaign?"
- "Show me the full campaign hierarchy"
- After `list_meta_campaigns` when user wants to drill into a specific campaign

Parameters:
- campaign_id: The Meta Campaign ID (required)
- include_hierarchy: Include ad sets and ads (default: false, set true for full view)

Execution time: 3-10 seconds (longer with hierarchy due to multiple API calls)
Data source: Meta Marketing API (live)

Workflow:
1. Use `list_meta_campaigns` to find campaign IDs
2. Use `get_meta_campaign_details` with `include_hierarchy=true` to see everything
3. Use `update_meta_ad_set` or `update_meta_ad` to make changes to specific items

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Meta Campaign ID to get details for (required) |
| `include_hierarchy` | `boolean` | no | Include full hierarchy with ad sets and ads (default: false). Set to true to see the complete campaign structure. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "include_hierarchy": false,
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_meta_campaign_details)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_meta_campaign_details"
}
```

---
## get_meta_campaign_performance

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_meta_campaign_performance/execute`

User asks about Meta/Facebook/Instagram ad performance, campaign metrics, ROAS, spend analysis, or wants to understand how their Meta ads are performing.

This tool retrieves comprehensive campaign performance metrics from Meta Ads.

Returns:
- Account summary (total spend, impressions, reach, conversions, ROAS)
- Campaign breakdown with status and objectives
- Top performing campaigns by ROAS
- Meta-specific metrics (reach, frequency, reach/impressions ratio)
- Optimization recommendations

When to use this tool:
- "How are my Meta ads performing?"
- "What's my Facebook campaign ROAS?"
- "Show me Instagram ad performance"
- "Which Meta campaigns are doing best?"
- "Analyze my Meta ad spend"

Parameters:
- lookback_days: 7, 14, 30 (default), 60, or 90 days
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- include_recommendations: true (default) or false
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 1-3 seconds (cached database query)
Data source: campaign_daily_metrics table (updated nightly)

Note: Unlike Google Ads, Meta does not have keyword or search term data. Meta uses interest-based targeting.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days. |
| `include_recommendations` | `boolean` | no | Include optimization recommendations. Default is true. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "include_recommendations": true
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_meta_campaign_performance)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_meta_campaign_performance"
}
```

---
## get_meta_lead_form_submissions

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_meta_lead_form_submissions/execute`

User wants to see lead submissions, lead data, or leads collected from a Meta lead form.

Retrieves individual lead submissions for a specific lead form, including contact details and associated ad/campaign information.

When to use this tool:
- "Show me the leads from form X"
- "Get my lead form submissions"
- "Download my Meta leads"
- "Show lead data from my campaign"
- "How many leads did I get?"

Returns per lead:
- Submission timestamp
- Field data (name, email, phone, etc. — varies by form)
- Source: organic or paid (with campaign name and ad name)
- Lead ID

Parameters:
- form_id: Lead form ID (required — get from list_meta_lead_forms)
- ad_account_id: Required for multi-account users. Get from list_connected_accounts
- limit: Maximum leads to return (default: 100)

Permission: Requires 'leads_retrieval' scope. If the user gets a permission error, they need to disconnect and reconnect their Meta account to grant the updated permissions.

Execution time: 2-5 seconds
Data source: Meta Marketing API (live)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `form_id` | `string` | yes | Lead form ID (required). Get available form IDs from list_meta_lead_forms tool. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `limit` | `integer` | no | Maximum number of lead submissions to return (default: 100) |

### Example request

```json
{
  "arguments": {
    "form_id": "string",
    "ad_account_id": "string",
    "limit": 100
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_meta_lead_form_submissions)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_meta_lead_form_submissions"
}
```

---
## list_meta_ad_sets

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_meta_ad_sets/execute`

User wants to see the ad sets within a specific Meta campaign, including their targeting, budgets, and optimization settings.

This tool retrieves ad sets for a given campaign with their status, budget, optimization goal, and billing event.

Returns:
- Ad set list with name, ID, status, budget, optimization goal, billing event
- Next step guidance for editing ad sets or viewing ads

When to use this tool:
- "Show me the ad sets in campaign [ID]"
- "What ad sets are running in my campaign?"
- "List the ad groups for this campaign"
- "Which ad sets are active/paused?"
- Before using `update_meta_ad_set` when user doesn't know the ad set ID

Parameters:
- campaign_id: Campaign ID to list ad sets for (required)
- status: Filter by status (optional)
- limit: Max ad sets to return (default: 100)
- ad_account_id: Optional

Execution time: 2-5 seconds
Data source: Meta Marketing API (live)

Workflow:
1. Use `list_meta_campaigns` to find campaign IDs
2. Use `list_meta_ad_sets` with campaign_id to see ad sets
3. Use `update_meta_ad_set` to edit targeting, budget, or placements
4. Use `list_meta_ads` with ad_set_id to see individual ads

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `object` | no | Filter ad sets to a specific campaign ID (optional - if not provided, lists all ad sets in the account) |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `status` | `object` | no | Filter by status: comma-separated values like 'ACTIVE,PAUSED' |
| `limit` | `integer` | no | Maximum number of ad sets to return (default: 100) |

### Example request

```json
{
  "arguments": {
    "campaign_id": "string",
    "ad_account_id": "string",
    "status": "string",
    "limit": 100
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_meta_ad_sets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_meta_ad_sets"
}
```

---
## list_meta_ads

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_meta_ads/execute`

User wants to see the individual ads within a specific Meta ad set, including their status and creative information.

This tool retrieves ads for a given ad set with their name, status, and creative ID.

Returns:
- Ad list with name, ID, status, creative ID
- Next step guidance for editing ads

When to use this tool:
- "Show me the ads in ad set [ID]"
- "What ads are running in this ad set?"
- "List the individual ads"
- "Which ads are active/paused?"
- Before using `update_meta_ad` when user doesn't know the ad ID

Parameters:
- ad_set_id: Ad Set ID to list ads for (required for direct listing)
- campaign_id: Campaign ID (will guide user to use ad_set_id)
- status: Filter by status (optional)
- limit: Max ads to return (default: 100)

Execution time: 2-5 seconds
Data source: Meta Marketing API (live)

Workflow:
1. Use `list_meta_ad_sets` to find ad set IDs
2. Use `list_meta_ads` with ad_set_id to see ads
3. Use `update_meta_ad` to pause/resume or swap creative

Alternative: Use `get_meta_campaign_details` with `include_hierarchy=true` to see the full campaign tree at once.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_set_id` | `object` | no | Filter ads to a specific ad set ID |
| `campaign_id` | `object` | no | Filter ads to a specific campaign ID |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `status` | `object` | no | Filter by status: comma-separated values like 'ACTIVE,PAUSED' |
| `limit` | `integer` | no | Maximum number of ads to return (default: 100) |

### Example request

```json
{
  "arguments": {
    "ad_set_id": "string",
    "campaign_id": "string",
    "ad_account_id": "string",
    "status": "string",
    "limit": 100
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_meta_ads)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_meta_ads"
}
```

---
## list_meta_campaigns

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_meta_campaigns/execute`

User wants to see their existing Meta/Facebook/Instagram campaigns, browse campaign structure, or find a campaign ID.

This tool retrieves all campaigns in the connected Meta ad account with their status, objective, budget, and creation date.

Returns:
- Campaign list with name, ID, status, objective, budget
- Status summary (how many ACTIVE, PAUSED, etc.)
- Next step guidance for drilling into campaign details

When to use this tool:
- "Show me my Meta campaigns"
- "List my Facebook ad campaigns"
- "What campaigns do I have running?"
- "Which campaigns are active?"
- "Find my campaign for [product/brand]"
- "I need to find a campaign ID"
- Before using update/pause/resume tools when user doesn't know the campaign ID

Parameters:
- status: Filter by status (comma-separated: 'ACTIVE', 'PAUSED', 'DELETED', 'ARCHIVED')
- effective_status: Filter by effective status (includes inherited states like 'CAMPAIGN_PAUSED')
- objective: Filter by objective (OUTCOME_TRAFFIC, OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_AWARENESS)
- limit: Max campaigns to return (default: 100)
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 2-5 seconds
Data source: Meta Marketing API (live)

Workflow:
1. Use `list_meta_campaigns` to find campaigns
2. Use `get_meta_campaign_details` with a campaign ID to see full structure
3. Use `update_meta_campaign` or `update_meta_ad_set` to make changes

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `status` | `object` | no | Filter by status: comma-separated values like 'ACTIVE,PAUSED'. Options: ACTIVE, PAUSED, DELETED, ARCHIVED |
| `effective_status` | `object` | no | Filter by effective status: comma-separated values. Options: ACTIVE, PAUSED, DELETED, ARCHIVED, IN_PROCESS, WITH_ISSUES, CAMPAIGN_PAUSED |
| `objective` | `object` | no | Filter by campaign objective: OUTCOME_TRAFFIC, OUTCOME_SALES, OUTCOME_LEADS, OUTCOME_AWARENESS, OUTCOME_ENGAGEMENT |
| `limit` | `integer` | no | Maximum number of campaigns to return (default: 100, max: 500) |

### Example request

```json
{
  "arguments": {
    "ad_account_id": "string",
    "status": "string",
    "effective_status": "string",
    "objective": "string",
    "limit": 100
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_meta_campaigns)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_meta_campaigns"
}
```

---
## list_meta_custom_audiences

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_meta_custom_audiences/execute`

User wants to browse, list, or select Custom Audiences for targeting — DB lists, lookalike audiences, remarketing segments, website visitors, engagement audiences.

Returns all Custom Audiences for the ad account with ID, name, type (subtype), approximate size, and delivery status.

Workflow:
1. Call this tool to discover available custom audiences
2. Use audience IDs with campaign creation tools via `custom_audiences` parameter
3. Optionally use `excluded_custom_audiences` to exclude specific audiences

When to use:
- Before creating campaigns that need custom audience targeting
- When user asks "which audiences do I have?" or "show me my lookalike audiences"
- When setting up remarketing or DB-list campaigns

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_meta_custom_audiences)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_meta_custom_audiences"
}
```

---
## list_meta_instagram_accounts

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_meta_instagram_accounts/execute`

User wants to run ads on Instagram, asks about Instagram accounts, or you need to find the instagram_account_id before campaign creation.

Returns Instagram accounts that are:
1. Authorized for ads on the ad account (from Business Manager)
2. Linked to Facebook Pages (from Page settings)

Workflow:
1. Call this tool to discover available Instagram accounts
2. Use the Instagram account ID with campaign creation tools via `instagram_account_id` parameter

When to use:
- Before any campaign creation to enable Instagram placements
- When user asks "which Instagram accounts can I use?"
- When an Instagram association fails and user needs to find the right account

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_account_id` | `object` | no | Meta ad account ID (e.g., 'act_123456'). If not provided, uses the connected account. |

### Example request

```json
{
  "arguments": {
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_meta_instagram_accounts)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_meta_instagram_accounts"
}
```

---
## list_meta_lead_forms

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_meta_lead_forms/execute`

User wants to see their Meta lead generation forms, list lead forms, or find a lead form ID.

Lists all lead generation forms for a Facebook Page associated with the ad account.

When to use this tool:
- "Show me my Meta lead forms"
- "List my Facebook lead generation forms"
- "What lead forms do I have?"
- "Find my lead form ID"
- "Show lead forms for my page"

Returns per form:
- Form name, status (ACTIVE/ARCHIVED), leads count
- Creation date
- Form questions (field names and types)
- Form ID (needed for get_meta_lead_form_submissions and campaign creation with lead_form_id)

Parameters:
- page_id: Facebook Page ID (optional — auto-resolved from ad account if not provided)
- ad_account_id: Required for multi-account users. Get from list_connected_accounts
- limit: Maximum forms to return (default: 50)

Note: Lead forms belong to Facebook Pages, not ad accounts. If you don't have the page_id, just omit it and the tool will auto-resolve from the ad account's promotable pages.

Permission: Requires 'leads_retrieval' scope. If the user gets a permission error, they need to disconnect and reconnect their Meta account to grant the updated permissions.

Execution time: 2-5 seconds
Data source: Meta Marketing API (live)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `page_id` | `object` | no | Facebook Page ID that owns the lead forms. If not provided, auto-resolves from the ad account's promotable pages. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |
| `limit` | `integer` | no | Maximum number of lead forms to return (default: 50) |

### Example request

```json
{
  "arguments": {
    "page_id": "string",
    "ad_account_id": "string",
    "limit": 50
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_meta_lead_forms)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_meta_lead_forms"
}
```

---
## list_meta_pixels

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_meta_pixels/execute`

User wants conversion tracking, asks about Meta Pixels, or before creating OUTCOME_SALES campaigns.

Returns all Meta Pixels for the ad account with their status and last fired time.

Workflow:
1. Call this tool to discover available pixels
2. Use the pixel ID with campaign creation tools via `pixel_id` parameter
3. Optionally specify `pixel_event_name` (default: PURCHASE)

When to use:
- Before creating OUTCOME_SALES campaigns (pixel_id is required for conversion tracking)
- When user asks "which pixels do I have?"
- When setting up conversion tracking for campaigns

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_account_id` | `object` | no | Meta ad account ID (e.g., 'act_123456'). If not provided, uses the connected account. |

### Example request

```json
{
  "arguments": {
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_meta_pixels)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_meta_pixels"
}
```

---
## optimize_meta_budget

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/optimize_meta_budget/execute`

User asks about Meta/Facebook/Instagram budget optimization, reallocating ad spend, maximizing conversions with their budget, or wants data-driven budget recommendations.

This tool uses linear programming (scipy.optimize) to find the optimal budget allocation across Meta Ads campaigns or ad sets to maximize conversions while respecting constraints.

Returns:
- Optimal budget allocation for each campaign/ad set
- Expected conversion lift from reallocation
- Campaigns to scale up (high ROAS performers)
- Campaigns to reduce (below target ROAS)
- Campaigns to consider pausing (ROAS < 1.0)
- Actionable recommendations
- CBO (Campaign Budget Optimization) notes

When to use this tool:
- "How should I allocate my Meta budget?"
- "Optimize my Facebook ad spend for conversions"
- "Which Instagram campaigns should I increase budget?"
- "Reallocate my $5000 Meta budget"
- "Maximize conversions with my current spend"
- "What's the optimal budget split across campaigns?"

Parameters:
- total_budget: Total daily budget to allocate (required, e.g., 5000.00)
- lookback_days: 7, 14, 30 (default), 60, or 90 days for analysis
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- target_roas: Optional override (default: from account goals or 2.0x)
- max_change_percentage: Max budget change per item (default: 0.5 = 50%)
- min_daily_budget: Minimum budget per item (default: 5.00)
- optimization_level: 'campaign' (default) or 'ad_set'
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 2-5 seconds (cached database query + optimization)
Data source: campaign_daily_metrics + ad_group_daily_metrics tables

Key concepts:
- Linear Programming: Mathematical optimization to maximize objective (conversions) subject to constraints
- Efficiency Score: Conversions per dollar spent - used to prioritize allocation
- Max Change Constraint: Prevents dramatic shifts (e.g., ±50% max from current)
- Target ROAS: 3-tier resolution: account_goals - 90-day historical - default 2.0x

Meta-specific considerations:
- CBO campaigns: Budget set at campaign level, Meta distributes to ad sets
- ABO campaigns: Budget set at ad set level, more granular control
- Learning Phase: New campaigns need 50+ conversions before optimization

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `total_budget` | `number` | yes | Total monthly budget to allocate across campaigns (in dollars). Required. |
| `lookback_days` | `integer` | no | Number of days to analyze for historical performance (7, 30, 60, 90, or 120 days). Default is 30 days. |
| `target_roas` | `object` | no | Optional target ROAS override (e.g., 2.0 for 2.0x ROAS). If not provided, will use account goals or historical average. |
| `max_change_percentage` | `number` | no | Maximum allowed budget change per campaign as decimal (0.3 for ±30%, 0.5 for ±50%, 0.7 for ±70%). Default is 0.5. |
| `min_daily_budget` | `number` | no | Minimum daily budget per campaign in dollars. Default is $5.00. |
| `optimization_level` | `string` | no | Level of optimization: 'campaign' (default) or 'ad_set'. Note: Ad set optimization may not apply to CBO-enabled campaigns. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "total_budget": 1.0,
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "target_roas": 1.0
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for optimize_meta_budget)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "optimize_meta_budget"
}
```

---
## optimize_meta_placements

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/optimize_meta_placements/execute`

User asks about Meta/Facebook/Instagram placement performance, which placements work best, Feed vs Stories vs Reels, should they use Audience Network, or wants placement optimization recommendations.

This tool analyzes placement-level performance (Feed, Stories, Reels, Audience Network, Messenger, etc.) and provides optimization recommendations including budget reallocation suggestions.

Returns:
- Placement ROAS ranking (sorted by performance)
- Placements categorized as SCALE/MAINTAIN/REDUCE/EXCLUDE
- Budget reallocation recommendations between placements
- Expected ROAS improvement from optimization
- Optimal placement mix for campaign objective
- Savings from excluding underperforming placements
- Actionable recommendations and quick actions

When to use this tool:
- "Which Meta placements should I use?"
- "Should I exclude Audience Network?"
- "Instagram Stories vs Reels - which is better?"
- "Feed vs Stories performance comparison"
- "Where should I allocate my Meta ad budget?"
- "Which placements are wasting money?"
- "What's the optimal placement mix for conversions?"
- "Facebook Marketplace performance?"

Parameters:
- lookback_days: 7, 14, 30 (default), 60, or 90 days
- start_date: Optional start date (YYYY-MM-DD). Overrides lookback_days when used with end_date.
- end_date: Optional end date (YYYY-MM-DD). Overrides lookback_days when used with start_date.
⚠️ DATE CLARIFICATION: If the user's date request is vague or ambiguous (e.g., "March to June" without a year, "last quarter", "recently", "a few months ago"), ask the user to specify exact dates before calling this tool. Do not assume or guess dates.
- objective: 'conversions' (default), 'traffic', or 'awareness' - for optimal mix recommendations
- target_roas: Optional override (default: from account goals or 2.0x)
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 2-5 seconds (cached database query with analysis)
Data source: meta_placement_daily_metrics table (placement-level daily metrics)

ROAS Thresholds for Recommendations:
- ✅ SCALE (ROAS ≥3.0x): Increase budget by 20-30%
- ➖ MAINTAIN (ROAS 1.5-3.0x): Keep current budget
- ⚠️ REDUCE (ROAS 1.0-1.5x): Reduce budget by 30-50%
- 🔴 EXCLUDE (ROAS <1.0x): Remove from campaigns

Meta Placements Analyzed:
- Facebook: Feed, Stories, Reels, Marketplace, Search Results
- Instagram: Feed, Stories, Reels, Explore
- Messenger: Inbox, Stories
- Audience Network: All placements

Common Insights:
- Audience Network often has lowest ROAS for conversion campaigns - consider excluding
- Instagram Feed and Reels typically have highest conversion rates
- Stories are great for awareness but may have lower conversion rates
- Facebook Marketplace can be effective for e-commerce

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_date` | `object` | no | Start date (YYYY-MM-DD). If provided with end_date, overrides lookback_days for custom date range queries. |
| `end_date` | `object` | no | End date (YYYY-MM-DD). If provided with start_date, overrides lookback_days for custom date range queries. |
| `date_range` | `object` | no | Date range preset: 'last_7_days', 'last_14_days', 'last_30_days', 'last_60_days', 'last_90_days'. Overrides lookback_days. Ignored if start_date/end_date are provided. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (spend, clicks, impressions, conversions, CPA, CPC, CTR, CVR, ROAS by campaign/ad/date). Strips severity labels, suggested bids/budgets, industry benchmarks, and optimization recommendat |
| `lookback_days` | `integer` | no | Number of days to analyze (7, 14, 30, 60, or 90 days). Default is 30 days. |
| `objective` | `string` | no | Campaign objective for optimal placement mix recommendations: 'conversions' (default), 'traffic', or 'awareness'. |
| `target_roas` | `object` | no | Optional target ROAS override (e.g., 3.0 for 3.0x ROAS). If not provided, will use account goals or historical average. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "start_date": "string",
    "end_date": "string",
    "date_range": "string",
    "raw_data": false,
    "lookback_days": 30,
    "objective": "conversions"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for optimize_meta_placements)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "optimize_meta_placements"
}
```

---
## pause_meta_campaign

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/pause_meta_campaign/execute`

User wants to pause a running Meta campaign.

IMPORTANT: Pausing a campaign stops all ad delivery immediately. No more budget will be spent until resumed.

Returns:
- Confirmation that campaign is paused
- Campaign details
- Ads Manager URL

When to use this tool:
- "Pause my Meta campaign"
- "Stop my Facebook ads"
- "Pause campaign 123456"
- "Turn off my Instagram campaign"

Parameters:
- campaign_id: The Meta Campaign ID to pause (required)

Execution time: 2-5 seconds
Effect: Campaign status changes to PAUSED immediately

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Meta Campaign ID to pause (required) |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for pause_meta_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "pause_meta_campaign"
}
```

---
## resume_meta_campaign

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/resume_meta_campaign/execute`

User wants to resume a paused Meta campaign.

IMPORTANT: Resuming a campaign restarts ad delivery. Budget will start being spent again.

Returns:
- Confirmation that campaign is active
- Campaign details
- Ads Manager URL

When to use this tool:
- "Resume my Meta campaign"
- "Turn on my Facebook ads"
- "Reactivate campaign 123456"
- "Start my paused Instagram campaign"

Parameters:
- campaign_id: The Meta Campaign ID to resume (required)

Execution time: 2-5 seconds
Effect: Campaign status changes to ACTIVE immediately

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Meta Campaign ID to resume (required) |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for resume_meta_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "resume_meta_campaign"
}
```

---
## search_meta_targeting

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/search_meta_targeting/execute`

User wants to find targeting options for their Meta (Facebook/Instagram) ad campaigns.

This tool searches the Meta Marketing API to find targeting options including interests, behaviors, demographics, locations, and more.

Returns:
- List of targeting options with IDs, names, and audience sizes
- Category/path information for interests and behaviors
- Location details including country, region for geo-targeting

When to use this tool:
- "Find interests related to fitness"
- "What targeting options are available for travel?"
- "Search for locations in California"
- "Find behaviors for online shoppers"
- "What demographics can I target?"
- "Find job titles for marketing professionals"
- "Search for schools like Harvard"

Search Types Available:
- interest: Topics and activities (e.g., 'fitness', 'cooking', 'travel')
- behavior: User behaviors (e.g., 'frequent travelers', 'online shoppers')
- demographic: Demographics (e.g., 'new parents', 'college educated')
- life_event: Life events (e.g., 'recently moved', 'newly engaged')
- location: Geo-targeting (e.g., 'New York', 'California', '90210')
- locale: Language targeting (e.g., 'Spanish', 'French')
- employer: Employer targeting (e.g., 'Google', 'Microsoft')
- job_title: Job title targeting (e.g., 'Software Engineer')
- school: Education school targeting (e.g., 'Harvard', 'Stanford')
- major: Education major targeting (e.g., 'Computer Science')

Parameters:
- search_type: Type of targeting to search (required)
- query: Search query string (required)
- limit: Maximum results (1-100, default: 50)
- locale: Locale for results (default: en_US)
- location_types: For location search - filter by types (country, region, city, zip)
- country_code: For location search - filter by country (e.g., 'US')
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 1-3 seconds
Data source: Meta Marketing API Targeting Search

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `search_type` | `string` | yes | Type of targeting to search for: - 'interest': Find interests for targeting (e.g., 'fitness', 'cooking', 'travel') - 'behavior': Find behaviors for targeting (e.g., 'frequent travelers', 'online shoppers') - 'demographic': Find demographic  |
| `query` | `string` | yes | Search query string (e.g., 'fitness' for interests, 'New York' for locations) |
| `limit` | `integer` | no | Maximum number of results to return (1-100). Default is 50. |
| `locale` | `string` | no | Locale for results (e.g., 'en_US', 'es_ES', 'fr_FR'). Default is 'en_US'. |
| `location_types` | `object` | no | For location search only: filter by location types. Options: 'country', 'region', 'city', 'zip', 'geo_market', 'place'. Example: ['city', 'region'] |
| `country_code` | `object` | no | For location search only: filter results to a specific country (e.g., 'US', 'GB', 'CA') |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "search_type": "string",
    "query": "string",
    "limit": 50,
    "locale": "en_US",
    "location_types": [
      "string"
    ],
    "country_code": "string",
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for search_meta_targeting)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "search_meta_targeting"
}
```

---
## select_meta_campaign_type

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/select_meta_campaign_type/execute`

User wants to create a Meta (Facebook/Instagram) ad campaign but hasn't specified the campaign type (image, video, or carousel).

IMPORTANT: This tool should be called BEFORE any asset discovery or campaign creation when the user says things like:
- "Create a Meta campaign"
- "Create a Facebook ad"
- "I want to run Instagram ads"
- "Set up a Meta advertising campaign"
- "Help me create ads on Facebook"

This tool asks the user what TYPE of campaign they want to create, then provides guidance on the next steps.

Campaign Types Available:
1. image - Single image ad (most common, good for static visuals)
2. video - Video ad including Reels (good for engagement, storytelling)
3. carousel - 2-10 swipeable cards (good for showcasing multiple products)

Do NOT use this tool if:
- User specifically asks for "image campaign" - use create_meta_image_campaign
- User specifically asks for "video campaign" or "Reels" - use create_meta_video_campaign
- User specifically asks for "carousel campaign" - use create_meta_carousel_campaign
- User is asking about performance/analytics - use performance analysis tools

Parameters:
- campaign_type: 'image', 'video', or 'carousel'

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_type` | `string` | yes | Type of Meta campaign to create. Options: 'image' (single image ad), 'video' (video/Reels ad), 'carousel' (2-10 swipeable cards) |

### Example request

```json
{
  "arguments": {
    "campaign_type": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for select_meta_campaign_type)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "select_meta_campaign_type"
}
```

---
## update_meta_ad

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_meta_ad/execute`

User wants to update an individual Meta ad — pause/resume it, rename it, or swap its creative.

IMPORTANT: This tool modifies REAL ads in Meta Ads Manager. Changes take effect immediately.

Returns:
- Confirmation of updates applied
- Summary of changes
- Ads Manager URL for the ad

When to use this tool:
- "Pause this ad"
- "Resume ad [ID]"
- "Rename this ad"
- "Swap the creative on this ad"
- "Change the image on this ad" (via creative swap)
- "Turn off the underperforming ad"

Parameters:
- ad_id: The Meta Ad ID to update (required)
- status: ACTIVE, PAUSED, DELETED, ARCHIVED (optional)
- name: New ad name (optional)
- creative_id: New creative ID for creative swap (optional)

At least one update field must be provided.

Execution time: 2-5 seconds
Modifies: Real ad in Meta Ads

Workflow for creative swap:
1. Use `discover_meta_assets` to find existing images/creatives
2. Get the creative ID you want to use
3. Use `update_meta_ad` with `creative_id` to swap

Workflow for pausing underperformers:
1. Use `analyze_meta_ad_performance` to identify underperforming ads
2. Use `list_meta_ads` to get ad IDs
3. Use `update_meta_ad` with `status=PAUSED`

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_id` | `string` | yes | The Meta Ad ID to update (required) |
| `status` | `object` | no | New status: 'ACTIVE', 'PAUSED', 'DELETED', 'ARCHIVED' |
| `name` | `object` | no | New ad name |
| `creative_id` | `object` | no | New creative ID for creative swap. Use this to replace the ad's creative with an existing creative. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_id": "string",
    "status": "string",
    "name": "string",
    "creative_id": "string",
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_meta_ad)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_meta_ad"
}
```

---
## update_meta_ad_set

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_meta_ad_set/execute`

User wants to edit an existing Meta ad set's targeting, budget, bid, placements, schedule, or optimization settings.

IMPORTANT: This tool modifies REAL ad sets in Meta Ads Manager. Changes take effect immediately.

This is the primary tool for:
- Changing audience targeting (age, gender, interests, locations)
- Excluding placements (e.g., remove Audience Network)
- Adjusting budgets at the ad set level
- Changing bid amounts
- Pausing/resuming specific ad sets
- Modifying optimization goals

Returns:
- Confirmation of updates applied
- Summary of all changes made
- Ads Manager URL for the ad set

When to use this tool:
- "Change the targeting on my ad set"
- "Exclude Audience Network from placements"
- "Update the budget on this ad set to $50/day"
- "Pause this ad set"
- "Change the age range to 25-45"
- "Add interest targeting for fitness"
- "Exclude custom audience from this ad set"
- "Change bid to $5"
- "Update the optimization goal"

Parameters:
- ad_set_id: The Meta Ad Set ID to update (required)
- status: ACTIVE, PAUSED, DELETED, ARCHIVED (optional)
- name: New ad set name (optional)
- daily_budget: New daily budget in USD (optional, min $1). DO NOT use for CBO campaigns.
- lifetime_budget: New lifetime budget in USD (optional). DO NOT use for CBO campaigns.
- daily_min_spend_target: CBO only — minimum daily spend for this ad set (use INSTEAD of daily_budget)
- daily_spend_cap: CBO only — maximum daily spend cap for this ad set (use INSTEAD of daily_budget)
- lifetime_min_spend_target: CBO only — minimum lifetime spend (for lifetime budget CBO)
- lifetime_spend_cap: CBO only — maximum lifetime spend cap (for lifetime budget CBO)
- bid_amount: New bid amount in USD (optional)
- targeting: New targeting spec as JSON (optional) — for placements, audiences, demographics
- start_time: New start time ISO format (optional)
- end_time: New end time ISO format (optional)
- optimization_goal: REACH, LINK_CLICKS, LANDING_PAGE_VIEWS, OFFSITE_CONVERSIONS, VALUE, etc. (optional)

**CBO (Advantage Campaign Budget) campaigns:**
For ad sets under CBO campaigns, do NOT set daily_budget or lifetime_budget.
Use daily_min_spend_target / daily_spend_cap to control spend distribution.
Setting daily_budget on a CBO ad set will cause Meta to reject with an error.

At least one update field must be provided.

Targeting Spec Examples:

*Exclude Audience Network:*
```json
{
  "publisher_platforms": ["facebook", "instagram"],
  "facebook_positions": ["feed", "stories", "reels"],
  "instagram_positions": ["stream", "story", "reels"]
}
```

*Change age and gender:*
```json
{
  "age_min": 25,
  "age_max": 45,
  "genders": [1]
}
```
(genders: 1=male, 2=female, omit for all)

*Add interest targeting:*
```json
{
  "flexible_spec": [{"interests": [{"id": "6003139266461", "name": "Fitness"}]}]
}
```

*Exclude custom audience:*
```json
{
  "excluded_custom_audiences": [{"id": "AUDIENCE_ID"}]
}
```

Execution time: 2-5 seconds
Modifies: Real ad set in Meta Ads

Workflow:
1. Use `list_meta_ad_sets` or `get_meta_campaign_details` to find the ad set ID
2. Use `update_meta_ad_set` with the changes you want to make
3. Verify changes in Ads Manager

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_set_id` | `string` | yes | The Meta Ad Set ID to update (required) |
| `status` | `object` | no | New status: 'ACTIVE', 'PAUSED', 'DELETED', 'ARCHIVED' |
| `name` | `object` | no | New ad set name |
| `daily_budget` | `object` | no | New daily budget in USD (minimum $1). Will be converted to cents for Meta API. |
| `lifetime_budget` | `object` | no | New lifetime budget in USD. Will be converted to cents for Meta API. |
| `bid_amount` | `object` | no | New bid amount in USD. Will be converted to cents for Meta API. |
| `targeting` | `object` | no | New targeting specification as a JSON object. Use this for placement exclusions, audience exclusions, age/gender changes, interest targeting updates, and location targeting changes. |
| `start_time` | `object` | no | New start time in ISO format YYYY-MM-DDTHH:MM:SS |
| `end_time` | `object` | no | New end time in ISO format YYYY-MM-DDTHH:MM:SS |
| `optimization_goal` | `object` | no | New optimization goal: REACH, IMPRESSIONS, LINK_CLICKS, LANDING_PAGE_VIEWS, OFFSITE_CONVERSIONS, VALUE, etc. |
| `daily_min_spend_target` | `object` | no | Minimum daily spend target in account currency for CBO campaigns. Use this instead of daily_budget when the campaign uses Advantage Campaign Budget. Set to 0 to remove the minimum. |
| `daily_spend_cap` | `object` | no | Maximum daily spend cap in account currency for CBO campaigns. Use this instead of daily_budget when the campaign uses Advantage Campaign Budget. Set to 0 to remove the cap. |
| `lifetime_min_spend_target` | `object` | no | Minimum lifetime spend target in account currency for CBO campaigns with lifetime budget. |
| `lifetime_spend_cap` | `object` | no | Maximum lifetime spend cap in account currency for CBO campaigns with lifetime budget. |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_set_id": "string",
    "status": "string",
    "name": "string",
    "daily_budget": 1.0,
    "lifetime_budget": 1.0,
    "bid_amount": 0.01,
    "targeting": {
      "age_min": 25,
      "age_max": 55,
      "genders": [
        1,
        2
      ],
      "geo_locations": {
        "countries": [
          "US"
        ]
      },
      "interests": [
        {
          "id": "6003107902433",
          "name": "Fitness and wellness"
        }
      ]
    }
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_meta_ad_set)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_meta_ad_set"
}
```

---
## update_meta_campaign

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_meta_campaign/execute`

User wants to update an existing Meta campaign's status, budget, name, or schedule.

IMPORTANT: This tool modifies REAL campaigns in Meta Ads Manager. Changes take effect immediately.

Returns:
- Confirmation of updates applied
- Updated campaign details
- Ads Manager URL

When to use this tool:
- "Update my Meta campaign budget"
- "Change my Facebook campaign name"
- "Pause my Instagram campaign"
- "Resume my paused campaign"
- "Set a new budget for campaign X"
- "Change the end date for my campaign"

Parameters:
- campaign_id: The Meta Campaign ID to update (required)
- status: New status - 'ACTIVE', 'PAUSED' (optional)
- name: New campaign name (optional)
- daily_budget: New daily budget in USD (optional)
- lifetime_budget: New lifetime budget in USD (optional)
- start_time: New start time in ISO format (optional)
- stop_time: New stop time in ISO format (optional)

At least one update field must be provided.

Execution time: 2-5 seconds
Modifies: Real campaign in Meta Ads

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | The Meta Campaign ID to update (required) |
| `status` | `object` | no | New status: 'ACTIVE', 'PAUSED' (optional) |
| `name` | `object` | no | New campaign name (optional) |
| `daily_budget` | `object` | no | New daily budget in USD (optional, minimum $1) |
| `lifetime_budget` | `object` | no | New lifetime budget in USD (optional) |
| `start_time` | `object` | no | New start time in ISO format YYYY-MM-DDTHH:MM:SS (optional) |
| `stop_time` | `object` | no | New stop time in ISO format YYYY-MM-DDTHH:MM:SS (optional) |
| `ad_account_id` | `object` | no | Meta Ad Account ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "status": "string",
    "name": "string",
    "daily_budget": 1.0,
    "lifetime_budget": 1.0,
    "start_time": "string",
    "stop_time": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_meta_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_meta_campaign"
}
```

---
## validate_and_prepare_meta_assets

**Platform:** Meta Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/validate_and_prepare_meta_assets/execute`

User wants to upload NEW images for Meta campaigns. This tool validates images and uploads them to Meta to get image hashes.

This tool validates image URLs against Meta's specifications and uploads them to get image hashes needed for campaign creation. It stores validated assets in a temporary bundle (60-minute TTL).

Returns:
- Validation results (pass/fail per image)
- Image hashes for successfully uploaded images
- Asset bundle ID for use in campaign creation
- Placement compatibility information

When to use this tool:
- "Upload this image for my Meta campaign"
- "Validate my product images for Facebook ads"
- "Prepare images for Instagram ads"
- User provides image URLs and wants to create a campaign

Parameters:
- image_urls: List of public URLs to validate and upload (1-10 images)
- placement: Target placement - 'feed' (default), 'stories_reels', or 'carousel'
- ad_account_id: Required for multi-account users. Get from list_connected_accounts

Execution time: 5-15 seconds (depends on image count and size)
Data source: Meta Ad Image Upload API

Image Requirements:
| Placement | Aspect Ratio | Min Dimensions | Max Size |
|-----------|-------------|----------------|----------|
| Feed | 1:1 or 4:5 | 600x600 | 30MB |
| Stories/Reels | 9:16 | 500x888 | 30MB |
| Carousel | 1:1 | 1080x1080 | 30MB |

Workflow:
1. Use `validate_and_prepare_meta_assets` with image URLs
2. If successful, receive an `asset_bundle_id`
3. Use that bundle_id with `create_meta_image_campaign`

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `image_urls` | `array` | yes | List of public image URLs to validate and upload (max 10). Images will be validated and uploaded to Meta to get image hashes. |
| `placement` | `string` | no | Target placement: 'feed' (default), 'stories_reels', or 'carousel' |
| `ad_account_id` | `object` | no | Meta Ad Account ID (optional) |

### Example request

```json
{
  "arguments": {
    "image_urls": [
      "string"
    ],
    "placement": "feed",
    "ad_account_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for validate_and_prepare_meta_assets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "validate_and_prepare_meta_assets"
}
```

---
# Monitoring & Reporting

## create_monitor

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_monitor/execute`

Create a monitoring alert for your campaigns. Checked daily.

**Supported metrics:** roas, ctr, cpc, cpa, cpm, cpv, spend, conversions, impressions, clicks, cost_per_lead, conversion_rate, budget_utilization, video_views, engagement_rate

**Operators:** less_than, greater_than, less_than_or_equal, greater_than_or_equal, changes_by

**Advanced features:**
- **Multiple conditions with AND/OR:** Use `conditions` array with `conditions_logic: "AND"` or `"OR"`
- **Consecutive days:** Add `consecutive_days: 3` to only trigger after 3 days in a row
- **Relative thresholds:** Set `threshold_type: "relative"` with `threshold_multiplier: 1.5` to mean "50% above average"
- **% change detection:** Use `operator: "changes_by"` with `direction: "decrease"` and `threshold: 30` for "dropped 30%"
- **Campaign targeting:** Use `campaign_ids` to monitor specific campaigns only
- **Auto-actions (coming soon):** `auto_action: "pause_campaign"` or `"increase_budget"` with `auto_action_value: 20`

**Examples:**

1. Simple: "Alert me if ROAS drops below 2"
→ metric: "roas", operator: "less_than", threshold: 2.0

2. Consecutive days: "Alert if CPA exceeds $50 for 3 days straight"
→ metric: "cpa", operator: "greater_than", threshold: 50, consecutive_days: 3

3. Compound AND: "Alert if CTR < 0.8% AND spend > $200"
→ conditions: [
    {metric: "ctr", operator: "less_than", threshold: 0.8},
    {metric: "spend", operator: "greater_than", threshold: 200}
  ], conditions_logic: "AND"

4. Relative: "Alert if CPA goes 50% above my 30-day average"
→ metric: "cpa", operator: "greater_than", threshold_type: "relative", threshold_multiplier: 1.5, threshold_timeframe: "last_30d"

5. % drop: "Notify me if cost per lead drops 30% in a day"
→ metric: "cost_per_lead", operator: "changes_by", threshold: 30, direction: "decrease", timeframe: "vs_previous_day"

**IMPORTANT:**
- If user doesn't specify an email for alerts, ASK them. Do not guess.
- If user doesn't specify platforms, default to all their connected platforms.
- If user doesn't specify campaigns, default to all campaigns.
- Do not assume timeframe — ask if unclear.
- Do not assume threshold values — ask if the user doesn't specify a number.
- ROAS monitors are skipped on traffic/awareness/engagement campaigns automatically.
- CPA monitors are skipped on campaigns with zero conversions automatically.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | `string` | yes | Name for this alert (e.g., 'High CPA Auto-Pause', 'ROAS Scaling Rule') |
| `monitor_type` | `object` | no | Type: 'performance_drop', 'budget_pace', 'anomaly_detection', 'spend_threshold', 'conversion_tracking' |
| `conditions` | `object` | no | List of conditions. Multiple conditions are evaluated with AND logic by default. For OR logic, set conditions_logic='OR'. If not provided, use the single-condition fields (metric, operator, threshold) below. |
| `conditions_logic` | `object` | no | How multiple conditions combine: 'AND' (all must be true) or 'OR' (any must be true). Default: 'AND'. |
| `metric` | `object` | no | Single condition metric (shorthand). Use 'conditions' array for multiple conditions. |
| `operator` | `object` | no | Single condition operator (shorthand). |
| `threshold` | `object` | no | Single condition threshold (shorthand). |
| `timeframe` | `object` | no | Single condition timeframe (shorthand). |
| `consecutive_days` | `object` | no | Single condition consecutive days (shorthand). |
| `platforms` | `object` | no | Platforms to monitor: ['google_ads'], ['meta_ads'], ['google_ads', 'meta_ads'], etc. Default: all connected platforms. |
| `campaign_ids` | `object` | no | Specific campaign IDs to monitor. Default: all campaigns. Get IDs from list_campaigns/list_meta_campaigns tools. |
| `scope_level` | `object` | no | What level to monitor: 'campaign' (default), 'ad_set' (ad group level), or 'ad' (individual ad/creative level). |
| `alert_method` | `object` | no | How to notify: 'email', 'slack', 'webhook'. Default: email. |
| `alert_destination` | `object` | no | Where to send alerts: email address, Slack webhook URL, etc. If not provided, uses the user's account email. |
| `auto_action` | `object` | no | Automatic action when triggered: 'pause_campaign', 'increase_budget', 'decrease_budget', 'notify_only'. Default: notify_only (no automatic changes). |
| `auto_action_value` | `object` | no | Value for auto-action: percentage for budget changes (e.g., 20 for +20%), ignored for pause/notify. |
| `check_frequency` | `object` | no | How often to check: 'daily' — checks once per day using data through yesterday. This is the only available frequency currently. |

### Example request

```json
{
  "arguments": {
    "name": "string",
    "monitor_type": "performance_drop",
    "conditions": [
      {
        "metric": "string",
        "operator": "string",
        "threshold": 1.0,
        "timeframe": "string",
        "consecutive_days": 1,
        "threshold_type": "string",
        "threshold_multiplier": 1.0,
        "threshold_timeframe": "string",
        "direction": "string"
      }
    ],
    "conditions_logic": "AND",
    "metric": "string",
    "operator": "string",
    "threshold": 1.0
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_monitor)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_monitor"
}
```

---
## delete_monitor

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/delete_monitor/execute`

Delete a monitoring alert by its task ID.

**When to use:**
- "Delete my ROAS monitor"
- "Remove this alert"
- "Stop monitoring my campaign"

Get the task_id from `list_monitors` first.

Accepts `task_id`, `alert_id`, or `monitor_id` — all treated the same.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `task_id` | `string` | yes | ID of the monitor to delete. Get from list_monitors. |

### Example request

```json
{
  "arguments": {
    "task_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for delete_monitor)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "delete_monitor"
}
```

---
## generate_report_now

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/generate_report_now/execute`

Generate an immediate performance report and deliver it now.

**What it does:**
- Generates a comprehensive PDF performance report immediately
- Fetches latest campaign data from all connected platforms
- Includes AI-powered recommendations and insights
- Delivers via email, Slack, or webhook

**When to use:**
- "Get me my campaign performance report"
- "Send me a detailed analysis of all my accounts"
- "Generate a report and email it to me"
- "I need my performance data now"
- "Prepare a report for all my campaigns"

**Report Types:**
- performance_brief: Quick overview of key metrics
- detailed_analysis: Deep dive with breakdowns
- executive_summary: High-level summary for stakeholders

**Execution time:** 2-5 minutes (async - you'll be notified when ready)

**Example:**
User: "Get me my detailed campaign performance for all accounts and email it to me"
→ Call generate_report_now with:
  - report_type: "detailed_analysis"
  - platforms: null (all platforms)
  - delivery_method: "email"
  - delivery_destination: "user@example.com"

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `report_type` | `string` | no | Type of report: 'performance_brief', 'detailed_analysis', or 'executive_summary' |
| `platforms` | `object` | no | Platforms to include: 'google_ads', 'meta_ads', 'tiktok_ads', 'linkedin_ads'. Default is all connected. |
| `account_ids` | `object` | no | Specific account IDs to include. Default is all accounts. |
| `campaign_ids` | `object` | no | Specific campaign IDs to include. Default is all campaigns. |
| `date_range` | `object` | no | Date range with 'start_date' and 'end_date' in YYYY-MM-DD format. Default is last 7 days. |
| `delivery_method` | `string` | no | How to deliver the report: 'email', 'slack', or 'webhook' |
| `delivery_destination` | `object` | no | Email address, Slack channel, or webhook URL. Default is user's email. |
| `include_recommendations` | `boolean` | no | Whether to include AI-powered recommendations in the report |
| `report_format` | `string` | no | Output format: 'pdf', 'html', or 'json' |

### Example request

```json
{
  "arguments": {
    "report_type": "performance_brief",
    "platforms": [
      "string"
    ],
    "account_ids": [
      "string"
    ],
    "campaign_ids": [
      "string"
    ],
    "date_range": {
      "start_date": "2026-03-01",
      "end_date": "2026-03-31"
    },
    "delivery_method": "email"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for generate_report_now)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "generate_report_now"
}
```

---
## get_monitor_history

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_monitor_history/execute`

Show trigger history for a monitoring alert.

**What it does:**
- Shows when the monitor triggered, what condition was met, and what value caused it
- Shows which campaigns triggered and whether notifications were sent
- Useful for understanding monitor behavior and verifying it works correctly

**When to use:**
- "Show history for my CPA monitor"
- "When did this alert last trigger?"
- "What campaigns triggered my ROAS alert?"

Requires the monitor's alert_id (get from list_monitors).

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `alert_id` | `string` | yes | ID of the monitoring alert to get history for |
| `limit` | `object` | no | Maximum number of trigger records to return. If not specified, returns all. |

### Example request

```json
{
  "arguments": {
    "alert_id": "string",
    "limit": 1
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_monitor_history)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_monitor_history"
}
```

---
## get_research_status

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_research_status/execute`

Check the status of a research job.

**What it does:**
- Shows current progress percentage
- Indicates if job is pending, in progress, completed, or failed
- Provides summary when complete

**When to use:**
- "Check on my research"
- "Is my analysis done?"
- "What's the status of job [ID]?"

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `job_id` | `string` | yes | ID of the research job |

### Example request

```json
{
  "arguments": {
    "job_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_research_status)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_research_status"
}
```

---
## list_monitors

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_monitors/execute`

List all your monitoring alerts.

**What it does:**
- Shows all active and paused monitors
- Displays conditions, trigger counts, and status
- Provides alert IDs for management

**When to use:**
- "Show my monitoring alerts"
- "What am I monitoring?"
- "List all my alerts"

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `status` | `object` | no | Filter by status: 'active', 'paused', or 'all' |

### Example request

```json
{
  "arguments": {
    "status": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_monitors)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_monitors"
}
```

---
## list_pending_actions

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_pending_actions/execute`

List auto-actions waiting for your approval.

**What it does:**
- Shows pending campaign actions (pause, budget changes) queued by your monitors
- Displays before/after state for each action
- Shows expiry time (actions expire after 48 hours)

**When to use:**
- "Show my pending actions"
- "What actions need approval?"
- "Any pending budget changes?"

After reviewing, use `manage_action` to approve or reject each action.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `status` | `object` | no | Filter: 'pending_approval', 'executed', 'rejected', 'expired', 'all' |

### Example request

```json
{
  "arguments": {
    "status": "pending_approval"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_pending_actions)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_pending_actions"
}
```

---
## list_scheduled_tasks

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_scheduled_tasks/execute`

List all your scheduled automation tasks.

**What it does:**
- Shows all scheduled briefs, monitors, and research jobs
- Displays status, schedule, and last execution
- Provides task IDs for management

**When to use:**
- "Show my scheduled tasks"
- "What automations do I have running?"
- "List my scheduled reports"

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `status` | `object` | no | Filter by status: 'active', 'paused', or 'all' |
| `task_type` | `object` | no | Filter by type: 'performance_brief', 'monitoring', 'research' |

### Example request

```json
{
  "arguments": {
    "status": "string",
    "task_type": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_scheduled_tasks)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_scheduled_tasks"
}
```

---
## manage_action

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/manage_action/execute`

Approve or reject a pending auto-action.

**What it does:**
- Approves a pending action → executes it immediately on the ad platform
- Rejects a pending action → cancels it, no changes made

**When to use:**
- "Approve action [action_id]"
- "Reject action [action_id]"
- "Approve all pending actions" (call multiple times)

**IMPORTANT:**
- Approved actions are EXECUTED IMMEDIATELY (campaign paused, budget changed)
- This is irreversible in the moment — verify before approving
- Actions expire after 48 hours if not approved

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `action_id` | `string` | yes | ID of the pending action to approve or reject |
| `decision` | `string` | yes | 'approve' or 'reject' |

### Example request

```json
{
  "arguments": {
    "action_id": "string",
    "decision": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for manage_action)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "manage_action"
}
```

---
## manage_scheduled_task

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/manage_scheduled_task/execute`

Manage a scheduled task — pause, resume, or delete. Works for briefs, monitors, and all task types.

**What it does:**
- Pause: Temporarily stop a scheduled task or monitoring alert
- Resume: Restart a paused task or monitor
- Delete: Permanently remove a task or monitor

**When to use:**
- "Pause my daily brief"
- "Resume my performance reports"
- "Delete the weekly summary task"
- "Pause this monitoring alert"

**Required parameters:**
- `task_id`: The ID of the task (get from `list_scheduled_tasks` or `list_monitors`)
- `action`: 'pause', 'resume', or 'delete'

To delete a monitoring alert specifically, you can also use `delete_monitor`.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `task_id` | `string` | yes | ID of the scheduled task |
| `action` | `string` | yes | Action to take: 'pause', 'resume', 'delete' |

### Example request

```json
{
  "arguments": {
    "task_id": "string",
    "action": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for manage_scheduled_task)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "manage_scheduled_task"
}
```

---
## schedule_brief

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/schedule_brief/execute`

Schedule recurring performance briefs delivered to your inbox.

**What it does:**
- Creates automated daily or weekly performance reports
- Summarizes key metrics across all your ad platforms
- Highlights top performers and underperformers
- Provides AI-generated recommendations

**When to use:**
- "Set up a daily performance report"
- "Send me weekly campaign summaries"
- "I want automated performance updates"
- "Create a scheduled brief for my campaigns"

**Execution time:** 2-3 seconds to schedule

**Example:**
User: "Send me daily performance updates at 9 AM"
→ Call schedule_brief with:
  - name: "Daily Performance Update"
  - schedule_type: "daily"
  - time: "09:00"
  - delivery_method: "email"
  - delivery_destination: "user@example.com"

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | `string` | yes | Name for this scheduled brief (e.g., 'Daily Performance Summary') |
| `schedule_type` | `string` | no | How often to send: 'daily', 'weekly', or 'every_n_days' |
| `time` | `string` | no | Time to send (24-hour format, e.g., '09:00' for 9 AM) |
| `timezone` | `string` | no | Timezone (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo') |
| `platforms` | `object` | no | Platforms to include: 'google_ads', 'meta_ads', 'tiktok_ads', 'linkedin_ads'. Default is all connected. |
| `delivery_method` | `string` | no | How to deliver: 'email', 'slack', or 'webhook' |
| `delivery_destination` | `string` | yes | Email address, Slack channel, or webhook URL |

### Example request

```json
{
  "arguments": {
    "name": "string",
    "delivery_destination": "https://example.com",
    "schedule_type": "weekly",
    "time": "09:00",
    "timezone": "America/New_York",
    "platforms": [
      "string"
    ],
    "delivery_method": "email"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for schedule_brief)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "schedule_brief"
}
```

---
## start_research

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/start_research/execute`

Start an AI-powered research job (runs in background).

**What it does:**
- Performs deep analysis that takes 5 minutes to 2 hours
- Runs asynchronously - you'll be notified when complete
- Provides comprehensive insights and recommendations

**Research Types:**

1. **competitor_analysis** - Analyze competitor ad strategies
   Required context: competitors (list of competitor domains/names)

2. **keyword_research** - Discover high-value keywords
   Required context: seed_keywords (list of starting keywords)

3. **market_landscape** - Industry overview and trends
   Required context: industry (industry name)

4. **audience_insights** - Target audience analysis
   Required context: target_audience (description of audience)

5. **campaign_strategy** - Strategic recommendations
   Required context: campaign_goals (what you want to achieve)

**Depth Levels:**
- quick: 5-10 minutes
- standard: 15-30 minutes
- comprehensive: 1-2 hours

**Example:**
User: "Research my competitors"
→ Call start_research with:
  - research_type: "competitor_analysis"
  - depth: "standard"
  - context: {"competitors": ["competitor1.com", "competitor2.com"]}

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `research_type` | `string` | yes | Type: 'competitor_analysis', 'keyword_research', 'market_landscape', 'audience_insights', 'campaign_strategy' |
| `depth` | `string` | no | Depth: 'quick' (5-10 min), 'standard' (15-30 min), 'comprehensive' (1-2 hours) |
| `context` | `object` | yes | Research context (varies by type). See tool description for required fields. |
| `notify_method` | `object` | no | How to notify when complete: 'email', 'slack', 'in_app' |
| `notify_destination` | `object` | no | Email address or Slack channel for notification |

### Example request

```json
{
  "arguments": {
    "research_type": "string",
    "context": {
      "industry": "ecommerce",
      "target_audience": "runners aged 25-45",
      "goals": [
        "increase brand awareness",
        "drive purchases"
      ]
    },
    "depth": "standard",
    "notify_method": "email",
    "notify_destination": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for start_research)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "start_research"
}
```

---
## test_monitor

**Platform:** Monitoring & Reporting

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/test_monitor/execute`

Dry-run a monitor against current data WITHOUT triggering alerts or sending notifications.

**What it does:**
- Evaluates the monitor's conditions against current campaign data
- Shows which campaigns would match and which would be skipped
- Does NOT create triggers, send emails, or queue auto-actions
- Useful for verifying a monitor is configured correctly before enabling

**When to use:**
- "Test my CPA monitor"
- "Would this alert trigger right now?"
- "Check if my monitor is set up correctly"
- "Dry run the budget overspend alert"

Requires the monitor's alert_id (get from list_monitors).

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `alert_id` | `string` | yes | ID of the monitoring alert to test |

### Example request

```json
{
  "arguments": {
    "alert_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for test_monitor)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "test_monitor"
}
```

---
# TikTok Ads

## add_tiktok_ad

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_tiktok_ad/execute`

Add a new ad to an existing TikTok ad group.

Use this when you need to add additional ads (creatives) to an ad group that was already created.

Supports all ad formats:
- SINGLE_IMAGE: Provide image_urls (public HTTPS URLs — auto-uploaded) OR image_ids (pre-uploaded TikTok IDs)
- SINGLE_VIDEO: Provide video_id (TikTok video ID from creative library)
- Spark Ads: Boost organic TikTok posts (requires tiktok_item_id + identity_type=TT_USER)
- Carousel: Multi-card ads (first call create_tiktok_carousel_card to get card_id, then provide card_id + card_type here)

Identity (identity_id) is auto-resolved — you do NOT need to provide it unless using Spark Ads.
Images are auto-uploaded from URLs — you do NOT need to call upload_tiktok_images first.

This is useful for:
- Testing different creatives in the same ad group
- Adding new ad variations without creating a new campaign
- A/B testing ad copy, images, or CTAs

Required: adgroup_id, ad_text, landing_page_url.
For image ads: provide image_urls (easiest) or image_ids.
For video ads: provide video_id.
For Spark Ads: provide tiktok_item_id and set identity_type to TT_USER.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `adgroup_id` | `string` | yes | Existing TikTok ad group ID to add the ad to |
| `ad_text` | `string` | yes | Ad text (1-100 characters). |
| `landing_page_url` | `string` | yes | Landing page URL (must be HTTPS). |
| `ad_name` | `object` | no | Ad name. Auto-generated if not provided. |
| `ad_format` | `object` | no | Ad format: SINGLE_IMAGE, SINGLE_VIDEO. Default: SINGLE_IMAGE. |
| `display_name` | `object` | no | Brand name (max 40 chars). |
| `call_to_action` | `object` | no | CTA button: LEARN_MORE, SHOP_NOW, SIGN_UP, DOWNLOAD, CONTACT_US, APPLY_NOW, etc. |
| `image_ids` | `object` | no | TikTok image IDs from upload_tiktok_images (for SINGLE_IMAGE). |
| `image_urls` | `object` | no | Public image URLs (HTTPS) — auto-uploaded to TikTok. Use this instead of image_ids for convenience. |
| `video_id` | `object` | no | TikTok video ID (for SINGLE_VIDEO format). |
| `identity_id` | `object` | no | Advertiser identity ID. Auto-resolved if not provided. |
| `identity_type` | `object` | no | Identity type: CUSTOMIZED_USER, BC_AUTH_TT, TT_USER. Auto-resolved if not provided. |
| `tiktok_item_id` | `object` | no | TikTok organic post ID for Spark Ads (boost existing TikTok posts as ads). |
| `card_id` | `object` | no | Carousel card ID. |
| `card_type` | `object` | no | Card type: IMAGE or PRODUCT. |
| `advertiser_id` | `object` | no | TikTok advertiser ID (optional). |

### Example request

```json
{
  "arguments": {
    "adgroup_id": "string",
    "ad_text": "string",
    "landing_page_url": "https://example.com",
    "ad_name": "string",
    "ad_format": "SINGLE_IMAGE",
    "display_name": "string",
    "call_to_action": "string",
    "image_ids": [
      "string"
    ],
    "image_urls": [
      "string"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_tiktok_ad)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_tiktok_ad"
}
```

---
## add_tiktok_ad_group

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/add_tiktok_ad_group/execute`

Add a new ad group to an existing TikTok campaign.

Use this when you need to add additional ad groups to a campaign that was already created.
Each ad group can have different targeting, budget, and placement settings.

This is useful for:
- A/B testing different audiences under the same campaign
- Splitting budget across different targeting strategies
- Adding new targeting segments to an existing campaign

Required: campaign_id, adgroup_name, budget.
The objective parameter determines automatic billing/optimization defaults (TRAFFIC→CPC, CONVERSIONS→OCPM, etc.).

After creating the ad group, use `add_tiktok_ad` to add ads to it.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | Existing TikTok campaign ID to add the ad group to |
| `adgroup_name` | `string` | yes | Name for the new ad group |
| `budget` | `number` | yes | Ad group budget in account currency (min varies by region) |
| `budget_mode` | `object` | no | BUDGET_MODE_DAY (daily) or BUDGET_MODE_TOTAL (lifetime). Default: daily. |
| `objective` | `object` | no | Campaign objective (determines billing/optimization defaults). Options: TRAFFIC, CONVERSIONS, LEAD_GENERATION, REACH, VIDEO_VIEWS, APP_PROMOTION. Default: TRAFFIC. |
| `location_ids` | `object` | no | TikTok location IDs. Default: ['6252001'] (US). |
| `age_groups` | `object` | no | Age groups: AGE_13_17, AGE_18_24, AGE_25_34, AGE_35_44, AGE_45_54, AGE_55_100. |
| `gender` | `object` | no | GENDER_UNLIMITED, GENDER_MALE, GENDER_FEMALE. |
| `schedule_start_time` | `object` | no | Start time: YYYY-MM-DD HH:MM:SS. |
| `schedule_end_time` | `object` | no | End time: YYYY-MM-DD HH:MM:SS. Required for BUDGET_MODE_TOTAL. |
| `pixel_id` | `object` | no | TikTok Pixel ID for conversion tracking. |
| `optimization_event` | `object` | no | Conversion event: COMPLETE_PAYMENT, ON_WEB_CART, ON_WEB_DETAIL, ON_WEB_REGISTER, FORM, CONVERSION_LEADS, PAGE_VISIT, CLICK_LANDING_PAGE, PHONE_CONNECT, SEARCH, SUBSCRIBE, DOWNLOAD_FINISH. |
| `interest_category_ids` | `object` | no | Interest category IDs for targeting. |
| `audience_ids` | `object` | no | Custom audience IDs to include. |
| `excluded_audience_ids` | `object` | no | Custom audience IDs to exclude. |
| `languages` | `object` | no | Language codes (e.g., ['en', 'es']). |
| `placement_type` | `object` | no | PLACEMENT_TYPE_AUTOMATIC or PLACEMENT_TYPE_NORMAL. |
| `placements` | `object` | no | Manual placements: PLACEMENT_TIKTOK, PLACEMENT_PANGLE, PLACEMENT_GLOBAL_APP_BUNDLE. |
| `operating_systems` | `object` | no | Target OS: ANDROID, IOS. |
| `video_download_disabled` | `object` | no | Disable video download. |
| `comment_disabled` | `object` | no | Disable comments on ads. |
| `billing_event` | `object` | no | Override billing event: CPC, CPM, OCPM, CPV. |
| `optimization_goal` | `object` | no | Override optimization goal: CLICK, CONVERT, REACH, etc. |
| `promotion_type` | `object` | no | Override promotion type: WEBSITE, APP_ANDROID, APP_IOS. |
| `advertiser_id` | `object` | no | TikTok advertiser ID (optional). |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "adgroup_name": "string",
    "budget": 1.0,
    "budget_mode": "BUDGET_MODE_DAY",
    "objective": "TRAFFIC",
    "location_ids": [
      "string"
    ],
    "age_groups": [
      "string"
    ],
    "gender": "string",
    "schedule_start_time": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for add_tiktok_ad_group)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "add_tiktok_ad_group"
}
```

---
## analyze_tiktok_geo_performance

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/analyze_tiktok_geo_performance/execute`

Analyze TikTok geographic/country-level performance.

Returns: per-country spend, conversions, CPA, CTR, hook rate, engagement. Best/worst countries with recommendations.

Note: TikTok uses country-level breakdowns (not placement-level like Meta's Feed/Stories/Reels).

When to use: "TikTok performance by country", "Which countries perform best on TikTok?", "TikTok geo breakdown"

Parameters:
- lookback_days: default 30

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `lookback_days` | `integer` | no | Days to analyze |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |
| `start_date` | `object` | no | Start date YYYY-MM-DD |
| `end_date` | `object` | no | End date YYYY-MM-DD |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "lookback_days": 30,
    "advertiser_id": "string",
    "start_date": "string",
    "end_date": "string",
    "raw_data": false
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for analyze_tiktok_geo_performance)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "analyze_tiktok_geo_performance"
}
```

---
## analyze_tiktok_wasted_spend

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/analyze_tiktok_wasted_spend/execute`

Analyze TikTok campaigns for wasted ad spend. Identifies campaigns losing money (ROAS < 1.0) and underperforming campaigns (ROAS < target).

Returns: wasted spend by campaign, severity levels (CRITICAL/HIGH/MEDIUM), creative fatigue analysis, status-aware recommendations (distinguishes active vs disabled campaigns).

When to use: "Where am I wasting money on TikTok?", "TikTok wasted spend", "Which TikTok campaigns are losing money?"

⚠️ NEVER say "pause" for campaigns with conversions. Say "review" or "reduce budget".
⚠️ Campaigns in LEARNING phase (< 14 days) should not be paused.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `lookback_days` | `integer` | no | Days to analyze: 7, 30, 60, 90, 120 |
| `target_roas` | `object` | no | Target ROAS override (e.g., 3.0) |
| `include_fatigue` | `boolean` | no | Include creative fatigue analysis |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |
| `start_date` | `object` | no | Start date YYYY-MM-DD |
| `end_date` | `object` | no | End date YYYY-MM-DD |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "lookback_days": 30,
    "target_roas": 1.0,
    "include_fatigue": true,
    "advertiser_id": "string",
    "start_date": "string",
    "end_date": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for analyze_tiktok_wasted_spend)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "analyze_tiktok_wasted_spend"
}
```

---
## create_tiktok_campaign

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_tiktok_campaign/execute`

User wants to create a TikTok ad campaign with IMAGES, Spark Ads, or Carousel ads (not video).

LONG-RUNNING TOOL: Creates a TikTok In-Feed ad campaign with image creatives, Spark Ads (boost organic posts), or Carousel ads.

Supported creative types:
- Standard Image Ads: Provide asset_bundle_id or existing_image_ids
- Spark Ads: Provide tiktok_item_id to boost an existing organic TikTok post as a paid ad
- Carousel Ads: First call create_tiktok_carousel_card to get a card_id, then provide card_id + card_type here
- App Promotion: Set objective=APP_PROMOTION + app_id for app install campaigns

Emits MCP progress updates during campaign creation (typically 15-30 seconds).
Progress stages: validate - commit.

CRITICAL WARNING:
- Call this tool ONLY ONCE per campaign
- Creates REAL campaigns that cost REAL money
- Do NOT retry automatically if errors occur
- Report errors to user instead of retrying

YOUR ROLE: TikTok Ads Campaign Strategist

BEFORE calling this tool, YOU MUST:

1. Prepare Assets (choose ONE method):
   - METHOD A: Upload new images
     - Call `validate_and_prepare_tiktok_assets` with image URLs
     - Get `asset_bundle_id`
     - Use in create_tiktok_campaign
   - METHOD B: Reuse existing images
     - Call `discover_tiktok_assets` to browse library
     - Get `existing_image_ids`
     - Use in create_tiktok_campaign

2. Research the business:
   - Understand products/services, target audience
   - Review brand messaging and offers
   - Consider TikTok-specific creative best practices (authentic, engaging, mobile-first)

3. Craft compelling ad creative:
   - Ad text: 12-100 chars (50 or less recommended for no truncation)
   - Write for TikTok's young, mobile audience
   - Use casual, authentic language
   - Include clear call-to-action

4. Define targeting:
   - Locations: Default is USA, can specify others
   - Age groups: TikTok audience skews younger (18-34)
   - Gender: Unless product is gender-specific, use GENDER_UNLIMITED

Required Fields:
1. campaign_name - Descriptive name (auto-suffixed with timestamp)
2. budget_daily - Minimum $20/day for image ads
3. ad_text - Main message (12-100 chars, 50 recommended)
4. display_name - Brand name (max 40 chars)
5. landing_page_url - HTTPS URL where users land
6. asset_bundle_id OR existing_image_ids - Choose one

Optional Fields:
- target_locations - List of TikTok location IDs (default: USA)
- target_age_groups - Age ranges to target
- target_gender - Gender targeting (default: all)
- advertiser_id - TikTok advertiser account

What happens when you call this tool:
1. Creates Campaign with daily budget
2. Creates Ad Group with targeting and auto-bidding
3. Uploads images to TikTok (if using asset_bundle_id) OR links existing images
4. Creates Ad with your creative
5. Campaign goes ACTIVE immediately

Idempotency Support:
- If creation fails partway, partial state is logged (campaign_id, ad_group_id)
- You can retry with same parameters - system will resume from checkpoint
- Prevents duplicate campaigns

Execution time: 15-30 seconds (TikTok API has 10-second image processing wait)

Error handling:
- If validation fails: Error message with specific issue
- If TikTok API fails: Error with partial state IDs for manual cleanup
- Check TikTok Ads Manager for partial campaigns

Best Practices:
- Start with smaller budgets ($20-50/day) and scale what works
- Test multiple ad variations (different images, text)
- Monitor performance in TikTok Ads Manager after 2-3 days
- TikTok learning phase is ~7 days - don't make changes too quickly
- Use vertical 9:16 images - horizontal images won't work well

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (will be automatically suffixed with timestamp for uniqueness) |
| `objective` | `object` | no | Campaign objective. Options: 'TRAFFIC' (website visits), 'CONVERSIONS' (website conversions), 'LEAD_GENERATION' (leads), 'REACH' (maximum impressions), 'VIDEO_VIEWS' (video views), 'APP_PROMOTION' (app installs — requires app_id). Default:  |
| `app_id` | `object` | no | TikTok app ID for APP_PROMOTION campaigns. Required when objective is APP_PROMOTION. |
| `app_promotion_type` | `object` | no | App promotion type for APP_PROMOTION campaigns. Options: 'APP_INSTALL' (new installs), 'APP_RETARGETING' (re-engage existing users). Default: APP_INSTALL |
| `budget_daily` | `object` | no | Daily budget in account currency (minimum varies by region, typically $20-50/day). Mutually exclusive with budget_lifetime. |
| `budget_lifetime` | `object` | no | Lifetime budget in account currency (total spend over campaign duration). Requires schedule_end_time. Mutually exclusive with budget_daily. |
| `schedule_end_time` | `object` | no | Campaign end time in format 'YYYY-MM-DD HH:MM:SS'. Required when using budget_lifetime. Optional with budget_daily. |
| `budget_optimize_on` | `object` | no | Campaign Budget Optimization (CBO). TikTok default: true (enabled). When enabled, TikTok auto-distributes budget across ad groups. Set to false to manage budgets per ad group manually. |
| `asset_bundle_id` | `object` | no | Asset bundle ID from validate_and_prepare_tiktok_assets tool. Use this for NEW image uploads. Mutually exclusive with existing_image_ids. |
| `existing_image_ids` | `object` | no | List of existing TikTok image IDs from discover_tiktok_assets tool. Use this to REUSE images from TikTok Asset Library. Mutually exclusive with asset_bundle_id. |
| `ad_text` | `string` | yes | Ad text (1-100 characters). Recommended: 50 characters or less for no truncation. |
| `display_name` | `string` | yes | Brand/business name displayed on the ad (max 40 characters) |
| `landing_page_url` | `string` | yes | Landing page URL (must be HTTPS). |
| `call_to_action` | `object` | no | Call-to-action button text. Options: 'LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD', 'CONTACT_US', 'APPLY_NOW', 'BOOK_NOW', 'GET_QUOTE', 'SUBSCRIBE', 'ORDER_NOW', 'BUY_NOW', 'GET_OFFER', 'WATCH_NOW'. Default: auto-selected by TikTok. |
| `pixel_id` | `object` | no | TikTok Pixel ID for conversion tracking. Required for CONVERSIONS objective. |
| `optimization_event` | `object` | no | Conversion event to optimize for when pixel_id is provided. Options: COMPLETE_PAYMENT, ON_WEB_CART (add to cart), ON_WEB_DETAIL (view content), ON_WEB_REGISTER (registration), FORM (form submit), CONVERSION_LEADS (lead gen), INITIATE_ORDER, |
| `target_locations` | `object` | no | List of TikTok location IDs. Default: ['6252001'] (United States). Common IDs: USA=6252001, UK=2635167, Canada=6251999, Australia=2077456. |
| `target_age_groups` | `object` | no | Age groups to target. Options: 'AGE_13_17', 'AGE_18_24', 'AGE_25_34', 'AGE_35_44', 'AGE_45_54', 'AGE_55_100'. Default: all 18+. |
| `target_gender` | `object` | no | Gender targeting: 'GENDER_UNLIMITED', 'GENDER_MALE', 'GENDER_FEMALE'. |
| `interest_category_ids` | `object` | no | Interest category IDs for interest-based targeting. Get IDs from TikTok Ads Manager or search_tiktok_interests tool. |
| `audience_ids` | `object` | no | Custom audience IDs to include (DMP audiences, lookalikes). |
| `excluded_audience_ids` | `object` | no | Custom audience IDs to exclude. |
| `languages` | `object` | no | Language targeting codes (e.g., ['en', 'es']). Default: all languages. |
| `placement_type` | `object` | no | Placement type: 'PLACEMENT_TYPE_AUTOMATIC' (recommended) or 'PLACEMENT_TYPE_NORMAL' (manual). Default: PLACEMENT_TYPE_AUTOMATIC. |
| `placements` | `object` | no | Manual placement selection (when placement_type=PLACEMENT_TYPE_NORMAL). Options: 'PLACEMENT_TIKTOK', 'PLACEMENT_PANGLE', 'PLACEMENT_GLOBAL_APP_BUNDLE'. |
| `operating_systems` | `object` | no | Target device OS. Options: 'ANDROID', 'IOS'. Default: all. |
| `video_download_disabled` | `object` | no | Disable video download on TikTok. Default: false (users can download). |
| `comment_disabled` | `object` | no | Disable comments on ads. Default: false (comments allowed). |
| `tiktok_item_id` | `object` | no | TikTok organic post ID for Spark Ads. Boosts an existing TikTok post as a paid ad. The post's video/content becomes the ad creative. Get the post ID from TikTok Ads Manager > Spark Ads. When using Spark Ads, asset_bundle_id and existing_ima |
| `card_id` | `object` | no | Carousel card ID for multi-card image ads. Create carousel cards first in TikTok Ads Manager, then provide the card ID here. When using carousel, asset_bundle_id and existing_image_ids are NOT required. |
| `card_type` | `object` | no | Carousel card type: 'IMAGE' (image carousel) or 'PRODUCT' (product catalog carousel). Required when card_id is provided. |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "ad_text": "string",
    "display_name": "string",
    "landing_page_url": "https://example.com",
    "objective": "TRAFFIC",
    "app_id": "string",
    "app_promotion_type": "string",
    "budget_daily": 1.0,
    "budget_lifetime": 1.0,
    "schedule_end_time": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_tiktok_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_tiktok_campaign"
}
```

---
## create_tiktok_carousel_card

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_tiktok_carousel_card/execute`

Create a carousel card from multiple images for TikTok carousel ads.

IMPORTANT: Call this tool BEFORE creating a carousel campaign or carousel ad.

Carousel ads show 2-10 swipeable image cards. Each card can have its own ad text and landing page.

Workflow for carousel campaigns:
1. Get TikTok image IDs:
   - Option A: upload_tiktok_images with public image URLs (uploads to TikTok, returns image_ids)
   - Option B: discover_tiktok_assets to find existing images in your library
2. Call this tool with those image_ids to create a carousel card (get card_id back)
3. Use the card_id in create_tiktok_campaign or add_tiktok_ad with card_type='IMAGE'

Required: image_ids (2-10 TikTok image IDs).
Optional: ad_texts (one per card), landing_page_urls (one per card), call_to_action.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `image_ids` | `array` | yes | List of TikTok image IDs (2-10). Get image IDs from discover_tiktok_assets (existing library images) or from a previously created image campaign's assets. |
| `card_type` | `object` | no | Card type: 'IMAGE' (image carousel) or 'PRODUCT' (product catalog). Default: IMAGE. |
| `ad_texts` | `object` | no | Ad text for each card (one per image). If fewer texts than images, the first text is reused. |
| `landing_page_urls` | `object` | no | Landing page URL for each card (one per image, must be HTTPS). If fewer URLs than images, the first URL is reused. |
| `call_to_action` | `object` | no | CTA button for all cards: LEARN_MORE, SHOP_NOW, SIGN_UP, etc. |
| `advertiser_id` | `object` | no | TikTok advertiser ID (optional). |

### Example request

```json
{
  "arguments": {
    "image_ids": [
      "string"
    ],
    "card_type": "IMAGE",
    "ad_texts": [
      "string"
    ],
    "landing_page_urls": [
      "string"
    ],
    "call_to_action": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_tiktok_carousel_card)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_tiktok_carousel_card"
}
```

---
## create_tiktok_video_campaign

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/create_tiktok_video_campaign/execute`

User wants to create a TikTok ad campaign with a VIDEO (not images). Also supports Spark Ads and Carousel with video.

LONG-RUNNING TOOL: Creates a TikTok In-Feed video ad campaign. Also supports Spark Ads (boost organic posts) and Carousel ads.

Additional creative types supported:
- Spark Ads: Provide tiktok_item_id to boost an existing organic TikTok post as a paid ad
- Carousel Ads: First call create_tiktok_carousel_card to get a card_id, then provide card_id + card_type here
- App Promotion: Set objective=APP_PROMOTION + app_id for app install campaigns

Emits MCP progress updates during campaign creation (typically 30-60 seconds).
Progress stages: validate - upload - commit.

CRITICAL WARNING:
- Call this tool ONLY ONCE per campaign
- Creates REAL campaigns that cost REAL money
- Do NOT retry automatically if errors occur
- Report errors to user instead of retrying

YOUR ROLE: TikTok Video Ads Campaign Strategist

CRITICAL PRE-FLIGHT CHECKLIST - DO NOT SKIP:

1. Fix Video URL Format (CRITICAL!):

   Check if user provided Google Drive link:
   - BAD: https://drive.google.com/file/d/{FILE_ID}/view?usp=sharing
   - GOOD: https://drive.google.com/uc?export=download&id={FILE_ID}

   YOU MUST CONVERT Google Drive links! Extract FILE_ID and rebuild URL.
   Example:
   - Input: https://drive.google.com/file/d/1jTOcNDe1UfvlUka5VpD2RAblNpQ3FGm_/view?usp=sharing
   - Extract ID: 1jTOcNDe1UfvlUka5VpD2RAblNpQ3FGm_
   - Output: https://drive.google.com/uc?export=download&id=1jTOcNDe1UfvlUka5VpD2RAblNpQ3FGm_

   Other common fixes:
   - Dropbox: Add ?dl=1 to end of URL (not ?dl=0)
   - Vimeo: Use direct MP4 link, not player embed
   - YouTube: NOT SUPPORTED - TikTok cannot download from YouTube

2. Validate Video URL:
   - ALWAYS call `validate_video(video_url, platform='tiktok')` FIRST
   - Check response Content-Type:
     * GOOD: "video/mp4", "video/quicktime", "video/x-msvideo"
     * BAD: "text/html" (means it's a web page, not direct video)
   - If Content-Type is text/html, STOP and ask user for direct download link
   - Video requirements:
     * Duration: 5-60 seconds (TikTok sweet spot: 9-15 seconds)
     * File size: Max 500MB
     * Aspect ratio: 9:16 (vertical) STRONGLY preferred for TikTok
     * Formats: MP4, MOV, MPEG, AVI

3. Cover Image (OPTIONAL - Usually Not Needed!):

   DEFAULT BEHAVIOR (Recommended 95% of the time):
   - Do NOT provide cover_image_url parameter
   - TikTok automatically extracts a frame from your video as thumbnail
   - This is the SAFEST option - aspect ratios always match!

   ONLY provide cover_image_url if:
   - User explicitly requests a specific custom thumbnail
   - AND you've verified aspect ratios match (see validation below)

   If user insists on custom cover:
   - Ensure it's a direct image URL (not Google Drive share link!)
   - Aspect ratio MUST match video exactly:
     * Video 9:16 vertical - Cover 9:16 vertical (1080x1920)
     * Video 16:9 horizontal - Cover 16:9 horizontal (1920x1080)
   - If unsure: OMIT cover_image_url and explain TikTok will auto-extract

4. Research the business:
   - Understand products/services, target audience
   - Review brand messaging and offers
   - TikTok video best practices: hook viewers in first 3 seconds, authentic content, trending sounds/effects

5. Craft compelling ad creative:
   - Ad text: 12-100 chars (50 or less recommended for no truncation)
   - Write for TikTok's young, mobile-first audience
   - Use casual, conversational language
   - Call-to-action: WATCH_NOW, LEARN_MORE, SHOP_NOW, etc.

6. Define targeting:
   - Locations: Default is USA, can specify others
   - Age groups: TikTok core demographic is 18-34
   - Gender: Unless product is gender-specific, use GENDER_UNLIMITED

Required Fields:
1. campaign_name - Descriptive name (auto-suffixed with timestamp)
2. budget_daily - Minimum $50/day for video ads (higher than image ads)
3. video_url - Public video URL (must be validated first!)
4. ad_text - Main message (12-100 chars, 50 recommended)
5. landing_page_url - HTTPS URL where users land

Optional Fields:
- call_to_action - CTA button (default: WATCH_NOW)

- cover_image_url - Custom video thumbnail (OPTIONAL - Not Recommended!)

  IMPORTANT: This is OPTIONAL!
  - Default (omit this field): TikTok auto-extracts frame from video - RECOMMENDED
  - Only provide if user explicitly wants custom thumbnail AND aspect ratios match

  If you decide to provide cover_image_url:
  - Aspect ratio MUST exactly match video or TikTok will reject
  - Video 9:16 vertical - Cover 9:16 vertical (1080x1920)
  - Video 16:9 horizontal - Cover 16:9 horizontal (1920x1080)
  - When in doubt: OMIT this field - TikTok auto-extraction always works!

- target_locations - List of TikTok location IDs (default: USA)
- target_age_groups - Age ranges to target
- target_gender - Gender targeting (default: all)
- advertiser_id - TikTok advertiser account

What happens when you call this tool:
1. Creates Campaign with TRAFFIC objective
2. Creates Ad Group with PLACEMENT_TYPE_NORMAL (TikTok feed only)
3. Downloads video from URL and uploads to TikTok (UPLOAD_BY_URL with unique filename)
4. Uploads cover image (custom or auto-extracted from video)
5. Creates Ad with video + cover creative
6. Campaign goes ACTIVE immediately
7. TikTok processes video (5-15 minutes) and reviews ad creative (24 hours)

Idempotency Support:
- If creation fails partway, partial state is logged (campaign_id, ad_group_id, video_id)
- You can retry with same parameters - system will resume from checkpoint
- Prevents duplicate campaigns and video uploads

Execution time: 30-60 seconds (TikTok video upload takes longer than images)

Common Errors & How YOU Should Handle:

1. "text/html" Content-Type from validation:
   - Means URL is a web page, not direct video download
   - Common with: Google Drive share links, YouTube embeds
   - Your Action: Convert Google Drive links OR ask user for direct download URL

2. "Aspect ratio mismatch" error:
   - Cover image doesn't match video dimensions
   - Your Action: OMIT cover_image_url, tell user TikTok will auto-extract

3. "Video upload failed":
   - Usually means video URL became inaccessible or wrong format
   - Your Action: Re-validate URL, ensure it's direct download link

4. "Authorization failed":
   - User doesn't have TikTok connected or quota exceeded
   - Your Action: Guide user to connect TikTok account first

Best Practices:
- Start with $50-100/day budget for video (higher than images due to CPV)
- Test multiple video variations (different hooks, CTAs)
- Monitor video completion rate - TikTok rewards engaging videos with lower costs
- First 3 seconds are critical - hook viewers immediately
- Use vertical 9:16 format for best mobile experience
- Add captions - 85% of TikTok is watched with sound off
- Learning phase is 7 days - let algorithm optimize before making changes

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_name` | `string` | yes | Campaign name (will be automatically suffixed with timestamp for uniqueness) |
| `objective` | `object` | no | Campaign objective. Options: 'TRAFFIC', 'CONVERSIONS', 'LEAD_GENERATION', 'REACH', 'VIDEO_VIEWS', 'APP_PROMOTION' (requires app_id). Default: TRAFFIC |
| `app_id` | `object` | no | TikTok app ID for APP_PROMOTION campaigns. Required when objective is APP_PROMOTION. |
| `app_promotion_type` | `object` | no | App promotion type: 'APP_INSTALL' or 'APP_RETARGETING'. Default: APP_INSTALL. |
| `budget_daily` | `object` | no | Daily budget in account currency. Mutually exclusive with budget_lifetime. |
| `budget_lifetime` | `object` | no | Lifetime budget in account currency. Requires schedule_end_time. Mutually exclusive with budget_daily. |
| `schedule_end_time` | `object` | no | Campaign end time 'YYYY-MM-DD HH:MM:SS'. Required for budget_lifetime. |
| `budget_optimize_on` | `object` | no | Campaign Budget Optimization (CBO). TikTok default: true (enabled). When enabled, TikTok auto-distributes budget across ad groups. Set to false to manage budgets per ad group manually. |
| `video_url` | `string` | yes | Public video URL (from Google Drive, Vimeo, Dropbox, etc.). |
| `cover_image_url` | `object` | no | Custom cover image URL (9:16, 1080x1920). Auto-generated if not provided. |
| `ad_text` | `string` | yes | Ad text (1-100 characters). Recommended: 50 characters or less. |
| `display_name` | `object` | no | Brand/business name displayed on the ad (max 40 characters). If not provided, uses the campaign name. |
| `landing_page_url` | `string` | yes | Landing page URL (must be HTTPS). |
| `call_to_action` | `object` | no | CTA button: 'WATCH_NOW', 'LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD', 'APPLY_NOW', 'BOOK_NOW', 'CONTACT_US', 'GET_QUOTE', 'SUBSCRIBE', 'ORDER_NOW'. Default: auto-selected. |
| `pixel_id` | `object` | no | TikTok Pixel ID for conversion tracking. Required for CONVERSIONS objective. |
| `optimization_event` | `object` | no | Conversion event: COMPLETE_PAYMENT, ON_WEB_CART, ON_WEB_DETAIL, ON_WEB_REGISTER, FORM, CONVERSION_LEADS, PAGE_VISIT, CLICK_LANDING_PAGE, PHONE_CONNECT, SEARCH, SUBSCRIBE, DOWNLOAD_FINISH. Default: COMPLETE_PAYMENT |
| `target_locations` | `object` | no | TikTok location IDs. Default: ['6252001'] (US). UK=2635167, Canada=6251999, Australia=2077456. |
| `target_age_groups` | `object` | no | Age groups: 'AGE_13_17', 'AGE_18_24', 'AGE_25_34', 'AGE_35_44', 'AGE_45_54', 'AGE_55_100'. |
| `target_gender` | `object` | no | Gender: 'GENDER_UNLIMITED', 'GENDER_MALE', 'GENDER_FEMALE'. |
| `interest_category_ids` | `object` | no | Interest category IDs for targeting. |
| `audience_ids` | `object` | no | Custom audience IDs to include. |
| `excluded_audience_ids` | `object` | no | Custom audience IDs to exclude. |
| `languages` | `object` | no | Language codes (e.g., ['en', 'es']). |
| `placement_type` | `object` | no | 'PLACEMENT_TYPE_AUTOMATIC' (default) or 'PLACEMENT_TYPE_NORMAL' (manual). |
| `placements` | `object` | no | Manual placements: 'PLACEMENT_TIKTOK', 'PLACEMENT_PANGLE', 'PLACEMENT_GLOBAL_APP_BUNDLE'. |
| `operating_systems` | `object` | no | Target device OS. Options: 'ANDROID', 'IOS'. Default: all. |
| `video_download_disabled` | `object` | no | Disable video download on TikTok. Default: false (users can download). |
| `comment_disabled` | `object` | no | Disable comments on ads. Default: false (comments allowed). |
| `tiktok_item_id` | `object` | no | TikTok organic post ID for Spark Ads. Boosts an existing TikTok post as a paid ad. The post's video becomes the ad creative instead of video_url. Get the post ID from TikTok Ads Manager > Spark Ads. |
| `card_id` | `object` | no | Carousel card ID for multi-card ads. Create carousel cards first in TikTok Ads Manager. |
| `card_type` | `object` | no | Carousel card type: 'IMAGE' or 'PRODUCT'. Required when card_id is provided. |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_name": "string",
    "video_url": "https://example.com",
    "ad_text": "string",
    "landing_page_url": "https://example.com",
    "objective": "TRAFFIC",
    "app_id": "string",
    "app_promotion_type": "string",
    "budget_daily": 1.0,
    "budget_lifetime": 1.0,
    "schedule_end_time": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for create_tiktok_video_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "create_tiktok_video_campaign"
}
```

---
## detect_tiktok_creative_fatigue

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/detect_tiktok_creative_fatigue/execute`

Detect TikTok creative fatigue using video-specific metrics (hook rate decline, completion rate decline, engagement decline, frequency).

Returns: fatigue score (0-100) per ad, severely fatigued / at-risk / healthy counts, refresh schedule, recommendations.

TikTok-specific: Hook rate (first 2 seconds) is the primary fatigue signal (35% weight).

When to use: "Are my TikTok ads fatigued?", "TikTok creative refresh", "Which TikTok videos need replacing?"

Parameters:
- lookback_days: default 30
- frequency_threshold: override (default 3.5)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `lookback_days` | `integer` | no | Days to analyze |
| `frequency_threshold` | `object` | no | Override frequency threshold (default 3.5) |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |
| `start_date` | `object` | no | Start date YYYY-MM-DD |
| `end_date` | `object` | no | End date YYYY-MM-DD |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "lookback_days": 30,
    "frequency_threshold": 1.0,
    "advertiser_id": "string",
    "start_date": "string",
    "end_date": "string",
    "raw_data": false
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for detect_tiktok_creative_fatigue)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "detect_tiktok_creative_fatigue"
}
```

---
## discover_tiktok_assets

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/discover_tiktok_assets/execute`

User wants to reuse existing TikTok images instead of uploading new ones.

Browse existing images in TikTok Asset Library for reuse in campaigns.

IMPORTANT: This tool retrieves READ-ONLY data. Safe to call multiple times.

Returns:
- List of all images in your TikTok Asset Library
- Image IDs, dimensions, file sizes, upload dates
- Usage instructions for reusing assets

Parameters:
- advertiser_id: Optional (uses connected account if omitted)

Execution time: 2-5 seconds (direct backend API call)

Use this tool to:
- Find existing images you can reuse in new campaigns
- Avoid re-uploading the same images
- See what assets are available in your library

After calling this tool, you can use the returned image IDs in `create_tiktok_campaign` via the `existing_image_ids` parameter.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `advertiser_id` | `object` | no | TikTok advertiser ID (optional - will use primary account if not provided) |

### Example request

```json
{
  "arguments": {
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for discover_tiktok_assets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "discover_tiktok_assets"
}
```

---
## explain_tiktok_anomaly

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/explain_tiktok_anomaly/execute`

Explain why a TikTok metric changed during a specific period. Uses statistical analysis and factor detection.

Supported metrics: roas, ctr, cpc, cpm, conversions, conversion_rate, hook_rate, video_completion_rate, engagement_rate

Returns: current value vs historical averages, deviation %, contributing factors (CPM change, CTR change, hook rate change, engagement change, campaign structure changes), assessment, recommendations.

When to use: "Why did my TikTok CTR drop?", "Explain TikTok performance change", "What happened to my TikTok ROAS?"

Parameters:
- metric: REQUIRED — which metric to analyze
- period_start/period_end: REQUIRED — YYYY-MM-DD dates for the anomaly period (max 30 days)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `metric` | `string` | no | Metric: roas, ctr, cpc, cpm, conversions, hook_rate, engagement_rate |
| `period_start` | `string` | yes | Anomaly period start YYYY-MM-DD |
| `period_end` | `string` | yes | Anomaly period end YYYY-MM-DD |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "period_start": "string",
    "period_end": "string",
    "metric": "ctr",
    "advertiser_id": "string",
    "raw_data": false
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for explain_tiktok_anomaly)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "explain_tiktok_anomaly"
}
```

---
## get_tiktok_ad_performance

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_tiktok_ad_performance/execute`

Get TikTok ad-level performance with creative details, video metrics, and engagement.

Returns: per-ad metrics (spend, CTR, ROAS, hook rate, completion rate, engagement rate), creative metadata (ad name, format, video ID), and top/underperforming ads.

When to use: "Show my TikTok ad performance", "Which TikTok ads are best?", "TikTok creative performance"

Parameters:
- lookback_days: default 30
- limit: max ads to return (1-50, default 20)
- offset: pagination offset

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `lookback_days` | `integer` | no | Days to analyze |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |
| `start_date` | `object` | no | Start date YYYY-MM-DD |
| `end_date` | `object` | no | End date YYYY-MM-DD |
| `limit` | `integer` | no | Max ads to return (1-50) |
| `offset` | `integer` | no | Pagination offset |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "lookback_days": 30,
    "advertiser_id": "string",
    "start_date": "string",
    "end_date": "string",
    "limit": 20,
    "offset": 0
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_tiktok_ad_performance)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_tiktok_ad_performance"
}
```

---
## get_tiktok_audience_insights

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_tiktok_audience_insights/execute`

Analyze TikTok audience segment performance by age, gender, and combined demographics.

Returns: age group breakdown (AGE_18_24 to AGE_55_100), gender breakdown, best/worst segments, saturation detection, targeting recommendations.

Note: TikTok audience API doesn't return conversion values per demographic — segments ranked by CPA or CTR.

When to use: "TikTok audience performance", "Which age group works best on TikTok?", "TikTok demographic breakdown"

Parameters:
- breakdown_type: 'age', 'gender', 'age_gender', or 'all' (default)
- include_saturation: true/false (default true)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `lookback_days` | `integer` | no | Days to analyze |
| `breakdown_type` | `string` | no | Breakdown: age, gender, age_gender, or all |
| `include_saturation` | `boolean` | no | Include saturation trend analysis |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |
| `start_date` | `object` | no | Start date YYYY-MM-DD |
| `end_date` | `object` | no | End date YYYY-MM-DD |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "lookback_days": 30,
    "breakdown_type": "all",
    "include_saturation": true,
    "advertiser_id": "string",
    "start_date": "string",
    "end_date": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_tiktok_audience_insights)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_tiktok_audience_insights"
}
```

---
## get_tiktok_campaign_details

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_tiktok_campaign_details/execute`

Get detailed information about a specific TikTok campaign including status, budget, objective, and timestamps.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | TikTok campaign ID |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_tiktok_campaign_details)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_tiktok_campaign_details"
}
```

---
## get_tiktok_campaign_performance

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/get_tiktok_campaign_performance/execute`

Get TikTok campaign performance metrics including TikTok-specific video and engagement data.

Returns: account summary (spend, impressions, reach, conversions, ROAS), campaign breakdown with video metrics (hook rate, completion rate), engagement metrics (likes, shares, comments), and recommendations.

When to use: "How are my TikTok campaigns performing?", "Show TikTok performance", "TikTok ROAS"

Parameters:
- lookback_days: 7, 14, 30 (default), 60, or 90
- start_date/end_date: Optional YYYY-MM-DD (overrides lookback_days)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `lookback_days` | `integer` | no | Days to analyze: 7, 14, 30, 60, or 90 |
| `include_recommendations` | `boolean` | no | Include optimization recommendations |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |
| `start_date` | `object` | no | Start date YYYY-MM-DD (overrides lookback_days) |
| `end_date` | `object` | no | End date YYYY-MM-DD (overrides lookback_days) |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "lookback_days": 30,
    "include_recommendations": true,
    "advertiser_id": "string",
    "start_date": "string",
    "end_date": "string",
    "raw_data": false
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for get_tiktok_campaign_performance)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "get_tiktok_campaign_performance"
}
```

---
## list_tiktok_ad_groups

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_tiktok_ad_groups/execute`

List TikTok ad groups. Optionally filter by campaign ID.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `object` | no | Filter by campaign ID (optional) |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_tiktok_ad_groups)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_tiktok_ad_groups"
}
```

---
## list_tiktok_ads

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_tiktok_ads/execute`

List TikTok ads. Optionally filter by ad group ID or campaign ID.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `adgroup_id` | `object` | no | Filter by ad group ID (optional) |
| `campaign_id` | `object` | no | Filter by campaign ID (optional) |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "adgroup_id": "string",
    "campaign_id": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_tiktok_ads)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_tiktok_ads"
}
```

---
## list_tiktok_campaigns

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/list_tiktok_campaigns/execute`

List all TikTok campaigns with their status, objective, and budget.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for list_tiktok_campaigns)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "list_tiktok_campaigns"
}
```

---
## optimize_tiktok_budget

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/optimize_tiktok_budget/execute`

Optimize TikTok budget allocation using linear programming to maximize conversions.

Returns: current vs optimized allocation, expected conversion lift, campaigns to scale/reduce/pause/maintain.

When to use: "Optimize my TikTok budget", "How should I allocate TikTok spend?", "TikTok budget recommendations"

Parameters:
- total_budget: REQUIRED — total monthly budget to allocate
- target_roas: optional override (default uses historical avg or 2.0x)
- max_change_percentage: 0.3 (conservative) to 0.7 (aggressive), default 0.5

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `total_budget` | `number` | yes | Total monthly budget to allocate |
| `lookback_days` | `integer` | no | Days to analyze for historical performance |
| `target_roas` | `object` | no | Target ROAS override |
| `max_change_percentage` | `number` | no | Max budget change per campaign (0.3=conservative, 0.7=aggressive) |
| `min_daily_budget` | `number` | no | Minimum daily budget per campaign |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |
| `start_date` | `object` | no | Start date YYYY-MM-DD |
| `end_date` | `object` | no | End date YYYY-MM-DD |
| `raw_data` | `boolean` | no | If true, return ONLY raw metrics as a JSON code block (no severity labels, suggested bids/budgets, industry benchmarks, or optimization recommendations). Use when you run your own attribution model or want to minimize token usage. |

### Example request

```json
{
  "arguments": {
    "total_budget": 1.0,
    "lookback_days": 30,
    "target_roas": 1.0,
    "max_change_percentage": 0.5,
    "min_daily_budget": 5.0,
    "advertiser_id": "string",
    "start_date": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for optimize_tiktok_budget)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "optimize_tiktok_budget"
}
```

---
## pause_tiktok_ad

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/pause_tiktok_ad/execute`

Pause a TikTok ad. Sets status to DISABLE.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_id` | `string` | yes | TikTok ad ID |
| `status` | `string` | yes | New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE' |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_id": "string",
    "status": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for pause_tiktok_ad)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "pause_tiktok_ad"
}
```

---
## pause_tiktok_ad_group

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/pause_tiktok_ad_group/execute`

Pause a TikTok ad group. Sets status to DISABLE.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `adgroup_id` | `string` | yes | TikTok ad group ID |
| `status` | `string` | yes | New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE' |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "adgroup_id": "string",
    "status": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for pause_tiktok_ad_group)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "pause_tiktok_ad_group"
}
```

---
## pause_tiktok_campaign

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/pause_tiktok_campaign/execute`

Pause a TikTok campaign. Sets status to DISABLE. Use resume_tiktok_campaign to re-enable.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | TikTok campaign ID |
| `status` | `string` | yes | New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE' |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "status": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for pause_tiktok_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "pause_tiktok_campaign"
}
```

---
## resume_tiktok_ad

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/resume_tiktok_ad/execute`

Resume a paused TikTok ad. Sets status to ENABLE.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ad_id` | `string` | yes | TikTok ad ID |
| `status` | `string` | yes | New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE' |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "ad_id": "string",
    "status": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for resume_tiktok_ad)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "resume_tiktok_ad"
}
```

---
## resume_tiktok_ad_group

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/resume_tiktok_ad_group/execute`

Resume a paused TikTok ad group. Sets status to ENABLE.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `adgroup_id` | `string` | yes | TikTok ad group ID |
| `status` | `string` | yes | New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE' |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "adgroup_id": "string",
    "status": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for resume_tiktok_ad_group)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "resume_tiktok_ad_group"
}
```

---
## resume_tiktok_campaign

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/resume_tiktok_campaign/execute`

Resume a paused TikTok campaign. Sets status to ENABLE.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | TikTok campaign ID |
| `status` | `string` | yes | New status: 'ENABLE' (resume), 'DISABLE' (pause), or 'DELETE' |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "status": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for resume_tiktok_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "resume_tiktok_campaign"
}
```

---
## search_tiktok_targeting

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/search_tiktok_targeting/execute`

Search TikTok targeting options for campaign creation and ad group management.

IMPORTANT: Call this tool to find valid targeting IDs BEFORE creating campaigns or ad groups.

Covers ALL targeting types:
- interest_categories: Browse all interest categories (fashion, gaming, food, etc.) — returns IDs for interest_category_ids
- interest_keywords: Search interest keywords by a seed keyword (e.g., "fitness") — returns keyword IDs for interest_keyword_ids
- regions: Get available locations (countries, provinces, cities) — returns location IDs for location_ids/target_locations
- languages: Get available language codes — returns codes for languages targeting
- action_categories: Get app event categories — for conversion tracking
- carriers: Get mobile carrier names — for carrier targeting
- device_models: Get device models — for device targeting

Common usage patterns:
- Before create_tiktok_campaign: search regions + interest_categories to set targeting
- Before add_tiktok_ad_group: search interest_keywords for new audience segments
- Before update_tiktok_ad_group: search regions to expand/narrow location targeting

Parameters:
- targeting_type: REQUIRED — which type to search (see above)
- keyword: REQUIRED for interest_keywords — seed keyword to search
- level_range: For regions — TO_COUNTRY (default), TO_PROVINCE, TO_CITY, TO_DISTRICT
- language: Display language for results (default: en)

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `targeting_type` | `string` | yes | Type of targeting to search. Options: 'interest_categories' (browse all interest categories), 'interest_keywords' (search keywords by seed keyword — requires 'keyword'), 'regions' (available locations — countries, provinces, cities), 'langu |
| `keyword` | `object` | no | Seed keyword for interest_keywords search. Required when targeting_type='interest_keywords'. |
| `language` | `object` | no | Language for results. Default: 'en'. Options: en, zh, ja, de, es, fr, id, it, ko, ru, th, tr, vi, ar, pt, ms. |
| `limit` | `object` | no | Max results for interest_keywords (1-50). Default: 50. |
| `mode` | `object` | no | Search mode for interest_keywords: 'FUZZ_MATCH' (default) or 'SEMANTIC_RECOMMEND'. |
| `audience_type` | `object` | no | Audience type for interest_keywords: 'GENERAL_INTEREST' (default) or 'PURCHASE_INTENTION'. |
| `placements` | `object` | no | Placements filter: ['PLACEMENT_TIKTOK'], ['PLACEMENT_PANGLE'], etc. Default: TikTok only. |
| `objective_type` | `object` | no | Campaign objective for regions lookup. Default: TRAFFIC. |
| `level_range` | `object` | no | Region detail level: 'ALL', 'TO_COUNTRY', 'TO_PROVINCE', 'TO_CITY', 'TO_DISTRICT'. Default: TO_COUNTRY. |
| `operating_system` | `object` | no | OS filter for regions: 'ANDROID' or 'IOS'. Optional. |
| `advertiser_id` | `object` | no | TikTok advertiser ID (optional). |

### Example request

```json
{
  "arguments": {
    "targeting_type": "string",
    "keyword": "string",
    "language": "en",
    "limit": 50,
    "mode": "FUZZ_MATCH",
    "audience_type": "GENERAL_INTEREST",
    "placements": [
      "string"
    ]
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for search_tiktok_targeting)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "search_tiktok_targeting"
}
```

---
## update_tiktok_ad_group

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_tiktok_ad_group/execute`

Update TikTok ad group settings: name, budget, targeting (age, gender, locations), schedule.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `adgroup_id` | `string` | yes | TikTok ad group ID |
| `name` | `object` | no | New ad group name |
| `budget` | `object` | no | New budget in account currency |
| `age_groups` | `object` | no | Age groups: AGE_13_17, AGE_18_24, AGE_25_34, AGE_35_44, AGE_45_54, AGE_55_100 |
| `gender` | `object` | no | GENDER_MALE, GENDER_FEMALE, or GENDER_UNLIMITED |
| `location_ids` | `object` | no | TikTok location IDs |
| `schedule_end_time` | `object` | no | End time: YYYY-MM-DD HH:MM:SS |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "adgroup_id": "string",
    "name": "string",
    "budget": 1.0,
    "age_groups": [
      "string"
    ],
    "gender": "string",
    "location_ids": [
      "string"
    ],
    "schedule_end_time": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_tiktok_ad_group)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_tiktok_ad_group"
}
```

---
## update_tiktok_campaign

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/update_tiktok_campaign/execute`

Update TikTok campaign settings like name, budget, or budget mode.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `campaign_id` | `string` | yes | TikTok campaign ID |
| `name` | `object` | no | New campaign name |
| `budget` | `object` | no | New daily budget in account currency |
| `budget_mode` | `object` | no | BUDGET_MODE_DAY or BUDGET_MODE_TOTAL |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "campaign_id": "<campaign_id>",
    "name": "string",
    "budget": 1.0,
    "budget_mode": "string",
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for update_tiktok_campaign)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "update_tiktok_campaign"
}
```

---
## upload_tiktok_images

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/upload_tiktok_images/execute`

Upload images to TikTok Asset Library from public URLs.

Returns TikTok image_ids that can be used for:
- create_tiktok_carousel_card — build carousel cards from uploaded images
- create_tiktok_campaign with existing_image_ids — standard image campaigns
- add_tiktok_ad with image_ids — add image ads to existing ad groups

TikTok downloads images directly from the provided URLs (no intermediate storage needed).

Supported formats: JPG, PNG, WEBP. Max 10MB per image. Max 20 images per call.

Use this tool when the user provides image URLs and you need TikTok image IDs.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `image_urls` | `array` | yes | List of public HTTPS image URLs to upload to TikTok (1-20). TikTok downloads images directly from these URLs. Supported formats: JPG, PNG, WEBP. Max 10MB per image. |
| `advertiser_id` | `object` | no | TikTok advertiser ID (optional). |

### Example request

```json
{
  "arguments": {
    "image_urls": [
      "string"
    ],
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for upload_tiktok_images)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "upload_tiktok_images"
}
```

---
## validate_and_prepare_tiktok_assets

**Platform:** TikTok Ads

**Endpoint:** `POST https://api.adspirer.ai/api/v1/tools/validate_and_prepare_tiktok_assets/execute`

User provides image URLs to validate BEFORE creating TikTok image campaign.

Validate and prepare new image assets for TikTok ad campaigns.

IMPORTANT: Call this tool BEFORE create_tiktok_campaign if uploading NEW images.

TikTok Image Requirements:
- Aspect ratio: 9:16 (vertical, e.g., 1080x1920 or 540x960)
- Maximum file size: 10MB
- Formats: JPEG, PNG, WEBP
- Minimum size: 540x960px
- Recommended: 1080x1920px

Parameters:
- image_urls: List of public image URLs (e.g., from postimages.org, imgbb.com)

Returns:
- Validation results for each image
- asset_bundle_id (valid for 60 minutes)
- Use this bundle ID in create_tiktok_campaign

Execution time: 5-15 seconds (downloads and validates images)

Use this tool to:
- Upload new images you haven't used before
- Validate images meet TikTok's specifications
- Get detailed error messages if images don't meet requirements

After validation, use the returned `asset_bundle_id` in `create_tiktok_campaign`.

### Arguments

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `image_urls` | `array` | yes | List of public image URLs to validate (e.g., from postimages.org). Must be publicly accessible HTTPS URLs. Images will be validated against TikTok specs: 9:16 aspect ratio, max 10MB. |
| `advertiser_id` | `object` | no | TikTok advertiser ID. Required for multi-account users. Get from list_connected_accounts. |

### Example request

```json
{
  "arguments": {
    "image_urls": [
      "string"
    ],
    "advertiser_id": "string"
  }
}
```

### Example 200 response

```json
{
  "success": true,
  "data": {
    "text": "(tool-specific textual output for validate_and_prepare_tiktok_assets)",
    "quota": {
      "used": 42,
      "limit": 150,
      "tier": "plus",
      "period_end": "2026-05-01"
    }
  },
  "tool": "validate_and_prepare_tiktok_assets"
}
```

---
